-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
pglite-adapter #23877
base: main
Are you sure you want to change the base?
pglite-adapter #23877
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,38 @@ | ||||||
# @prisma/adapter-pg-lite | ||||||
|
||||||
This package contains the driver adapter for Prisma ORM that enables usage of the [`PGlite`](https://github.com/electric-sql/pglite) database driver for PostgreSQL running in WebAssembly. You can learn more in the [documentation](https://pris.ly/d/adapter-pg-lite). | ||||||
|
||||||
`PGlite` is a lightweight, WASM-based PostgreSQL build that allows running PostgreSQL directly in the browser, Node.js, and Bun environments without any external dependencies. It is only 2.6mb gzipped and supports both ephemeral in-memory databases and persistent storage. | ||||||
|
||||||
> **Note:**: Support for the `PGlite` driver is available from Prisma versions [5.4.2](https://github.com/prisma/prisma/releases/tag/5.4.2) and later. | ||||||
|
||||||
## Usage | ||||||
|
||||||
This section explains how you can use it with Prisma ORM and the `@prisma/adapter-pg-lite` driver adapter. Be sure that the `DATABASE_URL` environment variable is set to your PostgreSQL connection string (e.g., in a `.env` file) or use a direct in-memory connection. | ||||||
|
||||||
### 1. Enable the `driverAdapters` Preview feature flag | ||||||
|
||||||
Since driver adapters are currently in [Preview](/orm/more/releases#preview), you need to enable its feature flag on the `datasource` block in your Prisma schema: | ||||||
|
||||||
```prisma | ||||||
// schema.prisma | ||||||
generator client { | ||||||
provider = "prisma-client-js" | ||||||
previewFeatures = ["driverAdapters"] | ||||||
} | ||||||
|
||||||
datasource db { | ||||||
provider = "postgresql" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Tobbe I wondered that myself but if you look at the Neon adapter https://github.com/prisma/prisma/tree/main/packages/adapter-neon it specifies ‘postgres’ and in the PlanetScale adapter ‘mysql’ so it might be the underlying provider? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting. I went off of the new provider added here https://github.com/BLamy/prisma/blob/bd007e4b5f2cc2063d828c2325642de4d3388f70/packages/driver-adapter-utils/src/types.ts#L86 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’d have to check with the Prisma team by I thought of it as “flavor” and “client” — meaning this flavor of SQL and the features it supports is “postgres” (for example arrays and enums are supported — how you define your schema etc ), but the client used to make queries etc is the adapter “pglite”. But still learning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I was a little confused by this. I think what I want is that I want postgres to be used on both prod and dev. But if NODE_ENV=test I want it to be pglite. Not sure if that goal aligns with what everyone else wants (or even if it's a good goal for that matter) |
||||||
url = env("DATABASE_URL") | ||||||
} | ||||||
``` | ||||||
|
||||||
Once you have added the feature flag to your schema, re-generate Prisma Client: | ||||||
|
||||||
``` | ||||||
npx prisma generate | ||||||
``` | ||||||
|
||||||
### 2. Install the dependencies | ||||||
|
||||||
Next, install the `@electric-sql/pglite` package and Prisma ORM's driver adapter: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { build } from '../../../helpers/compile/build' | ||
import { adapterConfig } from '../../../helpers/compile/configs' | ||
|
||
void build(adapterConfig) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
preset: '../../helpers/test/presets/default.js', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
{ | ||
"name": "@prisma/adapter-pg", | ||
"version": "0.0.0", | ||
"description": "Prisma's driver adapter for \"pg\"", | ||
"main": "dist/index.js", | ||
"module": "dist/index.mjs", | ||
"types": "dist/index.d.ts", | ||
"exports": { | ||
".": { | ||
"require": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"import": { | ||
"types": "./dist/index.d.mts", | ||
"default": "./dist/index.mjs" | ||
} | ||
} | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/prisma/prisma.git", | ||
"directory": "packages/adapter-pg" | ||
}, | ||
"scripts": { | ||
"dev": "DEV=true tsx helpers/build.ts", | ||
"build": "tsx helpers/build.ts", | ||
"test": "jest" | ||
}, | ||
"files": [ | ||
"dist", | ||
"README.md" | ||
], | ||
"keywords": [], | ||
"author": "Tom Houlé <houle@prisma.io>", | ||
"license": "Apache-2.0", | ||
"sideEffects": false, | ||
"dependencies": { | ||
"@electric-sql/pglite": "0.1.3", | ||
"@prisma/driver-adapter-utils": "workspace:*", | ||
"postgres-array": "3.0.2" | ||
}, | ||
"devDependencies": { | ||
"@swc/core": "1.4.11", | ||
"@swc/jest": "0.2.36", | ||
"@types/pg": "8.11.5", | ||
"jest": "29.7.0", | ||
"jest-junit": "16.0.0", | ||
"pg": "8.11.5" | ||
}, | ||
"peerDependencies": { | ||
"pg": "^8.11.3" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { PrismaPgLite } from './pg-lite' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Client, Pool } from 'pg' | ||
|
||
import { PrismaPgLite } from './pg-lite' | ||
|
||
describe('validation', () => { | ||
test('throws if passed Client instance', () => { | ||
const client = new Client() | ||
|
||
expect(() => { | ||
// @ts-ignore | ||
new PrismaPg(client) | ||
}).toThrowErrorMatchingInlineSnapshot(` | ||
"PrismaPg must be initialized with an instance of Pool: | ||
import { Pool } from 'pg' | ||
const pool = new Pool({ connectionString: url }) | ||
const adapter = new PrismaPg(pool) | ||
" | ||
`) | ||
}) | ||
|
||
test('accepts Pool instance', () => { | ||
const pool = new Pool() | ||
|
||
expect(() => { | ||
new PrismaPgLite(pool) | ||
}).not.toThrow() | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* eslint-disable @typescript-eslint/require-await */ | ||
import { PGlite } from '@electric-sql/pglite' | ||
import type { | ||
ColumnType, | ||
ConnectionInfo, | ||
DriverAdapter, | ||
Query, | ||
Queryable, | ||
Result, | ||
ResultSet, | ||
Transaction, | ||
TransactionOptions, | ||
} from '@prisma/driver-adapter-utils' | ||
import { Debug, err, ok } from '@prisma/driver-adapter-utils' | ||
|
||
import { name as packageName } from '../package.json' | ||
|
||
const debug = Debug('prisma:driver-adapter:pglite') | ||
|
||
class PgLiteQueryable<ClientT extends PGlite> implements Queryable { | ||
readonly provider = 'pglite' | ||
readonly adapterName = packageName | ||
|
||
constructor(protected readonly client: ClientT) {} | ||
|
||
async queryRaw(query: Query): Promise<Result<ResultSet>> { | ||
const tag = '[js::query_raw]' | ||
debug(`${tag} %O`, query) | ||
|
||
const res = await this.performIO(query) | ||
|
||
if (!res.ok) { | ||
return err(res.error) | ||
} | ||
|
||
const { fields, rows } = res.value | ||
const columnNames = fields.map((field) => field.name) | ||
const columnTypes: ColumnType[] = fields.map((field) => field.dataTypeID as ColumnType) | ||
|
||
return ok({ | ||
columnNames, | ||
columnTypes, | ||
rows, | ||
}) | ||
} | ||
|
||
async executeRaw(query: Query): Promise<Result<number>> { | ||
const tag = '[js::execute_raw]' | ||
debug(`${tag} %O`, query) | ||
|
||
return (await this.performIO(query)).map(({ affectedRows }) => affectedRows ?? 0) | ||
} | ||
|
||
private async performIO(query: Query): Promise<Result<any>> { | ||
const { sql, args: values } = query | ||
|
||
try { | ||
const result = await this.client.query(sql, values, { | ||
rowMode: 'array', | ||
}) | ||
return ok(result) | ||
} catch (e) { | ||
const error = e as Error | ||
debug('Error in performIO: %O', error) | ||
if (e && typeof e.code === 'string' && typeof e.severity === 'string' && typeof e.message === 'string') { | ||
return err({ | ||
kind: 'Postgres', | ||
code: e.code, | ||
severity: e.severity, | ||
message: e.message, | ||
detail: e.detail, | ||
column: e.column, | ||
hint: e.hint, | ||
}) | ||
} | ||
throw error | ||
} | ||
} | ||
} | ||
|
||
class PgLiteTransaction extends PgLiteQueryable<PGlite> implements Transaction { | ||
constructor(client: PGlite, readonly options: TransactionOptions) { | ||
super(client) | ||
} | ||
|
||
async commit(): Promise<Result<void>> { | ||
debug(`[js::commit]`) | ||
return ok(undefined) | ||
} | ||
|
||
async rollback(): Promise<Result<void>> { | ||
debug(`[js::rollback]`) | ||
return ok(undefined) | ||
} | ||
} | ||
|
||
export type PrismaPgLiteOptions = { | ||
schema?: string | ||
} | ||
|
||
export class PrismaPgLite extends PgLiteQueryable<PGlite> implements DriverAdapter { | ||
constructor(client: PGlite, private options?: PrismaPgLiteOptions) { | ||
if (!(client instanceof PGlite)) { | ||
throw new TypeError(`PrismaPgLite must be initialized with an instance of PGlite`) | ||
} | ||
super(client) | ||
} | ||
|
||
getConnectionInfo(): Result<ConnectionInfo> { | ||
return ok({ | ||
schemaName: this.options?.schema, | ||
}) | ||
} | ||
|
||
async startTransaction(): Promise<Result<Transaction>> { | ||
const options: TransactionOptions = { | ||
usePhantomQuery: false, | ||
} | ||
|
||
const tag = '[js::startTransaction]' | ||
debug(`${tag} options: %O`, options) | ||
|
||
return ok(new PgLiteTransaction(this.client, options)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "../../tsconfig.build.regular.json", | ||
"compilerOptions": { | ||
"outDir": "declaration" | ||
}, | ||
"include": ["src"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../tsconfig.json" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.