Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support pglite #110

Merged
merged 8 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions docs/2.connectors/pglite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
icon: simple-icons:postgresql
---

# PGlite

> Connect DB0 to Postgres using PGlite

:read-more{to="https://pglite.dev"}

## Usage

For this connector, you need to install [`@electric-sql/pglite`](https://www.npmjs.com/package/@electric-sql/pglite) dependency:

:pm-i{name="@electric-sql/pglite"}

Use `pglite` connector:

```js
import { createDatabase, sql } from "db0";
import pglite from "db0/connectors/pglite";

const db = createDatabase(
pglite({
/* options */
}),
);
```

<!-- copy from https://pglite.dev/docs/api#main-constructor -->
## Options

### `dataDir`

Path to the directory for storing the Postgres database. You can provide a URI scheme for various storage backends:

- **`file://` or unprefixed**: File system storage, available in Node and Bun.
- **`idb://`**: IndexedDB storage, available in the browser.
- **`memory://`**: In-memory ephemeral storage, available on all platforms.

### `options`

#### `dataDir`

The directory in which to store the Postgres database when not provided as the first argument.

**Type:** `string`

#### `debug`

Postgres debug level. Logs are sent to the console.

**Type:** `1 | 2 | 3 | 4 | 5`

#### `relaxedDurability`

Under relaxed durability mode, PGlite will not wait for flushes to storage to complete after each query before returning results. This is particularly useful when using the IndexedDB file system.

**Type:** `boolean`

#### `fs`

An alternative to providing a `dataDir` with a filesystem prefix. Initialize a `Filesystem` yourself and provide it here.

**Type:** `Filesystem`

#### `loadDataDir`

A tarball of a PGlite datadir to load when the database starts. This should be a tarball produced from the related `.dumpDataDir()` method.

**Type:** `Blob | File`

#### `extensions`

An object containing the extensions you wish to load.

**Type:** `{ [namespace: string]: Extension }`

#### `username`

The username of the user to connect to the database as. Permissions will be applied in the context of this user.

**Type:** `string`

#### `database`

The database from the Postgres cluster within the `dataDir` to connect to.

**Type:** `string`

#### `initialMemory`

The initial amount of memory in bytes to allocate for the PGlite instance. PGlite will grow the memory automatically, but if you have a particularly large database, you can set this higher to prevent the pause during memory growth.

**Type:** `number`
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"test:bun": "bun test ./test/connectors/bun-test.ts"
},
"devDependencies": {
"@electric-sql/pglite": "^0.2.4",
"@libsql/client": "^0.14.0",
"@planetscale/database": "^1.19.0",
"@types/better-sqlite3": "^7.6.11",
Expand All @@ -76,7 +77,8 @@
"@libsql/client": "*",
"better-sqlite3": "*",
"drizzle-orm": "*",
"mysql2": "*"
"mysql2": "*",
"@electric-sql/pglite": "*"
},
"peerDependenciesMeta": {
"@libsql/client": {
Expand All @@ -90,6 +92,9 @@
},
"mysql2": {
"optional": true
},
"@electric-sql/pglite": {
"optional": true
}
},
"packageManager": "pnpm@9.12.1"
Expand Down
47 changes: 22 additions & 25 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 75 additions & 0 deletions src/connectors/pglite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { PGlite, type PGliteOptions } from "@electric-sql/pglite";
import type { Connector, Statement } from "../types";

export type ConnectorOptions = PGliteOptions
pi0 marked this conversation as resolved.
Show resolved Hide resolved

export default function pgliteConnector(opts: ConnectorOptions = {}) {
let _client: PGlite | undefined;

function getClient(): PGlite {
if (_client) {
return _client;
}
_client = new PGlite(opts);
return _client;
}

async function query(sql: string, params: unknown[] = []) {
const client = await getClient();
const normalizedSql = normalizeParams(sql);
const result = await client.query(normalizedSql, params);
return result;
}

return <Connector>{
name: "pglite",
dialect: "postgresql",
exec(sql: string) {
return query(sql);
},

Check warning on line 29 in src/connectors/pglite.ts

View check run for this annotation

Codecov / codecov/patch

src/connectors/pglite.ts#L28-L29

Added lines #L28 - L29 were not covered by tests
prepare(sql: string) {
const stmt = <Statement>{
_sql: sql,
_params: [],
bind(...params: unknown[]) {
if (params.length > 0) {
this._params = params;
}
return this;
},

Check warning on line 39 in src/connectors/pglite.ts

View check run for this annotation

Codecov / codecov/patch

src/connectors/pglite.ts#L35-L39

Added lines #L35 - L39 were not covered by tests
async all(...params: unknown[]) {
const result = await query(
this._sql,
params.length > 0 ? params : this._params
);
return result.rows;
},
async run(...params: unknown[]) {
const result = await query(
this._sql,
params.length > 0 ? params : this._params
);
return {
success: true, // Adding the success property to match the expected type
result,
rows: result.rows,
};
},
async get(...params: unknown[]) {
const result = await query(
this._sql,
params.length > 0 ? params : this._params
);
return result.rows[0];
},

Check warning on line 64 in src/connectors/pglite.ts

View check run for this annotation

Codecov / codecov/patch

src/connectors/pglite.ts#L59-L64

Added lines #L59 - L64 were not covered by tests
};
return stmt;
},
};
}

// https://www.postgresql.org/docs/9.3/sql-prepare.html
function normalizeParams(sql: string) {
let i = 0;
return sql.replace(/\?/g, () => `$${++i}`);
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from "./types";
export const connectors = {
sqlite: "db0/connectors/better-sqlite3",
postgresql: "db0/connectors/postgresql",
pglite: "db0/connectors/pglite",
"cloudflare-d1": "db0/connectors/cloudflare-d1",
libsql: "db0/connectors/libsql/node",
"libsql-node": "db0/connectors/libsql/node",
Expand Down
16 changes: 16 additions & 0 deletions test/connectors/pglite.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { fileURLToPath } from "node:url";
import { rm, mkdir } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { describe } from "vitest";
import PGlite from "../../src/connectors/pglite"
import { testConnector } from "./_tests";

describe("connectors: pglite", async () => {
const dataDir = fileURLToPath(new URL(".tmp/pglite", import.meta.url));
await rm(dataDir, { recursive: true }).catch(() => { /* */ });
await mkdir(dirname(dataDir), { recursive: true });
testConnector({
dialect: "postgresql",
connector: PGlite({ dataDir }),
});
});