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 2 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 sqlite from "db0/connectors/pglite";
adrienZ marked this conversation as resolved.
Show resolved Hide resolved

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`
27 changes: 27 additions & 0 deletions examples/pglite/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createDatabase } from "../../src";
import pglite from "../../src/connectors/pglite";



async function main() {
const db = createDatabase(pglite({
dataDir: ".data/pglite-data"
}));

await db.sql`create table if not exists users (
id serial primary key,
full_name text
)`;

await db.sql`insert into users (full_name) values ('John Doe')`;

const res = (await db.sql`select * from users`).rows
console.log({ res });
}

// eslint-disable-next-line unicorn/prefer-top-level-await
main().catch((error) => {
console.error(error);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
});
12 changes: 12 additions & 0 deletions examples/pglite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "db0-with-pglite",
"private": true,
"scripts": {
"start": "jiti ./index.ts"
},
"devDependencies": {
"@electric-sql/pglite": "^0.2.4",
"db0": "latest",
"jiti": "^1.21.0"
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"test": "pnpm lint && vitest run --coverage && pnpm test:bun"
},
"devDependencies": {
"@electric-sql/pglite": "^0.2.4",
"@libsql/client": "^0.6.0",
"@types/better-sqlite3": "^7.6.10",
"@types/bun": "^1.1.0",
Expand Down
24 changes: 22 additions & 2 deletions pnpm-lock.yaml

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

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

// Since Filesystem is not exported, we will type it as `any` for now
export type ConnectorOptions = {
dataDir?: string;
debug?: 1 | 2 | 3 | 4 | 5;
relaxedDurability?: boolean;
fs?: any; // Change to any or remove if not needed
loadDataDir?: Blob | File;
extensions?: Extensions;
username?: string;
database?: string;
initialMemory?: number;
};

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

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

_client = opts.dataDir ? new PGlite(opts.dataDir, opts) : 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",
exec(sql: string) {
return query(sql);
},
prepare(sql: string) {
const stmt = <Statement>{
_sql: sql,
_params: [],
bind(...params: unknown[]) {
if (params.length > 0) {
this._params = params;
}
return this;
},
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];
},
};
return stmt;
},
};
}

// https://www.postgresql.org/docs/9.3/sql-prepare.html
function normalizeParams(sql: string) {
let i = 0;
return sql.replace(/\?/g, () => `$${++i}`);
}
22 changes: 22 additions & 0 deletions test/connectors/pglite.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { fileURLToPath } from "node:url";
import { existsSync, unlinkSync, mkdirSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { describe } from "vitest";
import PGlite from "../../src/connectors/pglite"
import { testConnector } from "./_tests";

describe("connectors: pglite", () => {
const dbPath = resolve(
dirname(fileURLToPath(import.meta.url)),
".tmp/pglite/.data/pglite-data",
);
if (existsSync(dbPath)) {
unlinkSync(dbPath);
}
mkdirSync(dirname(dbPath), { recursive: true });
testConnector({
connector: PGlite({
dataDir: `${dbPath}`,
}),
});
});