Skip to content

Commit cba05e7

Browse files
committed
feat: support narrowing by deep object keys in NarrowPartial
1 parent c45f254 commit cba05e7

File tree

3 files changed

+44
-13
lines changed

3 files changed

+44
-13
lines changed

src/util/type-utils.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { InsertResult } from '../query-builder/insert-result.js'
21
import type { DeleteResult } from '../query-builder/delete-result.js'
2+
import type { InsertResult } from '../query-builder/insert-result.js'
3+
import type { MergeResult } from '../query-builder/merge-result.js'
34
import type { UpdateResult } from '../query-builder/update-result.js'
45
import type { KyselyTypeError } from './type-error.js'
5-
import type { MergeResult } from '../query-builder/merge-result.js'
66

77
/**
88
* Given a database type and a union of table names in that db, returns
@@ -153,19 +153,25 @@ export type Equals<T, U> =
153153
? true
154154
: false
155155

156-
export type NarrowPartial<O, T> = DrainOuterGeneric<
157-
T extends object
158-
? {
159-
[K in keyof O & string]: K extends keyof T
160-
? T[K] extends NotNull
161-
? Exclude<O[K], null>
156+
export type NarrowPartial<O, T> = T extends object
157+
? DrainOuterGeneric<{
158+
[K in keyof O & string]: K extends keyof T
159+
? T[K] extends NotNull
160+
? Exclude<O[K], null>
161+
: T[K] extends object
162+
? SimplifyDeep<O[K] & NarrowPartial<O[K], T[K]>>
162163
: T[K] extends O[K]
163164
? T[K]
164165
: KyselyTypeError<`$narrowType() call failed: passed type does not exist in '${K}'s type union`>
165-
: O[K]
166-
}
167-
: never
168-
>
166+
: O[K]
167+
}>
168+
: never
169+
170+
type SimplifyDeep<T> = T extends object
171+
? T extends Date | RegExp | Map<unknown, unknown> | Set<unknown>
172+
? T
173+
: DrainOuterGeneric<{ [K in keyof T]: SimplifyDeep<T[K]> } & {}>
174+
: T
169175

170176
/**
171177
* A type constant for marking a column as not null. Can be used with `$narrowPartial`.

test/typings/shared.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ export interface PersonMetadata {
8080
}
8181
tags: string[]
8282
}>
83+
discriminatedUnionProfile: JSONColumnType<{
84+
auth:
85+
| { type: 'token'; token: string }
86+
| { type: 'session'; session_id: string }
87+
tags: string[]
88+
}>
8389
experience: JSONColumnType<
8490
{
8591
establishment: string

test/typings/test-d/select.test-d.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectError, expectType } from 'tsd'
12
import {
23
type Expression,
34
type ExpressionWrapper,
@@ -9,7 +10,6 @@ import {
910
sql,
1011
} from '..'
1112
import type { Database, Person } from '../shared'
12-
import { expectType, expectError } from 'tsd'
1313

1414
async function testSelectSingle(db: Kysely<Database>) {
1515
const qb = db.selectFrom('person')
@@ -115,6 +115,11 @@ async function testSelectSingle(db: Kysely<Database>) {
115115
.$narrowType<NarrowTarget>()
116116
.execute()
117117

118+
expectType<
119+
| { callback_url: string; queue_id: null }
120+
| { callback_url: null; queue_id: string }
121+
>(r15)
122+
118123
// Narrow not null
119124
const [r16] = await db
120125
.selectFrom('action')
@@ -134,6 +139,20 @@ async function testSelectSingle(db: Kysely<Database>) {
134139
expectType<string>(r17.callback_url)
135140
expectType<string>(r17.queue_id)
136141

142+
// Narrow discriminated union
143+
const [r18] = await db
144+
.selectFrom('person_metadata')
145+
.select(['discriminatedUnionProfile'])
146+
.$narrowType<{ discriminatedUnionProfile: { auth: { type: 'token' } } }>()
147+
.execute()
148+
149+
expectType<{
150+
discriminatedUnionProfile: {
151+
auth: { type: 'token'; token: string }
152+
tags: string[]
153+
}
154+
}>(r18)
155+
137156
const expr1 = db.selectFrom('person').select('first_name').$asScalar()
138157
expectType<ExpressionWrapper<Database, 'person', string>>(expr1)
139158

0 commit comments

Comments
 (0)