From e11a4d27227b018ae86273dd835b11f81be04e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20LUDWIG?= Date: Fri, 22 May 2026 15:50:53 +0200 Subject: [PATCH] feat: add MySQL support Adds `KyselyTypegenMySQLDialect`, exported from `kysely-typegen/mysql`. --- README.md | 34 ++- package-lock.json | 134 ++++++++++++ package.json | 3 + .../__snapshots__/mysql.test.ts.snapshot | 93 +++++++++ src/_test/mysql.test.ts | 196 ++++++++++++++++++ src/mysql.ts | 102 +++++++++ tsdown.config.ts | 2 +- 7 files changed, 560 insertions(+), 4 deletions(-) create mode 100644 src/_test/__snapshots__/mysql.test.ts.snapshot create mode 100644 src/_test/mysql.test.ts create mode 100644 src/mysql.ts diff --git a/README.md b/README.md index 797095f..6fa0400 100644 --- a/README.md +++ b/README.md @@ -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({ 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({ 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" diff --git a/package-lock.json b/package-lock.json index 6ce0c7e..697076d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index c574d3c..371f6ba 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/_test/__snapshots__/mysql.test.ts.snapshot b/src/_test/__snapshots__/mysql.test.ts.snapshot new file mode 100644 index 0000000..e1976c0 --- /dev/null +++ b/src/_test/__snapshots__/mysql.test.ts.snapshot @@ -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 extends ColumnType ? ColumnType : ColumnType", + "", + "export type Timestamp = ColumnType", + "", + "export type Numeric = ColumnType", + "", + "export type Int8 = ColumnType", + "", + "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", + " colTinyblob: Buffer", + " colTinyint: number", + " colTinytext: string", + " colVarbinary: Buffer", + " colVarchar: string", + " colYear: number", + " createdAt: Generated", + " id: Generated", + "}", + "", + "export interface Orders {", + " amountCents: number", + " createdAt: Generated", + " currency: Generated<\\"EUR\\" | \\"GBP\\" | \\"USD\\">", + " id: Generated", + " note: string | null", + " status: Generated<\\"cancelled\\" | \\"paid\\" | \\"pending\\" | \\"shipped\\">", + " userId: number", + "}", + "", + "export interface Users {", + " createdAt: Generated", + " email: string | null", + " id: Generated", + " isActive: Generated", + " role: Generated<\\"admin\\" | \\"guest\\" | \\"member\\">", + " username: string", + "}", + "", + "export interface DB {", + " AllTypes: AllTypes", + " Orders: Orders", + " Users: Users", + "}" + ], + "tablesCount": 3, + "enumsCount": 0, + "inlineEnumsCount": 3 +} +`; diff --git a/src/_test/mysql.test.ts b/src/_test/mysql.test.ts new file mode 100644 index 0000000..63f82df --- /dev/null +++ b/src/_test/mysql.test.ts @@ -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): Promise => { + 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 + + before(async () => { + container = await new MySqlContainer(MYSQL_IMAGE).start() + database = new Kysely({ + 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, + }) + }) +}) diff --git a/src/mysql.ts b/src/mysql.ts new file mode 100644 index 0000000..84eccf7 --- /dev/null +++ b/src/mysql.ts @@ -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 = { + 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 { + 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() + 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 +} diff --git a/tsdown.config.ts b/tsdown.config.ts index a3e9314..874e2a7 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -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, })