Skip to content

Commit 3a142f3

Browse files
authored
feat: Type check nested objects in generic objects (#439)
1 parent feb8dc7 commit 3a142f3

File tree

2 files changed

+93
-37
lines changed

2 files changed

+93
-37
lines changed

src/__tests__/mongodbTypes.test.ts

Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ describe('mongodb types', () => {
3535
direct: boolean;
3636
other?: number;
3737
};
38+
genericObject: Record<
39+
string,
40+
{
41+
id: number;
42+
}
43+
>;
3844
binary: Binary;
3945
bsonSymbol: BSONSymbol;
4046
dbRef: DBRef;
@@ -137,6 +143,11 @@ describe('mongodb types', () => {
137143
expectType<PaprFilter<TestDocument>>({ 'nestedObject.deep.deeper': 'foo' });
138144
expectType<PaprFilter<TestDocument>>({ 'nestedObject.deep.other': 123 });
139145

146+
expectType<PaprFilter<TestDocument>>({ 'genericObject.foo.id': 123 });
147+
expectType<PaprFilter<TestDocument>>({ 'genericObject.bar.id': 123 });
148+
149+
expectType<PaprFilter<TestDocument>>({ 'genericObject.foo': { id: 123 } });
150+
140151
// https://www.mongodb.com/docs/manual/tutorial/query-array-of-documents/#use-the-array-index-to-query-for-a-field-in-the-embedded-document
141152
expectType<PaprFilter<TestDocument>>({ 'list.0.direct': 'foo' });
142153
expectType<PaprFilter<TestDocument>>({ 'list.1.other': 123 });
@@ -164,6 +175,14 @@ describe('mongodb types', () => {
164175
// @ts-expect-error Type mismatch
165176
expectType<PaprFilter<TestDocument>>({ 'nestedObject.deep.other': 'foo' });
166177

178+
// @ts-expect-error Type mismatch
179+
expectType<PaprFilter<TestDocument>>({ 'genericObject.foo.id': 'foo' });
180+
// @ts-expect-error Type mismatch
181+
expectType<PaprFilter<TestDocument>>({ 'genericObject.bar.id': true });
182+
183+
// Support for this type-check is not available yet
184+
// expectType<PaprFilter<TestDocument>>({ 'genericObject.foo': { id: 'foo' } });
185+
167186
// @ts-expect-error Type mismatch
168187
expectType<PaprFilter<TestDocument>>({ 'list.0.direct': 123 });
169188
// @ts-expect-error Type mismatch
@@ -252,6 +271,9 @@ describe('mongodb types', () => {
252271
expectType<PaprFilter<TestDocument>>({ 'nestedObject.deep.deeper': { $in: ['foo'] } });
253272
expectType<PaprFilter<TestDocument>>({ 'nestedObject.deep.other': { $gte: 123 } });
254273

274+
expectType<PaprFilter<TestDocument>>({ 'genericObject.foo.id': { $gte: 123 } });
275+
expectType<PaprFilter<TestDocument>>({ 'genericObject.bar.id': { $in: [123, 456] } });
276+
255277
// https://www.mongodb.com/docs/manual/tutorial/query-array-of-documents/#use-the-array-index-to-query-for-a-field-in-the-embedded-document
256278
expectType<PaprFilter<TestDocument>>({ 'list.0.direct': { $ne: 'foo' } });
257279
expectType<PaprFilter<TestDocument>>({ 'list.1.other': { $ne: 123 } });
@@ -284,6 +306,11 @@ describe('mongodb types', () => {
284306
// @ts-expect-error Type mismatch
285307
expectType<PaprFilter<TestDocument>>({ 'nestedObject.deep.other': { $gte: 'foo' } });
286308

309+
// @ts-expect-error Type mismatch
310+
expectType<PaprFilter<TestDocument>>({ 'genericObject.foo.id': { $eq: 'foo' } });
311+
// @ts-expect-error Type mismatch
312+
expectType<PaprFilter<TestDocument>>({ 'genericObject.bar.id': { $in: ['foo'] } });
313+
287314
// @ts-expect-error Type mismatch
288315
expectType<PaprFilter<TestDocument>>({ 'list.0.direct': { $ne: 123 } });
289316
// @ts-expect-error Type mismatch
@@ -502,53 +529,78 @@ describe('mongodb types', () => {
502529
},
503530
});
504531
});
532+
});
505533

506-
describe('existing nested keys using dot notation', () => {
507-
test('valid types', () => {
508-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'nestedObject.direct': true } });
509-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'nestedObject.other': 123 } });
510-
511-
expectType<PaprUpdateFilter<TestDocument>>({
512-
$set: { 'nestedObject.deep': { deeper: 'foo' } },
513-
});
514-
expectType<PaprUpdateFilter<TestDocument>>({
515-
$set: { 'nestedObject.deep.deeper': 'foo' },
516-
});
517-
expectType<PaprUpdateFilter<TestDocument>>({
518-
$set: { 'nestedObject.deep.other': 123 },
519-
});
534+
describe('existing nested keys using dot notation', () => {
535+
test('valid types', () => {
536+
expectType<PaprUpdateFilter<TestDocument>>({
537+
$set: { 'nestedObject.direct': true },
538+
});
539+
expectType<PaprUpdateFilter<TestDocument>>({
540+
$set: { 'nestedObject.other': 123 },
541+
});
520542

521-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'tags.0': 'foo' } });
543+
expectType<PaprUpdateFilter<TestDocument>>({
544+
$set: { 'nestedObject.deep': { deeper: 'foo' } },
545+
});
546+
expectType<PaprUpdateFilter<TestDocument>>({
547+
$set: { 'nestedObject.deep.deeper': 'foo' },
548+
});
549+
expectType<PaprUpdateFilter<TestDocument>>({
550+
$set: { 'nestedObject.deep.other': 123 },
551+
});
522552

523-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.0.direct': 'foo' } });
524-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.1.other': 123 } });
553+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'genericObject.foo.id': 123 } });
554+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'genericObject.bar.id': 123 } });
525555

526-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.direct': 'foo' } });
527-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.other': 123 } });
556+
expectType<PaprUpdateFilter<TestDocument>>({
557+
$set: { 'genericObject.foo': { id: 123 } },
528558
});
529559

530-
test('invalid types', () => {
531-
// @ts-expect-error Type mismatch
532-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'nestedObject.direct': 'foo' } });
533-
// @ts-expect-error Type mismatch
534-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'nestedObject.other': 'foo' } });
535-
expectType<PaprUpdateFilter<TestDocument>>({
536-
// @ts-expect-error Type mismatch
537-
$set: { 'nestedObject.deep.deeper': 123 },
538-
});
539-
expectType<PaprUpdateFilter<TestDocument>>({
540-
// @ts-expect-error Type mismatch
541-
$set: { 'nestedObject.deep.other': 'foo' },
542-
});
560+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'tags.0': 'foo' } });
561+
562+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.0.direct': 'foo' } });
563+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.1.other': 123 } });
564+
565+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.direct': 'foo' } });
566+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.other': 123 } });
567+
});
543568

569+
test('invalid types', () => {
570+
// @ts-expect-error Type mismatch
571+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'nestedObject.direct': 'foo' } });
572+
// @ts-expect-error Type mismatch
573+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'nestedObject.other': 'foo' } });
574+
expectType<PaprUpdateFilter<TestDocument>>({
544575
// @ts-expect-error Type mismatch
545-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'tags.0': 123 } });
576+
$set: { 'nestedObject.deep.deeper': 123 },
577+
});
578+
expectType<PaprUpdateFilter<TestDocument>>({
579+
// @ts-expect-error Type mismatch
580+
$set: { 'nestedObject.deep.other': 'foo' },
581+
});
546582

583+
expectType<PaprUpdateFilter<TestDocument>>({
547584
// @ts-expect-error Type mismatch
548-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.0.direct': 123 } });
585+
$set: { 'genericObject.foo.id': 'foo' },
586+
});
587+
expectType<PaprUpdateFilter<TestDocument>>({
549588
// @ts-expect-error Type mismatch
550-
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.1.other': 'foo' } });
589+
$set: { 'genericObject.bar.id': true },
551590
});
591+
592+
// Support for this type-check is not available yet
593+
// expectType<PaprUpdateFilter<TestDocument>>({
594+
// $set: { 'genericObject.foo': { id: 'foo' } },
595+
// });
596+
597+
// @ts-expect-error Type mismatch
598+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'tags.0': 123 } });
599+
600+
// @ts-expect-error Type mismatch
601+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.0.direct': 123 } });
602+
// @ts-expect-error Type mismatch
603+
expectType<PaprUpdateFilter<TestDocument>>({ $set: { 'list.1.other': 'foo' } });
552604
});
553605
});
554606

src/utils.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,12 @@ export type PropertyType<Type, Property extends string> = string extends Propert
173173
? unknown
174174
: // object properties
175175
Property extends keyof Type
176-
? Type[Property]
177-
: NonNullable<Type> extends readonly (infer ArrayType)[]
176+
? Type extends Record<string, any>
177+
? Property extends `${string}.${string}`
178+
? PropertyNestedType<NonNullable<Type>, Property>
179+
: Type[Property]
180+
: Type[Property]
181+
: Type extends readonly (infer ArrayType)[]
178182
? // indexed array properties
179183
Property extends `${number}`
180184
? ArrayType

0 commit comments

Comments
 (0)