Skip to content

Commit

Permalink
implement CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
dnlsandiego committed Aug 29, 2024
1 parent 50fa273 commit fb92f41
Show file tree
Hide file tree
Showing 21 changed files with 948 additions and 393 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,7 @@ dist

pgdata/

.npmrc
.npmrc
lab
.vscode
.arc/
115 changes: 64 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
# kysely-pglite

[Kysely](https://github.com/kysely-org/kysely) dialect for [PGlite](https://github.com/electric-sql/pglite).

Generate types using the provided CLI.
[Kysely](https://github.com/kysely-org/kysely) dialect for [PGlite](https://github.com/electric-sql/pglite) with a [CLI](#generating-types) to generate TypeScript types.

Kysely specific wrapper over PGlite's [Live Queries](https://pglite.dev/docs/live-queries) extension to take advantage of Kysely's type-safe features.

## Installation

[`@electric-sql/pglite`](https://github.com/electric-sql/pglite) needs to be installed as well.

#### PNPM

```bash
pnpm add @electric-sql/pglite kysely-pglite
```

#### NPM

```bash
npm install @electric-sql/pglite kysely-pglite
```

#### Yarn

```bash
yarn add @electric-sql/pglite kysely-pglite
```

## Usage

The examples below use the static async method `await KyselyPGlite.create()` to align with the preferred way to create a PGlite instance as stated in the [PGlite docs](https://pglite.dev/docs/api#main-constructor). But an instance can still be created using the `new KyselyPGlite()` constructor.
The examples below mostly use the static async method `await KyselyPGlite.create()` to align with the [PGlite docs](https://pglite.dev/docs/api#main-constructor). But an instance can still be created using the `new KyselyPGlite()` constructor.

```typescript
import { Kysely } from 'kysely'
import { KyselyPGlite } from 'kysely-pglite'

// This will use in-memory Postgres
// Use in-memory Postgres
const { dialect } = await KyselyPGlite.create()

// For persisting the data to disk, pass in a path to a directory
Expand All @@ -23,7 +43,7 @@ const { dialect } = await KyselyPGlite.create()
const db = new Kysely<DB>({ dialect })
```

`PGlite` options can be passed in as the second parameter. See [PGlite options](https://pglite.dev/docs/api#options) for more info.
`PGlite` options can be passed in, it has the same function signature as PGlite. See [PGlite options](https://pglite.dev/docs/api#options) for more info.

```typescript
const { dialect } = await KyselyPGlite.create('./path/to/pgdata', {
Expand All @@ -34,21 +54,50 @@ const { dialect } = await KyselyPGlite.create('./path/to/pgdata', {

## Generating Types

`kysely-pglite` has a CLI to generate TypeScript types. It's a wrapper around [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen) to get around its requirement of a connection to a running database.
`kysely-pglite` provides a CLI to generate TypeScript types. It's a wrapper around [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen) to get around its requirement of a connection to a running database. So the CLI accepts most of `kysely-codegen`'s options just minus the connection specific settings.

Without a running database to connect to, the codegen needs a `path` to a file/directory of Kysely migrations or to a persisted PGlite database.

Using Kysely migrations, the `kysely-pglite` CLI expects a path to either a file or directory of migration files that exports 2 async functions called `up` and `down` (same pattern as in the [Kysely docs](https://kysely.dev/docs/migrations#migration-files)). For example:

You'll need to point the `kysely-pglite` CLI to a file that exports a `Kysely` instance or to a directory that stores the persisted Postgres database.
```typescript
// src/db/migrations/2024-05-04-create-user.ts
import { Kysely } from 'kysely'

export async function up(db: Kysely<any>) {
await db.schema
.createTable('user')
.ifNotExists()
.addColumn('id', 'serial', (cb) => cb.primaryKey())
.addColumn('name', 'text', (cb) => cb.notNull())
.execute()
}

If using a Kysely instance, the CLI will look through file's exports and find the Kysely instance so the object doesn't need to be named `db` or be the only thing exported.
export async function down(db: Kysely<any>) {
await db.schema.dropTable('user').execute()
}
```

```bash
npx kysely-pglite ./path/to/your/db.ts
npx kysely-pglite ./src/db/migrations --out-file ./src/db/types.ts
```

> [!TIP]
> The CLI is also aliased as `kpg` for easier typing.
A persisted PGlite's database can also be used to generate the types. `kysely-pglite` will automatically detect that the directory is a PGlite database and not migration files.

```bash
npx kysely-pglite --data-dir ./path/to/pgdata
npx kysely-pglite ./path/to/pgdata
```

You can also use the `Codegen` class to have more flexibilty
There's also a `--watch` option to make `kysely-pglite` watch the given `path` and automatically re-generate the types whenever a change is detected.

```bash
npx kysely-pglite --watch ./src/db/migrations --out-file ./src/db/types.ts
```

You can also import the `Codegen` class directly to have more flexibilty:

```typescript
import { Codegen } from 'kysely-pglite'
Expand All @@ -57,7 +106,7 @@ const { dialect } = new KyselyPGlite()

const codegen = new Codegen(dialect)

// See `kysely-pglite --help` for more options
// See `kysely-pglite help` for more options
const types = await codegen.generate({
// Your Kysely DB
db,
Expand All @@ -67,11 +116,9 @@ const types = await codegen.generate({
console.log(types) // stringified types
```

If you're starting fresh, you will probably need to create a temporary empty `interface DB {}` to create a Kysely instance, generate the types then update it to the generated `DB`.

## `KyselyLive` Usage

`KyselyLive` is a "bridge" for using PGlite's live queries extension and Kysely's type-safe features. To quickly compare:
`KyselyLive` is a [AsyncIterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator) class for using PGlite's live queries extension with Kysely's type-safe features when defining queries watch. To quickly compare:

```typescript
const ret = pg.live.query(
Expand Down Expand Up @@ -136,7 +183,7 @@ liveQuery.refresh()

## Migrations

Kysely migrations work well with `PGlite`. See example setup below:
Kysely migrations work well with `PGlite`. This example setup is mostly relevant when using in-memory storage as you'll probably need to create tables during server startup.

```typescript
// file: migrations/2025-08-01-create-user-table.ts
Expand Down Expand Up @@ -228,37 +275,3 @@ app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
```

> [!WARNING]
> Applying migrations with `await` in the same file that runs your server could potentially impact its start-up time if there are a lot of migrations to run.
## Installation

[`@electric-sql/pglite`](https://github.com/electric-sql/pglite) needs to be installed as well.

#### PNPM

```bash
pnpm add @electric-sql/pglite kysely-pglite
```

#### NPM

```bash
npm install @electric-sql/pglite kysely-pglite
```

#### Yarn

```bash
yarn add @electric-sql/pglite kysely-pglite
```

> [!WARNING]
> This dialect has not been tested on Deno yet.
## Todos

- Verify browser usage. `kysely` and `pglite` both work in browser

- Verify works on Deno
3 changes: 3 additions & 0 deletions bin/dev.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*
6 changes: 6 additions & 0 deletions bin/dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node

// eslint-disable-next-line n/shebang
import { execute } from '@oclif/core'

await execute({ development: true, dir: import.meta.url })
3 changes: 3 additions & 0 deletions bin/run.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node "%~dp0\run" %*
5 changes: 5 additions & 0 deletions bin/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env node

import {execute} from '@oclif/core'

await execute({dir: import.meta.url})
47 changes: 39 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"packageManager": "[email protected]",
"scripts": {
"build": "rimraf dist && tsc",
"dev": "tsc --watch",
"typecheck": "tsc --noEmit",
"prepublishOnly": "pnpm run build",
"prepare": "pnpm run build",
Expand All @@ -27,7 +28,11 @@
"files": [
"dist"
],
"bin": "./dist/cli.js",
"imports": {
"#*": {
"default": "./dist/*"
}
},
"exports": {
"import": {
"types": "./dist/index.d.ts",
Expand All @@ -39,22 +44,48 @@
"kysely": "*"
},
"devDependencies": {
"@repeaterjs/repeater": "^3.0.6",
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.5.0",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"tsx": "^4.18.0",
"tsx": "^4.19.0",
"type-fest": "^4.25.0",
"typescript": "^5.0.0",
"vitest": "^2.0.3"
"typescript": "^5.5.4",
"vitest": "^2.0.5"
},
"author": "Daniel Sandiego",
"dependencies": {
"@oclif/core": "^4.0.19",
"@repeaterjs/repeater": "^3.0.6",
"@sindresorhus/is": "^7.0.0",
"cleye": "^1.3.2",
"chokidar": "^3.6.0",
"consola": "^3.2.3",
"fs-extra": "^11.2.0",
"globby": "^14.0.2",
"jiti": "2.0.0-beta.3",
"kysely-codegen": "^0.15.0",
"radash": "^12.1.0"
}
},
"bin": {
"kysely-pglite": "./bin/run.js",
"kpg": "./bin/run.js"
},
"oclif": {
"binAliases": [
"kysely-pglite",
"kpg"
],
"commands": {
"strategy": "single",
"target": "./dist/cli.js"
},
"dirname": "kysely-pglite",
"topicSeparator": " "
},
"pnpm": {
"allowedDeprecatedVersions": {
"glob": "*",
"inflight": "*"
}
},
"author": "Daniel Sandiego"
}
Loading

0 comments on commit fb92f41

Please sign in to comment.