diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8239fa9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing + +Thanks a lot for your interest in contributing to **kysely-typegen**! 🎉 + +## Code of Conduct + +**kysely-typegen** adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read the full text so that you can understand what actions will and will not be tolerated. + +## Open Development + +All work on **kysely-typegen** happens directly on this repository. Both core team members and external contributors send pull requests which go through the same review process. + +## Types of contributions + +- Reporting a bug. +- Suggest a new feature idea. +- Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...). +- Improve structure/format/performance/refactor/tests of the code. + +## Pull Requests + +- **Please first discuss** the change you wish to make via [issue](https://github.com/theoludwig/kysely-typegen/issues) before making a change. It might avoid a waste of your time. + +- Ensure your code respect linting. + +- Make sure your **code passes the tests**. + +If you're adding new features to **kysely-typegen**, please include tests. + +## Commits + +The commit message guidelines adheres to [Conventional Commits](https://www.conventionalcommits.org/) and [Semantic Versioning](https://semver.org/) for releases. + +## Development Environment + +```sh +# Clone the repository +git clone git@github.com:theoludwig/kysely-typegen.git + +# Install dependencies +npm clean-install + +# Lint +# node --run lint:typescript # already covered by oxlint +node --run lint:oxlint +node --run lint:oxfmt + +# Tests +node --run test + +# Build +node --run build +``` diff --git a/README.md b/README.md index c6ca9d7..52bc68a 100644 --- a/README.md +++ b/README.md @@ -1 +1,178 @@ # kysely-typegen + +[![version](https://npmx.dev/api/registry/badge/version/kysely-typegen)](https://npmx.dev/package/kysely-typegen) [![license](https://npmx.dev/api/registry/badge/license/kysely-typegen)](https://npmx.dev/package/kysely-typegen) + +Generate [Kysely](https://npmx.dev/package/kysely) type definitions from your database. + +Thank you [kysely-codegen](https://npmx.dev/package/kysely-codegen) for inspiration and ideas! + +## Why? + +Why `kysely-typegen` if there is already `kysely-codegen`? Comparison: + +| | `kysely-codegen@0.20.0` | `kysely-typegen` | +| ----------------------------- | ---------------------------------------- | ------------------------------------------------------------ | +| **Install Size** | 6.8 MB | 5 kB | +| **Dependencies** | 35 total | 0 (no runtime dependencies) | +| **Type** | CLI | Library/Programmatic Usage | +| **Code Size/Maintainability** | Heavy | Less than 200 LOC/Simple and straightforward | +| **Database Support** | PostgreSQL, MySQL, SQLite, MSSQL, LibSQL | PostgreSQL (but can **easily be extended to more dialects**) | + +`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. + +For example: you can use [Node.js with the `--env-file` CLI option](https://nodejs.org/api/environment_variables.html), `dotenv` dependency is not required (but can be used), **you are in control**. + +**Note:** `kysely-typegen` doesn't have the same features and customization as `kysely-codegen`, it has less features to keep it simple and lightweight, but can be extended to your own needs. + +## Prerequisites + +[Node.js](https://nodejs.org/) >= 24.0.0 + +## Installation + +```sh +npm install --save-dev kysely-typegen +``` + +Peer dependencies: + +```sh +npm install kysely +``` + +Kysely dialect of your choice, for example: [kysely-postgres-js](https://github.com/kysely-org/kysely-postgres-js) + +```sh +npm install kysely-postgres-js postgres +``` + +## Usage + +### Setup Kysely database + +Create your Kysely database instance (example with [kysely-postgres-js](https://github.com/kysely-org/kysely-postgres-js)): + +```ts +// database.ts +import { Kysely } from "kysely" +import { PostgresJSDialect } from "kysely-postgres-js" +import postgres from "postgres" + +import type { DB } from "./codegen.ts" + +export const DATABASE_USER = process.env["DATABASE_USER"] ?? "user" +export const DATABASE_PASSWORD = process.env["DATABASE_PASSWORD"] ?? "password" +export const DATABASE_NAME = process.env["DATABASE_NAME"] ?? "database" +export const DATABASE_HOST = process.env["DATABASE_HOST"] ?? "localhost" +export const DATABASE_PORT = Number.parseInt(process.env["DATABASE_PORT"] ?? "5432", 10) + +const dialect = new PostgresJSDialect({ + postgres: postgres({ + database: DATABASE_NAME, + host: DATABASE_HOST, + user: DATABASE_USER, + password: DATABASE_PASSWORD, + port: DATABASE_PORT, + }), +}) + +export const database = new Kysely({ + dialect, +}) +``` + +### Generate the type definitions + +Create a script that uses `kysely-typegen` to introspect your database and write the generated types to a file: + +```ts +// codegen.ts +// This is an example, you can be more creative and include more logic, for example time it takes to generate, number of tables/enums generated, etc. +import fs from "node:fs" +import path from "node:path" + +import { KyselyTypegenPostgresDialect } from "kysely-typegen" + +import { database } from "./database.ts" + +const databaseTypegen = new KyselyTypegenPostgresDialect({ + database, +}) +const result = await databaseTypegen.typegen() +const codegenContent = result.lines.join("\n") +const codegenPath = path.join(process.cwd(), "src/codegen.ts") +await fs.promises.writeFile(codegenPath, codegenContent, "utf-8") +await database.destroy() +``` + +Run with Node.js (using `--env-file` to load environment variables, no `dotenv` dependency required): + +```sh +node --env-file=.env scripts/codegen.ts +``` + +### Using the type definitions + +Import `DB` into `new Kysely`, and you're done! + +```ts +import { database } from "./database.ts" + +const rows = await database.selectFrom("users").selectAll().execute() +// ^ { createdAt: Date; email: string; id: number; ... }[] +``` + +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. + +Only two things are required: + +- `scalars`: a `Record` mapping the database column types to TypeScript types. +- `getEnumsMap()`: a method returning a `Map` of enum names to their values (return an empty `Map` if your database doesn't support enums). + +```ts +import { KyselyTypegenDialect } from "kysely-typegen" +import type { Kysely } from "kysely" + +export class KyselyTypegenMySQLDialect extends KyselyTypegenDialect { + public database: KyselyTypegenDialect["database"] + + public readonly scalars: Record = { + bigint: "number", + char: "string", + datetime: "Timestamp", + int: "number", + json: "Json", + text: "string", + tinyint: "number", + varchar: "string", + // ... + } + + public constructor(input: { database: Kysely }) { + super() + this.database = input.database + } + + protected async getEnumsMap(): Promise> { + // Query your database's information schema for enum types. + // Key: enum name, Value: array of enum values. + return new Map() + } +} +``` + +The base class handles introspection (via Kysely's `database.introspection.getTables()`), sorting, and the final type generation. Contributions of new dialects are welcome! + +## Contributing + +Anyone can help to improve the project, submit a Feature Request, a bug report or even correct a simple spelling mistake. + +The steps to contribute can be found in the [CONTRIBUTING.md](/CONTRIBUTING.md) file. + +## License + +[MIT](./LICENSE) diff --git a/src/_test/index.test.ts b/src/_test/index.test.ts index 735995f..0b182dd 100644 --- a/src/_test/index.test.ts +++ b/src/_test/index.test.ts @@ -33,7 +33,7 @@ const createSchema = async (database: Kysely): Promise => { return column .notNull() .primaryKey() - .defaultTo(sql`gen_random_uuid()`) + .defaultTo(sql`uuidv7()`) }) .addColumn("colBool", "boolean", (column) => { return column.notNull() @@ -163,7 +163,7 @@ const createSchema = async (database: Kysely): Promise => { return column .notNull() .primaryKey() - .defaultTo(sql`gen_random_uuid()`) + .defaultTo(sql`uuidv7()`) }) .addColumn("username", "varchar(50)", (column) => { return column.notNull().unique() diff --git a/src/index.ts b/src/index.ts index 7a0fcfd..0c850f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,6 +60,9 @@ export abstract class KyselyTypegenDialect { const enumName = enumMetadata.name result.push( `export type ${enumName} = ${enumMetadata.values + .sort((a, b) => { + return a.localeCompare(b) + }) .map((value) => { return `"${value}"` }) @@ -170,12 +173,7 @@ export class KyselyTypegenPostgresDialect extends KyselyTypegenDialect { const enums = new Map() for (const row of rows) { const data = row as { enumName: string; enumValue: string } - enums.set( - data.enumName, - [...(enums.get(data.enumName) ?? []), data.enumValue].sort((a, b) => { - return a.localeCompare(b) - }), - ) + enums.set(data.enumName, [...(enums.get(data.enumName) ?? []), data.enumValue]) } return enums }