mirror of
https://github.com/theoludwig/kysely-typegen.git
synced 2026-05-22 16:23:25 +02:00
feat: add MySQL support
Adds `KyselyTypegenMySQLDialect`, exported from `kysely-typegen/mysql`.
This commit is contained in:
@@ -16,7 +16,7 @@ Why `kysely-typegen` if there is already `kysely-codegen`? Comparison:
|
||||
| **Dependencies** | 35 total | 0 (no runtime dependencies) |
|
||||
| **Type** | CLI | Library/Programmatic Usage |
|
||||
| **Code Size/Maintainability** | Heavy | Lightweight/Simple and straightforward (string manipulation instead of complex AST) |
|
||||
| **Database Support** | PostgreSQL, MySQL, SQLite, MSSQL, LibSQL | PostgreSQL (**can be easily extended to more**) |
|
||||
| **Database Support** | PostgreSQL, MySQL, SQLite, MSSQL, LibSQL | PostgreSQL, MySQL (**can be easily extended to more**) |
|
||||
|
||||
`kysely-typegen` is a **library** (not a CLI), which means you are in control of where and how to run it, and is designed to be **extensible**, easy to add support for more database dialects.
|
||||
|
||||
@@ -79,6 +79,34 @@ export const database = new Kysely<DB>({ dialect })
|
||||
export const databaseTypegen = new KyselyTypegenPostgresDialect({ database })
|
||||
```
|
||||
|
||||
#### MySQL
|
||||
|
||||
```sh
|
||||
npm install mysql2
|
||||
```
|
||||
|
||||
```ts
|
||||
// database.ts
|
||||
import { Kysely, MysqlDialect } from "kysely"
|
||||
import { KyselyTypegenMySQLDialect } from "kysely-typegen/mysql"
|
||||
import { createPool } from "mysql2"
|
||||
|
||||
import type { DB } from "./codegen.ts"
|
||||
|
||||
const dialect = new MysqlDialect({
|
||||
pool: createPool({
|
||||
database: process.env["DATABASE_NAME"] ?? "database",
|
||||
host: process.env["DATABASE_HOST"] ?? "localhost",
|
||||
user: process.env["DATABASE_USER"] ?? "user",
|
||||
password: process.env["DATABASE_PASSWORD"] ?? "password",
|
||||
port: Number.parseInt(process.env["DATABASE_PORT"] ?? "3306", 10),
|
||||
}),
|
||||
})
|
||||
|
||||
export const database = new Kysely<DB>({ dialect })
|
||||
export const databaseTypegen = new KyselyTypegenMySQLDialect({ database })
|
||||
```
|
||||
|
||||
### Generate the type definitions
|
||||
|
||||
Create a script that uses `databaseTypegen` to introspect your database and write the generated types to a file. This script is the same regardless of the underlying database:
|
||||
@@ -120,7 +148,7 @@ Fully type-safe queries derived from your actual database schema.
|
||||
|
||||
## Extending to other database dialects
|
||||
|
||||
`kysely-typegen` ships with `KyselyTypegenPostgresDialect`, but you can add support for any database by extending the abstract `KyselyTypegenDialect` class.
|
||||
`kysely-typegen` ships with `KyselyTypegenPostgresDialect` and `KyselyTypegenMySQLDialect`, but you can add support for any database by extending the abstract `KyselyTypegenDialect` class.
|
||||
|
||||
Only one thing is required:
|
||||
|
||||
@@ -150,7 +178,7 @@ export class KyselyTypegenMSSQLDialect extends KyselyTypegenDialect {
|
||||
If your database supports enums, override the optional `introspectEnums()` hook, which returns two maps:
|
||||
|
||||
- `named`: enum name → values (emitted as `export type Name = "a" | "b"`).
|
||||
- `inline`: `${tableName}.${columnName}` → values (emitted inline at the column site, for databases where enums are anonymous per-column).
|
||||
- `inline`: `${tableName}.${columnName}` → values (emitted inline at the column site, for databases like MySQL where enums are anonymous per-column).
|
||||
|
||||
```ts
|
||||
import type { IntrospectedEnums } from "kysely-typegen"
|
||||
|
||||
Generated
+134
@@ -9,11 +9,13 @@
|
||||
"version": "0.0.0-development",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@testcontainers/mysql": "12.0.0",
|
||||
"@testcontainers/postgresql": "12.0.0",
|
||||
"@types/node": "25.9.1",
|
||||
"@types/pg": "8.20.0",
|
||||
"kysely": "0.29.2",
|
||||
"kysely-postgres-js": "3.0.0",
|
||||
"mysql2": "3.22.3",
|
||||
"oxfmt": "0.51.0",
|
||||
"oxlint": "1.66.0",
|
||||
"oxlint-tsgolint": "0.23.0",
|
||||
@@ -2255,6 +2257,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@testcontainers/mysql": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@testcontainers/mysql/-/mysql-12.0.0.tgz",
|
||||
"integrity": "sha512-ofK3bPWIkqEhwQq5hv31QO3B6AlBGF10wI2QaJDjvDiYr7jQ1WGh1n4S1Eu3xwIWyO8FYSxKjgHgkoaNPPy4Ww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"testcontainers": "^12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testcontainers/postgresql": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-12.0.0.tgz",
|
||||
@@ -2688,6 +2700,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/b4a": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
|
||||
@@ -3654,6 +3676,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
@@ -4263,6 +4295,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -4485,6 +4527,23 @@
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -4661,6 +4720,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
|
||||
@@ -4978,6 +5044,22 @@
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/lru.min": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
|
||||
"integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
"deno": ">=1.30.0",
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wellwelwel"
|
||||
}
|
||||
},
|
||||
"node_modules/make-asynchronous": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz",
|
||||
@@ -5173,6 +5255,29 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mysql2": {
|
||||
"version": "3.22.3",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.3.tgz",
|
||||
"integrity": "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-ssl-profiles": "^1.1.2",
|
||||
"denque": "^2.1.0",
|
||||
"generate-function": "^2.3.1",
|
||||
"iconv-lite": "^0.7.2",
|
||||
"long": "^5.3.2",
|
||||
"lru.min": "^1.1.4",
|
||||
"named-placeholders": "^1.1.6",
|
||||
"sql-escaper": "^1.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/mz": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
@@ -5185,6 +5290,19 @@
|
||||
"thenify-all": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/named-placeholders": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
|
||||
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru.min": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.27.0.tgz",
|
||||
@@ -8537,6 +8655,22 @@
|
||||
"through2": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sql-escaper": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz",
|
||||
"integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
"deno": ">=2.0.0",
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ssh-remote-port-forward": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"types": "./dist/index.d.mts",
|
||||
"exports": {
|
||||
".": "./dist/index.mjs",
|
||||
"./mysql": "./dist/mysql.mjs",
|
||||
"./postgres": "./dist/postgres.mjs",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
@@ -39,11 +40,13 @@
|
||||
"release": "semantic-release"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testcontainers/mysql": "12.0.0",
|
||||
"@testcontainers/postgresql": "12.0.0",
|
||||
"@types/node": "25.9.1",
|
||||
"@types/pg": "8.20.0",
|
||||
"kysely": "0.29.2",
|
||||
"kysely-postgres-js": "3.0.0",
|
||||
"mysql2": "3.22.3",
|
||||
"oxfmt": "0.51.0",
|
||||
"oxlint": "1.66.0",
|
||||
"oxlint-tsgolint": "0.23.0",
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
exports[`typegen MySQL > generate types matching snapshot 1`] = `
|
||||
{
|
||||
"lines": [
|
||||
"// This file was automatically generated by \`kysely-typegen\`.",
|
||||
"// Do not edit this file manually.",
|
||||
"",
|
||||
"import type { ColumnType } from \\"kysely\\"",
|
||||
"",
|
||||
"export type Generated<T> = T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>",
|
||||
"",
|
||||
"export type Timestamp = ColumnType<Date, Date | string, Date | string>",
|
||||
"",
|
||||
"export type Numeric = ColumnType<string, number | string, number | string>",
|
||||
"",
|
||||
"export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>",
|
||||
"",
|
||||
"export type Json = JsonValue",
|
||||
"",
|
||||
"export type JsonArray = JsonValue[]",
|
||||
"",
|
||||
"export interface JsonObject {",
|
||||
" [x: string]: JsonValue | undefined",
|
||||
"}",
|
||||
"",
|
||||
"export type JsonPrimitive = boolean | number | string | null",
|
||||
"",
|
||||
"export type JsonValue = JsonArray | JsonObject | JsonPrimitive",
|
||||
"",
|
||||
"export interface AllTypes {",
|
||||
" colBigint: Int8",
|
||||
" colBinary: Buffer",
|
||||
" colBit: string",
|
||||
" colBlob: Buffer",
|
||||
" colBool: number",
|
||||
" colChar: string",
|
||||
" colDate: Timestamp",
|
||||
" colDatetime: Timestamp",
|
||||
" colDecimal: Numeric",
|
||||
" colDouble: number",
|
||||
" colFloat: number",
|
||||
" colInt: number",
|
||||
" colJson: Json",
|
||||
" colLongblob: Buffer",
|
||||
" colLongtext: string",
|
||||
" colMediumblob: Buffer",
|
||||
" colMediumint: number",
|
||||
" colMediumtext: string",
|
||||
" colNumeric: Numeric",
|
||||
" colSmallint: number",
|
||||
" colText: string",
|
||||
" colTextNullable: string | null",
|
||||
" colTime: string",
|
||||
" colTimestamp: Generated<Timestamp>",
|
||||
" colTinyblob: Buffer",
|
||||
" colTinyint: number",
|
||||
" colTinytext: string",
|
||||
" colVarbinary: Buffer",
|
||||
" colVarchar: string",
|
||||
" colYear: number",
|
||||
" createdAt: Generated<Timestamp>",
|
||||
" id: Generated<number>",
|
||||
"}",
|
||||
"",
|
||||
"export interface Orders {",
|
||||
" amountCents: number",
|
||||
" createdAt: Generated<Timestamp>",
|
||||
" currency: Generated<\\"EUR\\" | \\"GBP\\" | \\"USD\\">",
|
||||
" id: Generated<Int8>",
|
||||
" note: string | null",
|
||||
" status: Generated<\\"cancelled\\" | \\"paid\\" | \\"pending\\" | \\"shipped\\">",
|
||||
" userId: number",
|
||||
"}",
|
||||
"",
|
||||
"export interface Users {",
|
||||
" createdAt: Generated<Timestamp>",
|
||||
" email: string | null",
|
||||
" id: Generated<number>",
|
||||
" isActive: Generated<number>",
|
||||
" role: Generated<\\"admin\\" | \\"guest\\" | \\"member\\">",
|
||||
" username: string",
|
||||
"}",
|
||||
"",
|
||||
"export interface DB {",
|
||||
" AllTypes: AllTypes",
|
||||
" Orders: Orders",
|
||||
" Users: Users",
|
||||
"}"
|
||||
],
|
||||
"tablesCount": 3,
|
||||
"enumsCount": 0,
|
||||
"inlineEnumsCount": 3
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,196 @@
|
||||
import type { StartedMySqlContainer } from "@testcontainers/mysql"
|
||||
import { MySqlContainer } from "@testcontainers/mysql"
|
||||
import { Kysely, MysqlDialect, sql } from "kysely"
|
||||
import { createPool } from "mysql2"
|
||||
import { after, before, describe, it } from "node:test"
|
||||
|
||||
import { KyselyTypegenMySQLDialect } from "../mysql.ts"
|
||||
import "./_setup.ts"
|
||||
|
||||
const MYSQL_IMAGE =
|
||||
"docker.io/mysql:8.4@sha256:c36050afdca850f23cef85703f84c7531a5ae155a11b5ee1c60acb09937c4084"
|
||||
|
||||
const createSchema = async (database: Kysely<any>): Promise<void> => {
|
||||
await database.schema
|
||||
.createTable("AllTypes")
|
||||
.addColumn("id", "integer", (column) => {
|
||||
return column.notNull().autoIncrement().primaryKey()
|
||||
})
|
||||
.addColumn("colBigint", "bigint", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colSmallint", "smallint", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colMediumint", sql`mediumint`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colTinyint", sql`tinyint`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colInt", "integer", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colYear", sql`year`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colFloat", sql`float`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colDouble", "double precision", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colDecimal", sql`decimal(12, 2)`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colNumeric", sql`numeric(10, 4)`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colBool", "boolean", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colChar", sql`char(10)`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colVarchar", "varchar(255)", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colText", "text", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colTinytext", sql`tinytext`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colMediumtext", sql`mediumtext`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colLongtext", sql`longtext`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colTime", "time", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colBit", sql`bit(8)`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colBinary", sql`binary(16)`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colVarbinary", sql`varbinary(64)`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colBlob", sql`blob`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colTinyblob", sql`tinyblob`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colMediumblob", sql`mediumblob`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colLongblob", sql`longblob`, (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colDate", "date", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colDatetime", "datetime", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colTimestamp", "timestamp", (column) => {
|
||||
return column.notNull().defaultTo(sql`current_timestamp`)
|
||||
})
|
||||
.addColumn("colJson", "json", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("colTextNullable", "text")
|
||||
.addColumn("createdAt", "timestamp", (column) => {
|
||||
return column.notNull().defaultTo(sql`current_timestamp`)
|
||||
})
|
||||
.execute()
|
||||
|
||||
await database.schema
|
||||
.createTable("Users")
|
||||
.addColumn("id", "integer", (column) => {
|
||||
return column.notNull().autoIncrement().primaryKey()
|
||||
})
|
||||
.addColumn("username", "varchar(50)", (column) => {
|
||||
return column.notNull().unique()
|
||||
})
|
||||
.addColumn("email", "text")
|
||||
.addColumn("role", sql`enum('admin','member','guest')`, (column) => {
|
||||
return column.notNull().defaultTo("member")
|
||||
})
|
||||
.addColumn("isActive", sql`tinyint(1)`, (column) => {
|
||||
return column.notNull().defaultTo(1)
|
||||
})
|
||||
.addColumn("createdAt", "timestamp", (column) => {
|
||||
return column.notNull().defaultTo(sql`current_timestamp`)
|
||||
})
|
||||
.execute()
|
||||
|
||||
await database.schema
|
||||
.createTable("Orders")
|
||||
.addColumn("id", "bigint", (column) => {
|
||||
return column.notNull().autoIncrement().primaryKey()
|
||||
})
|
||||
.addColumn("userId", "integer", (column) => {
|
||||
return column.notNull().references("Users.id")
|
||||
})
|
||||
.addColumn("status", sql`enum('pending','paid','shipped','cancelled')`, (column) => {
|
||||
return column.notNull().defaultTo("pending")
|
||||
})
|
||||
.addColumn("currency", sql`enum('EUR','USD','GBP')`, (column) => {
|
||||
return column.notNull().defaultTo("EUR")
|
||||
})
|
||||
.addColumn("amountCents", "integer", (column) => {
|
||||
return column.notNull()
|
||||
})
|
||||
.addColumn("note", "text")
|
||||
.addColumn("createdAt", "timestamp", (column) => {
|
||||
return column.notNull().defaultTo(sql`current_timestamp`)
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
describe("typegen MySQL", () => {
|
||||
let container: StartedMySqlContainer
|
||||
let database: Kysely<any>
|
||||
|
||||
before(async () => {
|
||||
container = await new MySqlContainer(MYSQL_IMAGE).start()
|
||||
database = new Kysely<any>({
|
||||
dialect: new MysqlDialect({
|
||||
pool: createPool({
|
||||
host: container.getHost(),
|
||||
port: container.getPort(),
|
||||
user: container.getUsername(),
|
||||
password: container.getUserPassword(),
|
||||
database: container.getDatabase(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
await createSchema(database)
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await database.destroy()
|
||||
await container.stop()
|
||||
})
|
||||
|
||||
it("generate types matching snapshot", async (testContext) => {
|
||||
// Arrange - Given
|
||||
const databaseTypegen = new KyselyTypegenMySQLDialect({ database })
|
||||
|
||||
// Act - When
|
||||
const result = await databaseTypegen.typegen()
|
||||
|
||||
// Assert - Then
|
||||
testContext.assert.snapshot({
|
||||
lines: result.lines,
|
||||
tablesCount: result.tables.length,
|
||||
enumsCount: result.enums.length,
|
||||
inlineEnumsCount: result.inlineEnums.size,
|
||||
})
|
||||
})
|
||||
})
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import { sql } from "kysely"
|
||||
|
||||
import type { IntrospectedEnums } from "./index.ts"
|
||||
import { KyselyTypegenDialect } from "./index.ts"
|
||||
|
||||
export class KyselyTypegenMySQLDialect extends KyselyTypegenDialect {
|
||||
// Keys are lowercase values returned by information_schema.COLUMNS.DATA_TYPE,
|
||||
// which is what Kysely's MysqlIntrospector forwards as column.dataType.
|
||||
public override readonly scalars: Record<string, string> = {
|
||||
bigint: "Int8",
|
||||
binary: "Buffer",
|
||||
bit: "string",
|
||||
blob: "Buffer",
|
||||
bool: "boolean",
|
||||
boolean: "boolean",
|
||||
char: "string",
|
||||
date: "Timestamp",
|
||||
datetime: "Timestamp",
|
||||
decimal: "Numeric",
|
||||
double: "number",
|
||||
enum: "string",
|
||||
float: "number",
|
||||
int: "number",
|
||||
json: "Json",
|
||||
longblob: "Buffer",
|
||||
longtext: "string",
|
||||
mediumblob: "Buffer",
|
||||
mediumint: "number",
|
||||
mediumtext: "string",
|
||||
numeric: "Numeric",
|
||||
set: "string",
|
||||
smallint: "number",
|
||||
text: "string",
|
||||
time: "string",
|
||||
timestamp: "Timestamp",
|
||||
tinyblob: "Buffer",
|
||||
tinyint: "number",
|
||||
tinytext: "string",
|
||||
varbinary: "Buffer",
|
||||
varchar: "string",
|
||||
year: "number",
|
||||
}
|
||||
|
||||
protected override async introspectEnums(): Promise<IntrospectedEnums> {
|
||||
const rows = await this.database
|
||||
.withoutPlugins()
|
||||
.selectFrom("information_schema.columns as columns")
|
||||
.select([
|
||||
"columns.TABLE_NAME as tableName",
|
||||
"columns.COLUMN_NAME as columnName",
|
||||
"columns.COLUMN_TYPE as columnType",
|
||||
])
|
||||
.where("columns.TABLE_SCHEMA", "=", sql`database()`)
|
||||
.where("columns.DATA_TYPE", "in", ["enum", "set"])
|
||||
.execute()
|
||||
|
||||
const inline = new Map<string, string[]>()
|
||||
for (const row of rows) {
|
||||
const data = row as { tableName: string; columnName: string; columnType: string }
|
||||
inline.set(`${data.tableName}.${data.columnName}`, parseMysqlEnumColumnType(data.columnType))
|
||||
}
|
||||
return { named: [], inline }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses MySQL `enum(...)` / `set(...)` column type definitions into the list of declared values. MySQL doubles single quotes inside string literals.
|
||||
*
|
||||
* @example
|
||||
* parseMysqlEnumColumnType("enum('a','b','c''d')") // ["a", "b", "c'd"]
|
||||
*/
|
||||
const parseMysqlEnumColumnType = (columnType: string): string[] => {
|
||||
const open = columnType.indexOf("(")
|
||||
const close = columnType.lastIndexOf(")")
|
||||
if (open === -1 || close === -1 || close <= open) {
|
||||
return []
|
||||
}
|
||||
const inner = columnType.slice(open + 1, close)
|
||||
const values: string[] = []
|
||||
let current = ""
|
||||
let insideString = false
|
||||
for (let index = 0; index < inner.length; index++) {
|
||||
const character = inner[index]
|
||||
if (insideString) {
|
||||
if (character === "'" && inner[index + 1] === "'") {
|
||||
current += "'"
|
||||
index++
|
||||
continue
|
||||
}
|
||||
if (character === "'") {
|
||||
insideString = false
|
||||
values.push(current)
|
||||
current = ""
|
||||
continue
|
||||
}
|
||||
current += character
|
||||
} else if (character === "'") {
|
||||
insideString = true
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { defineConfig } from "tsdown"
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["./src/index.ts", "./src/postgres.ts"],
|
||||
entry: ["./src/index.ts", "./src/mysql.ts", "./src/postgres.ts"],
|
||||
dts: true,
|
||||
exports: true,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user