diff --git a/docs/2.connectors/neon.md b/docs/2.connectors/neon.md index 695b676..3eadee4 100644 --- a/docs/2.connectors/neon.md +++ b/docs/2.connectors/neon.md @@ -6,9 +6,33 @@ icon: cbi:neon > Connect DB0 to Neon Serverless Postgres. -:read-more{to="https://neon.tech/"} +:read-more{to="https://neon.tech"} -::read-more{to="https://github.com/unjs/db0/issues/32"} -This connector is planned to be supported. Follow up via [unjs/db0#32](https://github.com/unjs/db0/issues/32). -:: +## Usage +For this connector, you need to install [`@neondatabase/serverless`](https://www.npmjs.com/package/@neondatabase/serverless) dependency: + +:pm-install{name="@neondatabase/serverless"} + +Use `neon` connector: + +```js +import { createDatabase } from "db0"; +import neonConnector from "db0/connectors/neon"; + +const db = createDatabase( + neonConnector({ + /* options */ + }), +); +``` + +## Options + +### `url` + +The URL of the Neon Serverless Postgres instance. + +**Type:** `string` + +:read-more{to="https://neon.tech/docs/serverless/serverless-driver#neon-function-configuration-options"} diff --git a/package.json b/package.json index d936e6b..02e9ea6 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@libsql/client": "^0.14.0", "@planetscale/database": "^1.19.0", "@types/better-sqlite3": "^7.6.12", + "@neondatabase/serverless": "^0.10.1", "@types/bun": "^1.2.0", "@types/pg": "^8.11.11", "@types/sqlite3": "^3.1.11", @@ -80,6 +81,7 @@ "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", + "@neondatabase/serverless": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", @@ -89,6 +91,9 @@ "@libsql/client": { "optional": true }, + "@neondatabase/serverless": { + "optional": true + }, "better-sqlite3": { "optional": true }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0907cd4..6b893bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@libsql/client': specifier: ^0.14.0 version: 0.14.0 + '@neondatabase/serverless': + specifier: ^0.10.1 + version: 0.10.4 '@planetscale/database': specifier: ^1.19.0 version: 1.19.0 @@ -47,13 +50,13 @@ importers: version: 0.5.7(magicast@0.3.5) db0: specifier: ^0.2.1 - version: 0.2.1(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(better-sqlite3@11.8.1)(drizzle-orm@0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7))(mysql2@3.12.0) + version: 0.2.1(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(better-sqlite3@11.8.1)(drizzle-orm@0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7))(mysql2@3.12.0) dotenv: specifier: ^16.4.7 version: 16.4.7 drizzle-orm: specifier: ^0.38.4 - version: 0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7) + version: 0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7) eslint: specifier: ^9.18.0 version: 9.18.0(jiti@2.4.2) @@ -92,13 +95,13 @@ importers: devDependencies: db0: specifier: latest - version: 0.2.1(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(better-sqlite3@11.8.1)(drizzle-orm@0.29.5(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7))(mysql2@3.12.0) + version: 0.2.1(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(better-sqlite3@11.8.1)(drizzle-orm@0.29.5(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7))(mysql2@3.12.0) drizzle-kit: specifier: ^0.20.14 version: 0.20.18 drizzle-orm: specifier: ^0.29.4 - version: 0.29.5(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7) + version: 0.29.5(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7) jiti: specifier: ^1.21.0 version: 1.21.7 @@ -915,6 +918,9 @@ packages: '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} + '@neondatabase/serverless@0.10.4': + resolution: {integrity: sha512-2nZuh3VUO9voBauuh+IGYRhGU/MskWHt1IuZvHcJw6GLjDgtqj/KViKo7SIrLdGLdot7vFbiRRw+BgEy3wT9HA==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1216,6 +1222,9 @@ packages: '@types/pg@8.11.11': resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==} + '@types/pg@8.11.6': + resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -2562,7 +2571,6 @@ packages: libsql@0.4.7: resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lilconfig@3.1.3: @@ -4385,6 +4393,10 @@ snapshots: '@neon-rs/load@0.0.4': {} + '@neondatabase/serverless@0.10.4': + dependencies: + '@types/pg': 8.11.6 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4621,6 +4633,12 @@ snapshots: pg-protocol: 1.7.0 pg-types: 4.0.2 + '@types/pg@8.11.6': + dependencies: + '@types/node': 22.10.10 + pg-protocol: 1.7.0 + pg-types: 4.0.2 + '@types/resolve@1.20.2': {} '@types/sqlite3@3.1.11': @@ -5224,20 +5242,20 @@ snapshots: data-uri-to-buffer@4.0.1: {} - db0@0.2.1(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(better-sqlite3@11.8.1)(drizzle-orm@0.29.5(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7))(mysql2@3.12.0): + db0@0.2.1(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(better-sqlite3@11.8.1)(drizzle-orm@0.29.5(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7))(mysql2@3.12.0): optionalDependencies: '@electric-sql/pglite': 0.2.16 '@libsql/client': 0.14.0 better-sqlite3: 11.8.1 - drizzle-orm: 0.29.5(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7) + drizzle-orm: 0.29.5(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7) mysql2: 3.12.0 - db0@0.2.1(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(better-sqlite3@11.8.1)(drizzle-orm@0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7))(mysql2@3.12.0): + db0@0.2.1(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(better-sqlite3@11.8.1)(drizzle-orm@0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7))(mysql2@3.12.0): optionalDependencies: '@electric-sql/pglite': 0.2.16 '@libsql/client': 0.14.0 better-sqlite3: 11.8.1 - drizzle-orm: 0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7) + drizzle-orm: 0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7) mysql2: 3.12.0 debug@4.4.0: @@ -5336,9 +5354,10 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.29.5(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7): + drizzle-orm@0.29.5(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7): optionalDependencies: '@libsql/client': 0.14.0 + '@neondatabase/serverless': 0.10.4 '@planetscale/database': 1.19.0 '@types/better-sqlite3': 7.6.12 '@types/pg': 8.11.11 @@ -5348,10 +5367,11 @@ snapshots: pg: 8.13.1 sqlite3: 5.1.7 - drizzle-orm@0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7): + drizzle-orm@0.38.4(@electric-sql/pglite@0.2.16)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.4)(@planetscale/database@1.19.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.11)(better-sqlite3@11.8.1)(bun-types@1.2.0)(mysql2@3.12.0)(pg@8.13.1)(sqlite3@5.1.7): optionalDependencies: '@electric-sql/pglite': 0.2.16 '@libsql/client': 0.14.0 + '@neondatabase/serverless': 0.10.4 '@planetscale/database': 1.19.0 '@types/better-sqlite3': 7.6.12 '@types/pg': 8.11.11 diff --git a/src/_connectors.ts b/src/_connectors.ts index d9a8ae6..b0db018 100644 --- a/src/_connectors.ts +++ b/src/_connectors.ts @@ -8,12 +8,13 @@ import type { ConnectorOptions as LibSQLHttpOptions } from "db0/connectors/libsq import type { ConnectorOptions as LibSQLNodeOptions } from "db0/connectors/libsql/node"; import type { ConnectorOptions as LibSQLWebOptions } from "db0/connectors/libsql/web"; import type { ConnectorOptions as MySQL2Options } from "db0/connectors/mysql2"; +import type { ConnectorOptions as NeonOptions } from "db0/connectors/neon"; import type { ConnectorOptions as NodeSQLite3Options } from "db0/connectors/node-sqlite3"; import type { ConnectorOptions as PgliteOptions } from "db0/connectors/pglite"; import type { ConnectorOptions as PlanetscaleOptions } from "db0/connectors/planetscale"; import type { ConnectorOptions as PostgreSQLOptions } from "db0/connectors/postgresql"; -export type BuiltinConnectorName = "better-sqlite3" | "sqlite" | "bun-sqlite" | "bun" | "cloudflare-d1" | "libsql-core" | "libsql-http" | "libsql-node" | "libsql" | "libsql-web" | "mysql2" | "node-sqlite3" | "pglite" | "planetscale" | "postgresql"; +export type BuiltinConnectorName = "better-sqlite3" | "sqlite" | "bun-sqlite" | "bun" | "cloudflare-d1" | "libsql-core" | "libsql-http" | "libsql-node" | "libsql" | "libsql-web" | "mysql2" | "neon" | "node-sqlite3" | "pglite" | "planetscale" | "postgresql"; export type BuiltinConnectorOptions = { "better-sqlite3": BetterSQLite3Options; @@ -30,6 +31,7 @@ export type BuiltinConnectorOptions = { "libsql": LibSQLNodeOptions; "libsql-web": LibSQLWebOptions; "mysql2": MySQL2Options; + "neon": NeonOptions; "node-sqlite3": NodeSQLite3Options; "pglite": PgliteOptions; "planetscale": PlanetscaleOptions; @@ -51,6 +53,7 @@ export const builtinConnectors = Object.freeze({ "libsql": "db0/connectors/libsql/node", "libsql-web": "db0/connectors/libsql/web", "mysql2": "db0/connectors/mysql2", + "neon": "db0/connectors/neon", "node-sqlite3": "db0/connectors/node-sqlite3", "pglite": "db0/connectors/pglite", "planetscale": "db0/connectors/planetscale", diff --git a/src/connectors/neon.ts b/src/connectors/neon.ts new file mode 100644 index 0000000..0f19462 --- /dev/null +++ b/src/connectors/neon.ts @@ -0,0 +1,59 @@ +import { neon, HTTPTransactionOptions, NeonQueryFunction } from '@neondatabase/serverless'; + +import type { Connector, Statement } from "../types"; + +export interface ConnectorOptions extends HTTPTransactionOptions { + /** + * The URL of the Neon Serverless Postgres instance. + * + * @required + */ + url: string; +} + +export default function neonConnector(opts: ConnectorOptions) { + let _connection: NeonQueryFunction; + const getConnection = async () => { + if (_connection) { + return _connection; + } + + const { url, ...transactionOptions } = opts; + + _connection = neon(url, { + ...transactionOptions, + }) + + return _connection; + }; + + return { + name: "neon", + dialect: "postgresql", + exec(sql: string) { + return getConnection().then((c) => c(sql)); + }, + prepare(sql: string) { + const stmt = { + _sql: sql, + _params: [], + bind(...params) { + if (params.length > 0) { + this._params = params; + } + return stmt; + }, + all(...params) { + return getConnection().then((c) => c(this._sql, params || this._params)); + }, + run(...params) { + return getConnection().then((c) => c(this._sql, params || this._params)); + }, + get(...params) { + return getConnection().then((c) => c(this._sql, params || this._params)).then((res) => res[0]); + }, + }; + return stmt; + }, + }; +} diff --git a/test/connectors/neon.test.ts b/test/connectors/neon.test.ts new file mode 100644 index 0000000..4691660 --- /dev/null +++ b/test/connectors/neon.test.ts @@ -0,0 +1,15 @@ +import { describe } from "vitest"; +import connector from "../../src/connectors/neon"; +import { testConnector } from "./_tests"; + +describe.runIf(process.env.POSTGRESQL_URL)( + "connectors: neon.test", + () => { + testConnector({ + dialect: "postgresql", + connector: connector({ + url: process.env.POSTGRESQL_URL!, + }), + }); + }, +);