@agentuity/postgres 1.0.15 → 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/client.d.ts +68 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +173 -0
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mutation.d.ts +42 -0
- package/dist/mutation.d.ts.map +1 -0
- package/dist/mutation.js +241 -0
- package/dist/mutation.js.map +1 -0
- package/dist/types.d.ts +76 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/client.ts +197 -1
- package/src/index.ts +9 -1
- package/src/mutation.ts +245 -0
- package/src/types.ts +83 -2
package/dist/mutation.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strips leading whitespace and SQL comments (block and line) from a query string.
|
|
3
|
+
* Returns the remaining query text starting at the first non-comment token.
|
|
4
|
+
*
|
|
5
|
+
* Note: This regex does NOT support nested block comments. Use
|
|
6
|
+
* {@link stripLeadingComments} for full nested comment support.
|
|
7
|
+
*/
|
|
8
|
+
export const LEADING_COMMENTS_RE = /^(?:\s+|\/\*[\s\S]*?\*\/|--[^\n]*\n)*/;
|
|
9
|
+
/**
|
|
10
|
+
* Strips leading whitespace and SQL comments from a query string.
|
|
11
|
+
* Supports nested block comments.
|
|
12
|
+
* Returns the remaining query text starting at the first non-comment token.
|
|
13
|
+
*/
|
|
14
|
+
function stripLeadingComments(query) {
|
|
15
|
+
let i = 0;
|
|
16
|
+
const len = query.length;
|
|
17
|
+
while (i < len) {
|
|
18
|
+
const ch = query[i];
|
|
19
|
+
// Skip whitespace
|
|
20
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
21
|
+
i++;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// Skip line comment: -- ...\n
|
|
25
|
+
if (ch === '-' && i + 1 < len && query[i + 1] === '-') {
|
|
26
|
+
i += 2;
|
|
27
|
+
while (i < len && query[i] !== '\n')
|
|
28
|
+
i++;
|
|
29
|
+
if (i < len)
|
|
30
|
+
i++; // skip newline
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// Skip block comment with nesting: /* ... /* ... */ ... */
|
|
34
|
+
if (ch === '/' && i + 1 < len && query[i + 1] === '*') {
|
|
35
|
+
let commentDepth = 1;
|
|
36
|
+
i += 2;
|
|
37
|
+
while (i < len && commentDepth > 0) {
|
|
38
|
+
if (query[i] === '/' && i + 1 < len && query[i + 1] === '*') {
|
|
39
|
+
commentDepth++;
|
|
40
|
+
i += 2;
|
|
41
|
+
}
|
|
42
|
+
else if (query[i] === '*' && i + 1 < len && query[i + 1] === '/') {
|
|
43
|
+
commentDepth--;
|
|
44
|
+
i += 2;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
return query.substring(i);
|
|
55
|
+
}
|
|
56
|
+
/** Regex matching the first keyword of a mutation statement. */
|
|
57
|
+
const MUTATION_KEYWORD_RE = /^(INSERT|UPDATE|DELETE|COPY|TRUNCATE|MERGE|CALL|DO)\b/i;
|
|
58
|
+
/**
|
|
59
|
+
* Determines whether a SQL query is a mutation that requires transaction
|
|
60
|
+
* wrapping for safe retry.
|
|
61
|
+
*
|
|
62
|
+
* Detected mutation types: INSERT, UPDATE, DELETE, COPY, TRUNCATE, MERGE,
|
|
63
|
+
* CALL, DO. EXPLAIN queries are never wrapped (read-only analysis, even
|
|
64
|
+
* when the explained statement is a mutation like `EXPLAIN INSERT INTO ...`).
|
|
65
|
+
*
|
|
66
|
+
* Mutation statements wrapped in a transaction can be safely retried because
|
|
67
|
+
* PostgreSQL guarantees that uncommitted transactions are rolled back when
|
|
68
|
+
* the connection drops. This prevents:
|
|
69
|
+
* - Duplicate rows from retried INSERTs
|
|
70
|
+
* - Double-applied changes from retried UPDATEs (e.g., counter increments)
|
|
71
|
+
* - Repeated side effects from retried DELETEs (e.g., cascade triggers)
|
|
72
|
+
*
|
|
73
|
+
* Handles three patterns:
|
|
74
|
+
* 1. Direct mutations: `INSERT INTO ...`, `UPDATE ... SET`, `DELETE FROM ...`,
|
|
75
|
+
* `COPY ...`, `TRUNCATE ...`, `MERGE ...`, `CALL ...`, `DO ...`
|
|
76
|
+
* (with optional leading comments/whitespace)
|
|
77
|
+
* 2. CTE mutations: `WITH cte AS (...) INSERT|UPDATE|DELETE|... ...` — scans
|
|
78
|
+
* past the WITH clause by tracking parenthesis depth to skip CTE
|
|
79
|
+
* subexpressions, then checks if the first top-level DML keyword is a
|
|
80
|
+
* mutation. The scanner treats single-quoted strings, double-quoted
|
|
81
|
+
* identifiers, dollar-quoted strings, line comments (--), and block
|
|
82
|
+
* comments (including nested) as atomic regions so parentheses inside
|
|
83
|
+
* them do not corrupt depth tracking.
|
|
84
|
+
* 3. Multi-statement queries: `SELECT 1; INSERT INTO items VALUES (1)` —
|
|
85
|
+
* scans past semicolons at depth 0 to find mutation keywords in
|
|
86
|
+
* subsequent statements.
|
|
87
|
+
*
|
|
88
|
+
* @see https://github.com/agentuity/sdk/issues/911
|
|
89
|
+
*/
|
|
90
|
+
export function isMutationStatement(query) {
|
|
91
|
+
// Strip leading whitespace and SQL comments (supports nested block comments)
|
|
92
|
+
const stripped = stripLeadingComments(query);
|
|
93
|
+
// EXPLAIN never mutates (even EXPLAIN INSERT INTO...)
|
|
94
|
+
if (/^EXPLAIN\b/i.test(stripped))
|
|
95
|
+
return false;
|
|
96
|
+
// Fast path: direct mutation statement
|
|
97
|
+
if (MUTATION_KEYWORD_RE.test(stripped)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
// Fast path: no CTE prefix and no multi-statement separator → not a mutation
|
|
101
|
+
if (!/^WITH\s/i.test(stripped) && !stripped.includes(';')) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
// Full scan: walk the entire query character-by-character.
|
|
105
|
+
// Track parenthesis depth and check for mutation keywords at depth 0.
|
|
106
|
+
// This handles both CTE queries (WITH ... AS (...) DML ...) and
|
|
107
|
+
// multi-statement queries (SELECT ...; INSERT ...).
|
|
108
|
+
let depth = 0;
|
|
109
|
+
let i = 0;
|
|
110
|
+
const len = stripped.length;
|
|
111
|
+
while (i < len) {
|
|
112
|
+
const ch = stripped[i];
|
|
113
|
+
// ── Skip atomic regions (at any depth) ──────────────────────
|
|
114
|
+
// These regions may contain parentheses that must not affect depth.
|
|
115
|
+
// Single-quoted string: 'it''s a (test)'
|
|
116
|
+
if (ch === "'") {
|
|
117
|
+
i++;
|
|
118
|
+
while (i < len) {
|
|
119
|
+
if (stripped[i] === "'") {
|
|
120
|
+
i++;
|
|
121
|
+
if (i < len && stripped[i] === "'") {
|
|
122
|
+
i++; // escaped '' → still inside string
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
break; // end of string
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// Double-quoted identifier: "col(1)"
|
|
135
|
+
if (ch === '"') {
|
|
136
|
+
i++;
|
|
137
|
+
while (i < len) {
|
|
138
|
+
if (stripped[i] === '"') {
|
|
139
|
+
i++;
|
|
140
|
+
if (i < len && stripped[i] === '"') {
|
|
141
|
+
i++; // escaped "" → still inside identifier
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
i++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
// Line comment: -- has (parens)\n
|
|
154
|
+
if (ch === '-' && i + 1 < len && stripped[i + 1] === '-') {
|
|
155
|
+
i += 2;
|
|
156
|
+
while (i < len && stripped[i] !== '\n')
|
|
157
|
+
i++;
|
|
158
|
+
if (i < len)
|
|
159
|
+
i++; // skip newline
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Block comment: /* has (parens) */ — supports nesting
|
|
163
|
+
if (ch === '/' && i + 1 < len && stripped[i + 1] === '*') {
|
|
164
|
+
let commentDepth = 1;
|
|
165
|
+
i += 2;
|
|
166
|
+
while (i < len && commentDepth > 0) {
|
|
167
|
+
if (stripped[i] === '/' && i + 1 < len && stripped[i + 1] === '*') {
|
|
168
|
+
commentDepth++;
|
|
169
|
+
i += 2;
|
|
170
|
+
}
|
|
171
|
+
else if (stripped[i] === '*' && i + 1 < len && stripped[i + 1] === '/') {
|
|
172
|
+
commentDepth--;
|
|
173
|
+
i += 2;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
i++;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// Dollar-quoted string: $$has (parens)$$ or $tag$...$tag$
|
|
182
|
+
if (ch === '$') {
|
|
183
|
+
let tagEnd = i + 1;
|
|
184
|
+
while (tagEnd < len && /[a-zA-Z0-9_]/.test(stripped[tagEnd]))
|
|
185
|
+
tagEnd++;
|
|
186
|
+
if (tagEnd < len && stripped[tagEnd] === '$') {
|
|
187
|
+
const tag = stripped.substring(i, tagEnd + 1);
|
|
188
|
+
i = tagEnd + 1;
|
|
189
|
+
const closeIdx = stripped.indexOf(tag, i);
|
|
190
|
+
if (closeIdx !== -1) {
|
|
191
|
+
i = closeIdx + tag.length;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
i = len; // unterminated — skip to end
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
// Not a dollar-quote tag, fall through
|
|
199
|
+
}
|
|
200
|
+
// ── Track parenthesis depth ─────────────────────────────────
|
|
201
|
+
if (ch === '(') {
|
|
202
|
+
depth++;
|
|
203
|
+
i++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (ch === ')') {
|
|
207
|
+
depth--;
|
|
208
|
+
i++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
// Only inspect keywords at top level (depth === 0)
|
|
212
|
+
if (depth === 0) {
|
|
213
|
+
// Skip whitespace at top level
|
|
214
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
215
|
+
i++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
// Skip semicolons and commas between CTEs or statements
|
|
219
|
+
if (ch === ';' || ch === ',') {
|
|
220
|
+
i++;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
// Check for mutation keyword or skip other words
|
|
224
|
+
// (CTE names, AS, RECURSIVE, SELECT, WITH, etc.)
|
|
225
|
+
if (/\w/.test(ch)) {
|
|
226
|
+
const rest = stripped.substring(i);
|
|
227
|
+
if (MUTATION_KEYWORD_RE.test(rest)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
// Skip past this word
|
|
231
|
+
while (i < len && /\w/.test(stripped[i])) {
|
|
232
|
+
i++;
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
i++;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=mutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation.js","sourceRoot":"","sources":["../src/mutation.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type pg from 'pg';
|
|
2
|
+
export type UnsafeQueryResult = {
|
|
3
|
+
then: Promise<unknown>['then'];
|
|
4
|
+
catch: Promise<unknown>['catch'];
|
|
5
|
+
finally: Promise<unknown>['finally'];
|
|
6
|
+
values: () => Promise<unknown>;
|
|
7
|
+
};
|
|
2
8
|
/**
|
|
3
9
|
* TLS configuration options for PostgreSQL connections.
|
|
4
10
|
*/
|
|
@@ -125,17 +131,46 @@ export interface PostgresConfig {
|
|
|
125
131
|
*/
|
|
126
132
|
username?: string;
|
|
127
133
|
/**
|
|
128
|
-
* Database password.
|
|
134
|
+
* Database password for authentication.
|
|
135
|
+
* Can be a string or a function that returns the password (sync or async).
|
|
136
|
+
* Using a function enables rotating credentials (e.g., AWS RDS IAM tokens,
|
|
137
|
+
* GCP Cloud SQL IAM authentication).
|
|
129
138
|
*/
|
|
130
|
-
password?: string;
|
|
139
|
+
password?: string | (() => string | Promise<string>);
|
|
131
140
|
/**
|
|
132
141
|
* Database name.
|
|
133
142
|
*/
|
|
134
143
|
database?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Unix domain socket path for local PostgreSQL connections.
|
|
146
|
+
* Alternative to hostname/port for same-machine connections.
|
|
147
|
+
*
|
|
148
|
+
* @example '/var/run/postgresql/.s.PGSQL.5432'
|
|
149
|
+
*/
|
|
150
|
+
path?: string;
|
|
135
151
|
/**
|
|
136
152
|
* TLS configuration.
|
|
137
153
|
*/
|
|
138
154
|
tls?: TLSConfig | boolean;
|
|
155
|
+
/**
|
|
156
|
+
* PostgreSQL client runtime configuration parameters sent during
|
|
157
|
+
* connection startup.
|
|
158
|
+
*
|
|
159
|
+
* These correspond to PostgreSQL runtime configuration settings.
|
|
160
|
+
*
|
|
161
|
+
* @see https://www.postgresql.org/docs/current/runtime-config-client.html
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* {
|
|
166
|
+
* search_path: 'myapp,public',
|
|
167
|
+
* statement_timeout: '30s',
|
|
168
|
+
* application_name: 'my-service',
|
|
169
|
+
* timezone: 'UTC',
|
|
170
|
+
* }
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
connection?: Record<string, string | boolean | number>;
|
|
139
174
|
/**
|
|
140
175
|
* Maximum number of connections in the pool.
|
|
141
176
|
* @default 10
|
|
@@ -151,6 +186,40 @@ export interface PostgresConfig {
|
|
|
151
186
|
* @default 0 (no timeout)
|
|
152
187
|
*/
|
|
153
188
|
idleTimeout?: number;
|
|
189
|
+
/**
|
|
190
|
+
* Maximum lifetime of a connection in seconds.
|
|
191
|
+
* After this time, the connection is closed and a new one is created.
|
|
192
|
+
* Set to `0` for no maximum lifetime.
|
|
193
|
+
*
|
|
194
|
+
* This is useful in pooled environments to prevent stale connections
|
|
195
|
+
* and coordinate with connection pooler behavior.
|
|
196
|
+
*
|
|
197
|
+
* @default 0 (no maximum lifetime)
|
|
198
|
+
*/
|
|
199
|
+
maxLifetime?: number;
|
|
200
|
+
/**
|
|
201
|
+
* Whether to use named prepared statements.
|
|
202
|
+
*
|
|
203
|
+
* When `true`, Bun's SQL driver caches named prepared statements on the
|
|
204
|
+
* server for better performance with repeated queries.
|
|
205
|
+
*
|
|
206
|
+
* When `false`, queries use unnamed prepared statements that are parsed
|
|
207
|
+
* fresh each time. This is required when using connection poolers like
|
|
208
|
+
* PGBouncer (in transaction mode) or Supavisor, where the backend
|
|
209
|
+
* connection may change between queries, invalidating cached statements.
|
|
210
|
+
*
|
|
211
|
+
* @default false
|
|
212
|
+
*/
|
|
213
|
+
prepare?: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* Whether to return large integers as BigInt instead of strings.
|
|
216
|
+
*
|
|
217
|
+
* When `true`, integers outside the i32 range are returned as `BigInt`.
|
|
218
|
+
* When `false`, they are returned as strings.
|
|
219
|
+
*
|
|
220
|
+
* @default false
|
|
221
|
+
*/
|
|
222
|
+
bigint?: boolean;
|
|
154
223
|
/**
|
|
155
224
|
* Reconnection configuration.
|
|
156
225
|
*/
|
|
@@ -167,6 +236,11 @@ export interface PostgresConfig {
|
|
|
167
236
|
* @default false
|
|
168
237
|
*/
|
|
169
238
|
preconnect?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* Callback invoked when a connection attempt completes.
|
|
241
|
+
* Receives an Error on failure, or null on success.
|
|
242
|
+
*/
|
|
243
|
+
onconnect?: (error: Error | null) => void;
|
|
170
244
|
/**
|
|
171
245
|
* Callback invoked when the connection is closed.
|
|
172
246
|
*/
|
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,CAAC;AAEzB;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAE7B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;OAEG;IACH,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAE3C;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEvB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAE7B;;OAEG;IACH,kBAAkB,EAAE,IAAI,GAAG,IAAI,CAAC;IAEhC;;OAEG;IACH,sBAAsB,EAAE,IAAI,GAAG,IAAI,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentuity/postgres",
|
|
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",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"prepublishOnly": "bun run clean && bun run build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@agentuity/core": "1.0.
|
|
34
|
+
"@agentuity/core": "1.0.16",
|
|
35
35
|
"pg": "^8.13.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@agentuity/runtime": "1.0.
|
|
38
|
+
"@agentuity/runtime": "1.0.16"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@agentuity/runtime": {
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@agentuity/runtime": "1.0.
|
|
47
|
-
"@agentuity/test-utils": "1.0.
|
|
46
|
+
"@agentuity/runtime": "1.0.16",
|
|
47
|
+
"@agentuity/test-utils": "1.0.16",
|
|
48
48
|
"@types/bun": "latest",
|
|
49
49
|
"@types/pg": "^8.11.14",
|
|
50
50
|
"bun-types": "latest",
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { SQL as BunSQL, type SQLQuery, type SQL } from 'bun';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
PostgresConfig,
|
|
4
|
+
ConnectionStats,
|
|
5
|
+
TransactionOptions,
|
|
6
|
+
ReserveOptions,
|
|
7
|
+
UnsafeQueryResult,
|
|
8
|
+
} from './types';
|
|
3
9
|
import {
|
|
4
10
|
ConnectionClosedError,
|
|
5
11
|
PostgresError,
|
|
@@ -12,6 +18,73 @@ import { computeBackoff, sleep, mergeReconnectConfig } from './reconnect';
|
|
|
12
18
|
import { Transaction, ReservedConnection } from './transaction';
|
|
13
19
|
import { registerClient, unregisterClient } from './registry';
|
|
14
20
|
import { injectSslMode } from './tls';
|
|
21
|
+
import { isMutationStatement } from './mutation';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a lazy thenable with format locking for safe query execution.
|
|
25
|
+
*
|
|
26
|
+
* The returned object implements the {@link UnsafeQueryResult} interface: it is
|
|
27
|
+
* thenable (`.then()`, `.catch()`, `.finally()`) and exposes a `.values()`
|
|
28
|
+
* method for array-format results. Execution is deferred until the first
|
|
29
|
+
* consumption method is called.
|
|
30
|
+
*
|
|
31
|
+
* **Format locking:** The first access (`.then()` or `.values()`) determines
|
|
32
|
+
* the result format for the lifetime of the thenable. Attempting the other
|
|
33
|
+
* format afterwards throws an error, preventing accidental duplicate execution.
|
|
34
|
+
*
|
|
35
|
+
* @param makeExecutor - Factory that creates the execution promise.
|
|
36
|
+
* Called with `true` for array format (`.values()`) or `false` for row-object
|
|
37
|
+
* format (`.then()`).
|
|
38
|
+
* @returns A lazy thenable with `.values()` support
|
|
39
|
+
*
|
|
40
|
+
* @internal Exported for use by `@agentuity/drizzle` — not part of the public API.
|
|
41
|
+
*/
|
|
42
|
+
export function createThenable(
|
|
43
|
+
makeExecutor: (useValues: boolean) => Promise<unknown>
|
|
44
|
+
): UnsafeQueryResult {
|
|
45
|
+
let started: Promise<unknown> | null = null;
|
|
46
|
+
let executionMode: boolean | null = null; // tracks useValues for first call
|
|
47
|
+
|
|
48
|
+
const startExecution = (useValues: boolean): Promise<unknown> => {
|
|
49
|
+
if (started) {
|
|
50
|
+
if (executionMode !== useValues) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Cannot access .${useValues ? 'values()' : 'then()'} after .${!useValues ? 'values()' : 'then()'} ` +
|
|
53
|
+
'on the same query result. The result format is locked to the first access mode ' +
|
|
54
|
+
'to prevent duplicate query execution. Create a new unsafeQuery() call for a different format.'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return started;
|
|
58
|
+
}
|
|
59
|
+
executionMode = useValues;
|
|
60
|
+
started = makeExecutor(useValues);
|
|
61
|
+
return started;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return new Proxy({} as UnsafeQueryResult, {
|
|
65
|
+
get(_target, prop) {
|
|
66
|
+
if (prop === 'then') {
|
|
67
|
+
return <TResult1 = unknown, TResult2 = never>(
|
|
68
|
+
onfulfilled?: ((value: unknown) => TResult1 | PromiseLike<TResult1>) | null,
|
|
69
|
+
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
|
|
70
|
+
): Promise<TResult1 | TResult2> => startExecution(false).then(onfulfilled, onrejected);
|
|
71
|
+
}
|
|
72
|
+
if (prop === 'catch') {
|
|
73
|
+
return <TResult = never>(
|
|
74
|
+
onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
|
|
75
|
+
): Promise<unknown | TResult> => startExecution(false).catch(onrejected);
|
|
76
|
+
}
|
|
77
|
+
if (prop === 'finally') {
|
|
78
|
+
return (onfinally?: (() => void) | null): Promise<unknown> =>
|
|
79
|
+
startExecution(false).finally(onfinally ?? undefined);
|
|
80
|
+
}
|
|
81
|
+
if (prop === 'values') {
|
|
82
|
+
return () => startExecution(true);
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
15
88
|
|
|
16
89
|
/**
|
|
17
90
|
* Bun SQL options for PostgreSQL connections.
|
|
@@ -149,8 +222,29 @@ export class PostgresClient {
|
|
|
149
222
|
* ```
|
|
150
223
|
*/
|
|
151
224
|
query(strings: TemplateStringsArray, ...values: unknown[]): Promise<unknown[]> {
|
|
225
|
+
// Reconstruct query shape for mutation detection.
|
|
226
|
+
// Space as separator is safe — doesn't affect SQL keyword detection.
|
|
227
|
+
const queryShape = strings.join(' ');
|
|
228
|
+
const mutation = isMutationStatement(queryShape);
|
|
229
|
+
|
|
152
230
|
return this._executeWithRetry(async () => {
|
|
153
231
|
const sql = await this._ensureConnectedAsync();
|
|
232
|
+
if (mutation) {
|
|
233
|
+
await sql.unsafe('BEGIN');
|
|
234
|
+
try {
|
|
235
|
+
const result = await sql(strings, ...values);
|
|
236
|
+
await sql.unsafe('COMMIT');
|
|
237
|
+
return result as unknown[];
|
|
238
|
+
} catch (error) {
|
|
239
|
+
try {
|
|
240
|
+
await sql.unsafe('ROLLBACK');
|
|
241
|
+
} catch {
|
|
242
|
+
// Connection may already be dead; Postgres auto-rolls
|
|
243
|
+
// back uncommitted transactions on connection close.
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
154
248
|
return sql(strings, ...values);
|
|
155
249
|
});
|
|
156
250
|
}
|
|
@@ -278,6 +372,87 @@ export class PostgresClient {
|
|
|
278
372
|
return sql.unsafe(query);
|
|
279
373
|
}
|
|
280
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Execute a raw SQL query with automatic retry and transaction wrapping for mutations.
|
|
377
|
+
*
|
|
378
|
+
* Unlike {@link unsafe}, this method:
|
|
379
|
+
* - Automatically retries on retryable errors (connection drops, resets)
|
|
380
|
+
* - Wraps mutation statements in transactions for safe retry
|
|
381
|
+
* - Returns a thenable with `.values()` support matching Bun's SQLQuery interface
|
|
382
|
+
*
|
|
383
|
+
* Detected mutation types: INSERT, UPDATE, DELETE, COPY, TRUNCATE, MERGE, CALL, DO.
|
|
384
|
+
* EXPLAIN queries are never wrapped (read-only analysis).
|
|
385
|
+
*
|
|
386
|
+
* For SELECT queries, retries without transaction wrapping (idempotent).
|
|
387
|
+
* For mutations, wraps in BEGIN/COMMIT so PostgreSQL auto-rolls back
|
|
388
|
+
* uncommitted transactions on connection drop, making retry safe.
|
|
389
|
+
*
|
|
390
|
+
* **⚠️ COMMIT Uncertainty Window:**
|
|
391
|
+
* If the connection drops after the server processes COMMIT but before the client
|
|
392
|
+
* receives confirmation (~<1ms window), changes ARE committed but the client sees
|
|
393
|
+
* a failure. Retry will then duplicate the mutation. This is an inherent limitation
|
|
394
|
+
* of retry-based approaches. Use application-level idempotency (e.g., unique
|
|
395
|
+
* constraints with ON CONFLICT) for critical operations.
|
|
396
|
+
*
|
|
397
|
+
* **⚠️ Result Format Locking:**
|
|
398
|
+
* Each unsafeQuery() result can only be consumed via ONE access pattern — either
|
|
399
|
+
* `await result` (row objects) or `await result.values()` (arrays), not both.
|
|
400
|
+
* Attempting the second pattern throws an error to prevent accidental duplicate execution.
|
|
401
|
+
*
|
|
402
|
+
* @param query - The raw SQL query string
|
|
403
|
+
* @param params - Optional query parameters
|
|
404
|
+
* @returns A thenable that resolves to rows (objects) or arrays via `.values()`
|
|
405
|
+
*
|
|
406
|
+
* @see https://github.com/agentuity/sdk/issues/911
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```typescript
|
|
410
|
+
* // INSERT with safe retry
|
|
411
|
+
* const rows = await client.unsafeQuery('INSERT INTO items (name) VALUES ($1) RETURNING *', ['test']);
|
|
412
|
+
*
|
|
413
|
+
* // SELECT with retry (no transaction overhead)
|
|
414
|
+
* const items = await client.unsafeQuery('SELECT * FROM items WHERE id = $1', [42]);
|
|
415
|
+
*
|
|
416
|
+
* // Get raw arrays via .values()
|
|
417
|
+
* const arrays = await client.unsafeQuery('SELECT id, name FROM items').values();
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
unsafeQuery(query: string, params?: unknown[]): UnsafeQueryResult {
|
|
421
|
+
if (isMutationStatement(query)) {
|
|
422
|
+
const makeTransactionalExecutor = (useValues: boolean) =>
|
|
423
|
+
this._executeWithRetry(async () => {
|
|
424
|
+
const raw = this._ensureConnected();
|
|
425
|
+
await raw.unsafe('BEGIN');
|
|
426
|
+
try {
|
|
427
|
+
const q = params ? raw.unsafe(query, params) : raw.unsafe(query);
|
|
428
|
+
const result = useValues ? await q.values() : await q;
|
|
429
|
+
await raw.unsafe('COMMIT');
|
|
430
|
+
return result;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
try {
|
|
433
|
+
await raw.unsafe('ROLLBACK');
|
|
434
|
+
} catch {
|
|
435
|
+
// Connection may already be dead; Postgres auto-rolls
|
|
436
|
+
// back uncommitted transactions on connection close.
|
|
437
|
+
}
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
return createThenable(makeTransactionalExecutor);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Non-mutation: plain retry without transaction overhead
|
|
446
|
+
const makeExecutor = (useValues: boolean) =>
|
|
447
|
+
this._executeWithRetry(async () => {
|
|
448
|
+
const raw = this._ensureConnected();
|
|
449
|
+
const q = params ? raw.unsafe(query, params) : raw.unsafe(query);
|
|
450
|
+
return useValues ? q.values() : q;
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
return createThenable(makeExecutor);
|
|
454
|
+
}
|
|
455
|
+
|
|
281
456
|
/**
|
|
282
457
|
* Registers signal handlers to detect application shutdown.
|
|
283
458
|
* When shutdown is detected, reconnection is disabled.
|
|
@@ -335,10 +510,12 @@ export class PostgresClient {
|
|
|
335
510
|
if (this._config.username) bunOptions.username = this._config.username;
|
|
336
511
|
if (this._config.password) bunOptions.password = this._config.password;
|
|
337
512
|
if (this._config.database) bunOptions.database = this._config.database;
|
|
513
|
+
if (this._config.path) bunOptions.path = this._config.path;
|
|
338
514
|
if (this._config.max) bunOptions.max = this._config.max;
|
|
339
515
|
if (this._config.idleTimeout !== undefined) bunOptions.idleTimeout = this._config.idleTimeout;
|
|
340
516
|
if (this._config.connectionTimeout !== undefined)
|
|
341
517
|
bunOptions.connectionTimeout = this._config.connectionTimeout;
|
|
518
|
+
if (this._config.maxLifetime !== undefined) bunOptions.maxLifetime = this._config.maxLifetime;
|
|
342
519
|
|
|
343
520
|
// Handle TLS configuration
|
|
344
521
|
if (this._config.tls !== undefined) {
|
|
@@ -349,6 +526,23 @@ export class PostgresClient {
|
|
|
349
526
|
}
|
|
350
527
|
}
|
|
351
528
|
|
|
529
|
+
// Postgres client runtime configuration (search_path, statement_timeout, etc.)
|
|
530
|
+
if (this._config.connection) bunOptions.connection = this._config.connection;
|
|
531
|
+
|
|
532
|
+
// Default to unnamed prepared statements (prepare: false) to prevent
|
|
533
|
+
// "prepared statement did not exist" errors when backend connections
|
|
534
|
+
// rotate (e.g., connection poolers, hot reloads, server restarts).
|
|
535
|
+
// See: https://github.com/agentuity/sdk/issues/1005
|
|
536
|
+
bunOptions.prepare = this._config.prepare ?? false;
|
|
537
|
+
|
|
538
|
+
// BigInt handling for integers outside i32 range
|
|
539
|
+
if (this._config.bigint !== undefined) bunOptions.bigint = this._config.bigint;
|
|
540
|
+
|
|
541
|
+
// Set up onconnect handler
|
|
542
|
+
if (this._config.onconnect) {
|
|
543
|
+
bunOptions.onconnect = this._config.onconnect;
|
|
544
|
+
}
|
|
545
|
+
|
|
352
546
|
// Set up onclose handler for reconnection
|
|
353
547
|
bunOptions.onclose = (err: Error | null) => {
|
|
354
548
|
this._handleClose(err ?? undefined);
|
|
@@ -735,6 +929,7 @@ export class PostgresClient {
|
|
|
735
929
|
*/
|
|
736
930
|
export type CallablePostgresClient = PostgresClient & {
|
|
737
931
|
(strings: TemplateStringsArray, ...values: unknown[]): Promise<unknown[]>;
|
|
932
|
+
unsafeQuery(query: string, params?: unknown[]): UnsafeQueryResult;
|
|
738
933
|
};
|
|
739
934
|
|
|
740
935
|
/**
|
|
@@ -790,6 +985,7 @@ export function createCallableClient(config?: string | PostgresConfig): Callable
|
|
|
790
985
|
callable.close = client.close.bind(client);
|
|
791
986
|
callable.shutdown = client.shutdown.bind(client);
|
|
792
987
|
callable.unsafe = client.unsafe.bind(client);
|
|
988
|
+
callable.unsafeQuery = client.unsafeQuery.bind(client);
|
|
793
989
|
callable.waitForConnection = client.waitForConnection.bind(client);
|
|
794
990
|
callable.executeWithRetry = client.executeWithRetry.bind(client);
|
|
795
991
|
|