Skip to content

Commit 8e49b3e

Browse files
committed
get Pooler, HTTP, and Serverless connectors for Neon
1 parent 3da916f commit 8e49b3e

File tree

7 files changed

+222
-13
lines changed

7 files changed

+222
-13
lines changed

src/connectors/neon.ts renamed to src/connectors/neon/http.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from "@neondatabase/serverless";
66
import type { Connector, Primitive } from "db0";
77

8-
import { BoundableStatement } from "./_internal/statement.ts";
8+
import { BoundableStatement } from "../_internal/statement.ts";
99

1010
export type ConnectorOptions = {
1111
connectionString: string;

src/connectors/neon/index.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {
2+
neon,
3+
type FullQueryResults,
4+
type NeonQueryFunction,
5+
} from "@neondatabase/serverless";
6+
import type { Connector, Primitive, Statement } from "db0";
7+
8+
import { BoundableStatement } from "../_internal/statement.ts";
9+
10+
export type ConnectorOptions = {
11+
connectionString: string;
12+
};
13+
14+
type InternalQuery = (
15+
sql: string,
16+
params?: Primitive[],
17+
) => Promise<FullQueryResults<false>>;
18+
19+
export default function neonServerlessConnector(
20+
opts: ConnectorOptions,
21+
): Connector<NeonQueryFunction<false, true>> {
22+
let _client: undefined | NeonQueryFunction<false, true>;
23+
24+
function getClient() {
25+
if (_client) {
26+
return _client;
27+
}
28+
29+
_client = neon(opts.connectionString, { fullResults: true });
30+
31+
return _client;
32+
}
33+
34+
const query: InternalQuery = async (sql, params) => {
35+
const client = getClient();
36+
37+
return client.query(normalizeParams(sql), params);
38+
};
39+
40+
return {
41+
name: "neon",
42+
dialect: "postgresql",
43+
getInstance: (): NeonQueryFunction<false, true> => getClient(),
44+
exec: (sql: string) => query(sql),
45+
prepare: (sql: string): Statement => new StatementWrapper(sql, query),
46+
};
47+
}
48+
49+
// // https://www.postgresql.org/docs/9.3/sql-prepare.html
50+
function normalizeParams(sql: string) {
51+
let i = 0;
52+
return sql.replace(/\?/g, () => `$${++i}`);
53+
}
54+
55+
class StatementWrapper extends BoundableStatement<void> {
56+
#query: InternalQuery;
57+
#sql: string;
58+
59+
constructor(sql: string, query: InternalQuery) {
60+
super();
61+
this.#sql = sql;
62+
this.#query = query;
63+
}
64+
65+
async all(...params: Primitive[]) {
66+
const res = await this.#query(this.#sql, params);
67+
return res.rows;
68+
}
69+
70+
async run(...params: Primitive[]) {
71+
const res = await this.#query(this.#sql, params);
72+
return {
73+
success: true,
74+
...res,
75+
};
76+
}
77+
78+
async get(...params: Primitive[]) {
79+
const res = await this.#query(this.#sql, params);
80+
return res.rows[0];
81+
}
82+
}

src/connectors/neon/ws.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
neonConfig,
3+
Pool as NeonPool,
4+
type QueryResult,
5+
type WebSocketConstructor,
6+
} from "@neondatabase/serverless";
7+
import type { Connector, Primitive } from "db0";
8+
9+
import { BoundableStatement } from "../_internal/statement.ts";
10+
11+
export type ConnectorOptions = {
12+
connectionString: string;
13+
webSocketConstructor?: WebSocketConstructor;
14+
};
15+
16+
type InternalQuery = (
17+
sql: string,
18+
params?: Primitive[],
19+
) => Promise<QueryResult>;
20+
21+
/**
22+
* @description Creates a new Neon pool connector.
23+
* @param opts
24+
* @returns
25+
*/
26+
export default function neonPoolConnector(
27+
opts: ConnectorOptions,
28+
): Connector<NeonPool> {
29+
let _client: undefined | NeonPool | Promise<NeonPool>;
30+
function getClient() {
31+
if (_client) {
32+
return _client;
33+
}
34+
35+
const client = new NeonPool({ connectionString: opts.connectionString });
36+
_client = client.connect().then(() => {
37+
/**
38+
* @description Allow to override the WebSocket constructor or provide one when platform does not support it.
39+
* @see https://github.com/neondatabase/serverless?tab=readme-ov-file#pool-and-client
40+
*/
41+
if (opts.webSocketConstructor) {
42+
neonConfig.webSocketConstructor = opts.webSocketConstructor;
43+
}
44+
45+
_client = client;
46+
return _client;
47+
});
48+
49+
return _client;
50+
}
51+
52+
const query: InternalQuery = async (sql, params) => {
53+
const client = getClient();
54+
55+
return (await client).query(normalizeParams(sql), params);
56+
};
57+
58+
return {
59+
name: "neon",
60+
dialect: "postgresql",
61+
getInstance: () => getClient(),
62+
exec: (sql) => query(sql),
63+
prepare: (sql) => new StatementWrapper(sql, query),
64+
};
65+
}
66+
67+
// // https://www.postgresql.org/docs/9.3/sql-prepare.html
68+
function normalizeParams(sql: string) {
69+
let i = 0;
70+
return sql.replace(/\?/g, () => `$${++i}`);
71+
}
72+
73+
class StatementWrapper extends BoundableStatement<void> {
74+
#query: InternalQuery;
75+
#sql: string;
76+
77+
constructor(sql: string, query: InternalQuery) {
78+
super();
79+
this.#sql = sql;
80+
this.#query = query;
81+
}
82+
83+
async all(...params: Primitive[]) {
84+
const res = await this.#query(this.#sql, params);
85+
return res.rows;
86+
}
87+
88+
async run(...params: Primitive[]) {
89+
const res = await this.#query(this.#sql, params);
90+
return {
91+
success: true,
92+
...res,
93+
};
94+
}
95+
96+
async get(...params: Primitive[]) {
97+
const res = await this.#query(this.#sql, params);
98+
return res.rows[0];
99+
}
100+
}

test/connectors/neon.test.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

test/connectors/neon/http.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { describe } from "vitest";
2+
import { testConnector } from "../_tests";
3+
import neonClientConnector from "../../../src/connectors/neon/http";
4+
5+
describe.runIf(process.env.NEON_URL_HTTP)("connectors: neon.test", () => {
6+
testConnector({
7+
dialect: "postgresql",
8+
connector: neonClientConnector({
9+
connectionString: process.env.NEON_URL_HTTP!,
10+
}),
11+
});
12+
});

test/connectors/neon/index.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { describe } from "vitest";
2+
import connector from "../../../src/connectors/neon";
3+
import { testConnector } from "../_tests";
4+
5+
describe.runIf(process.env.NEON_URL_SERVERLESS)(
6+
"connectors: neon serverless (index)",
7+
() => {
8+
testConnector({
9+
dialect: "postgresql",
10+
connector: connector({
11+
connectionString: process.env.NEON_URL_SERVERLESS!,
12+
}),
13+
});
14+
},
15+
);

test/connectors/neon/ws.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { describe } from "vitest";
2+
import connector from "../../../src/connectors/neon/ws";
3+
import { testConnector } from "../_tests";
4+
5+
describe.runIf(process.env.NEON_URL_WS)("connectors: neon pool (ws)", () => {
6+
testConnector({
7+
dialect: "postgresql",
8+
connector: connector({
9+
connectionString: process.env.NEON_URL_WS!,
10+
}),
11+
});
12+
});

0 commit comments

Comments
 (0)