From ff6b169bb713ad7aed67a1435a643116f000f1af Mon Sep 17 00:00:00 2001 From: igalklebanov Date: Tue, 2 Jan 2024 18:54:01 +0200 Subject: [PATCH] kyselify. --- src/kyselify.ts | 300 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 src/kyselify.ts diff --git a/src/kyselify.ts b/src/kyselify.ts new file mode 100644 index 0000000..5caad90 --- /dev/null +++ b/src/kyselify.ts @@ -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 + * + * \@Column({ type: 'varchar', length: 255, unique: true }) + * username: string + * + * \@Column({ type: 'varchar', length: 255, nullable: true }) + * steamAccountId: string | null + * + * \@CreateDateColumn() + * createdAt: Generated + * + * \@UpdateDateColumn() + * updatedAt: Generated + * + * \@DeleteDateColumn() + * deletedAt: Generated + * + * \@VersionColumn() + * version: Generated + * + * \@Column() + * \@TypeORMGenerated('uuid') + * uuid: Generated + * } + * + * type User = KyselifyEntity + * // ^? { id: GeneratedAlways, username: string, steamAccountId: string | null, createdAt: Generated, updatedAt: Generated, deletedAt: Generated, version: Generated, uuid: Generated } + * ``` + */ +export type Generated = 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 + * + * \@Column({ type: 'varchar', length: 255, unique: true }) + * username: string + * + * \@Column({ type: 'varchar', length: 255, nullable: true }) + * steamAccountId: string | null + * + * \@CreateDateColumn() + * createdAt: Generated + * + * \@UpdateDateColumn() + * updatedAt: Generated + * + * \@DeleteDateColumn() + * deletedAt: Generated + * + * \@VersionColumn() + * version: Generated + * + * \@Column() + * \@TypeORMGenerated('uuid') + * uuid: Generated + * } + * + * type User = KyselifyEntity + * // ^? { id: GeneratedAlways, username: string, steamAccountId: string | null, createdAt: Generated, updatedAt: Generated, deletedAt: Generated, version: Generated, uuid: Generated } + * ``` + */ +export type GeneratedAlways = 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 + * + * \@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 + * + * \@ManyToOne(() => ClanEntity, (clan) => clan.users) + * \@JoinColumn({ name: 'clanId', referencedColumnName: 'id' }) + * clan: Populated + * + * \@RelationId((user) => user.clan) + * clanId: number | null + * + * \@ManyToMany(() => RoleEntity) + * \@JoinTable() + * roles: Populated + * + * \@RelationId((role) => role.users) + * roleIds: Populated + * + * \@VirtualColumn({ query: (alias) => `select count("id") from "posts" where "author_id" = ${alias}.id` }) + * totalPostsCount: Populated + * } + * + * type User = KyselifyEntity + * // ^? { id: Generated, username: string, steamAccountId: string | null, clanId: number | null } + * ``` + */ +export type Populated = 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 + * + * \@Column({ type: 'varchar', length: 255, unique: true }) + * username: string + * + * \@Column({ type: 'varchar', length: 255, nullable: true }) + * steamAccountId: string | null + * + * \@CreateDateColumn() + * createdAt: Generated + * + * \@UpdateDateColumn() + * updatedAt: Generated + * + * \@DeleteDateColumn() + * deletedAt: Generated + * + * \@VersionColumn() + * version: Generated + * + * \@Column() + * \@TypeORMGenerated('uuid') + * uuid: Generated + * + * \@OneToMany(() => PostEntity, (post) => post.user) + * posts: Populated + * + * \@ManyToOne(() => ClanEntity, (clan) => clan.users) + * \@JoinColumn({ name: 'clanId', referencedColumnName: 'id' }) + * clan: Populated + * + * \@RelationId((user) => user.clan) + * clanId: number | null + * + * \@ManyToMany(() => RoleEntity) + * \@JoinTable() + * roles: Populated + * + * \@RelationId((role) => role.users) + * roleIds: Populated + * + * \@VirtualColumn({ query: (alias) => `select count("id") from "posts" where "author_id" = ${alias}.id` }) + * totalPostsCount: Populated + * } + * + * export type User = KyselifyEntity + * // ^? { id: GeneratedAlways, username: string, steamAccountId: string | null, createdAt: Generated, updatedAt: Generated, deletedAt: Generated, version: Generated, uuid: Generated, 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( + * // ... + * ) + * ``` + */ +export type KyselifyEntity = { + [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 + ? KyselyGenerated> + : never + : '__kysely__generated__always__' extends keyof E[K] + ? E[K] extends GeneratedAlways + ? KyselyGeneratedAlways> + : never + : Exclude +}