From 18d417c43b6fea0f348134d80d7859182548918d Mon Sep 17 00:00:00 2001 From: Sleepful Date: Tue, 28 May 2024 23:30:05 -0600 Subject: [PATCH 1/5] Add sample code to use Kysely in Deno with PostgresJS --- site/docs/runtimes/deno.mdx | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/site/docs/runtimes/deno.mdx b/site/docs/runtimes/deno.mdx index f118a53ab..35ab3c0c6 100644 --- a/site/docs/runtimes/deno.mdx +++ b/site/docs/runtimes/deno.mdx @@ -52,3 +52,35 @@ const sql = query.compile() console.log(sql.sql) ``` + +Deno works well with [PostgresJS](https://github.com/porsager/postgres), to that end you can use the Kysely dialect and the driver: + +```ts +import { PostgresJSDialect } from "npm:kysely-postgres-js"; +import postgres from 'https://deno.land/x/postgresjs/mod.js' +import { Kysely } from "kysely"; + +interface Database {} + +// adjust accordingly +const databaseConfig = { + postgres: postgres({ + database: "my_database_name", + host: "localhost", + max: 10, + port: 5432, + user: "postgres", + }), +}; + +const db = new Kysely({ + dialect: new PostgresJSDialect(databaseConfig), +}); + +// example assuming there is a "users" table: +const users = await db.selectFrom("users") + .selectAll() + .execute(); + +console.log(users) +``` From 67c3e666237b0b8c1f89bbe7ce69ab1135645a47 Mon Sep 17 00:00:00 2001 From: Sleepful Date: Tue, 28 May 2024 23:59:07 -0600 Subject: [PATCH 2/5] Docs: Example database schema migrations in Deno. --- site/docs/migrations/_category_.json | 8 + site/docs/migrations/deno.mdx | 238 ++++++++++++++++++ .../overview.mdx} | 95 +------ site/docs/migrations/postgresql-migration.mdx | 46 ++++ site/docs/migrations/sqlite-migration.mdx | 46 ++++ site/package-lock.json | 2 +- 6 files changed, 352 insertions(+), 83 deletions(-) create mode 100644 site/docs/migrations/_category_.json create mode 100644 site/docs/migrations/deno.mdx rename site/docs/{migrations.mdx => migrations/overview.mdx} (67%) create mode 100644 site/docs/migrations/postgresql-migration.mdx create mode 100644 site/docs/migrations/sqlite-migration.mdx diff --git a/site/docs/migrations/_category_.json b/site/docs/migrations/_category_.json new file mode 100644 index 000000000..f74feb48b --- /dev/null +++ b/site/docs/migrations/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Migrations", + "position": 4, + "link": { + "type": "generated-index", + "description": "Manage database schema migrations with Kysely." + } +} diff --git a/site/docs/migrations/deno.mdx b/site/docs/migrations/deno.mdx new file mode 100644 index 000000000..4b975fa8b --- /dev/null +++ b/site/docs/migrations/deno.mdx @@ -0,0 +1,238 @@ +--- +sidebar_position: 4 +--- + +# Deno + +An example of the code in this guide can be found at [this repo](https://github.com/Sleepful/deno-kysely-migrations). + +To manage migrations we need a few things: + +- A way to create new migrations. +- A way to run our migrations. + +This guide will show how to do this in Deno. + +Assume the following configuration in `deno.json`: + +```ts +{ + "tasks": { + "dev": "deno run --watch main.ts", + "migrate": "deno run -A ./tasks/db_migrate.ts", + "new_migration": "deno run -A ./tasks/new_migration.ts", + }, + "imports": { + "$std/": "https://deno.land/std/", + "kysely": "npm:kysely", + "kysely-postgres-js": "npm:kysely-postgres-js", + "postgres": "https://deno.land/x/postgresjs/mod.js", + } +} +``` + +Lets put a script that creates our migrations, we can run this script with: +``` +$ deno new_migration +``` +The script will be located at `tasks/new_migration.ts` and it will look like this: + +```ts +// tasks/new_migration.ts +import { parseArgs } from "$std/cli/parse_args.ts"; + +// This is a Deno task, that means that CWD (Deno.cwd) is the root dir of the project, +// remember this when looking at the relative paths below. + +// Make sure there is this `migrations` dir in root of project +const migrationDir = "./migrations"; + +const isMigrationFile = (filename: string): boolean => { + const regex = /.*migration.ts$/; + return regex.test(filename); +}; + +const files = []; +for await (const f of Deno.readDir(migrationDir)) { + f.isFile && isMigrationFile(f.name) && files.push(f); +} + +const filename = (idx: number, filename: string) => { + const YYYYmmDD = new Date().toISOString().split("T")[0]; + // pad the index of the migration to 3 digits because + // the migrations will be executed in alphanumerical order + const paddedIdx = String(idx).padStart(3, "0"); + return `${paddedIdx}-${YYYYmmDD}-${filename}.migration.ts`; +}; + +const firstCLIarg = parseArgs(Deno.args)?._[0] as string ?? null; + +if (!firstCLIarg) { + console.error("You must pass-in the name of the migration file as an arg"); + Deno.exit(1); +} + +// make sure this is file is present in the project +const templateText = await Deno.readTextFile( + "./tasks/new_migration/migration_template.ts", +); + +const migrationFilename = filename(files.length, firstCLIarg); + +console.log(`Creating migration:\n\nmigrations/${migrationFilename}\n`); + +await Deno.writeTextFile(`./migrations/${migrationFilename}`, templateText); + +console.log("Done!"); +``` + +This script uses a template file for our migrations, lets create that too at `tasks/new_migration/migration_template.ts`: + +```ts +// tasks/new_migration/migration_template.ts +import { Kysely, sql } from "kysely"; + +export async function up(db: Kysely): Promise { + // Migration code +} + +export async function down(db: Kysely): Promise { + // Migration code +} +``` + +Finally, lets create the script that will run our migrations. We will use this script like this: +``` +$ deno migrate +$ deno migrate down +``` +The script will be located at `tasks/db_migrate.ts` and will look like this: +```ts +// tasks/db_migrate.ts +import { + Kysely, + type Migration, + type MigrationProvider, + Migrator, +} from "kysely"; +import { PostgresJSDialect } from "kysely-postgres-js"; +import postgres from "postgres"; +import * as Path from "$std/path/mod.ts"; +import { parseArgs } from "$std/cli/parse_args.ts"; + +const databaseConfig = { + postgres: postgres({ + database: "my_database_name", + host: "localhost", + max: 10, + port: 5432, + user: "postgres", + }), +}; + +// Migration files must be found in the `migrationDir`. +// and the migration files must follow name convention: +// number-.migration.ts +// where `number` identifies the order in which migrations are to be run. +// +// To create a new migration file use: +// deno task new_migration + +const migrationDir = "./migrations"; + +const allowUnorderedMigrations = false; + +// Documentation to understand why the code in this file looks as it does: +// Kysely docs: https://www.kysely.dev/docs/migrations/deno +// Example provider: https://github.com/kysely-org/kysely/blob/6f913552/src/migration/file-migration-provider.ts#L20 + +interface Database {} + +const db = new Kysely({ + dialect: new PostgresJSDialect(databaseConfig), +}); + +export interface FileMigrationProviderProps { + migrationDir: string; +} + +class DenoMigrationProvider implements MigrationProvider { + readonly #props: FileMigrationProviderProps; + + constructor(props: FileMigrationProviderProps) { + this.#props = props; + } + + isMigrationFile = (filename: string): boolean => { + const regex = /.*migration.ts$/; + return regex.test(filename); + }; + + async getMigrations(): Promise> { + const files: Deno.DirEntry[] = []; + for await (const f of Deno.readDir(this.#props.migrationDir)) { + f.isFile && this.isMigrationFile(f.name) && files.push(f); + } + + const migrations: Record = {}; + + for (const f of files) { + const filePath = Path.join(Deno.cwd(), this.#props.migrationDir, f.name); + const migration = await import(filePath); + const migrationKey = f.name.match(/(\d+-.*).migration.ts/)![1]; + migrations[migrationKey] = migration; + } + + return migrations; + } +} + +const migrator = new Migrator({ + db, + provider: new DenoMigrationProvider({ migrationDir }), + allowUnorderedMigrations, +}); + +const firstCLIarg = parseArgs(Deno.args)?._[0] as string ?? null; + +const migrate = () => { + if (firstCLIarg == "down") { + return migrator.migrateDown(); + } + return migrator.migrateToLatest(); +}; + +const { error, results } = await migrate(); +results?.forEach((it) => { + if (it.status === "Success") { + console.log(`Migration "${it.migrationName}" was executed successfully`); + } else if (it.status === "Error") { + console.error(`Failed to execute migration "${it.migrationName}"`); + } +}); + +if (error) { + console.error("Failed to migrate"); + console.error(error); + Deno.exit(1); +} + +await db.destroy(); + +Deno.exit(0); +``` + +Remember to adjust `databaseConfig` so that it connects to your database. + +Now you can create a migration with: +``` +deno task new_migration +``` +Then apply it to your database with: +``` +deno task migrate +``` +If you want to undo your last migration just use: +``` +deno task migrate down +``` diff --git a/site/docs/migrations.mdx b/site/docs/migrations/overview.mdx similarity index 67% rename from site/docs/migrations.mdx rename to site/docs/migrations/overview.mdx index 263b96add..c396eb7ce 100644 --- a/site/docs/migrations.mdx +++ b/site/docs/migrations/overview.mdx @@ -1,8 +1,14 @@ --- -sidebar_position: 4 +sidebar_position: 1 --- -# Migrations +# Overview + +To run migrations in kysely, you need two things: +- Migration files. +- A configured [Migrator](https://kysely-org.github.io/kysely-apidoc/classes/Migrator.html) to run its methods. + +## Migration files Migration files should look like this: @@ -24,6 +30,11 @@ Migrations should never depend on the current code of your app because they need Migrations can use the `Kysely.schema` module to modify the schema. Migrations can also run normal queries to modify data. +An example migration file for: + +- [PostgreSQL](./postgresql-migration.mdx) +- [SQLite](./sqlite-migration.mdx) + ## Execution order Migrations will be run in the alpha-numeric order of your migration names. An excellent way to name your migrations is to prefix them with an ISO 8601 date string. @@ -46,86 +57,6 @@ const migrator = new Migrator({ You don't need to store your migrations as separate files if you don't want to. You can easily implement your own MigrationProvider and give it to the Migrator class when you instantiate one. -## PostgreSQL migration example - -```ts -import { Kysely, sql } from 'kysely' - -export async function up(db: Kysely): Promise { - await db.schema - .createTable('person') - .addColumn('id', 'serial', (col) => col.primaryKey()) - .addColumn('first_name', 'varchar', (col) => col.notNull()) - .addColumn('last_name', 'varchar') - .addColumn('gender', 'varchar(50)', (col) => col.notNull()) - .addColumn('created_at', 'timestamp', (col) => - col.defaultTo(sql`now()`).notNull() - ) - .execute() - - await db.schema - .createTable('pet') - .addColumn('id', 'serial', (col) => col.primaryKey()) - .addColumn('name', 'varchar', (col) => col.notNull().unique()) - .addColumn('owner_id', 'integer', (col) => - col.references('person.id').onDelete('cascade').notNull() - ) - .addColumn('species', 'varchar', (col) => col.notNull()) - .execute() - - await db.schema - .createIndex('pet_owner_id_index') - .on('pet') - .column('owner_id') - .execute() -} - -export async function down(db: Kysely): Promise { - await db.schema.dropTable('pet').execute() - await db.schema.dropTable('person').execute() -} -``` - -## SQLite migration example - -```ts -import { Kysely, sql } from 'kysely' - -export async function up(db: Kysely): Promise { - await db.schema - .createTable('person') - .addColumn('id', 'integer', (col) => col.primaryKey()) - .addColumn('first_name', 'text', (col) => col.notNull()) - .addColumn('last_name', 'text') - .addColumn('gender', 'text', (col) => col.notNull()) - .addColumn('created_at', 'text', (col) => - col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull() - ) - .execute() - - await db.schema - .createTable('pet') - .addColumn('id', 'integer', (col) => col.primaryKey()) - .addColumn('name', 'text', (col) => col.notNull().unique()) - .addColumn('owner_id', 'integer', (col) => - col.references('person.id').onDelete('cascade').notNull() - ) - .addColumn('species', 'text', (col) => col.notNull()) - .execute() - - await db.schema - .createIndex('pet_owner_id_index') - .on('pet') - .column('owner_id') - .execute() -} - -export async function down(db: Kysely): Promise { - await db.schema.dropTable('pet').execute() - await db.schema.dropTable('person').execute() -} -``` - ## Running migrations You can then use diff --git a/site/docs/migrations/postgresql-migration.mdx b/site/docs/migrations/postgresql-migration.mdx new file mode 100644 index 000000000..a357157cd --- /dev/null +++ b/site/docs/migrations/postgresql-migration.mdx @@ -0,0 +1,46 @@ +--- +sidebar_position: 2 +--- + +# PostgreSQL migration example + +A migration file: + +```ts +import { Kysely, sql } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('person') + .addColumn('id', 'serial', (col) => col.primaryKey()) + .addColumn('first_name', 'varchar', (col) => col.notNull()) + .addColumn('last_name', 'varchar') + .addColumn('gender', 'varchar(50)', (col) => col.notNull()) + .addColumn('created_at', 'timestamp', (col) => + col.defaultTo(sql`now()`).notNull() + ) + .execute() + + await db.schema + .createTable('pet') + .addColumn('id', 'serial', (col) => col.primaryKey()) + .addColumn('name', 'varchar', (col) => col.notNull().unique()) + .addColumn('owner_id', 'integer', (col) => + col.references('person.id').onDelete('cascade').notNull() + ) + .addColumn('species', 'varchar', (col) => col.notNull()) + .execute() + + await db.schema + .createIndex('pet_owner_id_index') + .on('pet') + .column('owner_id') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('pet').execute() + await db.schema.dropTable('person').execute() +} +``` + diff --git a/site/docs/migrations/sqlite-migration.mdx b/site/docs/migrations/sqlite-migration.mdx new file mode 100644 index 000000000..3a254fea1 --- /dev/null +++ b/site/docs/migrations/sqlite-migration.mdx @@ -0,0 +1,46 @@ +--- +sidebar_position: 3 +--- + +# SQLite migration example + +A migration file: + +```ts +import { Kysely, sql } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('person') + .addColumn('id', 'integer', (col) => col.primaryKey()) + .addColumn('first_name', 'text', (col) => col.notNull()) + .addColumn('last_name', 'text') + .addColumn('gender', 'text', (col) => col.notNull()) + .addColumn('created_at', 'text', (col) => + col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull() + ) + .execute() + + await db.schema + .createTable('pet') + .addColumn('id', 'integer', (col) => col.primaryKey()) + .addColumn('name', 'text', (col) => col.notNull().unique()) + .addColumn('owner_id', 'integer', (col) => + col.references('person.id').onDelete('cascade').notNull() + ) + .addColumn('species', 'text', (col) => col.notNull()) + .execute() + + await db.schema + .createIndex('pet_owner_id_index') + .on('pet') + .column('owner_id') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('pet').execute() + await db.schema.dropTable('person').execute() +} +``` + diff --git a/site/package-lock.json b/site/package-lock.json index 3bf9ab01c..aaf7d4995 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "kysely", - "version": "0.25.0", + "version": "0.27.0", "dependencies": { "@docusaurus/core": "^2.4.1", "@docusaurus/preset-classic": "^2.4.1", From 1b8ddd0149d050a6449a4b44df7daf8a8e647d57 Mon Sep 17 00:00:00 2001 From: Sleepful Date: Tue, 18 Jun 2024 16:31:57 -0600 Subject: [PATCH 3/5] Fixes a few links to docs/migrations --- site/docs/getting-started/Summary.tsx | 2 +- site/docs/migrations/deno.mdx | 4 ++-- site/docusaurus.config.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/docs/getting-started/Summary.tsx b/site/docs/getting-started/Summary.tsx index 3ed5b8557..45bc78533 100644 --- a/site/docs/getting-started/Summary.tsx +++ b/site/docs/getting-started/Summary.tsx @@ -120,7 +120,7 @@ ${dialectSpecificCodeSnippet} As you can see, Kysely supports DDL queries. It also supports classic "up/down" migrations. Find out more at{' '} - Migrations. + Migrations. ) diff --git a/site/docs/migrations/deno.mdx b/site/docs/migrations/deno.mdx index 4b975fa8b..100ac7686 100644 --- a/site/docs/migrations/deno.mdx +++ b/site/docs/migrations/deno.mdx @@ -59,8 +59,8 @@ for await (const f of Deno.readDir(migrationDir)) { const filename = (idx: number, filename: string) => { const YYYYmmDD = new Date().toISOString().split("T")[0]; - // pad the index of the migration to 3 digits because - // the migrations will be executed in alphanumerical order + // Pad the index of the migration to 3 digits because + // the migrations will be executed in alphanumerical order. const paddedIdx = String(idx).padStart(3, "0"); return `${paddedIdx}-${YYYYmmDD}-${filename}.migration.ts`; }; diff --git a/site/docusaurus.config.js b/site/docusaurus.config.js index d69f89d47..124710348 100644 --- a/site/docusaurus.config.js +++ b/site/docusaurus.config.js @@ -132,7 +132,7 @@ const config = { }, { label: 'Migrations', - to: '/docs/migrations', + to: '/docs/category/migrations', }, { From 4a86326bb1936f0f31d3e1e5c2d3fe1388b0a33b Mon Sep 17 00:00:00 2001 From: Sleepful Date: Tue, 18 Jun 2024 16:32:39 -0600 Subject: [PATCH 4/5] Add README to kysely/site --- site/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 site/README.md diff --git a/site/README.md b/site/README.md new file mode 100644 index 000000000..6ecbbe828 --- /dev/null +++ b/site/README.md @@ -0,0 +1,8 @@ + +# kysely.dev Documentation + +Project powered by Docusaurus. + +Use `npm run build` to check for correctness in the site links. + +`npm start` runs the project on localhost From 943f3eb894c5978ecfd00f2fdc4d04a085bad3b8 Mon Sep 17 00:00:00 2001 From: Sleepful Date: Tue, 18 Jun 2024 16:57:33 -0600 Subject: [PATCH 5/5] Fix small typo --- site/docs/migrations/overview.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/site/docs/migrations/overview.mdx b/site/docs/migrations/overview.mdx index ef17ed4cb..59967fd0f 100644 --- a/site/docs/migrations/overview.mdx +++ b/site/docs/migrations/overview.mdx @@ -10,8 +10,6 @@ To run migrations in kysely, you need two things: ## Migration files -## Migration files - Migration files should look like this: ```ts