Reference — TypeORM → Kysely migration¶
Complements api-conventions.mdc § Stack Kysely investisseur.
Foundation¶
| Piece | Location |
|---|---|
| Kysely / TypeORM tx | ky-safe-pg-transaction.ts, safe-pg-transaction.postgres.ts |
| DB registry | database.ts — Database; add KyselyDb once if missing, then import |
| Parse | ky_parseRows / ky_parseFirstRow / ky_parseOneRow |
Do not reintroduce: InvestorDatabase, dispatch wrappers, queryRowsWithTypeorm, per-repo type KyselyDb.
Example merged PRs¶
| Ticket | Pattern |
|---|---|
| BRI-890 | UUID PK; dual tx (legacy positional inserts — see skill) |
| BRI-886 | PG enum; batch UPDATE without RETURNING |
| BRI-887 | Serial PK; business/ |
Schema templates¶
Row = z.infer<typeof row>. UUID PK → T = Row. Serial / JSONB + generated → KyselyTableFromZodType.
JSONB + generated timestamps:
export const metadata = z.object({ source: z.enum(['admin', 'system']), recordedAt: isoDate_zod })
export const row = z.object({ id, customerId: uuid_zod, metadata, createdAt: z.date(), updatedAt: z.date() })
export const insertRow = z.object({ id, customerId: uuid_zod, metadata })
export type T = KyselyTableFromZodType<typeof row, 'createdAt' | 'updatedAt', 'metadata'>
Serial PK — T = KyselyTableFromZodType<typeof row, 'id'>. PG enum — z.enum + .enum.{value}. FK branded — z.string().uuid().
Repository pattern¶
import { getKysely, type KyselyDb } from 'src/__new/lib/kysely/database'
async findOneById(params: { id: ExamplePgSchema.Id }) { /* getKysely()… */ }
async insertOneWithKysely(params: { trx: KyselyDb; row: ExamplePgSchema.Row }) { /* trx.insertInto */ }
async insertOneWithTypeorm(params: { transac: EntityManager; row: ExamplePgSchema.Row }) { /* query */ }
async findOneByIdWithTypeorm(params: { transac: EntityManager; id: ExamplePgSchema.Id }) { /* … */ }
async updateOneWithKysely(params: { trx: KyselyDb; id; patch }) { /* updateTable */ }
trx = Kysely, transac = TypeORM. Inserts always params object.
Transactions¶
One stack per tx. ky_safePgTransaction → *WithKysely. safePgTransaction → *WithTypeorm.
Integration tests¶
Test-first: legacy create + read → commit → migrate → same assertions. Seeds: insertOneWithKysely({ trx: getKysely(), row }).
Teardown¶
Delete entity + legacy repo; remove from index.ts, forFeature, all-entities.ts. Logic → {module}/business/.
Pitfalls¶
| Issue | Fix |
|---|---|
Local type KyselyDb per repo |
Import from database.ts |
Positional insert (trx, row) |
params: { trx, row } / { transac, row } |
T hand-written with Generated / Jsonb |
KyselyTableFromZodType |
trx on TypeORM / transac on Kysely |
trx = Kysely only, transac = TypeORM only |
| Mixing stacks in one tx | Match repo method to parent tx |
| Migration without create+read test | Test legacy path first |
Grep hints¶
KYC: customers-lemonway, investor-onboarding. Services: customers/services/investor-*. Admin: administration/services/. Scripts: scripts/.