Skip to content

Commit 29e8c96

Browse files
committed
fix(types): allow merge Record and litteral object
1 parent 50eb37c commit 29e8c96

File tree

3 files changed

+35
-8
lines changed

3 files changed

+35
-8
lines changed

src/types.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -131,24 +131,31 @@ export type CheckMatchingArrayTypes<Result, NewResult> =
131131

132132
type Simplify<T> = T extends object ? { [K in keyof T]: T[K] } : T
133133

134-
type MergeDeep<New, Row> = {
135-
[K in keyof New | keyof Row]: K extends keyof New
134+
// Extract only explicit (non-index-signature) keys.
135+
type ExplicitKeys<T> = {
136+
[K in keyof T]: string extends K ? never : K
137+
}[keyof T]
138+
139+
type MergeExplicit<New, Row> = {
140+
// We merge all the explicit keys which allows merge and override of types like
141+
// { [key: string]: unknown } and { someSpecificKey: boolean }
142+
[K in ExplicitKeys<New> | ExplicitKeys<Row>]: K extends keyof New
136143
? K extends keyof Row
137144
? Row[K] extends SelectQueryError<string>
138145
? New[K]
139-
: // Check if the override is on a embeded relation (array)
146+
: // Check if the override is on a embedded relation (array)
140147
New[K] extends any[]
141148
? Row[K] extends any[]
142149
? Array<Simplify<MergeDeep<NonNullable<New[K][number]>, NonNullable<Row[K][number]>>>>
143150
: New[K]
144-
: // Check if both properties are objects omiting a potential null union
151+
: // Check if both properties are objects omitting a potential null union
145152
IsPlainObject<NonNullable<New[K]>> extends true
146153
? IsPlainObject<NonNullable<Row[K]>> extends true
147154
? // If they are, use the new override as source of truth for the optionality
148155
ContainsNull<New[K]> extends true
149-
? // If the override want to preserve optionality
156+
? // If the override wants to preserve optionality
150157
Simplify<MergeDeep<NonNullable<New[K]>, NonNullable<Row[K]>>> | null
151-
: // If the override want to enforce non-null result
158+
: // If the override wants to enforce non-null result
152159
Simplify<MergeDeep<New[K], NonNullable<Row[K]>>>
153160
: New[K] // Override with New type if Row isn't an object
154161
: New[K] // Override primitives with New type
@@ -158,6 +165,15 @@ type MergeDeep<New, Row> = {
158165
: never
159166
}
160167

168+
type MergeDeep<New, Row> = Simplify<
169+
MergeExplicit<New, Row> &
170+
// Intersection here is to restore dynamic keys into the merging result
171+
// eg:
172+
// {[key: number]: string}
173+
// or Record<string, number | null>
174+
(string extends keyof Row ? { [K: string]: Row[string] } : {})
175+
>
176+
161177
// Helper to check if a type is a plain object (not an array)
162178
type IsPlainObject<T> = T extends any[] ? false : T extends object ? true : false
163179

test/override-types.test-d.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
247247
bar: { baz: number }
248248
en: 'ONE' | 'TWO' | 'THREE'
249249
record: Record<string, Json | undefined> | null
250+
recordNumber: Record<number, Json | undefined> | null
250251
qux: boolean
251252
}
252253
age_range: unknown
@@ -277,6 +278,7 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
277278
bar: { baz: number }
278279
en: 'ONE' | 'TWO' | 'THREE'
279280
record: Record<string, Json | undefined> | null
281+
recordNumber: Record<number, Json | undefined> | null
280282
qux: boolean
281283
} | null
282284
age_range: unknown
@@ -345,6 +347,7 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
345347
bar: { baz: number; newBaz: string }
346348
en: 'FOUR' // Overridden enum value
347349
record: Record<string, Json | undefined> | null
350+
recordNumber: Record<number, Json | undefined> | null
348351
}
349352
age_range: unknown
350353
catchphrase: unknown
@@ -359,7 +362,7 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
359362
const result = await postgrest
360363
.from('users')
361364
.select()
362-
.overrideTypes<{ data: { record: { baz: 'foo' } } }[]>()
365+
.overrideTypes<{ data: { record: { baz: 'foo' }; recordNumber: { bar: 'foo' } } }[]>()
363366
if (result.error) {
364367
throw new Error(result.error.message)
365368
}
@@ -375,7 +378,14 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
375378
baz: number
376379
}
377380
en: 'ONE' | 'TWO' | 'THREE'
378-
record: { baz: 'foo' }
381+
record: {
382+
[x: string]: Json | undefined
383+
baz: 'foo'
384+
}
385+
recordNumber: {
386+
[x: number]: Json | undefined
387+
bar: 'foo'
388+
}
379389
}
380390
age_range: unknown
381391
catchphrase: unknown

test/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type CustomUserDataType = {
77
}
88
en: 'ONE' | 'TWO' | 'THREE'
99
record: Record<string, Json | undefined> | null
10+
recordNumber: Record<number, Json | undefined> | null
1011
}
1112

1213
export type Database = {

0 commit comments

Comments
 (0)