Skip to content

Commit

Permalink
remove the magical UnionToIntersection type
Browse files Browse the repository at this point in the history
  • Loading branch information
koskimas committed Dec 30, 2021
1 parent a899c55 commit 0ca3b01
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 33 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kysely",
"version": "0.14.0",
"version": "0.14.1",
"description": "Type safe SQL query builder",
"repository": {
"type": "git",
Expand Down
9 changes: 4 additions & 5 deletions src/parser/reference-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import {
AnyColumn,
AnyColumnWithTable,
RowType,
ExtractColumnType,
ValueType,
} from '../util/type-utils.js'
import { RawBuilder } from '../raw-builder/raw-builder.js'
Expand Down Expand Up @@ -58,8 +58,7 @@ export type ExtractTypeFromReferenceExpression<
type ExtractTypeFromStringReference<
DB,
TB extends keyof DB,
RE extends string,
R = RowType<DB, TB>
RE extends string
> = RE extends `${infer SC}.${infer T}.${infer C}`
? `${SC}.${T}` extends TB
? C extends keyof DB[`${SC}.${T}`]
Expand All @@ -72,8 +71,8 @@ type ExtractTypeFromStringReference<
? DB[T][C]
: never
: never
: RE extends keyof R
? R[RE]
: RE extends AnyColumn<DB, TB>
? ExtractColumnType<DB, TB, RE>
: PrimitiveValue

export function parseReferenceExpressionOrList(
Expand Down
12 changes: 6 additions & 6 deletions src/parser/select-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AnyAliasedColumnWithTable,
AnyColumn,
AnyColumnWithTable,
ExtractColumnType,
RowType,
ValueType,
} from '../util/type-utils.js'
Expand Down Expand Up @@ -126,8 +127,7 @@ type ExtractTypeFromStringSelectExpression<
DB,
TB extends keyof DB,
SE extends string,
A extends keyof any,
R = RowType<DB, TB>
A extends keyof any
> = SE extends `${infer SC}.${infer T}.${infer C} as ${infer RA}`
? RA extends A
? `${SC}.${T}` extends TB
Expand All @@ -146,8 +146,8 @@ type ExtractTypeFromStringSelectExpression<
: never
: SE extends `${infer C} as ${infer RA}`
? RA extends A
? C extends keyof R
? R[C]
? C extends AnyColumn<DB, TB>
? ExtractColumnType<DB, TB, C>
: never
: never
: SE extends `${infer SC}.${infer T}.${infer C}`
Expand All @@ -167,8 +167,8 @@ type ExtractTypeFromStringSelectExpression<
: never
: never
: SE extends A
? SE extends keyof R
? R[SE]
? SE extends AnyColumn<DB, TB>
? ExtractColumnType<DB, TB, SE>
: never
: never

Expand Down
6 changes: 4 additions & 2 deletions src/query-builder/on-conflict-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
MutationObject,
parseUpdateObject,
} from '../parser/update-set-parser.js'
import { RawBuilder } from '../raw-builder/raw-builder.js'
import { freeze } from '../util/object-utils.js'
import { preventAwait } from '../util/prevent-await.js'
import { AnyColumn, AnyRawBuilder } from '../util/type-utils.js'
Expand Down Expand Up @@ -308,7 +307,10 @@ export class OnConflictBuilder<DB, TB extends keyof DB>
*/
doUpdateSet(
updates: MutationObject<DB, TB>
): OnConflictUpdateBuilder<DB & Record<'excluded', DB[TB]>, TB | 'excluded'> {
): OnConflictUpdateBuilder<
{ [K in keyof DB | 'excluded']: K extends keyof DB ? DB[K] : DB[TB] },
TB | 'excluded'
> {
return new OnConflictUpdateBuilder({
...this.#props,
onConflictNode: OnConflictNode.cloneWith(this.#props.onConflictNode, {
Expand Down
23 changes: 12 additions & 11 deletions src/util/type-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,11 @@ export type ValueType<T> = T[keyof T]
* // Row == Person & Movie
* ```
*/
export type RowType<DB, TB extends keyof DB> = UnionToIntersection<DB[TB]>

/**
* Evil typescript magic to convert a union type `A | B | C` into an
* intersection type `A & B & C`.
*/
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never
export type RowType<DB, TB extends keyof DB> = {
[C in AnyColumn<DB, TB>]: {
[T in TB]: C extends keyof DB[T] ? DB[T][C] : never
}[TB]
}

/**
* Given a database type and a union of table names in that db, returns
Expand Down Expand Up @@ -92,6 +86,13 @@ export type AnyColumn<DB, TB extends keyof DB> = {
}[TB] &
string

/**
* Extracts a column type.
*/
export type ExtractColumnType<DB, TB extends keyof DB, C> = {
[T in TB]: C extends keyof DB[T] ? DB[T][C] : never
}[TB]

/**
* Given a database type and a union of table names in that db, returns
* a union type with all possible `table`.`column` combinations.
Expand Down
4 changes: 2 additions & 2 deletions test/src/test-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,9 @@ async function insertToysForPet(
.executeTakeFirst()
}

async function insert(
async function insert<TB extends keyof Database>(
dialect: BuiltInDialect,
qb: InsertQueryBuilder<Database, keyof Database, InsertResult>
qb: InsertQueryBuilder<Database, TB, InsertResult>
): Promise<number> {
if (dialect === 'postgres') {
const { id } = await qb.returning('id').executeTakeFirstOrThrow()
Expand Down
47 changes: 43 additions & 4 deletions test/typings/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,16 @@ async function testJoin(db: Kysely<Database>) {
.selectAll()
.execute()

expectType<(Person & Movie)[]>(r1)
expectType<
{
id: number | string
first_name: string
last_name: string | null
age: number
gender: 'male' | 'female' | 'other'
stars: number
}[]
>(r1)

const r2 = await db
.selectFrom('person')
Expand Down Expand Up @@ -443,7 +452,17 @@ async function testJoin(db: Kysely<Database>) {
.selectAll()
.executeTakeFirstOrThrow()

expectType<Person & Nullable<Pet> & Nullable<Movie>>(r5)
expectType<{
id: number | string | null
first_name: string
last_name: string | null
age: number
gender: 'male' | 'female' | 'other'
name: string | null
species: 'dog' | 'cat' | null
owner_id: number | null
stars: number | null
}>(r5)

const r6 = await db
.selectFrom('person')
Expand All @@ -452,7 +471,17 @@ async function testJoin(db: Kysely<Database>) {
.selectAll()
.executeTakeFirstOrThrow()

expectType<Nullable<Person> & Nullable<Pet> & Movie>(r6)
expectType<{
id: number | string | null
first_name: string | null
last_name: string | null
age: number | null
gender: 'male' | 'female' | 'other' | null
name: string | null
species: 'dog' | 'cat' | null
owner_id: number | null
stars: number
}>(r6)

const r7 = await db
.selectFrom('person')
Expand All @@ -461,7 +490,17 @@ async function testJoin(db: Kysely<Database>) {
.selectAll()
.executeTakeFirstOrThrow()

expectType<Nullable<Person> & Nullable<Pet> & Nullable<Movie>>(r7)
expectType<{
id: number | string | null
first_name: string | null
last_name: string | null
age: number | null
gender: 'male' | 'female' | 'other' | null
name: string | null
species: 'dog' | 'cat' | null
owner_id: number | null
stars: number | null
}>(r7)

// Refer to table that's not joined
expectError(
Expand Down

0 comments on commit 0ca3b01

Please sign in to comment.