Skip to content

Commit

Permalink
fix(types): allow merge Record and litteral object
Browse files Browse the repository at this point in the history
  • Loading branch information
avallete committed Feb 23, 2025
1 parent 50eb37c commit 29e8c96
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 8 deletions.
28 changes: 22 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,31 @@ export type CheckMatchingArrayTypes<Result, NewResult> =

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

type MergeDeep<New, Row> = {
[K in keyof New | keyof Row]: K extends keyof New
// Extract only explicit (non-index-signature) keys.
type ExplicitKeys<T> = {
[K in keyof T]: string extends K ? never : K
}[keyof T]

type MergeExplicit<New, Row> = {
// We merge all the explicit keys which allows merge and override of types like
// { [key: string]: unknown } and { someSpecificKey: boolean }
[K in ExplicitKeys<New> | ExplicitKeys<Row>]: K extends keyof New
? K extends keyof Row
? Row[K] extends SelectQueryError<string>
? New[K]
: // Check if the override is on a embeded relation (array)
: // Check if the override is on a embedded relation (array)
New[K] extends any[]
? Row[K] extends any[]
? Array<Simplify<MergeDeep<NonNullable<New[K][number]>, NonNullable<Row[K][number]>>>>
: New[K]
: // Check if both properties are objects omiting a potential null union
: // Check if both properties are objects omitting a potential null union
IsPlainObject<NonNullable<New[K]>> extends true
? IsPlainObject<NonNullable<Row[K]>> extends true
? // If they are, use the new override as source of truth for the optionality
ContainsNull<New[K]> extends true
? // If the override want to preserve optionality
? // If the override wants to preserve optionality
Simplify<MergeDeep<NonNullable<New[K]>, NonNullable<Row[K]>>> | null
: // If the override want to enforce non-null result
: // If the override wants to enforce non-null result
Simplify<MergeDeep<New[K], NonNullable<Row[K]>>>
: New[K] // Override with New type if Row isn't an object
: New[K] // Override primitives with New type
Expand All @@ -158,6 +165,15 @@ type MergeDeep<New, Row> = {
: never
}

type MergeDeep<New, Row> = Simplify<
MergeExplicit<New, Row> &
// Intersection here is to restore dynamic keys into the merging result
// eg:
// {[key: number]: string}
// or Record<string, number | null>
(string extends keyof Row ? { [K: string]: Row[string] } : {})
>

// Helper to check if a type is a plain object (not an array)
type IsPlainObject<T> = T extends any[] ? false : T extends object ? true : false

Expand Down
14 changes: 12 additions & 2 deletions test/override-types.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
bar: { baz: number }
en: 'ONE' | 'TWO' | 'THREE'
record: Record<string, Json | undefined> | null
recordNumber: Record<number, Json | undefined> | null
qux: boolean
}
age_range: unknown
Expand Down Expand Up @@ -277,6 +278,7 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
bar: { baz: number }
en: 'ONE' | 'TWO' | 'THREE'
record: Record<string, Json | undefined> | null
recordNumber: Record<number, Json | undefined> | null
qux: boolean
} | null
age_range: unknown
Expand Down Expand Up @@ -345,6 +347,7 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
bar: { baz: number; newBaz: string }
en: 'FOUR' // Overridden enum value
record: Record<string, Json | undefined> | null
recordNumber: Record<number, Json | undefined> | null
}
age_range: unknown
catchphrase: unknown
Expand All @@ -359,7 +362,7 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
const result = await postgrest
.from('users')
.select()
.overrideTypes<{ data: { record: { baz: 'foo' } } }[]>()
.overrideTypes<{ data: { record: { baz: 'foo' }; recordNumber: { bar: 'foo' } } }[]>()
if (result.error) {
throw new Error(result.error.message)
}
Expand All @@ -375,7 +378,14 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
baz: number
}
en: 'ONE' | 'TWO' | 'THREE'
record: { baz: 'foo' }
record: {
[x: string]: Json | undefined
baz: 'foo'
}
recordNumber: {
[x: number]: Json | undefined
bar: 'foo'
}
}
age_range: unknown
catchphrase: unknown
Expand Down
1 change: 1 addition & 0 deletions test/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type CustomUserDataType = {
}
en: 'ONE' | 'TWO' | 'THREE'
record: Record<string, Json | undefined> | null
recordNumber: Record<number, Json | undefined> | null
}

export type Database = {
Expand Down

0 comments on commit 29e8c96

Please sign in to comment.