@agentuity/drizzle 1.0.14 → 1.0.16
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 +59 -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 +68 -110
- 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,CAoF7B;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,50 @@ 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
|
+
// NOTE: If the connection drops after the server processes
|
|
88
|
+
// COMMIT but before the client receives the response, the
|
|
89
|
+
// changes ARE committed. A retry would then apply them again.
|
|
90
|
+
// This window is extremely small (< 1ms typically) and is an
|
|
91
|
+
// inherent limitation of any retry-based approach without
|
|
92
|
+
// application-level idempotency (e.g., unique constraints
|
|
93
|
+
// with ON CONFLICT for INSERTs).
|
|
94
|
+
// See: https://github.com/agentuity/sdk/issues/911
|
|
95
|
+
const makeTransactionalExecutor = (useValues) => client.executeWithRetry(async () => {
|
|
96
|
+
// Re-resolve raw inside retry to get post-reconnect instance
|
|
148
97
|
const currentRaw = client.raw;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
98
|
+
await currentRaw.unsafe('BEGIN');
|
|
99
|
+
try {
|
|
100
|
+
const q = currentRaw.unsafe(query, params);
|
|
101
|
+
const result = useValues ? await q.values() : await q;
|
|
102
|
+
await currentRaw.unsafe('COMMIT');
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
try {
|
|
107
|
+
await currentRaw.unsafe('ROLLBACK');
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Connection may already be dead; Postgres auto-rolls
|
|
111
|
+
// back uncommitted transactions on connection close.
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
155
115
|
});
|
|
116
|
+
return createThenable(makeTransactionalExecutor);
|
|
156
117
|
}
|
|
157
118
|
const makeExecutor = (useValues) => client.executeWithRetry(async () => {
|
|
158
119
|
// Re-resolve raw inside retry to get post-reconnect instance
|
|
@@ -160,11 +121,7 @@ export function createResilientSQLProxy(client) {
|
|
|
160
121
|
const q = currentRaw.unsafe(query, params);
|
|
161
122
|
return useValues ? q.values() : q;
|
|
162
123
|
});
|
|
163
|
-
|
|
164
|
-
const result = makeExecutor(false);
|
|
165
|
-
return Object.assign(result, {
|
|
166
|
-
values: () => makeExecutor(true),
|
|
167
|
-
});
|
|
124
|
+
return createThenable(makeExecutor);
|
|
168
125
|
};
|
|
169
126
|
}
|
|
170
127
|
const value = raw[prop];
|
|
@@ -221,6 +178,11 @@ function extractPostgresConfigFromSql(client) {
|
|
|
221
178
|
'max',
|
|
222
179
|
'idleTimeout',
|
|
223
180
|
'connectionTimeout',
|
|
181
|
+
'prepare',
|
|
182
|
+
'bigint',
|
|
183
|
+
'maxLifetime',
|
|
184
|
+
'path',
|
|
185
|
+
'connection',
|
|
224
186
|
];
|
|
225
187
|
for (const key of keys) {
|
|
226
188
|
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.16",
|
|
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.16",
|
|
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.16",
|
|
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,23 +92,51 @@ 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
|
-
//
|
|
170
|
-
//
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
// NOTE: If the connection drops after the server processes
|
|
111
|
+
// COMMIT but before the client receives the response, the
|
|
112
|
+
// changes ARE committed. A retry would then apply them again.
|
|
113
|
+
// This window is extremely small (< 1ms typically) and is an
|
|
114
|
+
// inherent limitation of any retry-based approach without
|
|
115
|
+
// application-level idempotency (e.g., unique constraints
|
|
116
|
+
// with ON CONFLICT for INSERTs).
|
|
117
|
+
// See: https://github.com/agentuity/sdk/issues/911
|
|
118
|
+
const makeTransactionalExecutor = (useValues: boolean) =>
|
|
119
|
+
client.executeWithRetry(async () => {
|
|
120
|
+
// Re-resolve raw inside retry to get post-reconnect instance
|
|
121
|
+
const currentRaw = client.raw;
|
|
122
|
+
await currentRaw.unsafe('BEGIN');
|
|
123
|
+
try {
|
|
124
|
+
const q = currentRaw.unsafe(query, params);
|
|
125
|
+
const result = useValues ? await q.values() : await q;
|
|
126
|
+
await currentRaw.unsafe('COMMIT');
|
|
127
|
+
return result;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
try {
|
|
130
|
+
await currentRaw.unsafe('ROLLBACK');
|
|
131
|
+
} catch {
|
|
132
|
+
// Connection may already be dead; Postgres auto-rolls
|
|
133
|
+
// back uncommitted transactions on connection close.
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return createThenable(makeTransactionalExecutor);
|
|
183
140
|
}
|
|
184
141
|
|
|
185
142
|
const makeExecutor = (useValues: boolean) =>
|
|
@@ -190,11 +147,7 @@ export function createResilientSQLProxy(
|
|
|
190
147
|
return useValues ? q.values() : q;
|
|
191
148
|
});
|
|
192
149
|
|
|
193
|
-
|
|
194
|
-
const result = makeExecutor(false);
|
|
195
|
-
return Object.assign(result, {
|
|
196
|
-
values: () => makeExecutor(true),
|
|
197
|
-
});
|
|
150
|
+
return createThenable(makeExecutor);
|
|
198
151
|
};
|
|
199
152
|
}
|
|
200
153
|
|
|
@@ -263,6 +216,11 @@ function extractPostgresConfigFromSql(client: BunSQLClient): PostgresConfig | un
|
|
|
263
216
|
'max',
|
|
264
217
|
'idleTimeout',
|
|
265
218
|
'connectionTimeout',
|
|
219
|
+
'prepare',
|
|
220
|
+
'bigint',
|
|
221
|
+
'maxLifetime',
|
|
222
|
+
'path',
|
|
223
|
+
'connection',
|
|
266
224
|
] as const;
|
|
267
225
|
|
|
268
226
|
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
|
*/
|