@agentuity/drizzle 1.0.15 → 1.0.17
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/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +52 -97
- package/dist/postgres.js.map +1 -1
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/postgres.ts +61 -108
- package/src/types.ts +34 -0
package/dist/postgres.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,MAAM,EAAE,KAAK,GAAG,IAAI,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAC/E,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,MAAM,EAAE,KAAK,GAAG,IAAI,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAC/E,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAIN,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEtE;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAC1C,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC9D,MAAM,CAAC,EAAE,qBAAqB,CAAC,OAAO,CAAC,GAAG,cAAc,CAyCzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,sBAAsB,GAC5B,YAAY,CAAC,OAAO,MAAM,CAAC,CA+E7B;AAED,KAAK,uBAAuB,GAAG,MAAM,GAAG,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,CAAC;AAyHxE,iBAAS,QAAQ,CAChB,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC/D,OAAO,SAAS,YAAY,GAAG,YAAY,EAE3C,GAAG,MAAM,EACN,CAAC,OAAO,GAAG,MAAM,CAAC,GAClB,CAAC,OAAO,GAAG,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,GAC1C,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC;IAAE,UAAU,EAAE,uBAAuB,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,GAC3F,cAAc,CAAC,OAAO,CAAC,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAwBhD;kBAhCQ,QAAQ;eAkCA,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,mCAC9C,aAAa,CAAC,OAAO,CAAC,KAC7B,cAAc,CAAC,OAAO,CAAC,GAAG;QAAE,OAAO,EAAE,4CAA4C,CAAA;KAAE;;AAStF,eAAO,MAAM,OAAO,EAAe,OAAO,QAAQ,GAAG;IACpD,IAAI,EAAE,OAAO,QAAQ,CAAC,IAAI,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,qBAAqB,CACpC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC9D,MAAM,CAAC,EAAE,qBAAqB,CAAC,OAAO,CAAC,GAAG,eAAe,CAAC,OAAO,CAAC,CAoCnE"}
|
package/dist/postgres.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { drizzle as upstreamDrizzle } from 'drizzle-orm/bun-sql';
|
|
2
2
|
import { isConfig } from 'drizzle-orm/utils';
|
|
3
|
-
import { postgres } from '@agentuity/postgres';
|
|
3
|
+
import { postgres, isMutationStatement, createThenable, } from '@agentuity/postgres';
|
|
4
4
|
/**
|
|
5
5
|
* Resolves the PostgreSQL client configuration from Drizzle config options.
|
|
6
6
|
*
|
|
@@ -31,86 +31,19 @@ export function resolvePostgresClientConfig(config) {
|
|
|
31
31
|
if (config?.onReconnected) {
|
|
32
32
|
clientConfig.onreconnected = config.onReconnected;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* Strips leading whitespace and SQL comments (block and line) from a query string.
|
|
38
|
-
* Returns the remaining query text starting at the first non-comment token.
|
|
39
|
-
*/
|
|
40
|
-
const LEADING_COMMENTS_RE = /^(?:\s+|\/\*[\s\S]*?\*\/|--[^\n]*\n)*/;
|
|
41
|
-
/**
|
|
42
|
-
* Determines whether a SQL query is a non-retryable INSERT statement.
|
|
43
|
-
*
|
|
44
|
-
* Handles two patterns:
|
|
45
|
-
* 1. Direct INSERT: `INSERT INTO ...` (with optional leading comments/whitespace)
|
|
46
|
-
* 2. CTE INSERT: `WITH cte AS (...) INSERT INTO ...` — scans past the WITH clause
|
|
47
|
-
* by tracking parenthesis depth to skip CTE subexpressions, then checks
|
|
48
|
-
* if the first top-level DML keyword is INSERT.
|
|
49
|
-
*
|
|
50
|
-
* @see https://github.com/agentuity/sdk/issues/911
|
|
51
|
-
*/
|
|
52
|
-
function isNonRetryableInsert(query) {
|
|
53
|
-
// Strip leading whitespace and SQL comments
|
|
54
|
-
const stripped = query.replace(LEADING_COMMENTS_RE, '');
|
|
55
|
-
// Fast path: direct INSERT statement
|
|
56
|
-
if (/^INSERT\s/i.test(stripped)) {
|
|
57
|
-
return true;
|
|
34
|
+
// Forward prepare option
|
|
35
|
+
if (config?.prepare !== undefined) {
|
|
36
|
+
clientConfig.prepare = config.prepare;
|
|
58
37
|
}
|
|
59
|
-
//
|
|
60
|
-
if (
|
|
61
|
-
|
|
38
|
+
// Forward bigint option
|
|
39
|
+
if (config?.bigint !== undefined) {
|
|
40
|
+
clientConfig.bigint = config.bigint;
|
|
62
41
|
}
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// INSERT inside the parens.
|
|
67
|
-
let depth = 0;
|
|
68
|
-
let i = 4; // skip past "WITH"
|
|
69
|
-
const len = stripped.length;
|
|
70
|
-
while (i < len) {
|
|
71
|
-
const ch = stripped[i];
|
|
72
|
-
if (ch === '(') {
|
|
73
|
-
depth++;
|
|
74
|
-
i++;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (ch === ')') {
|
|
78
|
-
depth--;
|
|
79
|
-
i++;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
// Only inspect keywords at top level (depth === 0)
|
|
83
|
-
if (depth === 0) {
|
|
84
|
-
// Skip whitespace at top level
|
|
85
|
-
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
86
|
-
i++;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
// Skip commas between CTEs: WITH a AS (...), b AS (...)
|
|
90
|
-
if (ch === ',') {
|
|
91
|
-
i++;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
// Check for DML keywords at this position.
|
|
95
|
-
// We look for INSERT, UPDATE, DELETE, or SELECT — the first one
|
|
96
|
-
// we find at top level determines whether this is retryable.
|
|
97
|
-
const rest = stripped.substring(i);
|
|
98
|
-
const dmlMatch = /^(INSERT|UPDATE|DELETE|SELECT)\s/i.exec(rest);
|
|
99
|
-
if (dmlMatch) {
|
|
100
|
-
return dmlMatch[1].toUpperCase() === 'INSERT';
|
|
101
|
-
}
|
|
102
|
-
// Skip over any other word (e.g., CTE names, AS keyword, RECURSIVE)
|
|
103
|
-
// by advancing past alphanumeric/underscore characters
|
|
104
|
-
if (/\w/.test(ch)) {
|
|
105
|
-
while (i < len && /\w/.test(stripped[i])) {
|
|
106
|
-
i++;
|
|
107
|
-
}
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
i++;
|
|
42
|
+
// Forward maxLifetime option
|
|
43
|
+
if (config?.maxLifetime !== undefined) {
|
|
44
|
+
clientConfig.maxLifetime = config.maxLifetime;
|
|
112
45
|
}
|
|
113
|
-
return
|
|
46
|
+
return clientConfig;
|
|
114
47
|
}
|
|
115
48
|
/**
|
|
116
49
|
* Creates a dynamic SQL proxy that always delegates to the PostgresClient's
|
|
@@ -137,22 +70,43 @@ export function createResilientSQLProxy(client) {
|
|
|
137
70
|
// client.unsafe(query, params) → Promise<rows>
|
|
138
71
|
// client.unsafe(query, params).values() → Promise<rows>
|
|
139
72
|
return (query, params) => {
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
|
|
73
|
+
// Mutation statements (INSERT, UPDATE, DELETE) require special
|
|
74
|
+
// handling for safe retry. They are wrapped in a transaction
|
|
75
|
+
// (BEGIN/query/COMMIT) so that if the connection drops,
|
|
76
|
+
// PostgreSQL auto-rolls back, preventing duplicate inserts,
|
|
77
|
+
// double-applied updates, or repeated delete side effects.
|
|
78
|
+
const isMutation = isMutationStatement(query);
|
|
79
|
+
if (isMutation) {
|
|
80
|
+
// Mutation statements are wrapped in a transaction and retried
|
|
81
|
+
// via executeWithRetry. This is safe because PostgreSQL
|
|
82
|
+
// guarantees that uncommitted transactions are automatically
|
|
83
|
+
// rolled back when the connection drops. If the connection
|
|
84
|
+
// fails before COMMIT completes, no changes are applied, and
|
|
85
|
+
// the retry starts a fresh transaction on the new connection.
|
|
86
|
+
//
|
|
87
|
+
// We use sql.begin(callback) instead of manual BEGIN/COMMIT
|
|
88
|
+
// because Bun's SQL driver requires it for pool-safe
|
|
89
|
+
// transactions (ERR_POSTGRES_UNSAFE_TRANSACTION when max > 1).
|
|
90
|
+
// sql.begin() reserves a specific connection, auto-COMMITs on
|
|
91
|
+
// success, and auto-ROLLBACKs on error.
|
|
92
|
+
//
|
|
93
|
+
// NOTE: If the connection drops after the server processes
|
|
94
|
+
// COMMIT but before the client receives the response, the
|
|
95
|
+
// changes ARE committed. A retry would then apply them again.
|
|
96
|
+
// This window is extremely small (< 1ms typically) and is an
|
|
97
|
+
// inherent limitation of any retry-based approach without
|
|
98
|
+
// application-level idempotency (e.g., unique constraints
|
|
99
|
+
// with ON CONFLICT for INSERTs).
|
|
100
|
+
// See: https://github.com/agentuity/sdk/issues/911
|
|
101
|
+
const makeTransactionalExecutor = (useValues) => client.executeWithRetry(async () => {
|
|
102
|
+
// Re-resolve raw inside retry to get post-reconnect instance
|
|
148
103
|
const currentRaw = client.raw;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return Object.assign(result, {
|
|
154
|
-
values: () => makeDirectExecutor(true),
|
|
104
|
+
return currentRaw.begin(async (tx) => {
|
|
105
|
+
const q = tx.unsafe(query, params);
|
|
106
|
+
return useValues ? await q.values() : await q;
|
|
107
|
+
});
|
|
155
108
|
});
|
|
109
|
+
return createThenable(makeTransactionalExecutor);
|
|
156
110
|
}
|
|
157
111
|
const makeExecutor = (useValues) => client.executeWithRetry(async () => {
|
|
158
112
|
// Re-resolve raw inside retry to get post-reconnect instance
|
|
@@ -160,11 +114,7 @@ export function createResilientSQLProxy(client) {
|
|
|
160
114
|
const q = currentRaw.unsafe(query, params);
|
|
161
115
|
return useValues ? q.values() : q;
|
|
162
116
|
});
|
|
163
|
-
|
|
164
|
-
const result = makeExecutor(false);
|
|
165
|
-
return Object.assign(result, {
|
|
166
|
-
values: () => makeExecutor(true),
|
|
167
|
-
});
|
|
117
|
+
return createThenable(makeExecutor);
|
|
168
118
|
};
|
|
169
119
|
}
|
|
170
120
|
const value = raw[prop];
|
|
@@ -221,6 +171,11 @@ function extractPostgresConfigFromSql(client) {
|
|
|
221
171
|
'max',
|
|
222
172
|
'idleTimeout',
|
|
223
173
|
'connectionTimeout',
|
|
174
|
+
'prepare',
|
|
175
|
+
'bigint',
|
|
176
|
+
'maxLifetime',
|
|
177
|
+
'path',
|
|
178
|
+
'connection',
|
|
224
179
|
];
|
|
225
180
|
for (const key of keys) {
|
|
226
181
|
if (key in options) {
|
package/dist/postgres.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAuB,MAAM,qBAAqB,CAAC;AAEtF,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -37,6 +37,37 @@ export interface PostgresDrizzleConfig<TSchema extends Record<string, unknown> =
|
|
|
37
37
|
* Reconnection configuration passed to the underlying postgres client.
|
|
38
38
|
*/
|
|
39
39
|
reconnect?: ReconnectConfig;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to use named prepared statements for the underlying connection.
|
|
42
|
+
*
|
|
43
|
+
* When `false` (default), queries use unnamed prepared statements that
|
|
44
|
+
* are safe for connection poolers (PGBouncer, Supavisor) and
|
|
45
|
+
* environments where backend connections may rotate.
|
|
46
|
+
*
|
|
47
|
+
* When `true`, enables named prepared statement caching for better
|
|
48
|
+
* performance with repeated queries, but requires a stable backend
|
|
49
|
+
* connection.
|
|
50
|
+
*
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
prepare?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Whether to return large integers as BigInt instead of strings.
|
|
56
|
+
*
|
|
57
|
+
* When `true`, integers outside the i32 range are returned as `BigInt`.
|
|
58
|
+
* When `false` (default), they are returned as strings.
|
|
59
|
+
*
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
bigint?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Maximum lifetime of a connection in seconds.
|
|
65
|
+
* After this time, the connection is closed and a new one is created.
|
|
66
|
+
* Set to `0` for no maximum lifetime.
|
|
67
|
+
*
|
|
68
|
+
* @default 0 (no maximum lifetime)
|
|
69
|
+
*/
|
|
70
|
+
maxLifetime?: number;
|
|
40
71
|
/**
|
|
41
72
|
* Callback invoked when the initial connection is established.
|
|
42
73
|
*/
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAEnG;;;;GAIG;AACH,MAAM,WAAW,qBAAqB,CACrC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAE/D;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAEvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAC/F;;OAEG;IACH,EAAE,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IAE5B;;;OAGG;IACH,MAAM,EAAE,sBAAsB,CAAC;IAE/B;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAEnG;;;;GAIG;AACH,MAAM,WAAW,qBAAqB,CACrC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAE/D;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAEvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAC/F;;OAEG;IACH,EAAE,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IAE5B;;;OAGG;IACH,MAAM,EAAE,sBAAsB,CAAC;IAE/B;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentuity/drizzle",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"author": "Agentuity employees and contributors",
|
|
6
6
|
"type": "module",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"prepublishOnly": "bun run clean && bun run build"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@agentuity/postgres": "1.0.
|
|
44
|
+
"@agentuity/postgres": "1.0.17",
|
|
45
45
|
"drizzle-orm": "^0.45.0",
|
|
46
46
|
"better-auth": "^1.4.9"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@agentuity/test-utils": "1.0.
|
|
49
|
+
"@agentuity/test-utils": "1.0.17",
|
|
50
50
|
"@types/bun": "latest",
|
|
51
51
|
"bun-types": "latest",
|
|
52
52
|
"typescript": "^5.9.0"
|
package/src/postgres.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { SQL as BunSQL, type SQL as BunSQLClient, type SQLOptions } from 'bun';
|
|
|
2
2
|
import { drizzle as upstreamDrizzle, type BunSQLDatabase } from 'drizzle-orm/bun-sql';
|
|
3
3
|
import type { DrizzleConfig } from 'drizzle-orm';
|
|
4
4
|
import { isConfig } from 'drizzle-orm/utils';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
postgres,
|
|
7
|
+
isMutationStatement,
|
|
8
|
+
createThenable,
|
|
9
|
+
type CallablePostgresClient,
|
|
10
|
+
type PostgresConfig,
|
|
11
|
+
} from '@agentuity/postgres';
|
|
6
12
|
import type { PostgresDrizzleConfig, PostgresDrizzle } from './types';
|
|
7
13
|
|
|
8
14
|
/**
|
|
@@ -39,99 +45,22 @@ export function resolvePostgresClientConfig<
|
|
|
39
45
|
clientConfig.onreconnected = config.onReconnected;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Strips leading whitespace and SQL comments (block and line) from a query string.
|
|
47
|
-
* Returns the remaining query text starting at the first non-comment token.
|
|
48
|
-
*/
|
|
49
|
-
const LEADING_COMMENTS_RE = /^(?:\s+|\/\*[\s\S]*?\*\/|--[^\n]*\n)*/;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Determines whether a SQL query is a non-retryable INSERT statement.
|
|
53
|
-
*
|
|
54
|
-
* Handles two patterns:
|
|
55
|
-
* 1. Direct INSERT: `INSERT INTO ...` (with optional leading comments/whitespace)
|
|
56
|
-
* 2. CTE INSERT: `WITH cte AS (...) INSERT INTO ...` — scans past the WITH clause
|
|
57
|
-
* by tracking parenthesis depth to skip CTE subexpressions, then checks
|
|
58
|
-
* if the first top-level DML keyword is INSERT.
|
|
59
|
-
*
|
|
60
|
-
* @see https://github.com/agentuity/sdk/issues/911
|
|
61
|
-
*/
|
|
62
|
-
function isNonRetryableInsert(query: string): boolean {
|
|
63
|
-
// Strip leading whitespace and SQL comments
|
|
64
|
-
const stripped = query.replace(LEADING_COMMENTS_RE, '');
|
|
65
|
-
|
|
66
|
-
// Fast path: direct INSERT statement
|
|
67
|
-
if (/^INSERT\s/i.test(stripped)) {
|
|
68
|
-
return true;
|
|
48
|
+
// Forward prepare option
|
|
49
|
+
if (config?.prepare !== undefined) {
|
|
50
|
+
clientConfig.prepare = config.prepare;
|
|
69
51
|
}
|
|
70
52
|
|
|
71
|
-
//
|
|
72
|
-
if (
|
|
73
|
-
|
|
53
|
+
// Forward bigint option
|
|
54
|
+
if (config?.bigint !== undefined) {
|
|
55
|
+
clientConfig.bigint = config.bigint;
|
|
74
56
|
}
|
|
75
57
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// INSERT inside the parens.
|
|
80
|
-
let depth = 0;
|
|
81
|
-
let i = 4; // skip past "WITH"
|
|
82
|
-
const len = stripped.length;
|
|
83
|
-
|
|
84
|
-
while (i < len) {
|
|
85
|
-
const ch = stripped[i]!;
|
|
86
|
-
|
|
87
|
-
if (ch === '(') {
|
|
88
|
-
depth++;
|
|
89
|
-
i++;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (ch === ')') {
|
|
93
|
-
depth--;
|
|
94
|
-
i++;
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Only inspect keywords at top level (depth === 0)
|
|
99
|
-
if (depth === 0) {
|
|
100
|
-
// Skip whitespace at top level
|
|
101
|
-
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
102
|
-
i++;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Skip commas between CTEs: WITH a AS (...), b AS (...)
|
|
107
|
-
if (ch === ',') {
|
|
108
|
-
i++;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Check for DML keywords at this position.
|
|
113
|
-
// We look for INSERT, UPDATE, DELETE, or SELECT — the first one
|
|
114
|
-
// we find at top level determines whether this is retryable.
|
|
115
|
-
const rest = stripped.substring(i);
|
|
116
|
-
const dmlMatch = /^(INSERT|UPDATE|DELETE|SELECT)\s/i.exec(rest);
|
|
117
|
-
if (dmlMatch) {
|
|
118
|
-
return dmlMatch[1]!.toUpperCase() === 'INSERT';
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Skip over any other word (e.g., CTE names, AS keyword, RECURSIVE)
|
|
122
|
-
// by advancing past alphanumeric/underscore characters
|
|
123
|
-
if (/\w/.test(ch)) {
|
|
124
|
-
while (i < len && /\w/.test(stripped[i]!)) {
|
|
125
|
-
i++;
|
|
126
|
-
}
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
i++;
|
|
58
|
+
// Forward maxLifetime option
|
|
59
|
+
if (config?.maxLifetime !== undefined) {
|
|
60
|
+
clientConfig.maxLifetime = config.maxLifetime;
|
|
132
61
|
}
|
|
133
62
|
|
|
134
|
-
return
|
|
63
|
+
return clientConfig;
|
|
135
64
|
}
|
|
136
65
|
|
|
137
66
|
/**
|
|
@@ -163,24 +92,47 @@ export function createResilientSQLProxy(
|
|
|
163
92
|
// client.unsafe(query, params) → Promise<rows>
|
|
164
93
|
// client.unsafe(query, params).values() → Promise<rows>
|
|
165
94
|
return (query: string, params?: unknown[]) => {
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
95
|
+
// Mutation statements (INSERT, UPDATE, DELETE) require special
|
|
96
|
+
// handling for safe retry. They are wrapped in a transaction
|
|
97
|
+
// (BEGIN/query/COMMIT) so that if the connection drops,
|
|
98
|
+
// PostgreSQL auto-rolls back, preventing duplicate inserts,
|
|
99
|
+
// double-applied updates, or repeated delete side effects.
|
|
100
|
+
const isMutation = isMutationStatement(query);
|
|
101
|
+
|
|
102
|
+
if (isMutation) {
|
|
103
|
+
// Mutation statements are wrapped in a transaction and retried
|
|
104
|
+
// via executeWithRetry. This is safe because PostgreSQL
|
|
105
|
+
// guarantees that uncommitted transactions are automatically
|
|
106
|
+
// rolled back when the connection drops. If the connection
|
|
107
|
+
// fails before COMMIT completes, no changes are applied, and
|
|
108
|
+
// the retry starts a fresh transaction on the new connection.
|
|
109
|
+
//
|
|
110
|
+
// We use sql.begin(callback) instead of manual BEGIN/COMMIT
|
|
111
|
+
// because Bun's SQL driver requires it for pool-safe
|
|
112
|
+
// transactions (ERR_POSTGRES_UNSAFE_TRANSACTION when max > 1).
|
|
113
|
+
// sql.begin() reserves a specific connection, auto-COMMITs on
|
|
114
|
+
// success, and auto-ROLLBACKs on error.
|
|
115
|
+
//
|
|
116
|
+
// NOTE: If the connection drops after the server processes
|
|
117
|
+
// COMMIT but before the client receives the response, the
|
|
118
|
+
// changes ARE committed. A retry would then apply them again.
|
|
119
|
+
// This window is extremely small (< 1ms typically) and is an
|
|
120
|
+
// inherent limitation of any retry-based approach without
|
|
121
|
+
// application-level idempotency (e.g., unique constraints
|
|
122
|
+
// with ON CONFLICT for INSERTs).
|
|
170
123
|
// See: https://github.com/agentuity/sdk/issues/911
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const makeDirectExecutor = (useValues: boolean) => {
|
|
124
|
+
const makeTransactionalExecutor = (useValues: boolean) =>
|
|
125
|
+
client.executeWithRetry(async () => {
|
|
126
|
+
// Re-resolve raw inside retry to get post-reconnect instance
|
|
175
127
|
const currentRaw = client.raw;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return Object.assign(result, {
|
|
181
|
-
values: () => makeDirectExecutor(true),
|
|
128
|
+
return currentRaw.begin(async (tx) => {
|
|
129
|
+
const q = tx.unsafe(query, params);
|
|
130
|
+
return useValues ? await q.values() : await q;
|
|
131
|
+
});
|
|
182
132
|
});
|
|
183
|
-
|
|
133
|
+
|
|
134
|
+
return createThenable(makeTransactionalExecutor);
|
|
135
|
+
}
|
|
184
136
|
|
|
185
137
|
const makeExecutor = (useValues: boolean) =>
|
|
186
138
|
client.executeWithRetry(async () => {
|
|
@@ -190,11 +142,7 @@ export function createResilientSQLProxy(
|
|
|
190
142
|
return useValues ? q.values() : q;
|
|
191
143
|
});
|
|
192
144
|
|
|
193
|
-
|
|
194
|
-
const result = makeExecutor(false);
|
|
195
|
-
return Object.assign(result, {
|
|
196
|
-
values: () => makeExecutor(true),
|
|
197
|
-
});
|
|
145
|
+
return createThenable(makeExecutor);
|
|
198
146
|
};
|
|
199
147
|
}
|
|
200
148
|
|
|
@@ -263,6 +211,11 @@ function extractPostgresConfigFromSql(client: BunSQLClient): PostgresConfig | un
|
|
|
263
211
|
'max',
|
|
264
212
|
'idleTimeout',
|
|
265
213
|
'connectionTimeout',
|
|
214
|
+
'prepare',
|
|
215
|
+
'bigint',
|
|
216
|
+
'maxLifetime',
|
|
217
|
+
'path',
|
|
218
|
+
'connection',
|
|
266
219
|
] as const;
|
|
267
220
|
|
|
268
221
|
for (const key of keys) {
|
package/src/types.ts
CHANGED
|
@@ -46,6 +46,40 @@ export interface PostgresDrizzleConfig<
|
|
|
46
46
|
*/
|
|
47
47
|
reconnect?: ReconnectConfig;
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Whether to use named prepared statements for the underlying connection.
|
|
51
|
+
*
|
|
52
|
+
* When `false` (default), queries use unnamed prepared statements that
|
|
53
|
+
* are safe for connection poolers (PGBouncer, Supavisor) and
|
|
54
|
+
* environments where backend connections may rotate.
|
|
55
|
+
*
|
|
56
|
+
* When `true`, enables named prepared statement caching for better
|
|
57
|
+
* performance with repeated queries, but requires a stable backend
|
|
58
|
+
* connection.
|
|
59
|
+
*
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
prepare?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether to return large integers as BigInt instead of strings.
|
|
66
|
+
*
|
|
67
|
+
* When `true`, integers outside the i32 range are returned as `BigInt`.
|
|
68
|
+
* When `false` (default), they are returned as strings.
|
|
69
|
+
*
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
bigint?: boolean;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Maximum lifetime of a connection in seconds.
|
|
76
|
+
* After this time, the connection is closed and a new one is created.
|
|
77
|
+
* Set to `0` for no maximum lifetime.
|
|
78
|
+
*
|
|
79
|
+
* @default 0 (no maximum lifetime)
|
|
80
|
+
*/
|
|
81
|
+
maxLifetime?: number;
|
|
82
|
+
|
|
49
83
|
/**
|
|
50
84
|
* Callback invoked when the initial connection is established.
|
|
51
85
|
*/
|