diff --git a/src/mongo_types.ts b/src/mongo_types.ts index e68ff4fe86..f4dcc28e1d 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -68,7 +68,7 @@ export type WithoutId = Omit; export type Filter = | Partial | ({ - [Property in Join>, '.'>]?: Condition< + [Property in Join, []>, '.'>]?: Condition< PropertyType, Property> >; } & RootFilterOperators>); @@ -263,7 +263,7 @@ export type OnlyFieldsOfType = Readonly< { - [Property in Join, '.'>]?: PropertyType; + [Property in Join, '.'>]?: PropertyType; } & { [Property in `${NestedPathsOfType}.$${`[${string}]` | ''}`]?: ArrayElement< PropertyType @@ -272,7 +272,7 @@ export type MatchKeysAndValues = Readonly< [Property in `${NestedPathsOfType[]>}.$${ | `[${string}]` | ''}.${string}`]?: any; // Could be further narrowed - } + } & Document >; /** @public */ @@ -498,20 +498,29 @@ export type PropertyType = string extends Propert * @public * returns tuple of strings (keys to be joined on '.') that represent every path into a schema * https://docs.mongodb.com/manual/tutorial/query-embedded-documents/ + * + * @remarks + * Through testing we determined that a depth of 8 is safe for the typescript compiler + * and provides reasonable compilation times. This number is otherwise not special and + * should be changed if issues are found with this level of checking. Beyond this + * depth any helpers that make use of NestedPaths should devolve to not asserting any + * type safety on the input. */ -export type NestedPaths = Type extends - | string - | number - | boolean - | Date - | RegExp - | Buffer - | Uint8Array - | ((...args: any[]) => any) - | { _bsontype: string } +export type NestedPaths = Depth['length'] extends 8 + ? [] + : Type extends + | string + | number + | boolean + | Date + | RegExp + | Buffer + | Uint8Array + | ((...args: any[]) => any) + | { _bsontype: string } ? [] : Type extends ReadonlyArray - ? [] | [number, ...NestedPaths] + ? [] | [number, ...NestedPaths] : Type extends Map ? [string] : Type extends object @@ -529,9 +538,9 @@ export type NestedPaths = Type extends ArrayType extends Type ? [Key] // we have a recursive array union : // child is an array, but it's not a recursive array - [Key, ...NestedPaths] + [Key, ...NestedPaths] : // child is not structured the same as the parent - [Key, ...NestedPaths] | [Key]; + [Key, ...NestedPaths] | [Key]; }[Extract] : []; @@ -542,7 +551,7 @@ export type NestedPaths = Type extends */ export type NestedPathsOfType = KeysOfAType< { - [Property in Join, '.'>]: PropertyType; + [Property in Join, '.'>]: PropertyType; }, Type >; diff --git a/test/types/basic_schema.test-d.ts b/test/types/basic_schema.test-d.ts index 19634023cc..6afbbd29d1 100644 --- a/test/types/basic_schema.test-d.ts +++ b/test/types/basic_schema.test-d.ts @@ -1,6 +1,6 @@ import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; -import { ObjectId } from '../../src/bson'; +import { Document, ObjectId } from '../../src/bson'; import { Collection } from '../../src/collection'; import { Db } from '../../src/db'; import { MongoClient } from '../../src/mongo_client'; @@ -20,7 +20,7 @@ expectType>(new Collection(db, '')); //////////////////////////////////////////////////////////////////////////////////////////////////// // Simple Schema that does not define an _id // With _id -type InsertOneArgOf = Parameters['insertOne']>[0]; +type InsertOneArgOf = Parameters['insertOne']>[0]; expectAssignable>({ _id: new ObjectId(), a: 3 }); // Without _id expectAssignable>({ a: 3 }); diff --git a/test/types/community/collection/findX-recursive-types.test-d.ts b/test/types/community/collection/findX-recursive-types.test-d.ts deleted file mode 100644 index 96c4e095cc..0000000000 --- a/test/types/community/collection/findX-recursive-types.test-d.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { expectError } from 'tsd'; - -import type { Collection } from '../../../../src'; - -/** - * mutually recursive types are not supported and will not get type safety - */ -interface A { - b: B; -} - -interface B { - a: A; -} - -declare const mutuallyRecursive: Collection; -//@ts-expect-error -mutuallyRecursive.find({}); -mutuallyRecursive.find({ - b: {} -}); - -/** - * types that are not recursive in name but are recursive in structure are - * still supported - */ -interface RecursiveButNotReally { - a: { a: number; b: string }; - b: string; -} - -declare const recursiveButNotReallyCollection: Collection; -expectError( - recursiveButNotReallyCollection.find({ - 'a.a': 'asdf' - }) -); -recursiveButNotReallyCollection.find({ - 'a.a': 2 -}); - -/** - * recursive schemas are now supported, but with limited type checking support - */ -interface RecursiveSchema { - name: RecursiveSchema; - age: number; -} - -declare const recursiveCollection: Collection; -recursiveCollection.find({ - name: { - name: { - age: 23 - } - } -}); - -recursiveCollection.find({ - age: 23 -}); - -/** - * Recursive optional schemas are also supported with the same capabilities as - * standard recursive schemas - */ -interface RecursiveOptionalSchema { - name?: RecursiveOptionalSchema; - age: number; -} - -declare const recursiveOptionalCollection: Collection; - -recursiveOptionalCollection.find({ - name: { - name: { - age: 23 - } - } -}); - -recursiveOptionalCollection.find({ - age: 23 -}); - -/** - * recursive union types are supported - */ -interface Node { - next: Node | null; -} - -declare const nodeCollection: Collection; - -nodeCollection.find({ - next: null -}); - -expectError( - nodeCollection.find({ - next: 'asdf' - }) -); - -nodeCollection.find({ - 'next.next': 'asdf' -}); - -nodeCollection.find({ 'next.next.next': 'yoohoo' }); - -/** - * Recursive schemas with arrays are also supported - */ -interface MongoStrings { - projectId: number; - branches: Branch[]; - twoLevelsDeep: { - name: string; - }; -} - -interface Branch { - id: number; - name: string; - title?: string; - directories: Directory[]; -} - -interface Directory { - id: number; - name: string; - title?: string; - branchId: number; - files: (number | Directory)[]; -} - -declare const recursiveSchemaWithArray: Collection; -expectError( - recursiveSchemaWithArray.findOne({ - 'branches.0.id': 'hello' - }) -); - -expectError( - recursiveSchemaWithArray.findOne({ - 'branches.0.directories.0.id': 'hello' - }) -); - -// type safety breaks after the first -// level of nested types -recursiveSchemaWithArray.findOne({ - 'branches.0.directories.0.files.0.id': 'hello' -}); - -recursiveSchemaWithArray.findOne({ - branches: [ - { - id: 'asdf' - } - ] -}); - -// type inference works on properties but only at the top level -expectError( - recursiveSchemaWithArray.findOne({ - projectId: 'asdf' - }) -); - -recursiveSchemaWithArray.findOne({ - twoLevelsDeep: { - name: 3 - } -}); diff --git a/test/types/community/collection/recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts new file mode 100644 index 0000000000..cd1927e0cf --- /dev/null +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -0,0 +1,315 @@ +import { expectAssignable, expectError, expectNotAssignable, expectNotType } from 'tsd'; + +import type { Collection, Filter, UpdateFilter } from '../../../../src'; + +/** + * mutually recursive types are not supported and will not get type safety + */ +interface Author { + name: string; + bestBook: Book; +} + +interface Book { + title: string; + author: Author; +} + +expectAssignable>({ + bestBook: { + title: 'book title', + author: { + name: 'author name' + } + } +}); + +// Check that devolving to Document after a certain recursive depth does not affect checking +// cases where dot notation is not being used +expectNotType>({ + $set: { + bestBook: { + title: 'a title', + published: new Date(), + author: { + name: 23 + } + } + } +}); + +//////////// Filter +// Depth of 1 has type checking +expectNotAssignable>({ + 'bestBook.title': 23 +}); +// Depth of 2 has type checking +expectNotAssignable>({ + 'bestBook.author.name': 23 +}); +// Depth of 3 has type checking +expectNotAssignable>({ + 'bestBook.author.bestBook.title': 23 +}); +// Depth of 4 has type checking +expectNotAssignable>({ + 'bestBook.author.bestBook.author.name': 23 +}); +// Depth of 5 has type checking +expectNotAssignable>({ + 'bestBook.author.bestBook.author.bestBook.title': 23 +}); +// Depth of 6 has type checking +expectNotAssignable>({ + 'bestBook.author.bestBook.author.bestBook.author.name': 23 +}); +// Depth of 7 has type checking +expectNotAssignable>({ + 'bestBook.author.bestBook.author.bestBook.author.bestBook.title': 23 +}); +// Depth of 8 does **not** have type checking +expectAssignable>({ + 'bestBook.author.bestBook.author.bestBook.author.bestBook.author.name': 23 +}); + +//////////// UpdateFilter +// Depth of 1 has type checking +expectNotAssignable>({ + $set: { + 'bestBook.title': 23 + } +}); +// Depth of 2 has type checking +expectNotAssignable>({ + $set: { + 'bestBook.author.name': 23 + } +}); +// Depth of 3 has type checking +expectNotAssignable>({ + $set: { + 'bestBook.author.bestBook.title': 23 + } +}); +// Depth of 4 has type checking +expectNotAssignable>({ + $set: { + 'bestBook.author.bestBook.author.name': 23 + } +}); +// Depth of 5 has type checking +expectNotAssignable>({ + $set: { + 'bestBook.author.bestBook.author.bestBook.title': 23 + } +}); +// Depth of 6 has type checking +expectNotAssignable>({ + $set: { + 'bestBook.author.bestBook.author.bestBook.author.name': 23 + } +}); +// Depth of 7 has type checking +expectNotAssignable>({ + $set: { + 'bestBook.author.bestBook.author.bestBook.author.bestBook.title': 23 + } +}); +// Depth of 8 does **not** have type checking +expectAssignable>({ + $set: { + 'bestBook.author.bestBook.author.bestBook.author.bestBook.author.name': 23 + } +}); + +/** + * types that are not recursive in name but are recursive in structure are + * still supported + */ +interface RecursiveButNotReally { + a: { a: number; b: string }; + b: string; +} + +declare const recursiveButNotReallyCollection: Collection; +expectError( + recursiveButNotReallyCollection.find({ + 'a.a': 'asdf' + }) +); +recursiveButNotReallyCollection.find({ + 'a.a': 2 +}); + +/** + * recursive schemas are now supported, but with limited type checking support + */ +interface RecursiveSchema { + name: RecursiveSchema; + age: number; +} + +declare const recursiveCollection: Collection; +recursiveCollection.find({ + name: { + name: { + age: 23 + } + } +}); + +recursiveCollection.find({ + age: 23 +}); + +/** + * Recursive optional schemas are also supported with the same capabilities as + * standard recursive schemas + */ +interface RecursiveOptionalSchema { + name?: RecursiveOptionalSchema; + age: number; +} + +declare const recursiveOptionalCollection: Collection; + +recursiveOptionalCollection.find({ + name: { + name: { + age: 23 + } + } +}); + +recursiveOptionalCollection.find({ + age: 23 +}); + +/** + * recursive union types are supported + */ +interface Node { + next: Node | null; +} + +declare const nodeCollection: Collection; + +nodeCollection.find({ + next: null +}); + +expectError( + nodeCollection.find({ + next: 'asdf' + }) +); + +nodeCollection.find({ + 'next.next': 'asdf' +}); + +nodeCollection.find({ 'next.next.next': 'yoohoo' }); + +/** + * Recursive schemas with arrays are also supported + */ +interface MongoStrings { + projectId: number; + branches: Branch[]; + twoLevelsDeep: { + name: string; + }; +} + +interface Branch { + id: number; + name: string; + title?: string; + directories: Directory[]; +} + +interface Directory { + id: number; + name: string; + title?: string; + branchId: number; + files: (number | Directory)[]; +} + +declare const recursiveSchemaWithArray: Collection; +expectError( + recursiveSchemaWithArray.findOne({ + 'branches.0.id': 'hello' + }) +); + +expectError( + recursiveSchemaWithArray.findOne({ + 'branches.0.directories.0.id': 'hello' + }) +); + +// type safety breaks after the first +// level of nested types +recursiveSchemaWithArray.findOne({ + 'branches.0.directories.0.files.0.id': 'hello' +}); + +recursiveSchemaWithArray.findOne({ + branches: [ + { + id: 'asdf' + } + ] +}); + +// type inference works on properties but only at the top level +expectError( + recursiveSchemaWithArray.findOne({ + projectId: 'asdf' + }) +); + +recursiveSchemaWithArray.findOne({ + twoLevelsDeep: { + name: 3 + } +}); + +// Modeling A -> B -> C -> D -> A recursive type +type A = { + name: string; + b: B; +}; + +type B = { + name: string; + c: C; +}; + +type C = { + name: string; + d: D; +}; + +type D = { + name: string; + a: A; +}; + +expectAssignable>({ + 'b.c.d.a.b.c.d.a.b.name': 'a' +}); + +// Beyond the depth supported, there is no type checking +expectAssignable>({ + 'b.c.d.a.b.c.d.a.b.c.name': 3 +}); + +expectAssignable>({ + $set: { 'b.c.d.a.b.c.d.a.b.name': 'a' } +}); + +expectAssignable>({ + $set: { 'b.c.d.a.b.c.d.a.b.c.name': 'a' } +}); diff --git a/test/types/community/collection/updateX.test-d.ts b/test/types/community/collection/updateX.test-d.ts index 01b7ccfc6e..f5273efc01 100644 --- a/test/types/community/collection/updateX.test-d.ts +++ b/test/types/community/collection/updateX.test-d.ts @@ -221,7 +221,10 @@ expectError>({ $set: { 'subInterfaceField.nestedObject': { a: 1, b: '2' } } }); expectError(buildUpdateFilter({ $set: { 'subInterfaceField.field2': 2 } })); -expectError(buildUpdateFilter({ $set: { 'unknown.field': null } })); + +// NODE-3875 introduced intersection with Document to the MatchKeysAndValues so this no longer errors +expectAssignable>({ $set: { 'unknown.field': null } }); + expectAssignable>({ $set: { 'numberArray.$': 40 } }); expectAssignable>({ $set: { 'numberArray.$[bla]': 40 } }); expectAssignable>({ $set: { 'numberArray.$[]': 1000.2 } }); @@ -241,7 +244,10 @@ expectAssignable>({ $setOnInsert: { stringField: 'a' } } expectError(buildUpdateFilter({ $setOnInsert: { stringField: 123 } })); expectAssignable>({ $setOnInsert: { 'subInterfaceField.field1': '2' } }); expectError(buildUpdateFilter({ $setOnInsert: { 'subInterfaceField.field2': 2 } })); -expectError(buildUpdateFilter({ $setOnInsert: { 'unknown.field': null } })); + +// NODE-3875 introduced intersection with Document to the MatchKeysAndValues so this no longer errors +expectAssignable>({ $setOnInsert: { 'unknown.field': null } }); + expectAssignable>({ $setOnInsert: { 'numberArray.$': 40 } }); expectAssignable>({ $setOnInsert: { 'numberArray.$[bla]': 40 } }); expectAssignable>({ $setOnInsert: { 'numberArray.$[]': 1000.2 } }); diff --git a/test/types/community/slow-model.test-d.ts b/test/types/community/slow-model.test-d.ts new file mode 100644 index 0000000000..5f02486323 --- /dev/null +++ b/test/types/community/slow-model.test-d.ts @@ -0,0 +1,419 @@ +/* + * The following type testing was adapted from @ermik + * it is a reproduction of a slow down in typescript compilation + * as well as issues with compiling recursive schemas + * https://github.com/thinkalpha/node-mongodb-typechecking-performance + */ + +import type { UUID } from 'bson'; + +import type { Collection, Db } from '../../../src'; + +interface MomentInputObject { + years?: number; + year?: number; + y?: number; + + months?: number; + month?: number; + M?: number; + + days?: number; + day?: number; + d?: number; + + dates?: number; + date?: number; + D?: number; + + hours?: number; + hour?: number; + h?: number; + + minutes?: number; + minute?: number; + m?: number; + + seconds?: number; + second?: number; + s?: number; + + milliseconds?: number; + millisecond?: number; + ms?: number; +} + +interface MomentSetObject extends MomentInputObject { + weekYears?: number; + weekYear?: number; + gg?: number; + + isoWeekYears?: number; + isoWeekYear?: number; + GG?: number; + + quarters?: number; + quarter?: number; + Q?: number; + + weeks?: number; + week?: number; + w?: number; + + isoWeeks?: number; + isoWeek?: number; + W?: number; + + dayOfYears?: number; + dayOfYear?: number; + DDD?: number; + + weekdays?: number; + weekday?: number; + e?: number; + + isoWeekdays?: number; + isoWeekday?: number; + E?: number; +} + +export type Model = ModelBase; + +export type SpecificModel = Model; + +export interface ModelBase { + another: AnotherSpecificModel; +} + +export interface AnotherSpecificModel { + specifics: SpecificModel[]; +} + +export interface CircularModel { + _id: UUID; + data: SpecificModel; +} + +export type SlowModelPatch = Partial; + +export interface SlowModelBase { + prop1: string; + prop2: string; + prop3: Type1; + prop5: AltUnit | null; + prop6: string | null; + prop7: string | null; + prop8: string[]; + prop9: ExtraType2[]; + prop10: boolean; + prop11: string | null; + prop12: string[]; + prop13: ExtraType4 | null; + prop14: ExtraType5 | null; + prop15: ExtraType6 | null; + prop16: string[]; + prop17: string | null; + prop18: ExtraType7[]; + prop19: ExtraType8[]; + prop20: boolean; + prop21: boolean; + prop22: number; +} + +export declare type Type1 = AltType1 | ExtraType1; + +export declare type AltType1 = Type1A | Type1B | Type1C; + +export declare enum Type1A { + Key1 = 'value1', + Key2 = 'value2', + Key3 = 'value3', + Key4 = 'value4', + Key5 = 'value5', + Key6 = 'value6', + Key7 = 'value7', + Key8 = 'value8', + Key9 = 'value9', + Key10 = 'value10', + Key11 = 'value11', + Key12 = 'value12', + Key13 = 'value13', + Key14 = 'value14' +} + +export declare type Type1B = + | { + type: Type1A.Key1 | Type1A.Key2; + valueKey1Key2?: boolean; + } + | { + type: Type1A.Key12; + valueKey12?: boolean; + } + | { + type: Type1A.Key6; + } + | { + type: Type1A.Key7; + } + | { + type: Type1A.Key13; + } + | { + type: Type1A.Key5; + } + | { + type: Type1A.Key14; + } + | { + type: Type1A.Key8; + } + | { + type: Type1A.Key11; + } + | { + type: Type1A.Key9; + } + | { + type: Type1A.Key3; + } + | { + type: Type1A.Key10; + } + | { + type: Type1A.Key4; + }; + +export declare type ExtraType1 = { + index: number; +}; +export declare type Type1C = { + series: AltType1; + default?: TimeType; +}; + +export declare enum PeriodType { + second = 's', + minute = 'm', + hour = 'h', + day = 'd', + week = 'W', + month = 'Mo', + quarter = 'Q', + year = 'Y' +} +export declare const PeriodNames: readonly string[]; +export declare const PeriodNameMap: ReadonlyMap; +export declare const PeriodTypes: readonly PeriodType[]; +export declare type LengthType = LengthType2; +export interface LengthType2 { + value: number; + period: PeriodType; +} +export declare type TimeType2 = null | { + periods: LengthType2[]; + anchor: MomentSetObject | null; +}; +export declare type TimeType1 = Date; +export declare type TimeType3 = number; + +export declare type TimeType = TimeType1 | TimeType2 | TimeType3; + +export declare enum UnitType1 { + unit1 = 'unit1', + unit2 = 'unit2', + unit3 = 'unit3', + unit4 = 'unit4', + unit5 = 'unit5' +} +export declare enum UnitType2 { + unitA = 'unitA', + unitB = 'unitB', + unitC = 'unitC', + unitD = 'unitD' +} +export declare type ExtraUnit = { + index: number; +}; +export declare type AltUnit = UnitType1 | UnitType2; + +export interface ExtraType2 { + prop23?: string; + prop24?: string; + prop25: AltType1; + prop26?: AltUnit; + prop27?: string; + prop28: boolean; + prop29?: ExtraType9; + prop30?: ExtraType10; + prop31?: ExtraType11; + prop32?: ExtraType12; + prop33: boolean; +} + +export type ExtraType9 = ExtraType6; + +export enum UnitType3 { + unit_ = 'unit_', + unit__ = 'unit__', + unit___ = 'unit___', + unit____ = 'unit____', + unit_____ = 'unit_____' +} + +export interface ExtraType10 { + prop34?: UnitType3; + prop35?: number; + prop36?: number; + prop37?: boolean; +} + +export interface ExtraType11 { + prop38?: number; + prop39?: boolean; + prop40?: boolean; + prop41?: boolean; +} + +export interface ExtraType12 { + _id?: UUID; + prop42: ExtraType13[]; +} + +export interface ExtraType14 { + prop42: TimeType; + prop43?: TimeType | null; + prop44?: LengthType2; +} + +export type ExtraType15 = string | number | boolean | ExtraType14 | TimeType | LengthType2; +export type ExtraType16 = { + prop45: string | number; + prop46: ExtraType15; + prop47: ExtraType15; + prop48: string; +}; +export type ExtraType13 = ExtraType15 | ExtraType16; + +export interface ExtraType6 { + prop49: SpecialType[]; + prop50: string; +} + +export interface SlowModel extends SlowModelBase { + _id: UUID; +} + +export interface SpecialType extends SlowModel { + prop51: string; +} + +export enum ExtraType17 { + Key16 = 'key16', + Key17 = 'key17' +} + +export interface IType1 { + type: ExtraType17; +} +export interface Key17IType1 extends IType1 { + type: ExtraType17.Key17; + prop52: string; + prop53?: string; + prop54?: string; +} +export interface Key16IType1 extends IType1 { + type: ExtraType17.Key16; + prop55: string; + prop56: string | null; +} + +export type ExtraType4 = Key17IType1 | Key16IType1; + +export enum ExtraType18 { + Key18 = 'key18', + Key19 = 'key19' +} + +interface IType2 { + type: ExtraType18; +} +export type ExtraType5 = Key18IType2 | Key19IType2; + +export interface Key18IType2 extends IType2 { + type: ExtraType18.Key18; + prop57: string; + prop58: string; + prop59: boolean; + prop60: string | null; + prop61: boolean; + prop62?: string; +} +export interface Key19IType2 extends IType2 { + type: ExtraType18.Key19; + prop63: string; + prop64?: string; + prop65: boolean; +} + +export type ExtraType19 = ExtraType15[] | ExtraType20; +export interface ExtraType7 { + id: UUID; + prop66?: string; + prop67: T; + prop68?: ExtraType21; + prop69?: ExtraType22; + prop70?: string; +} + +export interface ExtraType20 { + prop71: string; + prop72: string; + prop73: number; + prop74: SpecialType[]; +} + +export interface ExtraType22 { + prop75: string; +} + +export interface ExtraType21 { + prop76: string; + prop77: string; + prop78: boolean; + prop79: boolean; + prop80: string | null; +} + +export interface ExtraType8 { + id: UUID; + prop81: string; + prop82: ExtraType15[]; + prop83: ExtraType15; +} + +export class ExampleImpl { + private circularCol: Collection; + private slowCol: Collection; + + constructor(db: Db) { + this.circularCol = db.collection('circular'); + this.slowCol = db.collection('slow'); + } + + public method = async (): Promise => { + /* + * This call shows the base issue with recursive types + */ + await this.circularCol.findOne({}); + }; + + public update = async (patch: SlowModelPatch, prop1: string): Promise => { + /* + * This call shows the major issue with type inference causing + * an infinite typechecking loop / major slowdown in TypeScript + * compilation times + */ + await this.slowCol.updateOne({}, { $set: { ...patch, prop1 } }); + }; +} diff --git a/test/types/union_schema.test-d.ts b/test/types/union_schema.test-d.ts index 65012beeda..904e187511 100644 --- a/test/types/union_schema.test-d.ts +++ b/test/types/union_schema.test-d.ts @@ -1,10 +1,10 @@ import { expectAssignable, expectError, expectNotAssignable, expectNotType, expectType } from 'tsd'; -import { ObjectId } from '../../src/bson'; +import { Document, ObjectId } from '../../src/bson'; import type { Collection } from '../../src/collection'; import type { WithId } from '../../src/mongo_types'; -type InsertOneFirstParam = Parameters['insertOne']>[0]; +type InsertOneFirstParam = Parameters['insertOne']>[0]; interface Circle { _id: ObjectId;