From 42c9507f9e7f20ae6bf5357581654e8d8059691f Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 4 Oct 2022 16:25:16 -0400 Subject: [PATCH 01/12] feat(NODE-3875): support recursive schema types --- src/mongo_types.ts | 34 ++++++----- ...es.test-d.ts => recursive-types.test-d.ts} | 61 ++++++++++++++++--- 2 files changed, 70 insertions(+), 25 deletions(-) rename test/types/community/collection/{findX-recursive-types.test-d.ts => recursive-types.test-d.ts} (57%) diff --git a/src/mongo_types.ts b/src/mongo_types.ts index e68ff4fe86..6ad6638bec 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 @@ -499,19 +499,21 @@ export type PropertyType = string extends Propert * 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/ */ -export type NestedPaths = Type extends - | string - | number - | boolean - | Date - | RegExp - | Buffer - | Uint8Array - | ((...args: any[]) => any) - | { _bsontype: string } +export type NestedPaths = Depth['length'] extends 10 + ? [] + : 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 +531,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 +544,7 @@ export type NestedPaths = Type extends */ export type NestedPathsOfType = KeysOfAType< { - [Property in Join, '.'>]: PropertyType; + [Property in Join, '.'>]: PropertyType; }, Type >; diff --git a/test/types/community/collection/findX-recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts similarity index 57% rename from test/types/community/collection/findX-recursive-types.test-d.ts rename to test/types/community/collection/recursive-types.test-d.ts index 96c4e095cc..0e1dd20075 100644 --- a/test/types/community/collection/findX-recursive-types.test-d.ts +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -1,23 +1,66 @@ -import { expectError } from 'tsd'; +import { expectAssignable, expectError, expectNotAssignable } from 'tsd'; -import type { Collection } from '../../../../src'; +import type { Collection, Filter, UpdateFilter } from '../../../../src'; /** * mutually recursive types are not supported and will not get type safety */ -interface A { - b: B; +interface Author { + books: Book[]; + favoritePublication: Book; + name: string; } -interface B { - a: A; +interface Book { + author: Author; + title: string; + published: Date; } -declare const mutuallyRecursive: Collection; -//@ts-expect-error +declare const mutuallyRecursive: Collection; mutuallyRecursive.find({}); mutuallyRecursive.find({ - b: {} + b: { a: { b: { a: null } } } +}); + +// Extremely deep type checking for recursive schemas +expectNotAssignable>({ + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.title': 23 +}); +expectAssignable>({ + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.title': + 'good soup' +}); +expectNotAssignable>({ + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 23 +}); + +// Beyond the depth of 10, `extends Document` permits anything (number for name is permitted) +expectAssignable>({ + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 23 +}); + +// Update filter has similar depth limit +expectAssignable>({ + $set: { + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': + 'joe' + } +}); + +// Depth below 9 is type checked +expectNotAssignable>({ + $set: { + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 3 + } +}); + +// Using keys that are beyond the permitted depth in $set still result in an error as opposed to falling back to any (known limitation) +expectNotAssignable>({ + $set: { + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': + 'name' + } }); /** From 18e267c15549faec74b1319404047a80d5e69fe5 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 5 Oct 2022 12:38:14 -0400 Subject: [PATCH 02/12] test: add example of explicit embedded usage --- .../collection/recursive-types.test-d.ts | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/test/types/community/collection/recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts index 0e1dd20075..6b5be685c6 100644 --- a/test/types/community/collection/recursive-types.test-d.ts +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -1,4 +1,4 @@ -import { expectAssignable, expectError, expectNotAssignable } from 'tsd'; +import { expectAssignable, expectError, expectNotAssignable, expectNotType } from 'tsd'; import type { Collection, Filter, UpdateFilter } from '../../../../src'; @@ -6,21 +6,33 @@ import type { Collection, Filter, UpdateFilter } from '../../../../src'; * mutually recursive types are not supported and will not get type safety */ interface Author { - books: Book[]; - favoritePublication: Book; name: string; + favoritePublication: Book; } interface Book { - author: Author; title: string; - published: Date; + author: Author; } -declare const mutuallyRecursive: Collection; -mutuallyRecursive.find({}); -mutuallyRecursive.find({ - b: { a: { b: { a: null } } } +expectAssignable>({ + favoritePublication: { + title: 'book title', + author: { + name: 'author name' + } + } +}); +expectNotType>({ + $set: { + favoritePublication: { + title: 'a title', + published: new Date(), + author: { + name: 23 + } + } + } }); // Extremely deep type checking for recursive schemas From 914ec9d89c1f3f96b3a000f7361cd655f36887ef Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 6 Oct 2022 16:15:14 -0400 Subject: [PATCH 03/12] complex example --- .../collection/recursive-types.test-d.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/types/community/collection/recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts index 6b5be685c6..b4c281289e 100644 --- a/test/types/community/collection/recursive-types.test-d.ts +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -228,3 +228,41 @@ recursiveSchemaWithArray.findOne({ 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' +}); + +expectNotAssignable>({ + 'b.c.d.a.b.c.d.a.b.name': 2 +}); + +expectAssignable>({ + 'b.c.d.a.b.c.d.a.b.c.name': 2 +}); + +// why does this blow up +expectNotAssignable>({ + $set: { 'b.c.d.a.b.c.d.a.b.c.name': 'a' } +}); From 3495780d171195c8400fd57ac07cf2a4797b96b2 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 7 Oct 2022 12:23:40 -0400 Subject: [PATCH 04/12] add Document case --- src/mongo_types.ts | 2 +- test/types/basic_schema.test-d.ts | 4 ++-- .../collection/recursive-types.test-d.ts | 20 ++++++++----------- .../community/collection/updateX.test-d.ts | 10 ++++++++-- test/types/union_schema.test-d.ts | 4 ++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 6ad6638bec..a61dd9434d 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -272,7 +272,7 @@ export type MatchKeysAndValues = Readonly< [Property in `${NestedPathsOfType[]>}.$${ | `[${string}]` | ''}.${string}`]?: any; // Could be further narrowed - } + } & Document >; /** @public */ 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/recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts index b4c281289e..b1fc69b3e3 100644 --- a/test/types/community/collection/recursive-types.test-d.ts +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -67,14 +67,6 @@ expectNotAssignable>({ } }); -// Using keys that are beyond the permitted depth in $set still result in an error as opposed to falling back to any (known limitation) -expectNotAssignable>({ - $set: { - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': - 'name' - } -}); - /** * types that are not recursive in name but are recursive in structure are * still supported @@ -254,15 +246,19 @@ expectAssignable>({ 'b.c.d.a.b.c.d.a.b.name': 'a' }); -expectNotAssignable>({ - 'b.c.d.a.b.c.d.a.b.name': 2 +// Beyond the depth supported, there is no type checking +expectAssignable>({ + 'b.c.d.a.b.c.d.a.b.c.name': 3 }); expectAssignable>({ 'b.c.d.a.b.c.d.a.b.c.name': 2 }); -// why does this blow up -expectNotAssignable>({ +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..dbbf88058b 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 +// expectError(buildUpdateFilter({ $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 +// expectError(buildUpdateFilter({ $setOnInsert: { 'unknown.field': null } })); + expectAssignable>({ $setOnInsert: { 'numberArray.$': 40 } }); expectAssignable>({ $setOnInsert: { 'numberArray.$[bla]': 40 } }); expectAssignable>({ $setOnInsert: { 'numberArray.$[]': 1000.2 } }); 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; From 213ecbc83046add91590df933d9a26f565181823 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 10 Oct 2022 17:08:08 -0400 Subject: [PATCH 05/12] fixes --- test/types/community/collection/recursive-types.test-d.ts | 4 ---- test/types/community/collection/updateX.test-d.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/test/types/community/collection/recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts index b1fc69b3e3..c1e65ada2e 100644 --- a/test/types/community/collection/recursive-types.test-d.ts +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -251,10 +251,6 @@ expectAssignable>({ 'b.c.d.a.b.c.d.a.b.c.name': 3 }); -expectAssignable>({ - 'b.c.d.a.b.c.d.a.b.c.name': 2 -}); - expectAssignable>({ $set: { 'b.c.d.a.b.c.d.a.b.name': 'a' } }); diff --git a/test/types/community/collection/updateX.test-d.ts b/test/types/community/collection/updateX.test-d.ts index dbbf88058b..f5273efc01 100644 --- a/test/types/community/collection/updateX.test-d.ts +++ b/test/types/community/collection/updateX.test-d.ts @@ -223,7 +223,7 @@ expectError>({ expectError(buildUpdateFilter({ $set: { 'subInterfaceField.field2': 2 } })); // NODE-3875 introduced intersection with Document to the MatchKeysAndValues so this no longer errors -// expectError(buildUpdateFilter({ $set: { 'unknown.field': null } })); +expectAssignable>({ $set: { 'unknown.field': null } }); expectAssignable>({ $set: { 'numberArray.$': 40 } }); expectAssignable>({ $set: { 'numberArray.$[bla]': 40 } }); @@ -246,7 +246,7 @@ expectAssignable>({ $setOnInsert: { 'subInterfaceField.f expectError(buildUpdateFilter({ $setOnInsert: { 'subInterfaceField.field2': 2 } })); // NODE-3875 introduced intersection with Document to the MatchKeysAndValues so this no longer errors -// expectError(buildUpdateFilter({ $setOnInsert: { 'unknown.field': null } })); +expectAssignable>({ $setOnInsert: { 'unknown.field': null } }); expectAssignable>({ $setOnInsert: { 'numberArray.$': 40 } }); expectAssignable>({ $setOnInsert: { 'numberArray.$[bla]': 40 } }); From 2ac55491b59d282721e9de2b54f77a35bf334019 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 11 Oct 2022 17:27:28 -0400 Subject: [PATCH 06/12] fix: set depth limit to 8 --- src/mongo_types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mongo_types.ts b/src/mongo_types.ts index a61dd9434d..9f15022230 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -499,7 +499,7 @@ export type PropertyType = string extends Propert * 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/ */ -export type NestedPaths = Depth['length'] extends 10 +export type NestedPaths = Depth['length'] extends 8 ? [] : Type extends | string From 521a1b8f604c106cab3d746d616223388291b707 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 11 Oct 2022 17:31:03 -0400 Subject: [PATCH 07/12] update tests --- test/types/community/collection/recursive-types.test-d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/types/community/collection/recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts index c1e65ada2e..3780fb47ea 100644 --- a/test/types/community/collection/recursive-types.test-d.ts +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -37,14 +37,14 @@ expectNotType>({ // Extremely deep type checking for recursive schemas expectNotAssignable>({ - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.title': 23 + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.title': 23 }); expectAssignable>({ 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.title': 'good soup' }); expectNotAssignable>({ - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 23 + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 23 }); // Beyond the depth of 10, `extends Document` permits anything (number for name is permitted) @@ -60,10 +60,10 @@ expectAssignable>({ } }); -// Depth below 9 is type checked +// Depth 7 or below is type checked expectNotAssignable>({ $set: { - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 3 + 'favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 3 } }); From 4375456891ec408c81a436552efb7398dc85e6e6 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 11 Oct 2022 17:39:07 -0400 Subject: [PATCH 08/12] add comment and slow model test --- src/mongo_types.ts | 7 + test/types/community/slow-model.test-d.ts | 412 ++++++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 test/types/community/slow-model.test-d.ts diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 9f15022230..f4dcc28e1d 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -498,6 +498,13 @@ 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 = Depth['length'] extends 8 ? [] 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..9c903d9bbd --- /dev/null +++ b/test/types/community/slow-model.test-d.ts @@ -0,0 +1,412 @@ +import type { UUID as MUUID } 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: MUUID; + 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?: MUUID; + 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: MUUID; +} + +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: MUUID; + 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: MUUID; + 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 } }); + }; +} From f3284f48cc069689d715572a703a5997d290c492 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 11 Oct 2022 17:40:16 -0400 Subject: [PATCH 09/12] rename uuid --- test/types/community/slow-model.test-d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/types/community/slow-model.test-d.ts b/test/types/community/slow-model.test-d.ts index 9c903d9bbd..844e085ec6 100644 --- a/test/types/community/slow-model.test-d.ts +++ b/test/types/community/slow-model.test-d.ts @@ -1,4 +1,4 @@ -import type { UUID as MUUID } from 'bson'; +import type { UUID } from 'bson'; import type { Collection, Db } from '../../../src'; @@ -83,7 +83,7 @@ export interface AnotherSpecificModel { } export interface CircularModel { - _id: MUUID; + _id: UUID; data: SpecificModel; } @@ -269,7 +269,7 @@ export interface ExtraType11 { } export interface ExtraType12 { - _id?: MUUID; + _id?: UUID; prop42: ExtraType13[]; } @@ -294,7 +294,7 @@ export interface ExtraType6 { } export interface SlowModel extends SlowModelBase { - _id: MUUID; + _id: UUID; } export interface SpecialType extends SlowModel { @@ -351,7 +351,7 @@ export interface Key19IType2 extends IType2 { export type ExtraType19 = ExtraType15[] | ExtraType20; export interface ExtraType7 { - id: MUUID; + id: UUID; prop66?: string; prop67: T; prop68?: ExtraType21; @@ -379,7 +379,7 @@ export interface ExtraType21 { } export interface ExtraType8 { - id: MUUID; + id: UUID; prop81: string; prop82: ExtraType15[]; prop83: ExtraType15; From df7f412b7abfaeb20cdd7f59c80d3c2cda126abb Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 12 Oct 2022 11:22:04 -0400 Subject: [PATCH 10/12] add credit! --- test/types/community/slow-model.test-d.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/types/community/slow-model.test-d.ts b/test/types/community/slow-model.test-d.ts index 844e085ec6..5f02486323 100644 --- a/test/types/community/slow-model.test-d.ts +++ b/test/types/community/slow-model.test-d.ts @@ -1,3 +1,10 @@ +/* + * 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'; From 0535c787421910290bd6ead7ac5571adcbb2a22b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 12 Oct 2022 16:15:16 -0400 Subject: [PATCH 11/12] add a test for each level --- .../collection/recursive-types.test-d.ts | 93 +++++++++++++++---- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/test/types/community/collection/recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts index 3780fb47ea..2ec4d7e4d4 100644 --- a/test/types/community/collection/recursive-types.test-d.ts +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -7,7 +7,7 @@ import type { Collection, Filter, UpdateFilter } from '../../../../src'; */ interface Author { name: string; - favoritePublication: Book; + bestBook: Book; } interface Book { @@ -16,16 +16,19 @@ interface Book { } expectAssignable>({ - favoritePublication: { + bestBook: { title: 'book title', author: { name: 'author name' } } }); + +// Check that devolving to Document after a certain recursive depth does not effect checking +// cases where dot notation is not being used expectNotType>({ $set: { - favoritePublication: { + bestBook: { title: 'a title', published: new Date(), author: { @@ -35,35 +38,87 @@ expectNotType>({ } }); -// Extremely deep type checking for recursive schemas +//////////// Filter +// Depth of 1 has type checking expectNotAssignable>({ - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.title': 23 + 'bestBook.title': 23 }); -expectAssignable>({ - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.title': - 'good soup' +// Depth of 2 has type checking +expectNotAssignable>({ + 'bestBook.author.name': 23 }); +// Depth of 3 has type checking expectNotAssignable>({ - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 23 + 'bestBook.author.bestBook.title': 23 }); - -// Beyond the depth of 10, `extends Document` permits anything (number for name is permitted) +// 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>({ - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 23 + 'bestBook.author.bestBook.author.bestBook.author.bestBook.author.name': 23 }); -// Update filter has similar depth limit -expectAssignable>({ +//////////// UpdateFilter +// Depth of 1 has type checking +expectNotAssignable>({ $set: { - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.favoritePublication.author.name': - 'joe' + 'bestBook.title': 23 } }); - -// Depth 7 or below is type checked +// Depth of 2 has type checking expectNotAssignable>({ $set: { - 'favoritePublication.author.favoritePublication.author.favoritePublication.author.name': 3 + '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 } }); From 92730e42c21c7ec7c908e6f7bf1f5fb96dab53e6 Mon Sep 17 00:00:00 2001 From: Daria Pardue Date: Wed, 12 Oct 2022 18:01:08 -0400 Subject: [PATCH 12/12] grammar --- test/types/community/collection/recursive-types.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/types/community/collection/recursive-types.test-d.ts b/test/types/community/collection/recursive-types.test-d.ts index 2ec4d7e4d4..cd1927e0cf 100644 --- a/test/types/community/collection/recursive-types.test-d.ts +++ b/test/types/community/collection/recursive-types.test-d.ts @@ -24,7 +24,7 @@ expectAssignable>({ } }); -// Check that devolving to Document after a certain recursive depth does not effect checking +// Check that devolving to Document after a certain recursive depth does not affect checking // cases where dot notation is not being used expectNotType>({ $set: {