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 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/_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..100ac7686 --- /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 66% rename from site/docs/migrations.mdx rename to site/docs/migrations/overview.mdx index f6abc8232..59967fd0f 100644 --- a/site/docs/migrations.mdx +++ b/site/docs/migrations/overview.mdx @@ -1,8 +1,12 @@ --- -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 @@ -26,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 read/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. @@ -48,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() -} -``` - ## CLI (optional) Kysely offers a CLI you can use for migrations (and more). It can help you create and run migrations. 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/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) +``` 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', }, { 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",