From 9da8d9dbb7aaffb4705037766f15b776427a5e71 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 27 May 2021 14:58:24 -0400 Subject: [PATCH 01/14] test(NODE-3297): translate community typings tests to tsd format - Tests from DefinitelyTyped included in our testing - Corrections to our types to bring us in sync with the community - list of the tests that break due to desired drivers changes --- src/collection.ts | 115 +++++-- src/cursor/aggregation_cursor.ts | 5 +- src/cursor/find_cursor.ts | 4 +- src/index.ts | 23 +- src/mongo_types.ts | 135 +++++++- src/operations/map_reduce.ts | 5 +- src/operations/stats.ts | 212 ++++++++++++- src/sessions.ts | 7 +- src/transactions.ts | 1 + .../types/community/changes_from_36.test-d.ts | 106 +++++++ .../community/collection/aggregate.test-d.ts | 57 ++++ .../community/collection/bulkWrite.test-d.ts | 254 +++++++++++++++ .../community/collection/count.test-d.ts | 10 + .../community/collection/distinct.test-d.ts | 58 ++++ .../collection/filterQuery.test-d.ts | 228 ++++++++++++++ .../community/collection/findX.test-d.ts | 138 +++++++++ .../community/collection/insertX.test-d.ts | 242 +++++++++++++++ .../community/collection/mapReduce.test-d.ts | 26 ++ .../community/collection/updateX.test-d.ts | 292 ++++++++++++++++++ test/types/community/createIndex.test-d.ts | 12 + test/types/community/cursor.test-d.ts | 78 +++++ test/types/community/index.ts | 108 +++++++ test/types/community/stats.test-d.ts | 13 + test/types/community/transaction.test-d.ts | 117 +++++++ test/types/community/tsconfig.json | 7 + test/types/distinct.test-d.ts | 8 - test/types/indexed_schema.test-d.ts | 3 - test/types/union_schema.test-d.ts | 4 +- test/types/utility_types.ts | 1 + test/unit/type_check.test.js | 12 +- 30 files changed, 2201 insertions(+), 80 deletions(-) create mode 100644 test/types/community/changes_from_36.test-d.ts create mode 100644 test/types/community/collection/aggregate.test-d.ts create mode 100644 test/types/community/collection/bulkWrite.test-d.ts create mode 100644 test/types/community/collection/count.test-d.ts create mode 100644 test/types/community/collection/distinct.test-d.ts create mode 100644 test/types/community/collection/filterQuery.test-d.ts create mode 100644 test/types/community/collection/findX.test-d.ts create mode 100644 test/types/community/collection/insertX.test-d.ts create mode 100644 test/types/community/collection/mapReduce.test-d.ts create mode 100644 test/types/community/collection/updateX.test-d.ts create mode 100644 test/types/community/createIndex.test-d.ts create mode 100644 test/types/community/cursor.test-d.ts create mode 100644 test/types/community/index.ts create mode 100644 test/types/community/stats.test-d.ts create mode 100644 test/types/community/transaction.test-d.ts create mode 100644 test/types/community/tsconfig.json delete mode 100644 test/types/distinct.test-d.ts create mode 100644 test/types/utility_types.ts diff --git a/src/collection.ts b/src/collection.ts index 0b577780aa7..1a4dbc3fb59 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -1,4 +1,4 @@ -import { DEFAULT_PK_FACTORY, emitWarningOnce, resolveOptions } from './utils'; +import { DEFAULT_PK_FACTORY, emitWarningOnce, isRecord, resolveOptions } from './utils'; import { ReadPreference, ReadPreferenceLike } from './read_preference'; import { normalizeHintField, @@ -79,7 +79,7 @@ import { } from './operations/map_reduce'; import { OptionsOperation } from './operations/options_operation'; import { RenameOperation, RenameOptions } from './operations/rename'; -import { CollStatsOperation, CollStatsOptions } from './operations/stats'; +import { CollStats, CollStatsOperation, CollStatsOptions } from './operations/stats'; import { executeOperation } from './operations/execute_operation'; import type { Db } from './db'; import type { OperationOptions, Hint } from './operations/operation'; @@ -89,7 +89,14 @@ import type { PkFactory } from './mongo_client'; import type { Logger, LoggerOptions } from './logger'; import { FindCursor } from './cursor/find_cursor'; import type { CountOptions } from './operations/count'; -import type { Filter, TODO_NODE_3286, UpdateQuery, WithId, OptionalId } from './mongo_types'; +import type { + Filter, + TODO_NODE_3286, + UpdateQuery, + WithId, + OptionalId, + FlattenIfArray +} from './mongo_types'; /** @public */ export interface ModifyResult { @@ -665,25 +672,42 @@ export class Collection { * @param options - Optional settings for the command * @param callback - An optional callback, a Promise will be returned if none is provided */ - findOne(): Promise; - findOne(callback: Callback): void; - findOne(filter: Filter): Promise; - findOne(filter: Filter, callback?: Callback): void; - findOne(filter: Filter, options: FindOptions): Promise; - findOne(filter: Filter, options: FindOptions, callback: Callback): void; + findOne(): Promise; + findOne(callback: Callback): void; + findOne(filter: Filter): Promise; + findOne(filter: Filter, callback?: Callback): void; + findOne(filter: Filter, options: FindOptions): Promise; findOne( - filter?: Filter | Callback, - options?: FindOptions | Callback, - callback?: Callback - ): Promise | void { + filter: Filter, + options: FindOptions, + callback: Callback + ): void; + + // allow an override of the schema. + findOne( + filter: Filter, + options?: FindOptions + ): Promise; + findOne( + filter: Filter, + options?: FindOptions, + callback?: Callback + ): void; + + findOne( + filter?: Filter | Callback, + options?: FindOptions | Callback, + callback?: Callback + ): Promise | void { if (callback !== undefined && typeof callback !== 'function') { throw new MongoDriverError('Third parameter to `findOne()` must be a callback or undefined'); } if (typeof filter === 'function') - (callback = filter as Callback), (filter = {}), (options = {}); + (callback = filter as Callback), (filter = {}), (options = {}); if (typeof options === 'function') (callback = options), (options = {}); - filter = filter || {}; + + filter ??= {}; return executeOperation( getTopology(this), @@ -704,6 +728,10 @@ export class Collection { find(): FindCursor; find(filter: Filter): FindCursor; find(filter: Filter, options: FindOptions): FindCursor; + find( + query: Filter, + options: FindOptions + ): FindCursor; find(filter?: Filter, options?: FindOptions): FindCursor { if (arguments.length > 2) { throw new MongoDriverError('Third parameter to `collection.find()` must be undefined'); @@ -1054,6 +1082,7 @@ export class Collection { options: CountDocumentsOptions, callback: Callback ): void; + countDocuments(filter: Filter, callback: Callback): void; countDocuments( filter?: Document | CountDocumentsOptions | Callback, options?: CountDocumentsOptions | Callback, @@ -1089,25 +1118,47 @@ export class Collection { * @param options - Optional settings for the command * @param callback - An optional callback, a Promise will be returned if none is provided */ - distinct>(key: Key): Promise; - distinct>(key: Key, callback: Callback): void; - distinct>(key: Key, filter: Filter): Promise; + distinct>( + key: Key + ): Promise[Key]>>>; + distinct>( + key: Key, + callback: Callback[Key]>>> + ): void; + distinct>( + key: Key, + filter: Filter + ): Promise[Key]>>>; distinct>( key: Key, filter: Filter, - callback: Callback + callback: Callback[Key]>>> ): void; distinct>( key: Key, filter: Filter, options: DistinctOptions - ): Promise; + ): Promise[Key]>>>; distinct>( key: Key, filter: Filter, options: DistinctOptions, + callback: Callback[Key]>>> + ): void; + + // Embedded documents overload + distinct(key: string): Promise; + distinct(key: string, callback: Callback): void; + distinct(key: string, filter: Filter): Promise; + distinct(key: string, filter: Filter, callback: Callback): void; + distinct(key: string, filter: Filter, options: DistinctOptions): Promise; + distinct( + key: string, + filter: Filter, + options: DistinctOptions, callback: Callback ): void; + // Implementation distinct>( key: Key, filter?: Filter | DistinctOptions | Callback, @@ -1164,14 +1215,14 @@ export class Collection { * @param options - Optional settings for the command * @param callback - An optional callback, a Promise will be returned if none is provided */ - stats(): Promise; - stats(callback: Callback): void; - stats(options: CollStatsOptions): Promise; - stats(options: CollStatsOptions, callback: Callback): void; + stats(): Promise; + stats(callback: Callback): void; + stats(options: CollStatsOptions): Promise; + stats(options: CollStatsOptions, callback: Callback): void; stats( - options?: CollStatsOptions | Callback, - callback?: Callback - ): Promise | void { + options?: CollStatsOptions | Callback, + callback?: Callback + ): Promise | void { if (typeof options === 'function') (callback = options), (options = {}); options = options ?? {}; @@ -1372,27 +1423,27 @@ export class Collection { * @param callback - An optional callback, a Promise will be returned if none is provided */ mapReduce( - map: string | MapFunction, + map: string | MapFunction, reduce: string | ReduceFunction ): Promise; mapReduce( - map: string | MapFunction, + map: string | MapFunction, reduce: string | ReduceFunction, callback: Callback ): void; mapReduce( - map: string | MapFunction, + map: string | MapFunction, reduce: string | ReduceFunction, options: MapReduceOptions ): Promise; mapReduce( - map: string | MapFunction, + map: string | MapFunction, reduce: string | ReduceFunction, options: MapReduceOptions, callback: Callback ): void; mapReduce( - map: string | MapFunction, + map: string | MapFunction, reduce: string | ReduceFunction, options?: MapReduceOptions | Callback, callback?: Callback diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index 411ad19f51d..a00c07525c0 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -10,6 +10,7 @@ import type { ClientSession } from '../sessions'; import type { OperationParent } from '../operations/command'; import type { AbstractCursorOptions } from './abstract_cursor'; import type { ExplainVerbosityLike } from '../explain'; +import type { Projection } from '../mongo_types'; /** @public */ export interface AggregationCursorOptions extends AbstractCursorOptions, AggregateOptions {} @@ -106,6 +107,7 @@ export class AggregationCursor extends AbstractCursor($group: Document): AggregationCursor; group($group: Document): this { assertUninitialized(this); this[kPipeline].push({ $group }); @@ -134,6 +136,7 @@ export class AggregationCursor extends AbstractCursor($project: Document): AggregationCursor; project($project: Document): this { assertUninitialized(this); this[kPipeline].push({ $project }); @@ -169,7 +172,7 @@ export class AggregationCursor extends AbstractCursor extends AbstractCursor { * * @param value - The field projection object. */ - project(value: Document): this { + project(value: SchemaMember): this; + project(value: Projection): this { assertUninitialized(this); this[kBuiltOptions].projection = value; return this; diff --git a/src/index.ts b/src/index.ts index 372ab34bfda..954765726bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,6 @@ import { Admin } from './admin'; import { MongoClient } from './mongo_client'; import { Db } from './db'; import { Collection } from './collection'; -import { ReadPreference } from './read_preference'; import { Logger } from './logger'; import { GridFSBucket } from './gridfs-stream'; import { CancellationToken } from './mongo_types'; @@ -49,7 +48,6 @@ export { MongoClient, Db, Collection, - ReadPreference, Logger, AbstractCursor, AggregationCursor, @@ -74,6 +72,12 @@ export { ExplainVerbosity } from './explain'; export { ReadConcernLevel } from './read_concern'; export { ReadPreferenceMode } from './read_preference'; export { ServerApiVersion } from './mongo_client'; + +// Helper classes +export { WriteConcern } from './write_concern'; +export { ReadConcern } from './read_concern'; +export { ReadPreference } from './read_preference'; + // events export { CommandStartedEvent, @@ -274,7 +278,12 @@ export type { RemoveUserOptions } from './operations/remove_user'; export type { RenameOptions } from './operations/rename'; export type { RunCommandOptions } from './operations/run_command'; export type { SetProfilingLevelOptions } from './operations/set_profiling_level'; -export type { CollStatsOptions, DbStatsOptions } from './operations/stats'; +export type { + CollStatsOptions, + DbStatsOptions, + CollStats, + WiredTigerData +} from './operations/stats'; export type { UpdateResult, UpdateOptions, @@ -282,7 +291,7 @@ export type { UpdateStatement } from './operations/update'; export type { ValidateCollectionOptions } from './operations/validate_collection'; -export type { ReadConcern, ReadConcernLike } from './read_concern'; +export type { ReadConcernLike } from './read_concern'; export type { ReadPreferenceLike, ReadPreferenceOptions, @@ -340,7 +349,7 @@ export type { HostAddress, EventEmitterWithState } from './utils'; -export type { WriteConcern, W, WriteConcernOptions, WriteConcernSettings } from './write_concern'; +export type { W, WriteConcernOptions, WriteConcernSettings } from './write_concern'; export type { ExecutionResult } from './operations/execute_operation'; export type { InternalAbstractCursorOptions } from './cursor/abstract_cursor'; export type { BulkOperationBase, BulkOperationPrivate, FindOperators, Batch } from './bulk/common'; @@ -356,8 +365,6 @@ export type { Filter, Projection, InferIdType, - ProjectionOperators, - MetaProjectionOperators, - MetaSortOperators + ProjectionOperators } from './mongo_types'; export type { serialize, deserialize } from './bson'; diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 8cf5dea9da9..420d5b826fb 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -1,4 +1,4 @@ -import type { Document, ObjectId } from './bson'; +import type { Binary, Document, ObjectId, BSONRegExp } from './bson'; import { EventEmitter } from 'events'; /** @internal */ @@ -42,34 +42,137 @@ export type EnhancedOmit = string extends keyof TRecor export type WithoutId = Omit; /** A MongoDB filter can be some portion of the schema or a set of operators @public */ -export type Filter = Partial & Document; +export type Filter = { + [P in keyof TSchema]?: Condition; +} & + RootFilterOperators; -/** A MongoDB UpdateQuery is set of operators @public */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export type UpdateQuery = Document; // TODO(NODE-3274) +export type Condition = AlternativeType | FilterOperators>; + +type AlternativeType = T extends ReadonlyArray + ? T | RegExpOrString + : RegExpOrString; + +// we can search using alternative types in mongodb e.g. +// string types can be searched using a regex in mongo +// array types can be searched using their element type +type RegExpOrString = T extends string ? BSONRegExp | RegExp | T : T; + +export interface RootFilterOperators extends Document { + $and?: Filter[]; + $nor?: Filter[]; + $or?: Filter[]; + $text?: { + $search: string; + $language?: string; + $caseSensitive?: boolean; + $diacriticSensitive?: boolean; + }; + $where?: string | ((this: TSchema) => boolean); + $comment?: string | Document; +} + +export interface FilterOperators extends Document { + // Comparison + $eq?: TValue; + $gt?: TValue; + $gte?: TValue; + $in?: TValue[]; + $lt?: TValue; + $lte?: TValue; + $ne?: TValue; + $nin?: TValue[]; + // Logical + $not?: TValue extends string ? FilterOperators | RegExp : FilterOperators; + // Element + /** + * When `true`, `$exists` matches the documents that contain the field, + * including documents where the field value is null. + */ + $exists?: boolean; + $type?: BSONType | BSONTypeAlias; + // Evaluation + $expr?: any; + $jsonSchema?: any; + $mod?: TValue extends number ? [number, number] : never; + $regex?: TValue extends string ? RegExp | BSONRegExp | string : never; + $options?: TValue extends string ? string : never; + // Geospatial + $geoIntersects?: { $geometry: Document }; + $geoWithin?: Document; + $near?: Document; + $nearSphere?: Document; + $maxDistance?: number; + // Array + $all?: TValue extends ReadonlyArray ? any[] : never; + $elemMatch?: TValue extends ReadonlyArray ? Document : never; + $size?: TValue extends ReadonlyArray ? number : never; + // Bitwise + $bitsAllClear?: BitwiseFilter; + $bitsAllSet?: BitwiseFilter; + $bitsAnyClear?: BitwiseFilter; + $bitsAnySet?: BitwiseFilter; +} + +type BitwiseFilter = + | number /** numeric bit mask */ + | Binary /** BinData bit mask */ + | number[]; /** `[ , , ... ]` */ -/** @see https://docs.mongodb.com/manual/reference/operator/aggregation/meta/#proj._S_meta @public */ -export type MetaSortOperators = 'textScore' | 'indexKey'; +/** @public */ +export const BSONType = Object.freeze({ + double: 1, + string: 2, + object: 3, + array: 4, + binData: 5, + undefined: 6, + objectId: 7, + bool: 8, + date: 9, + null: 10, + regex: 11, + dbPointer: 12, + javascript: 13, + symbol: 14, + javascriptWithScope: 15, + int: 16, + timestamp: 17, + long: 18, + decimal: 19, + minKey: -1, + maxKey: 127 +} as const); /** @public */ -export type MetaProjectionOperators = - | MetaSortOperators - /** Only for Atlas Search https://docs.atlas.mongodb.com/reference/atlas-search/scoring/ */ - | 'searchScore' - /** Only for Atlas Search https://docs.atlas.mongodb.com/reference/atlas-search/highlighting/ */ - | 'searchHighlights'; +export type BSONType = typeof BSONType[keyof typeof BSONType]; +/** @public */ +export type BSONTypeAlias = keyof typeof BSONType; + +/** A MongoDB UpdateQuery is set of operators @public */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type UpdateQuery = Document; // TODO(NODE-3274) /** @public */ -export interface ProjectionOperators { +export interface ProjectionOperators extends Document { $elemMatch?: Document; $slice?: number | [number, number]; - $meta?: MetaProjectionOperators; + $meta?: string; + /** @deprecated Since MongoDB 3.2, Use FindCursor#max */ + $max?: any; } /** @public */ export type Projection = { [Key in keyof TSchema]?: ProjectionOperators | 0 | 1 | boolean; -}; +} & + Partial>; + +/** @public */ +export type FlattenIfArray = T extends ReadonlyArray ? R : T; + +/** @public */ +export type SchemaMember = { [P in keyof T]?: V } | { [key: string]: V }; /** @public */ export type Nullable = AnyType | null | undefined; diff --git a/src/operations/map_reduce.ts b/src/operations/map_reduce.ts index 76246e49928..2873ef9ae2c 100644 --- a/src/operations/map_reduce.ts +++ b/src/operations/map_reduce.ts @@ -38,10 +38,7 @@ const exclusionList = [ /** @public */ export type MapFunction = (this: TSchema) => void; /** @public */ -export type ReduceFunction = ( - key: TKey, - values: TValue[] -) => TValue; +export type ReduceFunction = (key: TKey, values: TValue[]) => TValue; /** @public */ export type FinalizeFunction = ( key: TKey, diff --git a/src/operations/stats.ts b/src/operations/stats.ts index d150f920748..a0f8158f04b 100644 --- a/src/operations/stats.ts +++ b/src/operations/stats.ts @@ -33,7 +33,7 @@ export class CollStatsOperation extends CommandOperation { this.collectionName = collection.collectionName; } - execute(server: Server, session: ClientSession, callback: Callback): void { + execute(server: Server, session: ClientSession, callback: Callback): void { const command: Document = { collStats: this.collectionName }; if (this.options.scale != null) { command.scale = this.options.scale; @@ -68,5 +68,215 @@ export class DbStatsOperation extends CommandOperation { } } +/** + * @public + * @see https://docs.mongodb.org/manual/reference/command/collStats/ + */ +export interface CollStats { + /** + * Namespace. + */ + ns: string; + /** + * Number of documents. + */ + count: number; + /** + * Collection size in bytes. + */ + size: number; + /** + * Average object size in bytes. + */ + avgObjSize: number; + /** + * (Pre)allocated space for the collection in bytes. + */ + storageSize: number; + /** + * Number of extents (contiguously allocated chunks of datafile space). + */ + numExtents: number; + /** + * Number of indexes. + */ + nindexes: number; + /** + * Size of the most recently created extent in bytes. + */ + lastExtentSize: number; + /** + * Padding can speed up updates if documents grow. + */ + paddingFactor: number; + /** + * A number that indicates the user-set flags on the collection. userFlags only appears when using the mmapv1 storage engine. + */ + userFlags?: number; + /** + * Total index size in bytes. + */ + totalIndexSize: number; + /** + * Size of specific indexes in bytes. + */ + indexSizes: { + _id_: number; + [index: string]: number; + }; + /** + * `true` if the collection is capped. + */ + capped: boolean; + /** + * The maximum number of documents that may be present in a capped collection. + */ + max: number; + /** + * The maximum size of a capped collection. + */ + maxSize: number; + wiredTiger?: WiredTigerData; + indexDetails?: any; + ok: number; +} + +/** @public */ +export interface WiredTigerData { + LSM: { + 'bloom filter false positives': number; + 'bloom filter hits': number; + 'bloom filter misses': number; + 'bloom filter pages evicted from cache': number; + 'bloom filter pages read into cache': number; + 'bloom filters in the LSM tree': number; + 'chunks in the LSM tree': number; + 'highest merge generation in the LSM tree': number; + 'queries that could have benefited from a Bloom filter that did not exist': number; + 'sleep for LSM checkpoint throttle': number; + 'sleep for LSM merge throttle': number; + 'total size of bloom filters': number; + }; + 'block-manager': { + 'allocations requiring file extension': number; + 'blocks allocated': number; + 'blocks freed': number; + 'checkpoint size': number; + 'file allocation unit size': number; + 'file bytes available for reuse': number; + 'file magic number': number; + 'file major version number': number; + 'file size in bytes': number; + 'minor version number': number; + }; + btree: { + 'btree checkpoint generation': number; + 'column-store fixed-size leaf pages': number; + 'column-store internal pages': number; + 'column-store variable-size RLE encoded values': number; + 'column-store variable-size deleted values': number; + 'column-store variable-size leaf pages': number; + 'fixed-record size': number; + 'maximum internal page key size': number; + 'maximum internal page size': number; + 'maximum leaf page key size': number; + 'maximum leaf page size': number; + 'maximum leaf page value size': number; + 'maximum tree depth': number; + 'number of key/value pairs': number; + 'overflow pages': number; + 'pages rewritten by compaction': number; + 'row-store internal pages': number; + 'row-store leaf pages': number; + }; + cache: { + 'bytes currently in the cache': number; + 'bytes read into cache': number; + 'bytes written from cache': number; + 'checkpoint blocked page eviction': number; + 'data source pages selected for eviction unable to be evicted': number; + 'hazard pointer blocked page eviction': number; + 'in-memory page passed criteria to be split': number; + 'in-memory page splits': number; + 'internal pages evicted': number; + 'internal pages split during eviction': number; + 'leaf pages split during eviction': number; + 'modified pages evicted': number; + 'overflow pages read into cache': number; + 'overflow values cached in memory': number; + 'page split during eviction deepened the tree': number; + 'page written requiring lookaside records': number; + 'pages read into cache': number; + 'pages read into cache requiring lookaside entries': number; + 'pages requested from the cache': number; + 'pages written from cache': number; + 'pages written requiring in-memory restoration': number; + 'tracked dirty bytes in the cache': number; + 'unmodified pages evicted': number; + }; + cache_walk: { + 'Average difference between current eviction generation when the page was last considered': number; + 'Average on-disk page image size seen': number; + 'Clean pages currently in cache': number; + 'Current eviction generation': number; + 'Dirty pages currently in cache': number; + 'Entries in the root page': number; + 'Internal pages currently in cache': number; + 'Leaf pages currently in cache': number; + 'Maximum difference between current eviction generation when the page was last considered': number; + 'Maximum page size seen': number; + 'Minimum on-disk page image size seen': number; + 'On-disk page image sizes smaller than a single allocation unit': number; + 'Pages created in memory and never written': number; + 'Pages currently queued for eviction': number; + 'Pages that could not be queued for eviction': number; + 'Refs skipped during cache traversal': number; + 'Size of the root page': number; + 'Total number of pages currently in cache': number; + }; + compression: { + 'compressed pages read': number; + 'compressed pages written': number; + 'page written failed to compress': number; + 'page written was too small to compress': number; + 'raw compression call failed, additional data available': number; + 'raw compression call failed, no additional data available': number; + 'raw compression call succeeded': number; + }; + cursor: { + 'bulk-loaded cursor-insert calls': number; + 'create calls': number; + 'cursor-insert key and value bytes inserted': number; + 'cursor-remove key bytes removed': number; + 'cursor-update value bytes updated': number; + 'insert calls': number; + 'next calls': number; + 'prev calls': number; + 'remove calls': number; + 'reset calls': number; + 'restarted searches': number; + 'search calls': number; + 'search near calls': number; + 'truncate calls': number; + 'update calls': number; + }; + reconciliation: { + 'dictionary matches': number; + 'fast-path pages deleted': number; + 'internal page key bytes discarded using suffix compression': number; + 'internal page multi-block writes': number; + 'internal-page overflow keys': number; + 'leaf page key bytes discarded using prefix compression': number; + 'leaf page multi-block writes': number; + 'leaf-page overflow keys': number; + 'maximum blocks required for a page': number; + 'overflow values written': number; + 'page checksum matches': number; + 'page reconciliation calls': number; + 'page reconciliation calls for eviction': number; + 'pages deleted': number; + }; +} + defineAspects(CollStatsOperation, [Aspect.READ_OPERATION]); defineAspects(DbStatsOperation, [Aspect.READ_OPERATION]); diff --git a/src/sessions.ts b/src/sessions.ts index 201877e14f5..858269e25e9 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -63,7 +63,7 @@ export interface ClientSessionOptions { } /** @public */ -export type WithTransactionCallback = (session: ClientSession) => Promise | void; +export type WithTransactionCallback = (session: ClientSession) => Promise; /** @public */ export type ClientSessionEvents = { @@ -335,7 +335,10 @@ class ClientSession extends TypedEventEmitter { * @param fn - A lambda to run within a transaction * @param options - Optional settings for the transaction */ - withTransaction(fn: WithTransactionCallback, options?: TransactionOptions): Promise { + withTransaction( + fn: WithTransactionCallback, + options?: TransactionOptions + ): ReturnType { const startTime = now(); return attemptTransaction(this, startTime, fn, options); } diff --git a/src/transactions.ts b/src/transactions.ts index ed263b2a3d5..4fa70ae7e01 100644 --- a/src/transactions.ts +++ b/src/transactions.ts @@ -50,6 +50,7 @@ const stateMachine: { [state in TxnState]: TxnState[] } = { * @public */ export interface TransactionOptions extends CommandOperationOptions { + // TODO: Improve the types and handling (NODE-3297) /** A default read concern for commands in this transaction */ readConcern?: ReadConcern; /** A default writeConcern for commands in this transaction */ diff --git a/test/types/community/changes_from_36.test-d.ts b/test/types/community/changes_from_36.test-d.ts new file mode 100644 index 00000000000..124e69d2f44 --- /dev/null +++ b/test/types/community/changes_from_36.test-d.ts @@ -0,0 +1,106 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import type { PeerCertificate } from 'tls'; +import type { PropExists } from '../utility_types'; +import { expectAssignable, expectError, expectNotType, expectType } from 'tsd'; +import { + LoggerLevel, + MongoClient, + MongoClientOptions, + ReadPreference, + ReadPreferenceMode +} from '../../../src'; + +type MongoDBImport = typeof import('../../../src'); + +const mongodb: MongoDBImport = (null as unknown) as MongoDBImport; + +expectNotType(mongodb); +expectType>(false); +// (mongodb.ObjectId); + +// MongoClientOptions +const options: MongoClientOptions = {}; +// .readPreference no longer accepts boolean +expectNotType(options.readPreference); +// .pkFactory cannot be an empty object +expectNotType<{}>(options.pkFactory); +// .checkServerIdentity cannot be `true` +expectNotType(options.checkServerIdentity); +// .sslCA cannot be string[] +expectNotType(options.sslCA); +// .sslCRL cannot be string[] +expectNotType(options.sslCRL); +// .sslCert cannot be a Buffer +expectNotType(options.sslCert); +// .sslKey cannot be a Buffer +expectNotType(options.sslKey); +// .sslPass cannot be a Buffer +expectNotType(options.sslPass); + +// Legacy option kept? +expectType>(true); +// Removed options +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); + +expectType(options.authSource); +expectType(options.loggerLevel); +expectType(options.readPreference); +expectType(options.promoteValues); +expectType(options.family); +expectType(options.ssl); +expectType(options.sslValidate); +expectAssignable<((host: string, cert: PeerCertificate) => Error | undefined) | undefined>( + options.checkServerIdentity +); + +// MongoClient.connect +// the callback's error isn't specifically MongoError (actually, it is probably true in 3.6 that other errors could be passed here, but there is a ticket in progress to fix this.) + +// No such thing as UnifiedTopologyOptions + +// compression options have simpler specification: +// old way: {compression: { compressors: ['zlib', 'snappy'] }} +expectNotType<{ compressors: string[] }>(options.compressors); +expectType<('none' | 'snappy' | 'zlib')[] | undefined>(options.compressors); + +// Removed cursor API +const cursor = new MongoClient('').db().aggregate(); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); + +// Doesn't return cursor of new type: +// .map(result => ({ foo: result.age })); + +// Cursor returning functions don't take a callback +const db = new MongoClient('').db(); +const collection = db.collection(''); +// collection.find +expectError(collection.find({}, {}, (e: unknown, c: unknown) => undefined)); +// collection.aggregate +expectError(collection.aggregate({}, {}, (e: unknown, c: unknown) => undefined)); +// db.aggregate +expectError(db.aggregate({}, {}, (e: unknown, c: unknown) => undefined)); + +// insertOne and insertMany doesn't return: +const insertOneRes = await collection.insertOne({}); +const insertManyRes = await collection.insertOne({}); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); diff --git a/test/types/community/collection/aggregate.test-d.ts b/test/types/community/collection/aggregate.test-d.ts new file mode 100644 index 00000000000..2c326b29c41 --- /dev/null +++ b/test/types/community/collection/aggregate.test-d.ts @@ -0,0 +1,57 @@ +import { expectType } from 'tsd'; +import { AggregationCursor, MongoClient } from '../../../../src/index'; + +// collection.aggregate tests +const client = new MongoClient(''); +const db = client.db('test'); +const collection = db.collection('test.find'); + +interface Payment { + total: number; +} +const cursor: AggregationCursor = collection.aggregate([{}]); + +cursor.match({ bar: 1 }).limit(10); + +collection.aggregate([{ $match: { bar: 1 } }, { $limit: 10 }]); +collection.aggregate([{ $match: { bar: 1 } }]).limit(10); +collection.aggregate([]).match({ bar: 1 }).limit(10); +collection.aggregate().match({ bar: 1 }).limit(10); +collection.aggregate().unwind('total'); +collection.aggregate().unwind({ path: 'total' }); + +collection + .aggregate([{ $match: { bar: 1 } }]) + .limit(10); + +collection.aggregate([]).match({ bar: 1 }).limit(10); + +collection.aggregate().match({ bar: 1 }).limit(10); + +interface Employee { + firstName: string; + lastName: string; + department: string; +} + +interface EmployeeName { + fullName: string; +} + +expectType>( + collection.aggregate().project({ + fullName: { $concat: ['$firstName', ' ', '$lastName'] } + }) +); + +interface DepartmentSummary { + _id: string; + count: number; +} + +expectType>( + collection.aggregate().group({ + _id: '$department', + count: { $sum: 1 } + }) +); diff --git a/test/types/community/collection/bulkWrite.test-d.ts b/test/types/community/collection/bulkWrite.test-d.ts new file mode 100644 index 00000000000..effe9a782ac --- /dev/null +++ b/test/types/community/collection/bulkWrite.test-d.ts @@ -0,0 +1,254 @@ +import { expectError } from 'tsd'; +import { MongoClient, ObjectId } from '../../../../src/index'; + +// collection.bulkWrite tests +const client = new MongoClient(''); +const db = client.db('test'); + +interface SubTestSchema { + field1: string; + field2: string; +} + +type FruitTypes = 'apple' | 'pear'; + +// test with collection type +interface TestSchema { + _id: ObjectId; + stringField: string; + numberField: number; + optionalNumberField?: number; + dateField: Date; + fruitTags: string[]; + maybeFruitTags?: FruitTypes[]; + readonlyFruitTags: ReadonlyArray; + subInterfaceField: SubTestSchema; + subInterfaceArray: SubTestSchema[]; +} +const collectionType = db.collection('test.update'); + +const testDocument: TestSchema = { + _id: new ObjectId(), + stringField: 'foo', + numberField: 123, + dateField: new Date(), + fruitTags: ['apple'], + readonlyFruitTags: ['pear'], + subInterfaceField: { + field1: 'foo', + field2: 'bar' + }, + subInterfaceArray: [] +}; +const { _id, ...testDocumentWithoutId } = testDocument; + +// insertOne + +collectionType.bulkWrite([ + { + insertOne: { + document: testDocument + } + } +]); +collectionType.bulkWrite([ + { + insertOne: { + document: testDocumentWithoutId + } + } +]); +expectError( + collectionType.bulkWrite([{ insertOne: { document: { ...testDocument, stringField: 123 } } }]) +); + +// updateOne + +collectionType.bulkWrite([ + { + updateOne: { + filter: { stringField: 'foo' }, + update: { + $set: { + numberField: 123, + 'dot.notation': true + } + } + } + } +]); +collectionType.bulkWrite([ + { + updateOne: { + filter: {}, + update: { + $set: { + optionalNumberField: 123, + fruitTags: ['apple'] + } + }, + upsert: true + } + } +]); + +expectError( + collectionType.bulkWrite([ + { updateOne: { filter: { stringField: 123 }, update: { $set: { numberField: 123 } } } } + ]) +); + +collectionType.bulkWrite([ + { updateOne: { filter: { stringField: 'foo' }, update: { $set: { numberField: 'bar' } } } } +]); + +collectionType.bulkWrite([ + { updateOne: { filter: { stringField: 'foo' }, update: { 'dot.notation': true } } } +]); + +// updateMany + +collectionType.bulkWrite([ + { + updateMany: { + filter: { stringField: 'foo' }, + update: { + $set: { + numberField: 123, + 'dot.notation': true + } + } + } + } +]); +collectionType.bulkWrite([ + { + updateMany: { + filter: {}, + update: { + $set: { + optionalNumberField: 123, + fruitTags: ['apple'] + } + }, + upsert: true + } + } +]); + +expectError( + collectionType.bulkWrite([ + { updateMany: { filter: { stringField: 123 }, update: { $set: { numberField: 123 } } } } + ]) +); + +collectionType.bulkWrite([ + { updateMany: { filter: { stringField: 'foo' }, update: { $set: { numberField: 'bar' } } } } +]); + +collectionType.bulkWrite([ + { updateMany: { filter: { stringField: 'foo' }, update: { 'dot.notation': true } } } +]); + +// replaceOne + +collectionType.bulkWrite([ + { + replaceOne: { + filter: { stringField: 'foo' }, + replacement: testDocument + } + } +]); +collectionType.bulkWrite([ + { + replaceOne: { + filter: {}, + replacement: testDocument, + upsert: true + } + } +]); + +expectError( + collectionType.bulkWrite([ + { replaceOne: { filter: { stringField: 123 }, replacement: testDocument } } + ]) +); + +expectError( + collectionType.bulkWrite([ + { + replaceOne: { + filter: { stringField: 'foo' }, + replacement: { ...testDocument, stringField: 123 } + } + } + ]) +); + +// deleteOne + +collectionType.bulkWrite([ + { + deleteOne: { + filter: { stringField: 'foo' } + } + } +]); + +expectError(collectionType.bulkWrite([{ deleteOne: { filter: { stringField: 123 } } }])); + +// deleteMany + +collectionType.bulkWrite([ + { + deleteMany: { + filter: { stringField: 'foo' } + } + } +]); + +expectError(collectionType.bulkWrite([{ deleteMany: { filter: { stringField: 123 } } }])); + +// combined + +collectionType.bulkWrite([ + { + insertOne: { + document: testDocument + } + }, + { + updateMany: { + filter: { stringField: 'foo' }, + update: { + $set: { numberField: 123 } + } + } + }, + { + updateMany: { + filter: { stringField: 'foo' }, + update: { + $set: { numberField: 123 } + } + } + }, + { + replaceOne: { + filter: { stringField: 'foo' }, + replacement: testDocument + } + }, + { + deleteOne: { + filter: { stringField: 'foo' } + } + }, + { + deleteMany: { + filter: { stringField: 'foo' } + } + } +]); diff --git a/test/types/community/collection/count.test-d.ts b/test/types/community/collection/count.test-d.ts new file mode 100644 index 00000000000..be52838654d --- /dev/null +++ b/test/types/community/collection/count.test-d.ts @@ -0,0 +1,10 @@ +import { expectType } from 'tsd'; +import { MongoClient } from '../../../../src/index'; + +// test collection.countDocuments and collection.count functions +const client = new MongoClient(''); +const collection = client.db('test').collection('test.count'); + +expectType(await collection.countDocuments()); +expectType(await collection.countDocuments({ foo: 1 })); +expectType(await collection.countDocuments({ foo: 1 }, { limit: 10 })); diff --git a/test/types/community/collection/distinct.test-d.ts b/test/types/community/collection/distinct.test-d.ts new file mode 100644 index 00000000000..f3cc76b6cd4 --- /dev/null +++ b/test/types/community/collection/distinct.test-d.ts @@ -0,0 +1,58 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { expectType } from 'tsd'; +import { MongoClient, ObjectId } from '../../../../src/index'; + +// test collection.distinct functions +interface Collection { + foo: number; + nested: { num: number }; + array: string[]; + readonlyArray: ReadonlyArray; + test: string; +} + +const client = new MongoClient(''); +const collection = client.db('test').collection('test.distinct'); + +expectType(await collection.distinct('test')); +expectType(await collection.distinct('test', { foo: 1 })); +expectType(await collection.distinct('test', { foo: 1 }, { maxTimeMS: 400 })); + +collection.distinct('_id', (err, fields) => { + // callbacks always have the second argument undefined + // incase of error + expectType(fields); +}); +collection.distinct('_id', { foo: 1 }, (err, fields) => { + expectType(fields); +}); +collection.distinct('_id', { foo: 1 }, { maxTimeMS: 400 }, (err, fields) => { + expectType(fields); +}); + +expectType(await collection.distinct('_id')); +expectType(await collection.distinct('_id', { foo: 1 })); +expectType(await collection.distinct('_id', { foo: 1 }, { maxTimeMS: 400 })); + +collection.distinct('a.d'); +collection.distinct('nested.num', (err, fields) => { + expectType(fields); +}); +collection.distinct('nested.num', { foo: 1 }, (err, fields) => { + expectType(fields); +}); +collection.distinct('nested.num', { foo: 1 }, { maxTimeMS: 400 }, (err, fields) => { + expectType(fields); +}); + +expectType(await collection.distinct('nested.num')); +expectType(await collection.distinct('nested.num', { foo: 1 })); +expectType(await collection.distinct('nested.num', { foo: 1 }, { maxTimeMS: 400 })); + +expectType(await collection.distinct('array')); +expectType(await collection.distinct('array', { foo: 1 })); +expectType(await collection.distinct('array', { foo: 1 }, { maxTimeMS: 400 })); + +expectType(await collection.distinct('readonlyArray')); +expectType(await collection.distinct('readonlyArray', { foo: 1 })); +expectType(await collection.distinct('readonlyArray', { foo: 1 }, { maxTimeMS: 400 })); diff --git a/test/types/community/collection/filterQuery.test-d.ts b/test/types/community/collection/filterQuery.test-d.ts new file mode 100644 index 00000000000..ebd9b646cf6 --- /dev/null +++ b/test/types/community/collection/filterQuery.test-d.ts @@ -0,0 +1,228 @@ +import { BSONRegExp, Decimal128, ObjectId } from 'bson'; +import { expectAssignable, expectNotType, expectType } from 'tsd'; +import { Filter, MongoClient } from '../../../../src'; + +/** + * test the Filter type using collection.find() method + * MongoDB uses Filter type for every method that performs a document search + * for example: findX, updateX, deleteX, distinct, countDocuments + * So we don't add duplicate tests for every collection method and only use find + */ +const client = new MongoClient(''); +const db = client.db('test'); + +/** + * Test the generic Filter using collection.find() method + */ + +// a collection model for all possible MongoDB BSON types and TypeScript types +interface PetModel { + _id: ObjectId; // ObjectId field + name?: string; // optional field + family: string; // string field + age: number; // number field + type: 'dog' | 'cat' | 'fish'; // union field + isCute: boolean; // boolean field + bestFriend?: PetModel; // object field (Embedded/Nested Documents) + createdAt: Date; // date field + treats: string[]; // array of string + playTimePercent: Decimal128; // bson Decimal128 type + readonly friends?: ReadonlyArray; // readonly array of objects + playmates?: PetModel[]; // writable array of objects +} + +const spot = { + _id: new ObjectId('577fa2d90c4cc47e31cf4b6f'), + name: 'Spot', + family: 'Andersons', + age: 2, + type: 'dog' as const, + isCute: true, + createdAt: new Date(), + treats: ['kibble', 'bone'], + playTimePercent: new Decimal128('0.999999') +}; + +expectAssignable(spot); + +const collectionT = db.collection('test.filterQuery'); + +// Assert that collection.find uses the Filter helper like so: +const filter: Filter = {}; +expectType[0]>(filter); +// Now tests below can directly test the Filter helper, and are implicitly checking collection.find + +/** + * test simple field queries e.g. `{ name: 'Spot' }` + */ +/// it should query __string__ fields +expectType(await collectionT.find({ name: 'Spot' }).toArray()); +// it should query string fields by regex +expectType(await collectionT.find({ name: /Blu/i }).toArray()); +// it should query string fields by RegExp object, and bson regex +expectType(await collectionT.find({ name: new RegExp('MrMeow', 'i') }).toArray()); +expectType(await collectionT.find({ name: new BSONRegExp('MrMeow', 'i') }).toArray()); +/// it should not accept wrong types for string fields +expectNotType>({ name: 23 }); +expectNotType>({ name: { suffix: 'Jr' } }); +expectNotType>({ name: ['Spot'] }); + +/// it should query __number__ fields +await collectionT.find({ age: 12 }).toArray(); +/// it should not accept wrong types for number fields +expectNotType>({ age: /12/i }); // it cannot query number fields by regex +expectNotType>({ age: '23' }); +expectNotType>({ age: { prefix: 43 } }); +expectNotType>({ age: [23, 43] }); + +/// it should query __nested document__ fields only by exact match +// TODO: we currently cannot enforce field order but field order is important for mongo +await collectionT.find({ bestFriend: spot }).toArray(); +/// nested documents query should contain all required fields +expectNotType>({ bestFriend: { family: 'Andersons' } }); +/// it should not accept wrong types for nested document fields +expectNotType>({ bestFriend: 21 }); +expectNotType>({ bestFriend: 'Andersons' }); +expectNotType>({ bestFriend: [spot] }); +expectNotType>({ bestFriend: [{ family: 'Andersons' }] }); + +/// it should query __array__ fields by exact match +await collectionT.find({ treats: ['kibble', 'bone'] }).toArray(); +/// it should query __array__ fields by element type +expectType(await collectionT.find({ treats: 'kibble' }).toArray()); +expectType(await collectionT.find({ treats: /kibble/i }).toArray()); +expectType(await collectionT.find({ friends: spot }).toArray()); +/// it should not query array fields by wrong types +expectNotType>({ treats: 12 }); +expectNotType>({ friends: { name: 'not a full model' } }); + +/// it should accept MongoDB ObjectId and Date as query parameter +await collectionT.find({ createdAt: new Date() }).toArray(); +await collectionT.find({ _id: new ObjectId() }).toArray(); +/// it should not accept other types for ObjectId and Date +expectNotType>({ createdAt: { a: 12 } }); +expectNotType>({ createdAt: spot }); +expectNotType>({ _id: '577fa2d90c4cc47e31cf4b6f' }); +expectNotType>({ _id: { a: 12 } }); + +/** + * test comparison query operators + */ +/// $eq $ne $gt $gte $lt $lte queries should behave exactly like simple queries above +await collectionT.find({ name: { $eq: 'Spot' } }).toArray(); +await collectionT.find({ name: { $eq: /Spot/ } }).toArray(); +await collectionT.find({ type: { $eq: 'dog' } }).toArray(); +await collectionT.find({ age: { $gt: 12, $lt: 13 } }).toArray(); +await collectionT.find({ treats: { $eq: 'kibble' } }).toArray(); +await collectionT.find({ scores: { $gte: 23 } }).toArray(); +await collectionT.find({ createdAt: { $lte: new Date() } }).toArray(); +await collectionT.find({ friends: { $ne: spot } }).toArray(); +/// it should not accept wrong queries +expectNotType>({ name: { $ne: 12 } }); +expectNotType>({ gender: { $eq: '123' } }); +expectNotType>({ createdAt: { $lte: '1232' } }); +/// it should not accept undefined query selectors in query object +expectNotType>({ age: { $undefined: 12 } }); + +/// it should query simple fields using $in and $nin selectors +await collectionT.find({ name: { $in: ['Spot', 'MrMeow', 'Bubbles'] } }).toArray(); +await collectionT.find({ age: { $in: [12, 13] } }).toArray(); +await collectionT.find({ friends: { $in: [spot] } }).toArray(); +await collectionT.find({ createdAt: { $nin: [new Date()] } }).toArray(); +/// it should query array fields using $in and $nin selectors +await collectionT.find({ treats: { $in: ['kibble', 'bone', 'tuna'] } }).toArray(); +await collectionT.find({ treats: { $in: [/kibble/, /bone/, /tuna/] } }).toArray(); +/// it should not accept wrong params for $in and $nin selectors +expectNotType>({ name: { $in: ['Spot', 32, 42] } }); +expectNotType>({ age: { $in: [/12/, 12] } }); +expectNotType>({ createdAt: { $nin: [12] } }); +expectNotType>({ friends: { $in: [{ name: 'MrMeow' }] } }); +expectNotType>({ treats: { $in: [{ $eq: 21 }] } }); + +/** + * test logical query operators + */ +/// it should accept any query selector for __$not operator__ +await collectionT.find({ name: { $not: { $eq: 'Spot' } } }).toArray(); +/// it should accept regex for string fields +await collectionT.find({ name: { $not: /Hi/i } }).toArray(); +await collectionT.find({ treats: { $not: /Hi/ } }).toArray(); +/// it should not accept simple queries in $not operator +expectNotType>({ name: { $not: 'Spot' } }); +/// it should not accept regex for non strings +expectNotType>({ age: { $not: /sdsd/ } }); + +/// it should accept any filter query for __$and, $or, $nor operator__ +await collectionT.find({ $and: [{ name: 'Spot' }] }).toArray(); +await collectionT.find({ $and: [{ name: 'Spot' }, { age: { $in: [12, 14] } }] }).toArray(); +await collectionT.find({ $or: [{ name: /Spot/i }, { treats: 's12' }] }).toArray(); +await collectionT.find({ $nor: [{ name: { $ne: 'Spot' } }] }).toArray(); +/// it should not accept __$and, $or, $nor operator__ as non-root query +expectNotType>({ name: { $or: ['Spot', 'Bubbles'] } }); +/// it should not accept single objects for __$and, $or, $nor operator__ query +expectNotType>({ $and: { name: 'Spot' } }); + +/** + * test 'element' query operators + */ +/// it should query using $exists +await collectionT.find({ name: { $exists: true } }).toArray(); +await collectionT.find({ name: { $exists: false } }).toArray(); +/// it should not query $exists by wrong values +expectNotType>({ name: { $exists: '' } }); +expectNotType>({ name: { $exists: 'true' } }); + +/** + * test evaluation query operators + */ +/// it should query using $regex +await collectionT.find({ name: { $regex: /12/i } }).toArray(); +/// it should query using $regex and $options +await collectionT.find({ name: { $regex: /12/, $options: 'i' } }).toArray(); +/// it should not accept $regex for none string fields +expectNotType>({ age: { $regex: /12/ } }); +expectNotType>({ age: { $options: '3' } }); + +/// it should query using $mod +await collectionT.find({ age: { $mod: [12, 2] } }).toArray(); +/// it should not accept $mod for none number fields +expectNotType>({ name: { $mod: [12, 2] } }); +/// it should not accept $mod with less/more than 2 elements +expectNotType>({ age: { $mod: [12, 2, 2] } }); +expectNotType>({ age: { $mod: [12] } }); +expectNotType>({ age: { $mod: [] } }); + +/// it should fulltext search using $text +await collectionT.find({ $text: { $search: 'Hello' } }).toArray(); +await collectionT.find({ $text: { $search: 'Hello', $caseSensitive: true } }).toArray(); +/// it should fulltext search only by string +expectNotType>({ $text: { $search: 21, $caseSensitive: 'true' } }); +expectNotType>({ $text: { $search: { name: 'MrMeow' } } }); +expectNotType>({ $text: { $search: /regex/g } }); + +/// it should query using $where +await collectionT.find({ $where: 'function() { return true }' }).toArray(); +await collectionT + .find({ + $where: function () { + expectType(this); + return this.name === 'MrMeow'; + } + }) + .toArray(); +/// it should not fail when $where is not Function or String +expectNotType>({ $where: 12 }); +expectNotType>({ $where: /regex/g }); + +/** + * test array query operators + */ +/// it should query array fields +await collectionT.find({ treats: { $size: 2 } }).toArray(); +await collectionT.find({ treats: { $all: ['kibble', 'bone'] } }).toArray(); +await collectionT.find({ friends: { $elemMatch: { name: 'MrMeow' } } }).toArray(); +await collectionT.find({ playmates: { $elemMatch: { name: 'MrMeow' } } }).toArray(); +/// it should not query non array fields +expectNotType>({ name: { $all: ['world', 'world'] } }); +expectNotType>({ age: { $elemMatch: [1, 2] } }); +expectNotType>({ type: { $size: 2 } }); diff --git a/test/types/community/collection/findX.test-d.ts b/test/types/community/collection/findX.test-d.ts new file mode 100644 index 00000000000..4677cd1d4b3 --- /dev/null +++ b/test/types/community/collection/findX.test-d.ts @@ -0,0 +1,138 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { expectNotType, expectType } from 'tsd'; +import { FindCursor, FindOptions, MongoClient, Document } from '../../../../src'; +import type { PropExists } from '../../utility_types'; + +// collection.findX tests +const client = new MongoClient(''); +const db = client.db('test'); +const collection = db.collection('test.find'); + +// Locate all the entries using find +collection.find({}).toArray((err, fields) => { + expectType(fields); +}); + +// test with collection type +interface TestModel { + stringField: string; + numberField?: number; + fruitTags: string[]; + readonlyFruitTags: readonly string[]; +} + +const collectionT = db.collection('testCollection'); +await collectionT.find({ + $and: [{ numberField: { $gt: 0 } }, { numberField: { $lt: 100 } }], + readonlyFruitTags: { $all: ['apple', 'pear'] } +}); +expectType>(collectionT.find({})); + +await collectionT.findOne( + {}, + { + projection: {}, + sort: {} + } +); + +const optionsWithComplexProjection: FindOptions = { + projection: { + stringField: { $meta: 'textScore' }, + fruitTags: { $min: 'fruitTags' }, + max: { $max: ['$max', 0] } + }, + sort: { stringField: -1, text: { $meta: 'textScore' }, notExistingField: -1 } +}; + +await collectionT.findOne({}, optionsWithComplexProjection); + +// test with discriminated union type +interface DUModelEmpty { + type: 'empty'; +} +interface DUModelString { + type: 'string'; + value: string; +} +type DUModel = DUModelEmpty | DUModelString; +const collectionDU = db.collection('testDU'); +const duValue = await collectionDU.findOne({}); +if (duValue && duValue.type === 'string') { + // we can still narrow the result + // permitting fetching other keys that haven't been asserted in the if stmt + expectType(duValue.value); +} + +// collection.findX() generic tests +interface Bag { + cost: number; + color: string; +} + +const collectionBag = db.collection('bag'); + +const cursor: FindCursor = collectionBag.find({ color: 'black' }); + +cursor.toArray((err, bags) => { + expectType(bags); +}); + +cursor.forEach( + bag => { + expectType(bag); + }, + () => { + return null; + } +); + +expectType( + await collectionBag.findOne({ color: 'red' }, { projection: { cost: 1 } }) +); + +const overrideFind = await collectionBag.findOne<{ cost: number }>( + { color: 'white' }, + { projection: { cost: 1 } } +); +expectType>(false); + +// Overriding findOne, makes the return that exact type +expectType<{ cost: number } | undefined>( + await collectionBag.findOne<{ cost: number }>({ color: 'red' }, { projection: { cost: 1 } }) +); + +interface Car { + make: string; +} +interface House { + windows: number; +} + +const car = db.collection('car'); + +expectNotType(await car.findOne({})); + +interface Car { + make: string; +} + +function printCar(car: Car | undefined) { + console.log(car ? `A car of ${car.make} make` : 'No car'); +} + +const options: FindOptions = {}; +const optionsWithProjection: FindOptions = { + projection: { + make: 1 + } +}; + +expectNotType>({ + projection: { + make: 'invalid' + } +}); + +printCar(await car.findOne({}, options)); +printCar(await car.findOne({}, optionsWithProjection)); diff --git a/test/types/community/collection/insertX.test-d.ts b/test/types/community/collection/insertX.test-d.ts new file mode 100644 index 00000000000..1ab3d04e64d --- /dev/null +++ b/test/types/community/collection/insertX.test-d.ts @@ -0,0 +1,242 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { expectError, expectNotType, expectType } from 'tsd'; +import { MongoClient, ObjectId, OptionalId } from '../../../../src'; +import type { PropExists } from '../../utility_types'; + +// test collection.insertX functions +const client = new MongoClient(''); +const db = client.db('test'); + +const anyCollection = db.collection('test-any-type'); + +// should accept any _id type when it is not provided in Schema + +/** + * test no collection type ("any") + */ +// test insertOne results +expectType<{ acknowledged: boolean; insertedId: ObjectId }>( + await anyCollection.insertOne({ a: 2 }) +); +// test insertMany results +expectType<{ + acknowledged: boolean; + insertedIds: { [key: number]: ObjectId }; + insertedCount: number; +}>(await anyCollection.insertMany([{ a: 2 }])); + +// should accept _id with ObjectId type +const insertManyWithIdResult = await anyCollection.insertMany([{ _id: new ObjectId(), a: 2 }]); +expectType(insertManyWithIdResult.insertedCount); +expectType<{ [key: number]: ObjectId }>(insertManyWithIdResult.insertedIds); + +// should accept any _id type when it is not provided in Schema +// TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// await anyCollection.insertMany([{ _id: 12, a: 2 }]); + +/** + * test with collection type + */ +interface TestModel { + stringField: string; + numberField?: number; + fruitTags: string[]; +} +type TestModelWithId = TestModel & { _id: ObjectId }; +const collection = db.collection('testCollection'); + +const testDoc: OptionalId = { stringField: 'a', fruitTags: [] }; +expectType[0]>(testDoc); + +const resultOne = await collection.insertOne({ + stringField: 'hola', + fruitTags: ['Strawberry'] +}); +const resultMany = await collection.insertMany([ + { stringField: 'hola', fruitTags: ['Apple', 'Lemon'] }, + { stringField: 'hola', numberField: 1, fruitTags: [] } +]); + +// test results type +// should add a _id field with ObjectId type if it does not exist on collection type +expectType>(false); +expectType>(false); + +expectType<{ [key: number]: ObjectId }>(resultMany.insertedIds); +expectType(resultOne.insertedId); + +/** + * test custom _id type + */ +interface TestModelWithCustomId { + _id: number; + stringField: string; + numberField?: number; + fruitTags: string[]; +} +const collectionWithId = db.collection('testCollection'); + +const resultOneWithId = await collectionWithId.insertOne({ + _id: 1, + stringField: 'hola', + fruitTags: ['Strawberry'] +}); +const resultManyWithId = await collectionWithId.insertMany([ + { _id: 2, stringField: 'hola', fruitTags: ['Apple', 'Lemon'] }, + { _id: 2, stringField: 'hola', numberField: 1, fruitTags: [] } +]); + +// should demand _id if it is not ObjectId +expectError(await collectionWithId.insertOne({ stringField: 'hola', fruitTags: ['Strawberry'] })); +expectError( + await collectionWithId.insertMany([ + { stringField: 'hola', fruitTags: ['Apple', 'Lemon'] }, + { _id: 2, stringField: 'hola', numberField: 1, fruitTags: [] } + ]) +); + +// should not accept wrong _id type +expectError( + await collectionWithId.insertMany([ + { _id: new ObjectId(), stringField: 'hola', fruitTags: ['Apple', 'Lemon'] }, + { _id: 2, stringField: 'hola', numberField: 1, fruitTags: [] } + ]) +); + +expectType>(false); +expectType(resultOneWithId.insertedId); +expectType<{ [key: number]: number }>(resultManyWithId.insertedIds); + +/** + * test custom _id type (ObjectId) + */ +interface TestModelWithCustomObjectId { + _id: ObjectId; + stringField: string; + numberField?: number; + fruitTags: string[]; +} +const collectionWithObjectId = db.collection('testCollection'); + +// should accept ObjectId +await collectionWithObjectId.insertOne({ + _id: new ObjectId(), + stringField: 'hola', + numberField: 23, + fruitTags: ['hi'] +}); +// should not demand _id if it is ObjectId +await collectionWithObjectId.insertOne({ + stringField: 'hola', + numberField: 23, + fruitTags: ['hi'] +}); + +/** + * test indexed types + */ +interface IndexTypeTestModel { + stringField: string; + numberField?: number; + [key: string]: any; +} +const indexTypeCollection1 = db.collection('testCollection'); + +const indexTypeResult1 = await indexTypeCollection1.insertOne({ + stringField: 'hola', + numberField: 23, + randomField: [34, 54, 32], + randomFiel2: 32 +}); +const indexTypeResultMany1 = await indexTypeCollection1.insertMany([ + { stringField: 'hola', numberField: 0 }, + { _id: new ObjectId(), stringField: 'hola', randomField: [34, 54, 32] } +]); + +// should not accept wrong _id type +expectError( + await indexTypeCollection1.insertMany([{ _id: 12, stringField: 'hola', numberField: 0 }]) +); +// should demand missing fields +expectError(await indexTypeCollection1.insertMany([{ randomField: [34, 54, 32] }])); + +expectType>(false); + +expectType(indexTypeResult1.insertedId); +expectType<{ [key: number]: ObjectId }>(indexTypeResultMany1.insertedIds); + +/** + * test indexed types with custom _id (not ObjectId) + */ +interface IndexTypeTestModelWithId { + _id: number; + stringField: string; + numberField?: number; + [key: string]: any; +} +const indexTypeCollection2 = db.collection('testCollection'); + +const indexTypeResult2 = await indexTypeCollection2.insertOne({ + _id: 1, + stringField: 'hola', + numberField: 23, + randomField: [34, 54, 32], + randomFiel2: 32 +}); +const indexTypeResultMany2 = await indexTypeCollection2.insertMany([ + { _id: 1, stringField: 'hola', numberField: 0 }, + { _id: 2, stringField: 'hola', randomField: [34, 54, 32] } +]); + +// should only accept _id type provided in Schema +expectError( + await indexTypeCollection2.insertOne({ + _id: '12', + stringField: 'hola', + numberField: 23, + randomField: [34, 54, 32], + randomFiel2: 32 + }) +); + +expectError( + await indexTypeCollection2.insertMany([ + { _id: '1', stringField: 'hola', numberField: 0 }, + { _id: 2, stringField: 'hola', randomField: [34, 54, 32] } + ]) +); + +// should demand _id if it is defined and is not ObjectId +expectNotType>({ + stringField: 'hola', + numberField: 23, + randomField: [34, 54, 32], + randomFiel2: 32 +}); + +expectError( + await indexTypeCollection2.insertMany([ + { stringField: 'hola', numberField: 0 }, + { _id: 12, stringField: 'hola', randomField: [34, 54, 32] } + ]) +); + +expectType>(false); +expectType>(false); + +expectType(indexTypeResult2.insertedId); +expectType<{ [key: number]: number }>(indexTypeResultMany2.insertedIds); + +/** + * test indexed types with custom _id (ObjectId) + */ +interface IndexTypeTestModelWithObjectId { + _id: ObjectId; + stringField: string; + numberField?: number; + [key: string]: any; +} +const indexTypeCollection3 = db.collection('testCollection'); + +// TODO: should not demand _id if it is ObjectId +// await indexTypeCollection3.insertOne({ stringField: 'hola', numberField: 23, randomField: [34, 54, 32], randomFiel2: 32 }); diff --git a/test/types/community/collection/mapReduce.test-d.ts b/test/types/community/collection/mapReduce.test-d.ts new file mode 100644 index 00000000000..1e1ea356046 --- /dev/null +++ b/test/types/community/collection/mapReduce.test-d.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { MongoClient, ReduceFunction } from '../../../../src/index'; + +// https://docs.mongodb.com/manual/core/map-reduce/ +// Declare emit function to be called inside map function +declare function emit(key: any, value: any): void; + +interface TestMapReduceSchema { + cust_id: string; + amount: number; + status: string; +} + +function testCollectionMapFunction(this: TestMapReduceSchema) { + emit(this.cust_id, this.amount); +} + +function testCollectionReduceFunction(key: string, values: any[]): number { + return values.length; +} + +const client = new MongoClient(''); +client + .db('test') + .collection('test-mapReduce-collection') + .mapReduce(testCollectionMapFunction, testCollectionReduceFunction); diff --git a/test/types/community/collection/updateX.test-d.ts b/test/types/community/collection/updateX.test-d.ts new file mode 100644 index 00000000000..61e4ae0df4b --- /dev/null +++ b/test/types/community/collection/updateX.test-d.ts @@ -0,0 +1,292 @@ +import { + Decimal128, + Double, + Int32, + Long, + ObjectId, + UpdateQuery, + Timestamp, + MongoClient +} from '../../../../src/index'; + +// collection.updateX tests +const client = new MongoClient(''); +const db = client.db('test'); + +interface SubTestModel { + _id: ObjectId; + field1: string; + field2?: string; +} + +type FruitTypes = 'apple' | 'pear'; + +// test with collection type +interface TestModel { + stringField: string; + numberField: number; + decimal128Field: Decimal128; + doubleField: Double; + int32Field: Int32; + longField: Long; + optionalNumberField?: number; + dateField: Date; + otherDateField: Date; + oneMoreDateField: Date; + fruitTags: string[]; + readonlyFruitTags: ReadonlyArray; + maybeFruitTags?: FruitTypes[]; + subInterfaceField: SubTestModel; + subInterfaceArray: SubTestModel[]; + timestampField: Timestamp; +} +const collectionTType = db.collection('test.update'); + +function buildUpdateQuery(updateQuery: UpdateQuery): UpdateQuery { + return updateQuery; +} + +const justASample = buildUpdateQuery({ $currentDate: { dateField: true } }); + +buildUpdateQuery({ $currentDate: { dateField: true } }); +buildUpdateQuery({ $currentDate: { otherDateField: { $type: 'date' } } }); +buildUpdateQuery({ $currentDate: { otherDateField: { $type: 'timestamp' } } }); +buildUpdateQuery({ $currentDate: { timestampField: { $type: 'timestamp' } } }); +buildUpdateQuery({ $currentDate: { 'dot.notation': true } }); +buildUpdateQuery({ $currentDate: { 'subInterfaceArray.$': true } }); +buildUpdateQuery({ $currentDate: { 'subInterfaceArray.$[bla]': { $type: 'date' } } }); +buildUpdateQuery({ $currentDate: { 'subInterfaceArray.$[]': { $type: 'timestamp' } } }); + +// buildUpdateQuery({ $currentDate: { stringField: true } }); // stringField is not a date Field + +buildUpdateQuery({ $inc: { numberField: 1 } }); +buildUpdateQuery({ $inc: { decimal128Field: Decimal128.fromString('1.23') } }); +buildUpdateQuery({ $inc: { doubleField: new Double(1.23) } }); +buildUpdateQuery({ $inc: { int32Field: new Int32(10) } }); +buildUpdateQuery({ $inc: { longField: Long.fromString('999') } }); +buildUpdateQuery({ $inc: { optionalNumberField: 1 } }); +buildUpdateQuery({ $inc: { 'dot.notation': 2 } }); +buildUpdateQuery({ $inc: { 'subInterfaceArray.$': -10 } }); +buildUpdateQuery({ $inc: { 'subInterfaceArray.$[bla]': 40 } }); +buildUpdateQuery({ $inc: { 'subInterfaceArray.$[]': 1000.2 } }); + +buildUpdateQuery({ $min: { numberField: 1 } }); +buildUpdateQuery({ $min: { decimal128Field: Decimal128.fromString('1.23') } }); +buildUpdateQuery({ $min: { doubleField: new Double(1.23) } }); +buildUpdateQuery({ $min: { int32Field: new Int32(10) } }); +buildUpdateQuery({ $min: { longField: Long.fromString('999') } }); +buildUpdateQuery({ $min: { stringField: 'a' } }); +buildUpdateQuery({ $min: { 'dot.notation': 2 } }); +buildUpdateQuery({ $min: { 'subInterfaceArray.$': 'string' } }); +buildUpdateQuery({ $min: { 'subInterfaceArray.$[bla]': 40 } }); +buildUpdateQuery({ $min: { 'subInterfaceArray.$[]': 1000.2 } }); + +// buildUpdateQuery({ $min: { numberField: 'a' } }); // Matches the type of the keys + +buildUpdateQuery({ $max: { numberField: 1 } }); +buildUpdateQuery({ $max: { decimal128Field: Decimal128.fromString('1.23') } }); +buildUpdateQuery({ $max: { doubleField: new Double(1.23) } }); +buildUpdateQuery({ $max: { int32Field: new Int32(10) } }); +buildUpdateQuery({ $max: { longField: Long.fromString('999') } }); +buildUpdateQuery({ $max: { stringField: 'a' } }); +buildUpdateQuery({ $max: { 'dot.notation': 2 } }); +buildUpdateQuery({ $max: { 'subInterfaceArray.$': -10 } }); +buildUpdateQuery({ $max: { 'subInterfaceArray.$[bla]': 40 } }); +buildUpdateQuery({ $max: { 'subInterfaceArray.$[]': 1000.2 } }); + +// buildUpdateQuery({ $min: { numberField: 'a' } }); // Matches the type of the keys + +buildUpdateQuery({ $mul: { numberField: 1 } }); +buildUpdateQuery({ $mul: { decimal128Field: Decimal128.fromString('1.23') } }); +buildUpdateQuery({ $mul: { doubleField: new Double(1.23) } }); +buildUpdateQuery({ $mul: { int32Field: new Int32(10) } }); +buildUpdateQuery({ $mul: { longField: Long.fromString('999') } }); +buildUpdateQuery({ $mul: { optionalNumberField: 1 } }); +buildUpdateQuery({ $mul: { 'dot.notation': 2 } }); +buildUpdateQuery({ $mul: { 'subInterfaceArray.$': -10 } }); +buildUpdateQuery({ $mul: { 'subInterfaceArray.$[bla]': 40 } }); +buildUpdateQuery({ $mul: { 'subInterfaceArray.$[]': 1000.2 } }); + +buildUpdateQuery({ $set: { numberField: 1 } }); +buildUpdateQuery({ $set: { decimal128Field: Decimal128.fromString('1.23') } }); +buildUpdateQuery({ $set: { doubleField: new Double(1.23) } }); +buildUpdateQuery({ $set: { int32Field: new Int32(10) } }); +buildUpdateQuery({ $set: { longField: Long.fromString('999') } }); +buildUpdateQuery({ $set: { stringField: 'a' } }); +// $ExpectError +buildUpdateQuery({ $set: { stringField: 123 } }); +buildUpdateQuery({ $set: { 'dot.notation': 2 } }); +buildUpdateQuery({ $set: { 'subInterfaceArray.$': -10 } }); +buildUpdateQuery({ $set: { 'subInterfaceArray.$[bla]': 40 } }); +buildUpdateQuery({ $set: { 'subInterfaceArray.$[]': 1000.2 } }); + +buildUpdateQuery({ $setOnInsert: { numberField: 1 } }); +buildUpdateQuery({ $setOnInsert: { decimal128Field: Decimal128.fromString('1.23') } }); +buildUpdateQuery({ $setOnInsert: { doubleField: new Double(1.23) } }); +buildUpdateQuery({ $setOnInsert: { int32Field: new Int32(10) } }); +buildUpdateQuery({ $setOnInsert: { longField: Long.fromString('999') } }); +buildUpdateQuery({ $setOnInsert: { stringField: 'a' } }); +// $ExpectError +buildUpdateQuery({ $setOnInsert: { stringField: 123 } }); +buildUpdateQuery({ $setOnInsert: { 'dot.notation': 2 } }); +buildUpdateQuery({ $setOnInsert: { 'subInterfaceArray.$': -10 } }); +buildUpdateQuery({ $setOnInsert: { 'subInterfaceArray.$[bla]': 40 } }); +buildUpdateQuery({ $setOnInsert: { 'subInterfaceArray.$[]': 1000.2 } }); + +buildUpdateQuery({ $unset: { numberField: '' } }); +buildUpdateQuery({ $unset: { decimal128Field: '' } }); +buildUpdateQuery({ $unset: { doubleField: '' } }); +buildUpdateQuery({ $unset: { int32Field: '' } }); +buildUpdateQuery({ $unset: { longField: '' } }); +buildUpdateQuery({ $unset: { dateField: '' } }); +buildUpdateQuery({ $unset: { 'dot.notation': '' } }); +buildUpdateQuery({ $unset: { 'subInterfaceArray.$': '' } }); +buildUpdateQuery({ $unset: { 'subInterfaceArray.$[bla]': '' } }); +buildUpdateQuery({ $unset: { 'subInterfaceArray.$[]': '' } }); + +buildUpdateQuery({ $unset: { numberField: 1 } }); +buildUpdateQuery({ $unset: { decimal128Field: 1 } }); +buildUpdateQuery({ $unset: { doubleField: 1 } }); +buildUpdateQuery({ $unset: { int32Field: 1 } }); +buildUpdateQuery({ $unset: { longField: 1 } }); +buildUpdateQuery({ $unset: { dateField: 1 } }); +buildUpdateQuery({ $unset: { 'dot.notation': 1 } }); +buildUpdateQuery({ $unset: { 'subInterfaceArray.$': 1 } }); +buildUpdateQuery({ $unset: { 'subInterfaceArray.$[bla]': 1 } }); +buildUpdateQuery({ $unset: { 'subInterfaceArray.$[]': 1 } }); + +buildUpdateQuery({ $rename: { numberField2: 'stringField' } }); + +buildUpdateQuery({ $addToSet: { fruitTags: 'stringField' } }); +// $ExpectError +buildUpdateQuery({ $addToSet: { fruitTags: 123 } }); +buildUpdateQuery({ $addToSet: { fruitTags: { $each: ['stringField'] } } }); +buildUpdateQuery({ $addToSet: { readonlyFruitTags: 'apple' } }); +buildUpdateQuery({ $addToSet: { readonlyFruitTags: { $each: ['apple'] } } }); +buildUpdateQuery({ $addToSet: { maybeFruitTags: 'apple' } }); +buildUpdateQuery({ $addToSet: { 'dot.notation': 'stringField' } }); +buildUpdateQuery({ $addToSet: { 'dot.notation': { $each: ['stringfield'] } } }); +buildUpdateQuery({ + $addToSet: { + subInterfaceArray: { field1: 'foo' } + } +}); +buildUpdateQuery({ + $addToSet: { + subInterfaceArray: { + _id: new ObjectId(), + field1: 'foo' + } + } +}); +buildUpdateQuery({ + $addToSet: { + subInterfaceArray: { + $each: [{ field1: 'foo' }] + } + } +}); +buildUpdateQuery({ + // $ExpectError + $addToSet: { subInterfaceArray: { field1: 123 } } +}); + +buildUpdateQuery({ $pop: { fruitTags: 1 } }); +buildUpdateQuery({ $pop: { fruitTags: -1 } }); +buildUpdateQuery({ $pop: { 'dot.notation': 1 } }); +buildUpdateQuery({ $pop: { 'subInterfaceArray.$[]': -1 } }); + +buildUpdateQuery({ $pull: { fruitTags: 'a' } }); +// $ExpectError +buildUpdateQuery({ $pull: { fruitTags: 123 } }); +buildUpdateQuery({ $pull: { fruitTags: { $in: ['a'] } } }); +buildUpdateQuery({ $pull: { maybeFruitTags: 'apple' } }); +buildUpdateQuery({ $pull: { 'dot.notation': 1 } }); +buildUpdateQuery({ $pull: { 'subInterfaceArray.$[]': { $in: ['a'] } } }); +buildUpdateQuery({ $pull: { subInterfaceArray: { field1: 'a' } } }); +buildUpdateQuery({ $pull: { subInterfaceArray: { _id: { $in: [new ObjectId()] } } } }); +buildUpdateQuery({ $pull: { subInterfaceArray: { field1: { $in: ['a'] } } } }); + +buildUpdateQuery({ $push: { fruitTags: 'a' } }); +// $ExpectError +buildUpdateQuery({ $push: { fruitTags: 123 } }); +buildUpdateQuery({ $push: { fruitTags: { $each: ['a'] } } }); +buildUpdateQuery({ $push: { fruitTags: { $each: ['a'], $slice: 3 } } }); +buildUpdateQuery({ $push: { fruitTags: { $each: ['a'], $position: 1 } } }); +buildUpdateQuery({ $push: { fruitTags: { $each: ['a'], $sort: 1 } } }); +buildUpdateQuery({ $push: { fruitTags: { $each: ['a'], $sort: -1 } } }); +buildUpdateQuery({ $push: { fruitTags: { $each: ['stringField'] } } }); +buildUpdateQuery({ $push: { fruitTags: { $each: ['a'], $sort: { 'sub.field': -1 } } } }); +buildUpdateQuery({ $push: { maybeFruitTags: 'apple' } }); +buildUpdateQuery({ + $push: { + subInterfaceArray: { _id: new ObjectId(), field1: 'foo' } + } +}); +buildUpdateQuery({ + $push: { + subInterfaceArray: { + _id: new ObjectId(), + field1: 'foo' + } + } +}); +buildUpdateQuery({ + $push: { + subInterfaceArray: { + $each: [ + { + _id: new ObjectId(), + field1: 'foo', + field2: 'bar' + } + ] + } + } +}); +buildUpdateQuery({ + // $ExpectError + $push: { subInterfaceArray: { field1: 123 } } +}); +// buildUpdateQuery({ $push: { 'dot.notation': 1 } }); +// buildUpdateQuery({ $push: { 'subInterfaceArray.$[]': { $in: ['a'] } } }); + +collectionTType.updateOne({ stringField: 'bla' }, justASample); + +collectionTType.updateMany( + { numberField: 12 }, + { + $set: { + stringField: 'Banana' + } + } +); + +async function testPushWithId() { + interface Model { + _id: ObjectId; + foo: Array<{ _id?: string; name: string }>; + } + + const client = new MongoClient(''); + const db = client.db('test'); + const collection = db.collection('test'); + + await collection.updateOne( + {}, + { + $push: { + foo: { name: 'Foo' } + } + } + ); + + await collection.updateOne( + {}, + { + $push: { + foo: { _id: 'foo', name: 'Foo' } + } + } + ); +} diff --git a/test/types/community/createIndex.test-d.ts b/test/types/community/createIndex.test-d.ts new file mode 100644 index 00000000000..e9014c4c6a7 --- /dev/null +++ b/test/types/community/createIndex.test-d.ts @@ -0,0 +1,12 @@ +import { expectType } from 'tsd'; +import { CreateIndexesOptions, MongoClient, Document } from '../../../src'; + +const client = new MongoClient(''); +const db = client.db('test'); +const collection = db.collection('test.find'); + +const options: CreateIndexesOptions = { partialFilterExpression: { rating: { $exists: 1 } } }; +const indexName = collection.createIndex({}, options); + +expectType>(indexName); +expectType(options.partialFilterExpression); diff --git a/test/types/community/cursor.test-d.ts b/test/types/community/cursor.test-d.ts new file mode 100644 index 00000000000..c44b62c4c0f --- /dev/null +++ b/test/types/community/cursor.test-d.ts @@ -0,0 +1,78 @@ +import type { Readable } from 'stream'; +import { expectType } from 'tsd'; +import { FindCursor, MongoClient } from '../../../src/index'; + +const client = new MongoClient(''); +const db = client.db('test'); +const collection = db.collection<{ age: number }>('test.find'); + +const cursor = collection + .find() + .addCursorFlag('tailable', true) + .addQueryModifier('', true) + .batchSize(1) + .comment('') + .filter({ a: 1 }) + .hint({ age: 1 }) + .hint('age_1') + .limit(1) + .max({ age: 130 }) + .min({ age: 18 }) + .maxAwaitTimeMS(1) + .maxTimeMS(1) + .project({}) + .returnKey(true) + .showRecordId(true) + .skip(1) + .sort({}) + .map(result => ({ foo: result.age })); + +expectType>(cursor); +expectType(cursor.stream()); + +collection.find().project({}); +collection.find().project({ notExistingField: 1 }); +collection.find().sort({ text: { $meta: 'textScore' }, notExistingField: -1 }); +collection.find().sort({}); + +interface TypedDoc { + name: string; + age: number; + tag: { + name: string; + }; +} +const typedCollection = db.collection('test'); +typedCollection.find({ name: 'name' }, {}).map(x => x.tag); +typedCollection.find({ 'tag.name': 'name' }, {}); +typedCollection + .find({ 'tag.name': 'name' }, { projection: { 'tag.name': 1, max: { $max: [] } } }) + .sort({ score: { $meta: 'textScore' } }); + +expectType<{ name: string }[]>( + ( + await typedCollection + .find({ 'tag.name': 'name' }, { projection: { name: 1, max: { $max: [] } } }) + .toArray() + ).map(x => x.tag) +); + +// override the collection type +typedCollection + .find<{ name2: string; age2: number }>({ name: '123' }, { projection: { age2: 1 } }) + .map(x => x.name2 && x.age2); +typedCollection.find({ name: '123' }, { projection: { age: 1 } }).map(x => x.tag); + +typedCollection.find().project({ name: 1 }); +typedCollection.find().project({ notExistingField: 1 }); +typedCollection.find().project({ max: { $max: [] } }); + +// $ExpectType Cursor<{ name: string; }> +typedCollection.find().project<{ name: string }>({ name: 1 }); + +void async function () { + for await (const item of cursor) { + if (!item) break; + expectType(item.age); + } +}; diff --git a/test/types/community/index.ts b/test/types/community/index.ts new file mode 100644 index 00000000000..ab39a6410d4 --- /dev/null +++ b/test/types/community/index.ts @@ -0,0 +1,108 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +// Test source : https://github.com/mongodb/node-mongodb-native + +import { expectType } from 'tsd'; +import { + GridFSBucket, + MongoClient, + MongoClientOptions, + MongoError, + MongoNetworkError, + MongoParseError, + ReadPreference, + ReadPreferenceMode, + W +} from '../../../src/index'; + +export const connectionString = 'mongodb://127.0.0.1:27017/test'; + +const options: MongoClientOptions = { + authSource: ' ', + loggerLevel: 'debug', + w: 1, + wtimeoutMS: 300, + journal: true, + readPreference: ReadPreference.NEAREST ?? 'secondaryPreferred', + promoteValues: true, + maxPoolSize: 1, + family: 4, + ssl: true, + sslValidate: false, + checkServerIdentity(host, cert) { + return undefined; + }, + promoteBuffers: false, + authMechanism: 'SCRAM-SHA-1', + forceServerObjectId: false, + promiseLibrary: Promise, + directConnection: false +}; + +MongoClient.connect(connectionString, options, (err, client?: MongoClient) => { + if (err || !client) throw err; + const db = client.db('test'); + const collection = db.collection('test_crud'); + // Let's close the db + client.close(); +}); + +async function testFunc(): Promise { + const testClient: MongoClient = await MongoClient.connect(connectionString); + return testClient; +} + +MongoClient.connect(connectionString, err => { + if (err instanceof MongoError) { + expectType(err.hasErrorLabel('label')); + } +}); + +expectType>(MongoClient.connect(connectionString, options)); + +// TLS +const userName = ''; +const password = ''; +const url = `mongodb://${userName}:${password}@server:27017?authMechanism=MONGODB-X509&tls=true`; +const client = new MongoClient(url, { + tls: true, + tlsAllowInvalidHostnames: true, + tlsCAFile: `${__dirname}/certs/ca.pem`, + tlsCertificateKeyFile: `${__dirname}/certs/x509/client.pem`, + tlsCertificateKeyFilePassword: '10gen' +}); +expectType(client.readPreference.mode); +expectType(client.writeConcern?.w); + +// Test other error classes +new MongoNetworkError('network error'); +new MongoParseError('parse error'); + +// Streams +const gridFSBucketTests = (bucket: GridFSBucket) => { + const openUploadStream = bucket.openUploadStream('file.dat'); + openUploadStream.on('close', () => {}); + openUploadStream.on('end', () => {}); + expectType>(openUploadStream.abort()); // $ExpectType void + expectType( + openUploadStream.abort(() => { + openUploadStream.removeAllListeners(); + }) + ); + openUploadStream.abort(error => { + error; // $ExpectType MongoError + }); + openUploadStream.abort((error, result) => {}); +}; + +// Client-Side Field Level Encryption +const keyVaultNamespace = 'encryption.__keyVault'; +const secureClient = new MongoClient(url, { + monitorCommands: true, + autoEncryption: { + keyVaultNamespace, + kmsProviders: {}, + schemaMap: {}, + extraOptions: {} + } +}); diff --git a/test/types/community/stats.test-d.ts b/test/types/community/stats.test-d.ts new file mode 100644 index 00000000000..8c976aab358 --- /dev/null +++ b/test/types/community/stats.test-d.ts @@ -0,0 +1,13 @@ +import { expectType } from 'tsd'; +import { CollStats, MongoClient } from '../../../src/index'; + +const client = new MongoClient(''); +const db = client.db('test'); +const collection = db.collection('test.find'); + +expectType(await collection.stats()); + +const stats = await collection.stats(); +if (stats.wiredTiger) { + expectType(stats.wiredTiger.cache['bytes currently in the cache']); +} diff --git a/test/types/community/transaction.test-d.ts b/test/types/community/transaction.test-d.ts new file mode 100644 index 00000000000..9df87d4cba3 --- /dev/null +++ b/test/types/community/transaction.test-d.ts @@ -0,0 +1,117 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +// https://docs.com/manual/core/transactions/ + +import { ClientSession, MongoClient, ReadConcern } from '../../../src/index'; + +const client = new MongoClient(''); +const session = client.startSession(); + +async function commitWithRetry(session: ClientSession) { + try { + await session.commitTransaction(); + console.log('Transaction committed.'); + } catch (error) { + if (error.errorLabels && error.errorLabels.indexOf('UnknownTransactionCommitResult') < 0) { + console.log('UnknownTransactionCommitResult, retrying commit operation...'); + await commitWithRetry(session); + } else { + console.log('Error during commit...'); + throw error; + } + } +} + +async function runTransactionWithRetry( + txnFunc: (client: MongoClient, session: ClientSession) => Promise, + client: MongoClient, + session: ClientSession +) { + try { + await txnFunc(client, session); + } catch (error) { + console.log('Transaction aborted. Caught exception during transaction.'); + + // If transient error, retry the whole transaction + if (error.errorLabels && error.errorLabels.indexOf('TransientTransactionError') < 0) { + console.log('TransientTransactionError, retrying transaction ...'); + await runTransactionWithRetry(txnFunc, client, session); + } else { + throw error; + } + } +} + +async function updateEmployeeInfo(client: MongoClient, session: ClientSession) { + session.startTransaction({ + readConcern: new ReadConcern('available'), // NODE-3297 + writeConcern: { w: 'majority' } + }); + + const employeesCollection = client.db('hr').collection('employees'); + const eventsCollection = client.db('reporting').collection('events'); + + await employeesCollection.updateOne( + { employee: 3 }, + { $set: { status: 'Inactive' } }, + { session } + ); + await eventsCollection.insertOne( + { + employee: 3, + status: { new: 'Inactive', old: 'Active' } + }, + { session } + ); + + try { + await commitWithRetry(session); + } catch (error) { + await session.abortTransaction(); + throw error; + } +} + +const from = 'a_name'; +const to = 'b_name'; +const amount = 100; +const db = client.db(); +session.startTransaction(); +try { + const opts = { session, returnOriginal: false }; + const res = await db + .collection('Account') + .findOneAndUpdate({ name: from }, { $inc: { balance: -amount } }, opts); + const A = res.value!; + if (A.balance < 0) { + // If A would have negative balance, fail and abort the transaction + // `session.abortTransaction()` will undo the above `findOneAndUpdate()` + throw new Error('Insufficient funds: ' + (A.balance + amount)); + } + + const resB = await db + .collection('Account') + .findOneAndUpdate({ name: to }, { $inc: { balance: amount } }, opts); + const B = resB.value!; + + await session.commitTransaction(); + session.endSession(); + console.log({ from: A, to: B }); +} catch (error) { + // If an error occurred, abort the whole transaction and + // undo any changes that might have happened + await session.abortTransaction(); + session.endSession(); + throw error; // Rethrow so calling function sees error +} + +client.withSession(session => runTransactionWithRetry(updateEmployeeInfo, client, session)); + +const col = client.db('test').collection<{ _id: string }>('col'); +const ok = await session.withTransaction(async () => { + return await col.insertOne({ _id: 'one' }, { session }); +}); +if (ok) { + console.log('success'); +} else { + console.log('nothing done'); +} diff --git a/test/types/community/tsconfig.json b/test/types/community/tsconfig.json new file mode 100644 index 00000000000..0a2bf85447d --- /dev/null +++ b/test/types/community/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node" + } +} diff --git a/test/types/distinct.test-d.ts b/test/types/distinct.test-d.ts deleted file mode 100644 index aeb7d4d5ad9..00000000000 --- a/test/types/distinct.test-d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { expectType } from 'tsd'; - -import type { Collection } from '../../src/collection'; -import type { Movie } from './example_schemas'; - -// Ensure distinct takes all keys of the schema plus '_id' -const x = (null as unknown) as Parameters['distinct']>[0]; -expectType<'_id' | keyof Movie>(x); diff --git a/test/types/indexed_schema.test-d.ts b/test/types/indexed_schema.test-d.ts index be0e7ba154d..dd4ebe47869 100644 --- a/test/types/indexed_schema.test-d.ts +++ b/test/types/indexed_schema.test-d.ts @@ -19,9 +19,6 @@ expectType>(randomKeysIncludeIdC.insertOne({ a: 2, randomKey: expectError(randomKeysIncludeIdC.insertOne({ a: 2, randomKey: 23 })); expectError(randomKeysIncludeIdC.insertOne({ _id: 2, randomKey: 'string' })); -const arg1 = (null as unknown) as Parameters[0]; -expectAssignable>(arg1); - //////////////////////////////////////////////////////////////////////////////////////////////////// interface RandomKeysToNumber { diff --git a/test/types/union_schema.test-d.ts b/test/types/union_schema.test-d.ts index 069e947f27d..eb82a792143 100644 --- a/test/types/union_schema.test-d.ts +++ b/test/types/union_schema.test-d.ts @@ -31,9 +31,9 @@ expectAssignable({ height: 4, width: 4 }); expectAssignable({ radius: 4 }); const c: Collection = null as never; -expectType>(c.findOne({ height: 4, width: 4 })); +expectType>(c.findOne({ height: 4, width: 4 })); // collection API can only respect TSchema given, cannot pick a type inside a union -expectNotType>(c.findOne({ height: 4, width: 4 })); +expectNotType>(c.findOne({ height: 4, width: 4 })); interface A { _id: number; diff --git a/test/types/utility_types.ts b/test/types/utility_types.ts new file mode 100644 index 00000000000..3d868604a2a --- /dev/null +++ b/test/types/utility_types.ts @@ -0,0 +1 @@ +export type PropExists = Key extends keyof Type ? true : false; diff --git a/test/unit/type_check.test.js b/test/unit/type_check.test.js index 877b6d8b8e7..529b8429b54 100644 --- a/test/unit/type_check.test.js +++ b/test/unit/type_check.test.js @@ -3,14 +3,22 @@ const tsd = require('tsd').default; const { expect } = require('chai'); +/** + * @param {string} path + */ +function trimPath(path) { + const trimmings = path.split('test/types'); + return 'test/types' + trimmings[1]; +} + describe('Typescript definitions', () => { it('should pass assertions defined in test/types', async () => { const diagnostics = await tsd(); if (diagnostics.length !== 0) { const messages = diagnostics - .map(d => `${d.fileName}:${d.line}:${d.column} - [${d.severity}]: ${d.message}`) + .map(d => `${trimPath(d.fileName)}:${d.line}:${d.column} - [${d.severity}]: ${d.message}`) .join('\n'); - expect.fail('\n' + messages); + expect.fail(`\n\n${messages}\n\n${diagnostics.length} errors found.`); } }); }); From 913dc0f58ea303db61d2f36fe7d41ed47cef46e6 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 28 May 2021 10:34:11 -0400 Subject: [PATCH 02/14] fix: lint --- src/collection.ts | 2 +- src/cursor/aggregation_cursor.ts | 1 - src/index.ts | 12 ++++++++++- src/mongo_types.ts | 20 +++++++++++++------ .../unified-spec-runner/unified-utils.ts | 1 - test/types/.eslintrc.json | 3 ++- .../community/collection/distinct.test-d.ts | 1 - .../community/collection/findX.test-d.ts | 1 - .../community/collection/insertX.test-d.ts | 1 - .../community/collection/mapReduce.test-d.ts | 1 - .../community/collection/updateX.test-d.ts | 2 +- test/types/indexed_schema.test-d.ts | 2 +- 12 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/collection.ts b/src/collection.ts index 1a4dbc3fb59..b05073fda2a 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -1,4 +1,4 @@ -import { DEFAULT_PK_FACTORY, emitWarningOnce, isRecord, resolveOptions } from './utils'; +import { DEFAULT_PK_FACTORY, emitWarningOnce, resolveOptions } from './utils'; import { ReadPreference, ReadPreferenceLike } from './read_preference'; import { normalizeHintField, diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index a00c07525c0..fa3e86df0dd 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -10,7 +10,6 @@ import type { ClientSession } from '../sessions'; import type { OperationParent } from '../operations/command'; import type { AbstractCursorOptions } from './abstract_cursor'; import type { ExplainVerbosityLike } from '../explain'; -import type { Projection } from '../mongo_types'; /** @public */ export interface AggregationCursorOptions extends AbstractCursorOptions, AggregateOptions {} diff --git a/src/index.ts b/src/index.ts index 954765726bd..33014efad26 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,6 +72,7 @@ export { ExplainVerbosity } from './explain'; export { ReadConcernLevel } from './read_concern'; export { ReadPreferenceMode } from './read_preference'; export { ServerApiVersion } from './mongo_client'; +export { BSONType } from './mongo_types'; // Helper classes export { WriteConcern } from './write_concern'; @@ -365,6 +366,15 @@ export type { Filter, Projection, InferIdType, - ProjectionOperators + ProjectionOperators, + FlattenIfArray, + SchemaMember, + Condition, + RootFilterOperators, + AlternativeType, + FilterOperators, + BSONTypeAlias, + BitwiseFilter, + RegExpOrString } from './mongo_types'; export type { serialize, deserialize } from './bson'; diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 420d5b826fb..5259d1e5375 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -47,17 +47,23 @@ export type Filter = { } & RootFilterOperators; +/** @public */ export type Condition = AlternativeType | FilterOperators>; -type AlternativeType = T extends ReadonlyArray +/** + * It is possible to search using alternative types in mongodb e.g. + * string types can be searched using a regex in mongo + * array types can be searched using their element type + * @public + */ +export type AlternativeType = T extends ReadonlyArray ? T | RegExpOrString : RegExpOrString; -// we can search using alternative types in mongodb e.g. -// string types can be searched using a regex in mongo -// array types can be searched using their element type -type RegExpOrString = T extends string ? BSONRegExp | RegExp | T : T; +/** @public */ +export type RegExpOrString = T extends string ? BSONRegExp | RegExp | T : T; +/** @public */ export interface RootFilterOperators extends Document { $and?: Filter[]; $nor?: Filter[]; @@ -72,6 +78,7 @@ export interface RootFilterOperators extends Document { $comment?: string | Document; } +/** @public */ export interface FilterOperators extends Document { // Comparison $eq?: TValue; @@ -114,7 +121,8 @@ export interface FilterOperators extends Document { $bitsAnySet?: BitwiseFilter; } -type BitwiseFilter = +/** @public */ +export type BitwiseFilter = | number /** numeric bit mask */ | Binary /** BinData bit mask */ | number[]; /** `[ , , ... ]` */ diff --git a/test/functional/unified-spec-runner/unified-utils.ts b/test/functional/unified-spec-runner/unified-utils.ts index 57baf94afa7..ea5b8dfe59a 100644 --- a/test/functional/unified-spec-runner/unified-utils.ts +++ b/test/functional/unified-spec-runner/unified-utils.ts @@ -60,7 +60,6 @@ export function* zip( iter1: T[], iter2: U[] ): Generator<[T | undefined, U | undefined], void> { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const longerArrayLength = Math.max(iter1.length, iter2.length); for (let index = 0; index < longerArrayLength; index++) { yield [iter1[index], iter2[index]]; diff --git a/test/types/.eslintrc.json b/test/types/.eslintrc.json index 15cec797940..4ce486d81d0 100644 --- a/test/types/.eslintrc.json +++ b/test/types/.eslintrc.json @@ -23,6 +23,7 @@ "rules": { "prettier/prettier": "error", "tsdoc/syntax": "warn", - "typescript-eslint/no-explicit-any": "off" + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off" } } diff --git a/test/types/community/collection/distinct.test-d.ts b/test/types/community/collection/distinct.test-d.ts index f3cc76b6cd4..38d446477d3 100644 --- a/test/types/community/collection/distinct.test-d.ts +++ b/test/types/community/collection/distinct.test-d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { expectType } from 'tsd'; import { MongoClient, ObjectId } from '../../../../src/index'; diff --git a/test/types/community/collection/findX.test-d.ts b/test/types/community/collection/findX.test-d.ts index 4677cd1d4b3..5a4f162030b 100644 --- a/test/types/community/collection/findX.test-d.ts +++ b/test/types/community/collection/findX.test-d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { expectNotType, expectType } from 'tsd'; import { FindCursor, FindOptions, MongoClient, Document } from '../../../../src'; import type { PropExists } from '../../utility_types'; diff --git a/test/types/community/collection/insertX.test-d.ts b/test/types/community/collection/insertX.test-d.ts index 1ab3d04e64d..2c5338bccdc 100644 --- a/test/types/community/collection/insertX.test-d.ts +++ b/test/types/community/collection/insertX.test-d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { expectError, expectNotType, expectType } from 'tsd'; import { MongoClient, ObjectId, OptionalId } from '../../../../src'; import type { PropExists } from '../../utility_types'; diff --git a/test/types/community/collection/mapReduce.test-d.ts b/test/types/community/collection/mapReduce.test-d.ts index 1e1ea356046..aa9305b33dd 100644 --- a/test/types/community/collection/mapReduce.test-d.ts +++ b/test/types/community/collection/mapReduce.test-d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { MongoClient, ReduceFunction } from '../../../../src/index'; // https://docs.mongodb.com/manual/core/map-reduce/ diff --git a/test/types/community/collection/updateX.test-d.ts b/test/types/community/collection/updateX.test-d.ts index 61e4ae0df4b..4b213dcad93 100644 --- a/test/types/community/collection/updateX.test-d.ts +++ b/test/types/community/collection/updateX.test-d.ts @@ -262,7 +262,7 @@ collectionTType.updateMany( } ); -async function testPushWithId() { +export async function testPushWithId(): Promise { interface Model { _id: ObjectId; foo: Array<{ _id?: string; name: string }>; diff --git a/test/types/indexed_schema.test-d.ts b/test/types/indexed_schema.test-d.ts index dd4ebe47869..690f3e13ea0 100644 --- a/test/types/indexed_schema.test-d.ts +++ b/test/types/indexed_schema.test-d.ts @@ -1,4 +1,4 @@ -import { expectType, expectNotType, expectError, expectAssignable } from 'tsd'; +import { expectType, expectNotType, expectError } from 'tsd'; import { Collection } from '../../src/collection'; import { ObjectId } from '../../src/bson'; From a6820077d0970a0884a7fa9400e3afcf86b81832 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 28 May 2021 10:41:44 -0400 Subject: [PATCH 03/14] fix: add extends document to collstats types --- src/operations/stats.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/operations/stats.ts b/src/operations/stats.ts index a0f8158f04b..690111cb5ed 100644 --- a/src/operations/stats.ts +++ b/src/operations/stats.ts @@ -72,7 +72,7 @@ export class DbStatsOperation extends CommandOperation { * @public * @see https://docs.mongodb.org/manual/reference/command/collStats/ */ -export interface CollStats { +export interface CollStats extends Document { /** * Namespace. */ @@ -142,7 +142,7 @@ export interface CollStats { } /** @public */ -export interface WiredTigerData { +export interface WiredTigerData extends Document { LSM: { 'bloom filter false positives': number; 'bloom filter hits': number; @@ -156,7 +156,7 @@ export interface WiredTigerData { 'sleep for LSM checkpoint throttle': number; 'sleep for LSM merge throttle': number; 'total size of bloom filters': number; - }; + } & Document; 'block-manager': { 'allocations requiring file extension': number; 'blocks allocated': number; @@ -188,7 +188,7 @@ export interface WiredTigerData { 'pages rewritten by compaction': number; 'row-store internal pages': number; 'row-store leaf pages': number; - }; + } & Document; cache: { 'bytes currently in the cache': number; 'bytes read into cache': number; @@ -213,7 +213,7 @@ export interface WiredTigerData { 'pages written requiring in-memory restoration': number; 'tracked dirty bytes in the cache': number; 'unmodified pages evicted': number; - }; + } & Document; cache_walk: { 'Average difference between current eviction generation when the page was last considered': number; 'Average on-disk page image size seen': number; @@ -233,7 +233,7 @@ export interface WiredTigerData { 'Refs skipped during cache traversal': number; 'Size of the root page': number; 'Total number of pages currently in cache': number; - }; + } & Document; compression: { 'compressed pages read': number; 'compressed pages written': number; @@ -242,7 +242,7 @@ export interface WiredTigerData { 'raw compression call failed, additional data available': number; 'raw compression call failed, no additional data available': number; 'raw compression call succeeded': number; - }; + } & Document; cursor: { 'bulk-loaded cursor-insert calls': number; 'create calls': number; @@ -275,7 +275,7 @@ export interface WiredTigerData { 'page reconciliation calls': number; 'page reconciliation calls for eviction': number; 'pages deleted': number; - }; + } & Document; } defineAspects(CollStatsOperation, [Aspect.READ_OPERATION]); From 6754040d9565efb84f9f5878a8f74a00aea83dfc Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 9 Jun 2021 15:23:40 -0400 Subject: [PATCH 04/14] fix: address comments --- src/collection.ts | 28 ++++++++----------- src/cursor/find_cursor.ts | 1 + src/transactions.ts | 2 +- test/types/.eslintrc.json | 2 +- .../types/community/changes_from_36.test-d.ts | 13 ++++----- .../community/collection/bulkWrite.test-d.ts | 2 +- .../community/collection/count.test-d.ts | 8 ++++-- .../community/collection/distinct.test-d.ts | 1 - .../collection/filterQuery.test-d.ts | 2 +- .../community/collection/insertX.test-d.ts | 26 ++++------------- .../community/collection/mapReduce.test-d.ts | 3 +- test/types/community/index.ts | 15 +++++----- test/types/community/transaction.test-d.ts | 1 - test/unit/type_check.test.js | 15 +++++----- 14 files changed, 49 insertions(+), 70 deletions(-) diff --git a/src/collection.ts b/src/collection.ts index b05073fda2a..d0a47a23107 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -675,7 +675,7 @@ export class Collection { findOne(): Promise; findOne(callback: Callback): void; findOne(filter: Filter): Promise; - findOne(filter: Filter, callback?: Callback): void; + findOne(filter: Filter, callback: Callback): void; findOne(filter: Filter, options: FindOptions): Promise; findOne( filter: Filter, @@ -684,20 +684,20 @@ export class Collection { ): void; // allow an override of the schema. + findOne(): Promise; + findOne(callback: Callback): void; + findOne(filter: Filter): Promise; + findOne(filter: Filter, options?: FindOptions): Promise; findOne( - filter: Filter, - options?: FindOptions - ): Promise; - findOne( - filter: Filter, - options?: FindOptions, - callback?: Callback + filter: Filter, + options?: FindOptions, + callback?: Callback ): void; findOne( filter?: Filter | Callback, options?: FindOptions | Callback, - callback?: Callback + callback?: Callback ): Promise | void { if (callback !== undefined && typeof callback !== 'function') { throw new MongoDriverError('Third parameter to `findOne()` must be a callback or undefined'); @@ -716,7 +716,7 @@ export class Collection { filter, resolveOptions(this, options) ) as TODO_NODE_3286, - callback + callback as TODO_NODE_3286 ); } @@ -726,12 +726,8 @@ export class Collection { * @param filter - The filter predicate. If unspecified, then all documents in the collection will match the predicate */ find(): FindCursor; - find(filter: Filter): FindCursor; - find(filter: Filter, options: FindOptions): FindCursor; - find( - query: Filter, - options: FindOptions - ): FindCursor; + find(filter: Filter, options?: FindOptions): FindCursor; + find(filter: Filter, options?: FindOptions): FindCursor; find(filter?: Filter, options?: FindOptions): FindCursor { if (arguments.length > 2) { throw new MongoDriverError('Third parameter to `collection.find()` must be undefined'); diff --git a/src/cursor/find_cursor.ts b/src/cursor/find_cursor.ts index cb16557eba3..9add956c551 100644 --- a/src/cursor/find_cursor.ts +++ b/src/cursor/find_cursor.ts @@ -342,6 +342,7 @@ export class FindCursor extends AbstractCursor { * * @param value - The field projection object. */ + // TODO(NODE-3343): add parameterized cursor return type project(value: SchemaMember): this; project(value: Projection): this { assertUninitialized(this); diff --git a/src/transactions.ts b/src/transactions.ts index 4fa70ae7e01..42cad2f9776 100644 --- a/src/transactions.ts +++ b/src/transactions.ts @@ -50,7 +50,7 @@ const stateMachine: { [state in TxnState]: TxnState[] } = { * @public */ export interface TransactionOptions extends CommandOperationOptions { - // TODO: Improve the types and handling (NODE-3297) + // TODO(NODE-TODO): These options use the proper class forms of these settings, it should accept the basic enum values too /** A default read concern for commands in this transaction */ readConcern?: ReadConcern; /** A default writeConcern for commands in this transaction */ diff --git a/test/types/.eslintrc.json b/test/types/.eslintrc.json index 4ce486d81d0..1327c5fd146 100644 --- a/test/types/.eslintrc.json +++ b/test/types/.eslintrc.json @@ -24,6 +24,6 @@ "prettier/prettier": "error", "tsdoc/syntax": "warn", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off" + "@typescript-eslint/no-unused-vars": "error" } } diff --git a/test/types/community/changes_from_36.test-d.ts b/test/types/community/changes_from_36.test-d.ts index 124e69d2f44..9afb75d9c04 100644 --- a/test/types/community/changes_from_36.test-d.ts +++ b/test/types/community/changes_from_36.test-d.ts @@ -16,7 +16,6 @@ const mongodb: MongoDBImport = (null as unknown) as MongoDBImport; expectNotType(mongodb); expectType>(false); -// (mongodb.ObjectId); // MongoClientOptions const options: MongoClientOptions = {}; @@ -37,7 +36,7 @@ expectNotType(options.sslKey); // .sslPass cannot be a Buffer expectNotType(options.sslPass); -// Legacy option kept? +// Legacy option kept expectType>(true); // Removed options expectType>(false); @@ -67,14 +66,9 @@ expectAssignable<((host: string, cert: PeerCertificate) => Error | undefined) | options.checkServerIdentity ); -// MongoClient.connect -// the callback's error isn't specifically MongoError (actually, it is probably true in 3.6 that other errors could be passed here, but there is a ticket in progress to fix this.) - -// No such thing as UnifiedTopologyOptions - // compression options have simpler specification: // old way: {compression: { compressors: ['zlib', 'snappy'] }} -expectNotType<{ compressors: string[] }>(options.compressors); +expectType>(false); expectType<('none' | 'snappy' | 'zlib')[] | undefined>(options.compressors); // Removed cursor API @@ -91,10 +85,13 @@ expectType>(false); const db = new MongoClient('').db(); const collection = db.collection(''); // collection.find +// eslint-disable-next-line @typescript-eslint/no-unused-vars expectError(collection.find({}, {}, (e: unknown, c: unknown) => undefined)); // collection.aggregate +// eslint-disable-next-line @typescript-eslint/no-unused-vars expectError(collection.aggregate({}, {}, (e: unknown, c: unknown) => undefined)); // db.aggregate +// eslint-disable-next-line @typescript-eslint/no-unused-vars expectError(db.aggregate({}, {}, (e: unknown, c: unknown) => undefined)); // insertOne and insertMany doesn't return: diff --git a/test/types/community/collection/bulkWrite.test-d.ts b/test/types/community/collection/bulkWrite.test-d.ts index effe9a782ac..a49287e2903 100644 --- a/test/types/community/collection/bulkWrite.test-d.ts +++ b/test/types/community/collection/bulkWrite.test-d.ts @@ -40,7 +40,7 @@ const testDocument: TestSchema = { }, subInterfaceArray: [] }; -const { _id, ...testDocumentWithoutId } = testDocument; +const { ...testDocumentWithoutId } = testDocument; // insertOne diff --git a/test/types/community/collection/count.test-d.ts b/test/types/community/collection/count.test-d.ts index be52838654d..2acaaa096fc 100644 --- a/test/types/community/collection/count.test-d.ts +++ b/test/types/community/collection/count.test-d.ts @@ -1,10 +1,14 @@ -import { expectType } from 'tsd'; +import { expectDeprecated, expectType } from 'tsd'; import { MongoClient } from '../../../../src/index'; -// test collection.countDocuments and collection.count functions +// test collection.countDocuments const client = new MongoClient(''); const collection = client.db('test').collection('test.count'); expectType(await collection.countDocuments()); expectType(await collection.countDocuments({ foo: 1 })); expectType(await collection.countDocuments({ foo: 1 }, { limit: 10 })); + +// Make sure count is deprecated + +expectDeprecated(collection.count); diff --git a/test/types/community/collection/distinct.test-d.ts b/test/types/community/collection/distinct.test-d.ts index 38d446477d3..f62e2e2c2b2 100644 --- a/test/types/community/collection/distinct.test-d.ts +++ b/test/types/community/collection/distinct.test-d.ts @@ -33,7 +33,6 @@ expectType(await collection.distinct('_id')); expectType(await collection.distinct('_id', { foo: 1 })); expectType(await collection.distinct('_id', { foo: 1 }, { maxTimeMS: 400 })); -collection.distinct('a.d'); collection.distinct('nested.num', (err, fields) => { expectType(fields); }); diff --git a/test/types/community/collection/filterQuery.test-d.ts b/test/types/community/collection/filterQuery.test-d.ts index ebd9b646cf6..2a70777784a 100644 --- a/test/types/community/collection/filterQuery.test-d.ts +++ b/test/types/community/collection/filterQuery.test-d.ts @@ -49,7 +49,7 @@ const collectionT = db.collection('test.filterQuery'); // Assert that collection.find uses the Filter helper like so: const filter: Filter = {}; -expectType[0]>(filter); +collectionT.find(filter); // Now tests below can directly test the Filter helper, and are implicitly checking collection.find /** diff --git a/test/types/community/collection/insertX.test-d.ts b/test/types/community/collection/insertX.test-d.ts index 2c5338bccdc..0f5fbde4bc5 100644 --- a/test/types/community/collection/insertX.test-d.ts +++ b/test/types/community/collection/insertX.test-d.ts @@ -30,7 +30,7 @@ expectType(insertManyWithIdResult.insertedCount); expectType<{ [key: number]: ObjectId }>(insertManyWithIdResult.insertedIds); // should accept any _id type when it is not provided in Schema -// TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// NODE-3342 // await anyCollection.insertMany([{ _id: 12, a: 2 }]); /** @@ -57,10 +57,10 @@ const resultMany = await collection.insertMany([ ]); // test results type -// should add a _id field with ObjectId type if it does not exist on collection type expectType>(false); expectType>(false); +// should add a _id field with ObjectId type if it does not exist on collection type expectType<{ [key: number]: ObjectId }>(resultMany.insertedIds); expectType(resultOne.insertedId); @@ -145,7 +145,7 @@ const indexTypeResult1 = await indexTypeCollection1.insertOne({ stringField: 'hola', numberField: 23, randomField: [34, 54, 32], - randomFiel2: 32 + randomField2: 32 }); const indexTypeResultMany1 = await indexTypeCollection1.insertMany([ { stringField: 'hola', numberField: 0 }, @@ -180,7 +180,7 @@ const indexTypeResult2 = await indexTypeCollection2.insertOne({ stringField: 'hola', numberField: 23, randomField: [34, 54, 32], - randomFiel2: 32 + randomField2: 32 }); const indexTypeResultMany2 = await indexTypeCollection2.insertMany([ { _id: 1, stringField: 'hola', numberField: 0 }, @@ -194,7 +194,7 @@ expectError( stringField: 'hola', numberField: 23, randomField: [34, 54, 32], - randomFiel2: 32 + randomField2: 32 }) ); @@ -210,7 +210,7 @@ expectNotType>({ stringField: 'hola', numberField: 23, randomField: [34, 54, 32], - randomFiel2: 32 + randomField2: 32 }); expectError( @@ -225,17 +225,3 @@ expectType>(false); expectType(indexTypeResult2.insertedId); expectType<{ [key: number]: number }>(indexTypeResultMany2.insertedIds); - -/** - * test indexed types with custom _id (ObjectId) - */ -interface IndexTypeTestModelWithObjectId { - _id: ObjectId; - stringField: string; - numberField?: number; - [key: string]: any; -} -const indexTypeCollection3 = db.collection('testCollection'); - -// TODO: should not demand _id if it is ObjectId -// await indexTypeCollection3.insertOne({ stringField: 'hola', numberField: 23, randomField: [34, 54, 32], randomFiel2: 32 }); diff --git a/test/types/community/collection/mapReduce.test-d.ts b/test/types/community/collection/mapReduce.test-d.ts index aa9305b33dd..cfeffae678d 100644 --- a/test/types/community/collection/mapReduce.test-d.ts +++ b/test/types/community/collection/mapReduce.test-d.ts @@ -1,6 +1,5 @@ -import { MongoClient, ReduceFunction } from '../../../../src/index'; +import { MongoClient } from '../../../../src/index'; -// https://docs.mongodb.com/manual/core/map-reduce/ // Declare emit function to be called inside map function declare function emit(key: any, value: any): void; diff --git a/test/types/community/index.ts b/test/types/community/index.ts index ab39a6410d4..e2980e720ef 100644 --- a/test/types/community/index.ts +++ b/test/types/community/index.ts @@ -1,7 +1,4 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -// Test source : https://github.com/mongodb/node-mongodb-native - import { expectType } from 'tsd'; import { GridFSBucket, @@ -29,6 +26,7 @@ const options: MongoClientOptions = { family: 4, ssl: true, sslValidate: false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars checkServerIdentity(host, cert) { return undefined; }, @@ -42,12 +40,12 @@ const options: MongoClientOptions = { MongoClient.connect(connectionString, options, (err, client?: MongoClient) => { if (err || !client) throw err; const db = client.db('test'); - const collection = db.collection('test_crud'); + db.collection('test_crud'); // Let's close the db client.close(); }); -async function testFunc(): Promise { +export async function testFunc(): Promise { const testClient: MongoClient = await MongoClient.connect(connectionString); return testClient; } @@ -79,7 +77,7 @@ new MongoNetworkError('network error'); new MongoParseError('parse error'); // Streams -const gridFSBucketTests = (bucket: GridFSBucket) => { +export function gridTest(bucket: GridFSBucket): void { const openUploadStream = bucket.openUploadStream('file.dat'); openUploadStream.on('close', () => {}); openUploadStream.on('end', () => {}); @@ -92,12 +90,13 @@ const gridFSBucketTests = (bucket: GridFSBucket) => { openUploadStream.abort(error => { error; // $ExpectType MongoError }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars openUploadStream.abort((error, result) => {}); -}; +} // Client-Side Field Level Encryption const keyVaultNamespace = 'encryption.__keyVault'; -const secureClient = new MongoClient(url, { +new MongoClient(url, { monitorCommands: true, autoEncryption: { keyVaultNamespace, diff --git a/test/types/community/transaction.test-d.ts b/test/types/community/transaction.test-d.ts index 9df87d4cba3..fb1030a9101 100644 --- a/test/types/community/transaction.test-d.ts +++ b/test/types/community/transaction.test-d.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -// https://docs.com/manual/core/transactions/ import { ClientSession, MongoClient, ReadConcern } from '../../../src/index'; diff --git a/test/unit/type_check.test.js b/test/unit/type_check.test.js index 529b8429b54..af27dd2b4ae 100644 --- a/test/unit/type_check.test.js +++ b/test/unit/type_check.test.js @@ -3,20 +3,19 @@ const tsd = require('tsd').default; const { expect } = require('chai'); -/** - * @param {string} path - */ -function trimPath(path) { - const trimmings = path.split('test/types'); - return 'test/types' + trimmings[1]; -} +const REPO_ROOT = __dirname.replace('test/unit', ''); describe('Typescript definitions', () => { it('should pass assertions defined in test/types', async () => { const diagnostics = await tsd(); if (diagnostics.length !== 0) { const messages = diagnostics - .map(d => `${trimPath(d.fileName)}:${d.line}:${d.column} - [${d.severity}]: ${d.message}`) + .map( + d => + `${d.fileName.replace(REPO_ROOT, '')}:${d.line}:${d.column} - [${d.severity}]: ${ + d.message + }` + ) .join('\n'); expect.fail(`\n\n${messages}\n\n${diagnostics.length} errors found.`); } From 114301f466cee1902fe8ff3dcba18da637f5f244 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 9 Jun 2021 15:40:31 -0400 Subject: [PATCH 05/14] fix: add ticket to transaction options --- src/transactions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transactions.ts b/src/transactions.ts index 42cad2f9776..9dae2d964c3 100644 --- a/src/transactions.ts +++ b/src/transactions.ts @@ -50,7 +50,7 @@ const stateMachine: { [state in TxnState]: TxnState[] } = { * @public */ export interface TransactionOptions extends CommandOperationOptions { - // TODO(NODE-TODO): These options use the proper class forms of these settings, it should accept the basic enum values too + // TODO(NODE-3344): These options use the proper class forms of these settings, it should accept the basic enum values too /** A default read concern for commands in this transaction */ readConcern?: ReadConcern; /** A default writeConcern for commands in this transaction */ From 55950231a1c0dd4ab458116bda5586cdb1cea357 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 9 Jun 2021 16:01:05 -0400 Subject: [PATCH 06/14] fix: add follow up ticket numbers --- test/types/community/collection/bulkWrite.test-d.ts | 2 ++ test/types/community/cursor.test-d.ts | 2 ++ test/types/community/transaction.test-d.ts | 2 ++ 3 files changed, 6 insertions(+) diff --git a/test/types/community/collection/bulkWrite.test-d.ts b/test/types/community/collection/bulkWrite.test-d.ts index a49287e2903..ed69d71698f 100644 --- a/test/types/community/collection/bulkWrite.test-d.ts +++ b/test/types/community/collection/bulkWrite.test-d.ts @@ -1,6 +1,8 @@ import { expectError } from 'tsd'; import { MongoClient, ObjectId } from '../../../../src/index'; +// TODO(NODE-3347): Improve these tests to use more expect assertions + // collection.bulkWrite tests const client = new MongoClient(''); const db = client.db('test'); diff --git a/test/types/community/cursor.test-d.ts b/test/types/community/cursor.test-d.ts index c44b62c4c0f..5a8b531c6b4 100644 --- a/test/types/community/cursor.test-d.ts +++ b/test/types/community/cursor.test-d.ts @@ -2,6 +2,8 @@ import type { Readable } from 'stream'; import { expectType } from 'tsd'; import { FindCursor, MongoClient } from '../../../src/index'; +// TODO(NODE-3346): Improve these tests to use expect assertions more + const client = new MongoClient(''); const db = client.db('test'); const collection = db.collection<{ age: number }>('test.find'); diff --git a/test/types/community/transaction.test-d.ts b/test/types/community/transaction.test-d.ts index fb1030a9101..619cadd0775 100644 --- a/test/types/community/transaction.test-d.ts +++ b/test/types/community/transaction.test-d.ts @@ -2,6 +2,8 @@ import { ClientSession, MongoClient, ReadConcern } from '../../../src/index'; +// TODO(NODE-3345): Improve these tests to use expect assertions more + const client = new MongoClient(''); const session = client.startSession(); From f5221ad4092e210fb2295a5f0225a6dffd5ecdcd Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 9 Jun 2021 16:19:58 -0400 Subject: [PATCH 07/14] refactor: better file name for index.ts --- test/types/community/{index.ts => client.test-d.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/types/community/{index.ts => client.test-d.ts} (100%) diff --git a/test/types/community/index.ts b/test/types/community/client.test-d.ts similarity index 100% rename from test/types/community/index.ts rename to test/types/community/client.test-d.ts From 3ff673a064120bdfd742671fc45c50cf9603bf3f Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 9 Jun 2021 16:23:15 -0400 Subject: [PATCH 08/14] fix: remove extra newline --- test/types/community/collection/count.test-d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/types/community/collection/count.test-d.ts b/test/types/community/collection/count.test-d.ts index 2acaaa096fc..74c1195741b 100644 --- a/test/types/community/collection/count.test-d.ts +++ b/test/types/community/collection/count.test-d.ts @@ -10,5 +10,4 @@ expectType(await collection.countDocuments({ foo: 1 })); expectType(await collection.countDocuments({ foo: 1 }, { limit: 10 })); // Make sure count is deprecated - expectDeprecated(collection.count); From 119f276c716d32d8525a98bc6c971e44f3d19163 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 9 Jun 2021 16:28:44 -0400 Subject: [PATCH 09/14] fix: add ticket number to client.test-d.ts --- test/types/community/client.test-d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/types/community/client.test-d.ts b/test/types/community/client.test-d.ts index e2980e720ef..51aa990211e 100644 --- a/test/types/community/client.test-d.ts +++ b/test/types/community/client.test-d.ts @@ -12,6 +12,8 @@ import { W } from '../../../src/index'; +// TODO(NODE-3348): Improve the tests to expectType assertions + export const connectionString = 'mongodb://127.0.0.1:27017/test'; const options: MongoClientOptions = { From 27e8b252eee0f2c6d1354aa161cb651430ebe041 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 9 Jun 2021 17:17:39 -0400 Subject: [PATCH 10/14] fix: comments --- test/types/community/changes_from_36.test-d.ts | 3 --- test/types/community/collection/insertX.test-d.ts | 2 -- 2 files changed, 5 deletions(-) diff --git a/test/types/community/changes_from_36.test-d.ts b/test/types/community/changes_from_36.test-d.ts index 9afb75d9c04..5292541365c 100644 --- a/test/types/community/changes_from_36.test-d.ts +++ b/test/types/community/changes_from_36.test-d.ts @@ -78,9 +78,6 @@ expectType>(false); expectType>(false); expectType>(false); -// Doesn't return cursor of new type: -// .map(result => ({ foo: result.age })); - // Cursor returning functions don't take a callback const db = new MongoClient('').db(); const collection = db.collection(''); diff --git a/test/types/community/collection/insertX.test-d.ts b/test/types/community/collection/insertX.test-d.ts index 0f5fbde4bc5..8d51559fdea 100644 --- a/test/types/community/collection/insertX.test-d.ts +++ b/test/types/community/collection/insertX.test-d.ts @@ -8,8 +8,6 @@ const db = client.db('test'); const anyCollection = db.collection('test-any-type'); -// should accept any _id type when it is not provided in Schema - /** * test no collection type ("any") */ From 53e6d3ca162f9251fb4aeaa0248fb4b405dce831 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 10 Jun 2021 11:26:24 -0400 Subject: [PATCH 11/14] fix: type test --- test/types/community/cursor.test-d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/types/community/cursor.test-d.ts b/test/types/community/cursor.test-d.ts index 5a8b531c6b4..286328af7dc 100644 --- a/test/types/community/cursor.test-d.ts +++ b/test/types/community/cursor.test-d.ts @@ -29,7 +29,7 @@ const cursor = collection .sort({}) .map(result => ({ foo: result.age })); -expectType>(cursor); +expectType>(cursor); expectType(cursor.stream()); collection.find().project({}); @@ -75,6 +75,6 @@ typedCollection.find().project<{ name: string }>({ name: 1 }); void async function () { for await (const item of cursor) { if (!item) break; - expectType(item.age); + expectType(item.foo); } }; From f90a10a470dd2cc41229ce7586ac6a9d9bd9772c Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 10 Jun 2021 13:59:48 -0400 Subject: [PATCH 12/14] fix: add fields to collstats and FilterOperators --- src/mongo_types.ts | 1 + src/operations/stats.ts | 71 +++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 5259d1e5375..f4830076b5e 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -119,6 +119,7 @@ export interface FilterOperators extends Document { $bitsAllSet?: BitwiseFilter; $bitsAnyClear?: BitwiseFilter; $bitsAnySet?: BitwiseFilter; + $rand: Record; } /** @public */ diff --git a/src/operations/stats.ts b/src/operations/stats.ts index 690111cb5ed..98de40765c6 100644 --- a/src/operations/stats.ts +++ b/src/operations/stats.ts @@ -73,72 +73,53 @@ export class DbStatsOperation extends CommandOperation { * @see https://docs.mongodb.org/manual/reference/command/collStats/ */ export interface CollStats extends Document { - /** - * Namespace. - */ + /** Namespace */ ns: string; - /** - * Number of documents. - */ + /** Number of documents */ count: number; - /** - * Collection size in bytes. - */ + /** Collection size in bytes */ size: number; - /** - * Average object size in bytes. - */ + /** Average object size in bytes */ avgObjSize: number; - /** - * (Pre)allocated space for the collection in bytes. - */ + /** (Pre)allocated space for the collection in bytes */ storageSize: number; - /** - * Number of extents (contiguously allocated chunks of datafile space). - */ + /** Number of extents (contiguously allocated chunks of datafile space) */ numExtents: number; - /** - * Number of indexes. - */ + /** Number of indexes */ nindexes: number; - /** - * Size of the most recently created extent in bytes. - */ + /** Size of the most recently created extent in bytes */ lastExtentSize: number; - /** - * Padding can speed up updates if documents grow. - */ + /** Padding can speed up updates if documents grow */ paddingFactor: number; - /** - * A number that indicates the user-set flags on the collection. userFlags only appears when using the mmapv1 storage engine. - */ + /** A number that indicates the user-set flags on the collection. userFlags only appears when using the mmapv1 storage engine */ userFlags?: number; - /** - * Total index size in bytes. - */ + /** Total index size in bytes */ totalIndexSize: number; - /** - * Size of specific indexes in bytes. - */ + /** Size of specific indexes in bytes */ indexSizes: { _id_: number; [index: string]: number; }; - /** - * `true` if the collection is capped. - */ + /** `true` if the collection is capped */ capped: boolean; - /** - * The maximum number of documents that may be present in a capped collection. - */ + /** The maximum number of documents that may be present in a capped collection */ max: number; - /** - * The maximum size of a capped collection. - */ + /** The maximum size of a capped collection */ maxSize: number; + /** This document contains data reported directly by the WiredTiger engine and other data for internal diagnostic use */ wiredTiger?: WiredTigerData; + /** The fields in this document are the names of the indexes, while the values themselves are documents that contain statistics for the index provided by the storage engine */ indexDetails?: any; ok: number; + + /** The amount of storage available for reuse. The scale argument affects this value. */ + freeStorageSize?: number; + /** An array that contains the names of the indexes that are currently being built on the collection */ + indexBuilds?: number; + /** The sum of the storageSize and totalIndexSize. The scale argument affects this value */ + totalSize: number; + /** The scale value used by the command. */ + scaleFactor: number; } /** @public */ From 779dce56de074e82883b343c210b3fef875859cc Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 14 Jun 2021 11:31:26 -0400 Subject: [PATCH 13/14] test: skip failing connectivity --- test/manual/atlas_connectivity.test.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/manual/atlas_connectivity.test.js b/test/manual/atlas_connectivity.test.js index 6427954df21..5ac318faa05 100644 --- a/test/manual/atlas_connectivity.test.js +++ b/test/manual/atlas_connectivity.test.js @@ -1,6 +1,9 @@ 'use strict'; const { MongoClient } = require('../../src'); +// TODO(NODE-3357): Unskip this test +const SKIP_TESTS = ['replica_set_4_0_free']; + describe('Atlas Connectivity', function () { if (process.env.ATLAS_CONNECTIVITY == null) { console.error( @@ -15,7 +18,10 @@ describe('Atlas Connectivity', function () { context(configName, function () { CONFIGS[configName].forEach(connectionString => { const name = connectionString.indexOf('mongodb+srv') >= 0 ? 'mongodb+srv' : 'normal'; - it(`${name}`, makeConnectionTest(connectionString)); + it(`${name}`, function () { + if (SKIP_TESTS.include(configName)) this.skip(); + makeConnectionTest(connectionString); + }); }); }); }); From 542068cffafc129b607d9809f549e42e239c240d Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 14 Jun 2021 12:28:00 -0400 Subject: [PATCH 14/14] test: undo skip --- test/manual/atlas_connectivity.test.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/manual/atlas_connectivity.test.js b/test/manual/atlas_connectivity.test.js index 5ac318faa05..6427954df21 100644 --- a/test/manual/atlas_connectivity.test.js +++ b/test/manual/atlas_connectivity.test.js @@ -1,9 +1,6 @@ 'use strict'; const { MongoClient } = require('../../src'); -// TODO(NODE-3357): Unskip this test -const SKIP_TESTS = ['replica_set_4_0_free']; - describe('Atlas Connectivity', function () { if (process.env.ATLAS_CONNECTIVITY == null) { console.error( @@ -18,10 +15,7 @@ describe('Atlas Connectivity', function () { context(configName, function () { CONFIGS[configName].forEach(connectionString => { const name = connectionString.indexOf('mongodb+srv') >= 0 ? 'mongodb+srv' : 'normal'; - it(`${name}`, function () { - if (SKIP_TESTS.include(configName)) this.skip(); - makeConnectionTest(connectionString); - }); + it(`${name}`, makeConnectionTest(connectionString)); }); }); });