Skip to content

Commit

Permalink
kyselify.
Browse files Browse the repository at this point in the history
  • Loading branch information
igalklebanov committed Jan 2, 2024
1 parent 7d717fd commit ff6b169
Showing 1 changed file with 300 additions and 0 deletions.
300 changes: 300 additions & 0 deletions src/kyselify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
import type {Generated as KyselyGenerated, GeneratedAlways as KyselyGeneratedAlways} from 'kysely'

/**
* This is used to mark entity properties that have their values generated by TypeORM
* or the database, so that {@link KyselifyEntity} can mark them as Kysely Generated.
*
* Kysely treats Generated properties as write-optional.
*
* Also see {@link GeneratedAlways} and {@link Populated}.
*
* ### example
*
* ```ts
* import type { Generated, GeneratedAlways, Populated } from 'kysely-typeorm'
* import {
* Column,
* CreateDateColumn,
* DeleteDateColumn,
* Entity,
* Generated as TypeORMGenerated,
* PrimaryGeneratedColumn,
* UpdateDateColumn,
* VersionColumn
* } from 'typeorm'
*
* \@Entity({ name: 'user' })
* export class UserEntity {
* \@PrimaryGeneratedColumn()
* id: GeneratedAlways<number>
*
* \@Column({ type: 'varchar', length: 255, unique: true })
* username: string
*
* \@Column({ type: 'varchar', length: 255, nullable: true })
* steamAccountId: string | null
*
* \@CreateDateColumn()
* createdAt: Generated<Date>
*
* \@UpdateDateColumn()
* updatedAt: Generated<Date>
*
* \@DeleteDateColumn()
* deletedAt: Generated<Date | null>
*
* \@VersionColumn()
* version: Generated<number>
*
* \@Column()
* \@TypeORMGenerated('uuid')
* uuid: Generated<string>
* }
*
* type User = KyselifyEntity<UserEntity>
* // ^? { id: GeneratedAlways<number>, username: string, steamAccountId: string | null, createdAt: Generated<Date>, updatedAt: Generated<Date>, deletedAt: Generated<Date | null>, version: Generated<number>, uuid: Generated<string> }
* ```
*/
export type Generated<T> = T & {
readonly __kysely__generated__?: unique symbol
}

/**
* This is used to mark entity properties that have their values generated by TypeORM
* or the database, so that {@link KyselifyEntity} can mark them as Kysely GeneratedAlways.
*
* Kysely treats GeneratedAlways properties as read-only.
*
* Also see {@link Generated} and {@link Populated}.
*
* ### example
*
* ```ts
* import type { Generated, GeneratedAlways, Populated } from 'kysely-typeorm'
* import {
* Column,
* CreateDateColumn,
* DeleteDateColumn,
* Generated as TypeORMGenerated,
* PrimaryGeneratedColumn,
* UpdateDateColumn,
* VersionColumn
* } from 'typeorm'
*
* \@Entity({ name: 'user' })
* export class UserEntity {
* \@PrimaryGeneratedColumn()
* id: GeneratedAlways<number>
*
* \@Column({ type: 'varchar', length: 255, unique: true })
* username: string
*
* \@Column({ type: 'varchar', length: 255, nullable: true })
* steamAccountId: string | null
*
* \@CreateDateColumn()
* createdAt: Generated<Date>
*
* \@UpdateDateColumn()
* updatedAt: Generated<Date>
*
* \@DeleteDateColumn()
* deletedAt: Generated<Date | null>
*
* \@VersionColumn()
* version: Generated<number>
*
* \@Column()
* \@TypeORMGenerated('uuid')
* uuid: Generated<string>
* }
*
* type User = KyselifyEntity<UserEntity>
* // ^? { id: GeneratedAlways<number>, username: string, steamAccountId: string | null, createdAt: Generated<Date>, updatedAt: Generated<Date>, deletedAt: Generated<Date | null>, version: Generated<number>, uuid: Generated<string> }
* ```
*/
export type GeneratedAlways<T> = T & {
readonly __kysely__generated__always__?: unique symbol
}

/**
* This is used to mark entity properties that are populated by TypeORM and do
* not exist in the database schema, so that {@link KyselifyEntity} can exclude
* them.
*
* ### example
*
* ```ts
* import type { GeneratedAlways, Populated } from 'kysely-typeorm'
* import {
* Column,
* Entity,
* JoinColumn,
* ManyToMany,
* ManyToOne,
* OneToMany,
* PrimaryGeneratedColumn,
* RelationId,
* VirtualColumn
* } from 'typeorm'
* import { ClanEntity } from './Clan'
* import { PostEntity } from './Post'
* import { RoleEntity } from './Role'
*
* \@Entity({ name: 'user' })
* export class UserEntity {
* \@PrimaryGeneratedColumn()
* id: GeneratedAlways<number>
*
* \@Column({ type: 'varchar', length: 255, unique: true })
* username: string
*
* \@Column({ type: 'varchar', length: 255, nullable: true })
* steamAccountId: string | null
*
* \@OneToMany(() => PostEntity, (post) => post.user)
* posts: Populated<PostEntity[]>
*
* \@ManyToOne(() => ClanEntity, (clan) => clan.users)
* \@JoinColumn({ name: 'clanId', referencedColumnName: 'id' })
* clan: Populated<ClanEntity>
*
* \@RelationId((user) => user.clan)
* clanId: number | null
*
* \@ManyToMany(() => RoleEntity)
* \@JoinTable()
* roles: Populated<RoleEntity[]>
*
* \@RelationId((role) => role.users)
* roleIds: Populated<number[]>
*
* \@VirtualColumn({ query: (alias) => `select count("id") from "posts" where "author_id" = ${alias}.id` })
* totalPostsCount: Populated<number>
* }
*
* type User = KyselifyEntity<UserEntity>
* // ^? { id: Generated<number>, username: string, steamAccountId: string | null, clanId: number | null }
* ```
*/
export type Populated<T> = T & {
readonly __kysely__populated__?: unique symbol
}

/**
* This is used to transform TypeORM entities into Kysely entities.
*
* Also see {@link Generated}, {@link GeneratedAlways} and {@link Populated}.
*
* ### example
*
* ```ts
* import type { Generated, GeneratedAlways, Populated } from 'kysely-typeorm'
* import {
* Column,
* CreateDateColumn,
* DeleteDateColumn,
* Entity,
* Generated as TypeORMGenerated,
* JoinColumn,
* JoinTable,
* ManyToMany,
* ManyToOne,
* OneToMany,
* PrimaryGeneratedColumn,
* RelationId,
* UpdateDateColumn,
* VersionColumn,
* VirtualColumn
* } from 'typeorm'
* import { ClanEntity } from './Clan'
* import { PostEntity } from './Post'
* import { RoleEntity } from './Role'
*
* \@Entity({ name: 'user' })
* export class UserEntity {
* \@PrimaryGeneratedColumn()
* id: GeneratedAlways<number>
*
* \@Column({ type: 'varchar', length: 255, unique: true })
* username: string
*
* \@Column({ type: 'varchar', length: 255, nullable: true })
* steamAccountId: string | null
*
* \@CreateDateColumn()
* createdAt: Generated<Date>
*
* \@UpdateDateColumn()
* updatedAt: Generated<Date>
*
* \@DeleteDateColumn()
* deletedAt: Generated<Date | null>
*
* \@VersionColumn()
* version: Generated<number>
*
* \@Column()
* \@TypeORMGenerated('uuid')
* uuid: Generated<string>
*
* \@OneToMany(() => PostEntity, (post) => post.user)
* posts: Populated<PostEntity[]>
*
* \@ManyToOne(() => ClanEntity, (clan) => clan.users)
* \@JoinColumn({ name: 'clanId', referencedColumnName: 'id' })
* clan: Populated<ClanEntity>
*
* \@RelationId((user) => user.clan)
* clanId: number | null
*
* \@ManyToMany(() => RoleEntity)
* \@JoinTable()
* roles: Populated<RoleEntity[]>
*
* \@RelationId((role) => role.users)
* roleIds: Populated<number[]>
*
* \@VirtualColumn({ query: (alias) => `select count("id") from "posts" where "author_id" = ${alias}.id` })
* totalPostsCount: Populated<number>
* }
*
* export type User = KyselifyEntity<UserEntity>
* // ^? { id: GeneratedAlways<number>, username: string, steamAccountId: string | null, createdAt: Generated<Date>, updatedAt: Generated<Date>, deletedAt: Generated<Date | null>, version: Generated<number>, uuid: Generated<string>, clandId: number | null }
* ```
*
* and then you can use it like this:
*
* ```ts
* import { Clan } from './Clan'
* import { Post } from './Post'
* import { Role } from './Role'
* import { User } from './User'
*
* export interface Database {
* clan: Clan
* post: Post
* role: Role
* user: User
* }
*
* export const kysely = new Kysely<Database>(
* // ...
* )
* ```
*/
export type KyselifyEntity<E> = {
[K in keyof E as E[K] extends (...args: any) => any
? never
: '__kysely__populated__' extends keyof E[K]
? never
: K]-?: '__kysely__generated__' extends keyof E[K]
? E[K] extends Generated<infer T>
? KyselyGenerated<Exclude<T, undefined>>
: never
: '__kysely__generated__always__' extends keyof E[K]
? E[K] extends GeneratedAlways<infer T>
? KyselyGeneratedAlways<Exclude<T, undefined>>
: never
: Exclude<E[K], undefined>
}

0 comments on commit ff6b169

Please sign in to comment.