@porulle/adapter-neon 0.10.4 → 0.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -13
- package/package.json +4 -3
- package/src/index.ts +38 -16
package/README.md
CHANGED
|
@@ -7,11 +7,11 @@ porulle's first adopter (a live iPad POS on Cloudflare Workers + Neon):
|
|
|
7
7
|
|
|
8
8
|
1. **Plain queries** go through `@neondatabase/serverless` HTTP — stateless,
|
|
9
9
|
no socket-reuse races across Workers isolates.
|
|
10
|
-
2. **`transaction()`** creates a fresh
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
2. **`transaction()`** creates a fresh client per call and closes it before the
|
|
11
|
+
request completes. Direct Neon connections use a WebSocket `Pool`;
|
|
12
|
+
Hyperdrive connections use Postgres.js over Workers TCP. This distinction is
|
|
13
|
+
required because Neon WebSocket clients cannot speak to Hyperdrive's TCP
|
|
14
|
+
endpoint. `drizzle-orm/neon-http` cannot run interactive transactions.
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
@@ -22,7 +22,7 @@ import { neonAdapter } from "@porulle/adapter-neon";
|
|
|
22
22
|
export default defineConfig({
|
|
23
23
|
databaseAdapter: neonAdapter({
|
|
24
24
|
connectionString: env.DATABASE_URL, // direct Neon URL
|
|
25
|
-
// Optional: route
|
|
25
|
+
// Optional: route transactions through Hyperdrive using Postgres.js
|
|
26
26
|
hyperdrive: env.HYPERDRIVE,
|
|
27
27
|
}),
|
|
28
28
|
// ...
|
|
@@ -33,8 +33,9 @@ export default defineConfig({
|
|
|
33
33
|
Used by the HTTP driver, and by transaction pools when no Hyperdrive
|
|
34
34
|
binding is given.
|
|
35
35
|
- `hyperdrive` — optional Cloudflare Hyperdrive binding (any object exposing
|
|
36
|
-
`connectionString`). When set,
|
|
37
|
-
plain queries keep using
|
|
36
|
+
`connectionString`). When set, transactions use a fresh Postgres.js client
|
|
37
|
+
over Hyperdrive; plain queries keep using Neon HTTP against
|
|
38
|
+
`connectionString`.
|
|
38
39
|
|
|
39
40
|
`.execute()` results are normalized to the postgres-js shape (an array of
|
|
40
41
|
rows), matching what `@porulle/core` and custom routes expect.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { type NeonHttpDatabase } from "drizzle-orm/neon-http";
|
|
2
2
|
import { type NeonDatabase } from "drizzle-orm/neon-serverless";
|
|
3
|
+
import { type PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
4
|
import type { DatabaseAdapter } from "@porulle/core";
|
|
4
5
|
type HttpClient = NeonHttpDatabase<Record<string, unknown>>;
|
|
5
6
|
type WsClient = NeonDatabase<Record<string, unknown>>;
|
|
6
|
-
type
|
|
7
|
+
type PgClient = PostgresJsDatabase<Record<string, never>>;
|
|
8
|
+
type AnyDb = HttpClient | WsClient | PgClient;
|
|
7
9
|
export interface NeonAdapterOptions {
|
|
8
10
|
/** Direct Neon connection string (postgresql://...neon.tech/...). */
|
|
9
11
|
connectionString: string;
|
|
10
12
|
/**
|
|
11
13
|
* Optional Cloudflare Hyperdrive binding (or any object exposing
|
|
12
|
-
* `connectionString`). When set,
|
|
13
|
-
* plain queries keep using
|
|
14
|
+
* `connectionString`). When set, transactions use a fresh Postgres.js TCP
|
|
15
|
+
* client through Hyperdrive; plain queries keep using Neon HTTP directly.
|
|
14
16
|
*/
|
|
15
17
|
hyperdrive?: {
|
|
16
18
|
connectionString: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoBA,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AACtF,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAExF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAMrD,KAAK,UAAU,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5D,KAAK,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AACtD,KAAK,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAC1D,KAAK,KAAK,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,UAAU,CAAC,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CACvD;AAED,MAAM,MAAM,mBAAmB,GAAG,eAAe,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,KAAK,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAsB/D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,mBAAmB,CA0D5E"}
|
package/dist/index.js
CHANGED
|
@@ -7,20 +7,21 @@
|
|
|
7
7
|
* 1. Plain queries (select / insert / update / delete / raw execute) go
|
|
8
8
|
* through `@neondatabase/serverless` HTTP — stateless, no socket-reuse
|
|
9
9
|
* races across Workers isolates.
|
|
10
|
-
* 2. `transaction()` creates a FRESH
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* a short-lived pool per transaction gives atomicity without the flake.
|
|
10
|
+
* 2. `transaction()` creates a FRESH client per call. Direct Neon uses its
|
|
11
|
+
* WebSocket `Pool`; Hyperdrive uses Postgres.js over Workers TCP. Both
|
|
12
|
+
* clients are closed before the request completes. `drizzle-orm/neon-http`
|
|
13
|
+
* throws on `db.transaction()` ("No transactions support").
|
|
15
14
|
*
|
|
16
15
|
* Hyperdrive-aware: pass the binding (`{ hyperdrive: env.HYPERDRIVE }`) and
|
|
17
|
-
* its connection string is used
|
|
18
|
-
*
|
|
19
|
-
* Neon, so a direct `connectionString` is
|
|
16
|
+
* its TCP connection string is used by Postgres.js for transactions. A Neon
|
|
17
|
+
* WebSocket client cannot speak to Hyperdrive's TCP endpoint. The HTTP driver
|
|
18
|
+
* always speaks directly to Neon, so a direct `connectionString` is required.
|
|
20
19
|
*/
|
|
21
20
|
import { Pool, neon, neonConfig } from "@neondatabase/serverless";
|
|
22
21
|
import { drizzle as drizzleHttp } from "drizzle-orm/neon-http";
|
|
23
22
|
import { drizzle as drizzleWs } from "drizzle-orm/neon-serverless";
|
|
23
|
+
import { drizzle as drizzlePg } from "drizzle-orm/postgres-js";
|
|
24
|
+
import postgres from "postgres";
|
|
24
25
|
if (typeof WebSocket !== "undefined") {
|
|
25
26
|
neonConfig.webSocketConstructor = WebSocket;
|
|
26
27
|
}
|
|
@@ -52,11 +53,10 @@ export function normalizeExecuteShape(db) {
|
|
|
52
53
|
}
|
|
53
54
|
export function neonAdapter(options) {
|
|
54
55
|
const httpConnectionString = options.connectionString;
|
|
55
|
-
const poolConnectionString = options.hyperdrive?.connectionString ?? options.connectionString;
|
|
56
56
|
const sql = neon(httpConnectionString);
|
|
57
57
|
const httpDb = normalizeExecuteShape(drizzleHttp(sql));
|
|
58
|
-
const
|
|
59
|
-
const pool = new Pool({ connectionString:
|
|
58
|
+
const runInFreshNeonPool = async (fn) => {
|
|
59
|
+
const pool = new Pool({ connectionString: options.connectionString });
|
|
60
60
|
try {
|
|
61
61
|
const wsDb = normalizeExecuteShape(drizzleWs(pool));
|
|
62
62
|
return await wsDb.transaction(async (tx) => fn(normalizeExecuteShape(tx)));
|
|
@@ -67,13 +67,31 @@ export function neonAdapter(options) {
|
|
|
67
67
|
await pool.end().catch(() => { });
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
|
+
const runInFreshHyperdriveClient = async (fn) => {
|
|
71
|
+
const client = postgres(options.hyperdrive.connectionString, {
|
|
72
|
+
max: 1,
|
|
73
|
+
prepare: false,
|
|
74
|
+
connect_timeout: 10,
|
|
75
|
+
idle_timeout: 5,
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
const pgDb = normalizeExecuteShape(drizzlePg(client));
|
|
79
|
+
return await pgDb.transaction(async (tx) => fn(normalizeExecuteShape(tx)));
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await client.end({ timeout: 1 }).catch(() => { });
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const runTransaction = options.hyperdrive
|
|
86
|
+
? runInFreshHyperdriveClient
|
|
87
|
+
: runInFreshNeonPool;
|
|
70
88
|
// Some core paths call `kernel.database.db.transaction(...)` directly —
|
|
71
89
|
// splice the pool-backed transaction onto the HTTP client so both entry
|
|
72
90
|
// points behave identically.
|
|
73
91
|
const dbWithTx = new Proxy(httpDb, {
|
|
74
92
|
get(target, prop, receiver) {
|
|
75
93
|
if (prop === "transaction") {
|
|
76
|
-
return
|
|
94
|
+
return runTransaction;
|
|
77
95
|
}
|
|
78
96
|
return Reflect.get(target, prop, receiver);
|
|
79
97
|
},
|
|
@@ -82,7 +100,7 @@ export function neonAdapter(options) {
|
|
|
82
100
|
provider: "postgresql",
|
|
83
101
|
db: dbWithTx,
|
|
84
102
|
async transaction(fn) {
|
|
85
|
-
return
|
|
103
|
+
return runTransaction(fn);
|
|
86
104
|
},
|
|
87
105
|
};
|
|
88
106
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/adapter-neon",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@neondatabase/serverless": "^1.0.1",
|
|
15
15
|
"drizzle-orm": "^0.45.1",
|
|
16
|
-
"
|
|
16
|
+
"postgres": "^3.4.7",
|
|
17
|
+
"@porulle/core": "0.10.6"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"@types/node": "^24.5.2",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"dist",
|
|
32
33
|
"README.md"
|
|
33
34
|
],
|
|
34
|
-
"description": "Workers-grade Neon DatabaseAdapter for @porulle/core: HTTP
|
|
35
|
+
"description": "Workers-grade Neon DatabaseAdapter for @porulle/core: Neon HTTP queries with request-scoped Neon or Hyperdrive transactions.",
|
|
35
36
|
"homepage": "https://porulle-docs.vercel.app",
|
|
36
37
|
"bugs": {
|
|
37
38
|
"url": "https://github.com/asyncdotengineering/porulle/issues"
|
package/src/index.ts
CHANGED
|
@@ -7,20 +7,21 @@
|
|
|
7
7
|
* 1. Plain queries (select / insert / update / delete / raw execute) go
|
|
8
8
|
* through `@neondatabase/serverless` HTTP — stateless, no socket-reuse
|
|
9
9
|
* races across Workers isolates.
|
|
10
|
-
* 2. `transaction()` creates a FRESH
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* a short-lived pool per transaction gives atomicity without the flake.
|
|
10
|
+
* 2. `transaction()` creates a FRESH client per call. Direct Neon uses its
|
|
11
|
+
* WebSocket `Pool`; Hyperdrive uses Postgres.js over Workers TCP. Both
|
|
12
|
+
* clients are closed before the request completes. `drizzle-orm/neon-http`
|
|
13
|
+
* throws on `db.transaction()` ("No transactions support").
|
|
15
14
|
*
|
|
16
15
|
* Hyperdrive-aware: pass the binding (`{ hyperdrive: env.HYPERDRIVE }`) and
|
|
17
|
-
* its connection string is used
|
|
18
|
-
*
|
|
19
|
-
* Neon, so a direct `connectionString` is
|
|
16
|
+
* its TCP connection string is used by Postgres.js for transactions. A Neon
|
|
17
|
+
* WebSocket client cannot speak to Hyperdrive's TCP endpoint. The HTTP driver
|
|
18
|
+
* always speaks directly to Neon, so a direct `connectionString` is required.
|
|
20
19
|
*/
|
|
21
20
|
import { Pool, neon, neonConfig } from "@neondatabase/serverless";
|
|
22
21
|
import { drizzle as drizzleHttp, type NeonHttpDatabase } from "drizzle-orm/neon-http";
|
|
23
22
|
import { drizzle as drizzleWs, type NeonDatabase } from "drizzle-orm/neon-serverless";
|
|
23
|
+
import { drizzle as drizzlePg, type PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
24
|
+
import postgres from "postgres";
|
|
24
25
|
import type { DatabaseAdapter } from "@porulle/core";
|
|
25
26
|
|
|
26
27
|
if (typeof WebSocket !== "undefined") {
|
|
@@ -29,15 +30,16 @@ if (typeof WebSocket !== "undefined") {
|
|
|
29
30
|
|
|
30
31
|
type HttpClient = NeonHttpDatabase<Record<string, unknown>>;
|
|
31
32
|
type WsClient = NeonDatabase<Record<string, unknown>>;
|
|
32
|
-
type
|
|
33
|
+
type PgClient = PostgresJsDatabase<Record<string, never>>;
|
|
34
|
+
type AnyDb = HttpClient | WsClient | PgClient;
|
|
33
35
|
|
|
34
36
|
export interface NeonAdapterOptions {
|
|
35
37
|
/** Direct Neon connection string (postgresql://...neon.tech/...). */
|
|
36
38
|
connectionString: string;
|
|
37
39
|
/**
|
|
38
40
|
* Optional Cloudflare Hyperdrive binding (or any object exposing
|
|
39
|
-
* `connectionString`). When set,
|
|
40
|
-
* plain queries keep using
|
|
41
|
+
* `connectionString`). When set, transactions use a fresh Postgres.js TCP
|
|
42
|
+
* client through Hyperdrive; plain queries keep using Neon HTTP directly.
|
|
41
43
|
*/
|
|
42
44
|
hyperdrive?: { connectionString: string } | undefined;
|
|
43
45
|
}
|
|
@@ -75,13 +77,12 @@ export function normalizeExecuteShape<T extends AnyDb>(db: T): T {
|
|
|
75
77
|
|
|
76
78
|
export function neonAdapter(options: NeonAdapterOptions): NeonDatabaseAdapter {
|
|
77
79
|
const httpConnectionString = options.connectionString;
|
|
78
|
-
const poolConnectionString = options.hyperdrive?.connectionString ?? options.connectionString;
|
|
79
80
|
|
|
80
81
|
const sql = neon(httpConnectionString);
|
|
81
82
|
const httpDb = normalizeExecuteShape(drizzleHttp(sql) as HttpClient);
|
|
82
83
|
|
|
83
|
-
const
|
|
84
|
-
const pool = new Pool({ connectionString:
|
|
84
|
+
const runInFreshNeonPool = async (fn: (tx: unknown) => Promise<unknown>): Promise<unknown> => {
|
|
85
|
+
const pool = new Pool({ connectionString: options.connectionString });
|
|
85
86
|
try {
|
|
86
87
|
const wsDb = normalizeExecuteShape(drizzleWs(pool) as WsClient);
|
|
87
88
|
return await wsDb.transaction(async (tx) => fn(normalizeExecuteShape(tx as WsClient)));
|
|
@@ -92,13 +93,34 @@ export function neonAdapter(options: NeonAdapterOptions): NeonDatabaseAdapter {
|
|
|
92
93
|
}
|
|
93
94
|
};
|
|
94
95
|
|
|
96
|
+
const runInFreshHyperdriveClient = async (
|
|
97
|
+
fn: (tx: unknown) => Promise<unknown>,
|
|
98
|
+
): Promise<unknown> => {
|
|
99
|
+
const client = postgres(options.hyperdrive!.connectionString, {
|
|
100
|
+
max: 1,
|
|
101
|
+
prepare: false,
|
|
102
|
+
connect_timeout: 10,
|
|
103
|
+
idle_timeout: 5,
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
const pgDb = normalizeExecuteShape(drizzlePg(client) as PgClient);
|
|
107
|
+
return await pgDb.transaction(async (tx) => fn(normalizeExecuteShape(tx as PgClient)));
|
|
108
|
+
} finally {
|
|
109
|
+
await client.end({ timeout: 1 }).catch(() => {});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const runTransaction = options.hyperdrive
|
|
114
|
+
? runInFreshHyperdriveClient
|
|
115
|
+
: runInFreshNeonPool;
|
|
116
|
+
|
|
95
117
|
// Some core paths call `kernel.database.db.transaction(...)` directly —
|
|
96
118
|
// splice the pool-backed transaction onto the HTTP client so both entry
|
|
97
119
|
// points behave identically.
|
|
98
120
|
const dbWithTx = new Proxy(httpDb, {
|
|
99
121
|
get(target, prop, receiver) {
|
|
100
122
|
if (prop === "transaction") {
|
|
101
|
-
return
|
|
123
|
+
return runTransaction;
|
|
102
124
|
}
|
|
103
125
|
return Reflect.get(target, prop, receiver);
|
|
104
126
|
},
|
|
@@ -108,7 +130,7 @@ export function neonAdapter(options: NeonAdapterOptions): NeonDatabaseAdapter {
|
|
|
108
130
|
provider: "postgresql",
|
|
109
131
|
db: dbWithTx,
|
|
110
132
|
async transaction<T>(fn: (tx: unknown) => Promise<T>): Promise<T> {
|
|
111
|
-
return
|
|
133
|
+
return runTransaction(fn) as Promise<T>;
|
|
112
134
|
},
|
|
113
135
|
};
|
|
114
136
|
}
|