postgres-tracked-pool 1.0.0
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/LICENSE +21 -0
- package/README.md +359 -0
- package/dist/TrackedPool.d.ts +34 -0
- package/dist/TrackedPool.d.ts.map +1 -0
- package/dist/TrackedPool.js +156 -0
- package/dist/TrackedPool.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Top Stats
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# postgres-tracked-pool
|
|
2
|
+
|
|
3
|
+
A PostgreSQL connection pool wrapper that automatically adds tracking comments to SQL queries with function name, file path, and line number. Perfect for debugging, performance monitoring, and query attribution.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔍 **Automatic Query Attribution** - Know exactly which function/method issued a query
|
|
8
|
+
- 🐛 **Easy Debugging** - Quickly locate the source of problematic queries
|
|
9
|
+
- 📊 **Performance Analysis** - Correlate slow queries with specific code paths
|
|
10
|
+
- 🔒 **Audit Logging** - Track query origins for security and compliance
|
|
11
|
+
- ⚡ **Zero Configuration** - Drop-in replacement for `pg.Pool`
|
|
12
|
+
- 🚀 **Minimal Overhead** - Negligible performance impact
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install postgres-tracked-pool pg
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { TrackedPool } from "postgres-tracked-pool";
|
|
24
|
+
|
|
25
|
+
const pool = new TrackedPool({
|
|
26
|
+
connectionString: process.env.DATABASE_URL
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Use it exactly like pg.Pool
|
|
30
|
+
const result = await pool.query("SELECT * FROM users WHERE id = $1", [userId]);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## How It Works
|
|
34
|
+
|
|
35
|
+
When you execute a query:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Your code at line 42 in src/user/service.ts
|
|
39
|
+
async function getUserById(userId: string) {
|
|
40
|
+
const result = await pool.query("SELECT * FROM users WHERE id = $1", [userId]);
|
|
41
|
+
return result.rows[0];
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
PostgreSQL receives:
|
|
46
|
+
|
|
47
|
+
```sql
|
|
48
|
+
/*func_name=getUserById,file=src/user/service.ts,line=43*/ SELECT * FROM users WHERE id = $1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
`TrackedPool` extends `pg.Pool` and is fully compatible with all PostgreSQL node driver features:
|
|
54
|
+
|
|
55
|
+
### Constructor
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const pool = new TrackedPool(config);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Accepts the same configuration as `pg.Pool`:
|
|
62
|
+
- `connectionString` - PostgreSQL connection string
|
|
63
|
+
- `host`, `port`, `database`, `user`, `password` - Connection parameters
|
|
64
|
+
- `max` - Maximum number of clients in the pool
|
|
65
|
+
- `idleTimeoutMillis` - How long a client can remain idle before being closed
|
|
66
|
+
- And all other [pg.Pool configuration options](https://node-postgres.com/apis/pool)
|
|
67
|
+
|
|
68
|
+
### Methods
|
|
69
|
+
|
|
70
|
+
All methods from `pg.Pool` are available:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Direct queries
|
|
74
|
+
await pool.query(sql, values);
|
|
75
|
+
|
|
76
|
+
// Get a client for transactions
|
|
77
|
+
const client = await pool.connect();
|
|
78
|
+
try {
|
|
79
|
+
await client.query("BEGIN");
|
|
80
|
+
await client.query("INSERT INTO ...");
|
|
81
|
+
await client.query("COMMIT");
|
|
82
|
+
} finally {
|
|
83
|
+
client.release();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// End the pool
|
|
87
|
+
await pool.end();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Use Cases
|
|
91
|
+
|
|
92
|
+
### PostgreSQL Logs
|
|
93
|
+
|
|
94
|
+
Enable statement logging in PostgreSQL to see query origins:
|
|
95
|
+
|
|
96
|
+
```sql
|
|
97
|
+
-- postgresql.conf
|
|
98
|
+
log_statement = 'all'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Log output:
|
|
102
|
+
```
|
|
103
|
+
LOG: statement: /*func_name=getRecentData,file=src/analytics.ts,line=123*/ SELECT time, value FROM metrics WHERE id = $1
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### pg_stat_statements
|
|
107
|
+
|
|
108
|
+
Track query performance by source function:
|
|
109
|
+
|
|
110
|
+
```sql
|
|
111
|
+
SELECT
|
|
112
|
+
substring(query from 'func_name=([^,]+)') as function_name,
|
|
113
|
+
substring(query from 'file=([^,]+)') as file,
|
|
114
|
+
calls,
|
|
115
|
+
mean_exec_time,
|
|
116
|
+
max_exec_time
|
|
117
|
+
FROM pg_stat_statements
|
|
118
|
+
WHERE query LIKE '/*func_name=%'
|
|
119
|
+
ORDER BY mean_exec_time DESC;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Monitoring & Alerting
|
|
123
|
+
|
|
124
|
+
Set up alerts for slow queries from specific functions:
|
|
125
|
+
|
|
126
|
+
```sql
|
|
127
|
+
SELECT
|
|
128
|
+
substring(query from 'func_name=([^,]+)') as function_name,
|
|
129
|
+
max(max_exec_time) as worst_case_ms
|
|
130
|
+
FROM pg_stat_statements
|
|
131
|
+
WHERE query LIKE '/*func_name=%'
|
|
132
|
+
GROUP BY function_name
|
|
133
|
+
HAVING max(max_exec_time) > 1000; -- Alert if > 1 second
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Custom Analytics
|
|
137
|
+
|
|
138
|
+
Parse logs with tools like pgBadger, PgHero, or custom scripts to:
|
|
139
|
+
- Identify hot paths in your application
|
|
140
|
+
- Track database usage patterns by module/feature
|
|
141
|
+
- Generate reports on query performance by code location
|
|
142
|
+
|
|
143
|
+
## Configuration
|
|
144
|
+
|
|
145
|
+
### Custom Path Extraction
|
|
146
|
+
|
|
147
|
+
By default, the tracker intelligently handles:
|
|
148
|
+
- **Workspace folders**: Shows as `src/user/service.ts`
|
|
149
|
+
- **node_modules**: Shows as `[package-name]` to avoid clutter
|
|
150
|
+
- **Unknown paths**: Shows just the filename
|
|
151
|
+
|
|
152
|
+
You can extend the `TrackedPool` class to customize path extraction:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { TrackedPool } from "postgres-tracked-pool";
|
|
156
|
+
|
|
157
|
+
class CustomTrackedPool extends TrackedPool {
|
|
158
|
+
// Override methods as needed
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Performance Impact
|
|
163
|
+
|
|
164
|
+
- **Stack trace capture**: ~100μs per query (negligible)
|
|
165
|
+
- **Storage overhead**: Zero (SQL comments are stripped by PostgreSQL)
|
|
166
|
+
- **Query plan impact**: None (comments don't affect execution plans)
|
|
167
|
+
- **Result overhead**: Zero (query results unchanged)
|
|
168
|
+
|
|
169
|
+
## TypeScript Support
|
|
170
|
+
|
|
171
|
+
Full TypeScript support with complete type definitions included.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { TrackedPool } from "postgres-tracked-pool";
|
|
175
|
+
import { PoolConfig, QueryResult } from "pg";
|
|
176
|
+
|
|
177
|
+
const config: PoolConfig = {
|
|
178
|
+
connectionString: process.env.DATABASE_URL
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const pool = new TrackedPool(config);
|
|
182
|
+
|
|
183
|
+
const result: QueryResult = await pool.query("SELECT NOW()");
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Migration from pg.Pool
|
|
187
|
+
|
|
188
|
+
Simply replace `new Pool()` with `new TrackedPool()`:
|
|
189
|
+
|
|
190
|
+
**Before:**
|
|
191
|
+
```typescript
|
|
192
|
+
import { Pool } from "pg";
|
|
193
|
+
|
|
194
|
+
const pool = new Pool({
|
|
195
|
+
connectionString: process.env.DATABASE_URL
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**After:**
|
|
200
|
+
```typescript
|
|
201
|
+
import { TrackedPool } from "postgres-tracked-pool";
|
|
202
|
+
|
|
203
|
+
const pool = new TrackedPool({
|
|
204
|
+
connectionString: process.env.DATABASE_URL
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
All existing code continues to work without modifications!
|
|
209
|
+
|
|
210
|
+
## Examples
|
|
211
|
+
|
|
212
|
+
### Basic Query
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { TrackedPool } from "postgres-tracked-pool";
|
|
216
|
+
|
|
217
|
+
const pool = new TrackedPool({
|
|
218
|
+
connectionString: "postgresql://localhost/mydb"
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
async function getUser(id: number) {
|
|
222
|
+
const result = await pool.query(
|
|
223
|
+
"SELECT * FROM users WHERE id = $1",
|
|
224
|
+
[id]
|
|
225
|
+
);
|
|
226
|
+
return result.rows[0];
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Transaction
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
async function transferFunds(fromId: number, toId: number, amount: number) {
|
|
234
|
+
const client = await pool.connect();
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
await client.query("BEGIN");
|
|
238
|
+
await client.query(
|
|
239
|
+
"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
|
|
240
|
+
[amount, fromId]
|
|
241
|
+
);
|
|
242
|
+
await client.query(
|
|
243
|
+
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
|
|
244
|
+
[amount, toId]
|
|
245
|
+
);
|
|
246
|
+
await client.query("COMMIT");
|
|
247
|
+
} catch (error) {
|
|
248
|
+
await client.query("ROLLBACK");
|
|
249
|
+
throw error;
|
|
250
|
+
} finally {
|
|
251
|
+
client.release();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Connection Pool Management
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const pool = new TrackedPool({
|
|
260
|
+
host: "localhost",
|
|
261
|
+
port: 5432,
|
|
262
|
+
database: "mydb",
|
|
263
|
+
user: "myuser",
|
|
264
|
+
password: "mypass",
|
|
265
|
+
max: 20, // maximum number of clients
|
|
266
|
+
idleTimeoutMillis: 30000,
|
|
267
|
+
connectionTimeoutMillis: 2000
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Graceful shutdown
|
|
271
|
+
process.on("SIGTERM", async () => {
|
|
272
|
+
await pool.end();
|
|
273
|
+
process.exit(0);
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Troubleshooting
|
|
278
|
+
|
|
279
|
+
### Anonymous Functions
|
|
280
|
+
|
|
281
|
+
Queries from anonymous functions are labeled as:
|
|
282
|
+
```sql
|
|
283
|
+
/*func_name=anonymous,file=src/index.ts,line=123*/ SELECT ...
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Unknown File Paths
|
|
287
|
+
|
|
288
|
+
If stack traces can't be captured (rare):
|
|
289
|
+
```sql
|
|
290
|
+
/*func_name=unknown,file=unknown,line=0*/ SELECT ...
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Disabling Tracking
|
|
294
|
+
|
|
295
|
+
For testing or specific scenarios, use standard `pg.Pool`:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { Pool } from "pg";
|
|
299
|
+
|
|
300
|
+
const pool = process.env.NODE_ENV === "test"
|
|
301
|
+
? new Pool({ connectionString })
|
|
302
|
+
: new TrackedPool({ connectionString });
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Requirements
|
|
306
|
+
|
|
307
|
+
- Node.js >= 14.0.0
|
|
308
|
+
- PostgreSQL client library (`pg`) >= 8.0.0
|
|
309
|
+
|
|
310
|
+
## Testing
|
|
311
|
+
|
|
312
|
+
This package includes comprehensive test suites to ensure tracking comments work correctly:
|
|
313
|
+
|
|
314
|
+
### Unit Tests
|
|
315
|
+
|
|
316
|
+
The unit tests verify that tracking comments are properly added to all query types:
|
|
317
|
+
```bash
|
|
318
|
+
npm test
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Integration Tests
|
|
322
|
+
|
|
323
|
+
Integration tests verify that tracking comments appear in `pg_stat_statements` with a real PostgreSQL instance:
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
# Start PostgreSQL with pg_stat_statements enabled
|
|
327
|
+
docker run -d --name postgres-test \
|
|
328
|
+
-e POSTGRES_PASSWORD=test \
|
|
329
|
+
-p 5432:5432 \
|
|
330
|
+
postgres:latest
|
|
331
|
+
|
|
332
|
+
# Enable the extension
|
|
333
|
+
docker exec -it postgres-test psql -U postgres \
|
|
334
|
+
-c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;"
|
|
335
|
+
|
|
336
|
+
# Run integration tests
|
|
337
|
+
npm run test:integration
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
For detailed testing documentation, see [TESTING.md](./TESTING.md).
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
MIT
|
|
345
|
+
|
|
346
|
+
## Contributing
|
|
347
|
+
|
|
348
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
349
|
+
|
|
350
|
+
## Related Projects
|
|
351
|
+
|
|
352
|
+
- [node-postgres](https://github.com/brianc/node-postgres) - PostgreSQL client for Node.js
|
|
353
|
+
- [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) - PostgreSQL query performance tracking
|
|
354
|
+
|
|
355
|
+
## Support
|
|
356
|
+
|
|
357
|
+
- 📖 [Documentation](https://github.com/top-stats/postgres-tracked-pool#readme)
|
|
358
|
+
- 🐛 [Issue Tracker](https://github.com/top-stats/postgres-tracked-pool/issues)
|
|
359
|
+
- 💬 [Discussions](https://github.com/top-stats/postgres-tracked-pool/discussions)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
/**
|
|
3
|
+
* A wrapper around pg.Pool that automatically adds tracking comments to SQL queries
|
|
4
|
+
* Comments include: function name, file path, and line number
|
|
5
|
+
* Example format in SQL: slash-star func_name=functionName,file=filePath,line=lineNumber star-slash
|
|
6
|
+
*/
|
|
7
|
+
export declare class TrackedPool extends pg.Pool {
|
|
8
|
+
/**
|
|
9
|
+
* Adds tracking comment to SQL query
|
|
10
|
+
* @param sql The SQL query string
|
|
11
|
+
* @param callSite Stack trace entry containing caller information
|
|
12
|
+
* @returns SQL query with tracking comment prepended
|
|
13
|
+
*/
|
|
14
|
+
private addTrackingComment;
|
|
15
|
+
/**
|
|
16
|
+
* Gets the caller's stack frame (skipping internal wrapper calls)
|
|
17
|
+
* @returns The stack frame of the actual caller
|
|
18
|
+
*/
|
|
19
|
+
private getCallerSite;
|
|
20
|
+
/**
|
|
21
|
+
* Overrides the query method to add tracking comments
|
|
22
|
+
*/
|
|
23
|
+
query(queryTextOrConfig: any, values?: any, callback?: any): any;
|
|
24
|
+
/**
|
|
25
|
+
* Overrides the connect method to return a tracked client
|
|
26
|
+
*/
|
|
27
|
+
connect(): Promise<pg.PoolClient>;
|
|
28
|
+
connect(callback: (err: Error | undefined, client: pg.PoolClient | undefined, done: (release?: any) => void) => void): void;
|
|
29
|
+
/**
|
|
30
|
+
* Wraps a PoolClient with tracking functionality
|
|
31
|
+
*/
|
|
32
|
+
private wrapClient;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=TrackedPool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrackedPool.d.ts","sourceRoot":"","sources":["../src/TrackedPool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,EAAE,CAAC,IAAI;IACtC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAuC1B;;;OAGG;IACH,OAAO,CAAC,aAAa;IA+BrB;;OAEG;IAEH,KAAK,CAAC,iBAAiB,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,GAAG;IAqBhE;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC;IAEjC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,SAAS,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,IAAI;IAoB3H;;OAEG;IACH,OAAO,CAAC,UAAU;CA2BnB"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TrackedPool = void 0;
|
|
7
|
+
const pg_1 = __importDefault(require("pg"));
|
|
8
|
+
/**
|
|
9
|
+
* A wrapper around pg.Pool that automatically adds tracking comments to SQL queries
|
|
10
|
+
* Comments include: function name, file path, and line number
|
|
11
|
+
* Example format in SQL: slash-star func_name=functionName,file=filePath,line=lineNumber star-slash
|
|
12
|
+
*/
|
|
13
|
+
class TrackedPool extends pg_1.default.Pool {
|
|
14
|
+
/**
|
|
15
|
+
* Adds tracking comment to SQL query
|
|
16
|
+
* @param sql The SQL query string
|
|
17
|
+
* @param callSite Stack trace entry containing caller information
|
|
18
|
+
* @returns SQL query with tracking comment prepended
|
|
19
|
+
*/
|
|
20
|
+
addTrackingComment(sql, callSite) {
|
|
21
|
+
if (!callSite) {
|
|
22
|
+
return sql;
|
|
23
|
+
}
|
|
24
|
+
// Check if SQL already has a tracking comment (to prevent duplicates)
|
|
25
|
+
if (sql.trim().endsWith("*/") && sql.includes("/*func_name=")) {
|
|
26
|
+
return sql;
|
|
27
|
+
}
|
|
28
|
+
const functionName = callSite.getFunctionName() || callSite.getMethodName() || "anonymous";
|
|
29
|
+
const fileName = callSite.getFileName() || "unknown";
|
|
30
|
+
const lineNumber = callSite.getLineNumber() || 0;
|
|
31
|
+
// Extract relative path from file name (remove workspace path prefix and node_modules)
|
|
32
|
+
let relativePath = fileName;
|
|
33
|
+
// First, try to match workspace folders
|
|
34
|
+
const workspaceMatch = fileName.match(/\/(sdk|bot|api|web|interactions|moderation|servers)\/(.+)$/);
|
|
35
|
+
if (workspaceMatch) {
|
|
36
|
+
relativePath = `${workspaceMatch[1]}/${workspaceMatch[2]}`;
|
|
37
|
+
}
|
|
38
|
+
else if (fileName.includes("node_modules")) {
|
|
39
|
+
// If it's from node_modules, just show the package name
|
|
40
|
+
const nodeModulesMatch = fileName.match(/node_modules\/(@[^/]+\/[^/]+|[^/]+)/);
|
|
41
|
+
if (nodeModulesMatch) {
|
|
42
|
+
relativePath = `[${nodeModulesMatch[1]}]`;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
relativePath = "[node_modules]";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Fallback: just use the filename without full path
|
|
50
|
+
relativePath = fileName.split("/").pop() || fileName;
|
|
51
|
+
}
|
|
52
|
+
const comment = `/*func_name=${functionName},file=${relativePath},line=${lineNumber}*/`;
|
|
53
|
+
return sql.trim() + " " + comment;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Gets the caller's stack frame (skipping internal wrapper calls)
|
|
57
|
+
* @returns The stack frame of the actual caller
|
|
58
|
+
*/
|
|
59
|
+
getCallerSite() {
|
|
60
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
61
|
+
try {
|
|
62
|
+
Error.prepareStackTrace = (_, stack) => stack;
|
|
63
|
+
const stack = new Error().stack;
|
|
64
|
+
// Find the first stack frame that's not from this file or pg-pool internals
|
|
65
|
+
const callerFrame = stack.find(frame => {
|
|
66
|
+
const fileName = frame.getFileName();
|
|
67
|
+
if (!fileName)
|
|
68
|
+
return false;
|
|
69
|
+
// Skip our wrapper files
|
|
70
|
+
if (fileName.includes("TrackedPool.ts") || fileName.includes("TrackedPool.js")) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
// Skip pg-pool internal files
|
|
74
|
+
if (fileName.includes("pg-pool") || fileName.includes("node_modules/pg/")) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
});
|
|
79
|
+
return callerFrame;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Overrides the query method to add tracking comments
|
|
87
|
+
*/
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
query(queryTextOrConfig, values, callback) {
|
|
90
|
+
const callSite = this.getCallerSite();
|
|
91
|
+
// Handle different query signatures
|
|
92
|
+
if (typeof queryTextOrConfig === "string") {
|
|
93
|
+
// Simple query: query(text, values?, callback?)
|
|
94
|
+
const trackedQuery = this.addTrackingComment(queryTextOrConfig, callSite);
|
|
95
|
+
return super.query(trackedQuery, values, callback);
|
|
96
|
+
}
|
|
97
|
+
else if (queryTextOrConfig && typeof queryTextOrConfig === "object") {
|
|
98
|
+
// Query config object: query({ text, values, ... })
|
|
99
|
+
const trackedConfig = {
|
|
100
|
+
...queryTextOrConfig,
|
|
101
|
+
text: this.addTrackingComment(queryTextOrConfig.text, callSite)
|
|
102
|
+
};
|
|
103
|
+
return super.query(trackedConfig, values, callback);
|
|
104
|
+
}
|
|
105
|
+
// Fallback to original behavior
|
|
106
|
+
return super.query(queryTextOrConfig, values, callback);
|
|
107
|
+
}
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
|
+
connect(callback) {
|
|
110
|
+
if (callback) {
|
|
111
|
+
// Callback-based signature
|
|
112
|
+
return super.connect((err, client, done) => {
|
|
113
|
+
if (err || !client) {
|
|
114
|
+
return callback(err, client, done);
|
|
115
|
+
}
|
|
116
|
+
const trackedClient = this.wrapClient(client);
|
|
117
|
+
callback(undefined, trackedClient, done);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Promise-based signature
|
|
122
|
+
return super.connect().then(client => {
|
|
123
|
+
return this.wrapClient(client);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Wraps a PoolClient with tracking functionality
|
|
129
|
+
*/
|
|
130
|
+
wrapClient(client) {
|
|
131
|
+
const originalQuery = client.query.bind(client);
|
|
132
|
+
const addTrackingComment = this.addTrackingComment.bind(this);
|
|
133
|
+
const getCallerSite = this.getCallerSite.bind(this);
|
|
134
|
+
// Override the query method
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
client.query = function (queryTextOrConfig, values, callback) {
|
|
137
|
+
const callSite = getCallerSite();
|
|
138
|
+
// Handle different query signatures
|
|
139
|
+
if (typeof queryTextOrConfig === "string") {
|
|
140
|
+
const trackedQuery = addTrackingComment(queryTextOrConfig, callSite);
|
|
141
|
+
return originalQuery(trackedQuery, values, callback);
|
|
142
|
+
}
|
|
143
|
+
else if (queryTextOrConfig && typeof queryTextOrConfig === "object") {
|
|
144
|
+
const trackedConfig = {
|
|
145
|
+
...queryTextOrConfig,
|
|
146
|
+
text: addTrackingComment(queryTextOrConfig.text, callSite)
|
|
147
|
+
};
|
|
148
|
+
return originalQuery(trackedConfig, values, callback);
|
|
149
|
+
}
|
|
150
|
+
return originalQuery(queryTextOrConfig, values, callback);
|
|
151
|
+
};
|
|
152
|
+
return client;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
exports.TrackedPool = TrackedPool;
|
|
156
|
+
//# sourceMappingURL=TrackedPool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrackedPool.js","sourceRoot":"","sources":["../src/TrackedPool.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TrackedPool = void 0;
|
|
4
|
+
var TrackedPool_1 = require("./TrackedPool");
|
|
5
|
+
Object.defineProperty(exports, "TrackedPool", { enumerable: true, get: function () { return TrackedPool_1.TrackedPool; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAAnC,0GAAA,WAAW,OAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postgres-tracked-pool",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A PostgreSQL connection pool wrapper that automatically adds tracking comments to SQL queries with function name, file path, and line number",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest",
|
|
12
|
+
"test:ui": "vitest --ui",
|
|
13
|
+
"test:coverage": "vitest run --coverage"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"postgresql",
|
|
17
|
+
"postgres",
|
|
18
|
+
"pg",
|
|
19
|
+
"pool",
|
|
20
|
+
"tracking",
|
|
21
|
+
"monitoring",
|
|
22
|
+
"debugging",
|
|
23
|
+
"query",
|
|
24
|
+
"sql",
|
|
25
|
+
"database",
|
|
26
|
+
"observability"
|
|
27
|
+
],
|
|
28
|
+
"author": "Top Stats",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/top-stats/postgres-tracked-pool.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/top-stats/postgres-tracked-pool/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/top-stats/postgres-tracked-pool#readme",
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"pg": "^8.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"@types/pg": "^8.0.0",
|
|
44
|
+
"@vitest/ui": "^4.0.17",
|
|
45
|
+
"pg": "^8.17.2",
|
|
46
|
+
"pg-mem": "^3.0.5",
|
|
47
|
+
"typescript": "^5.0.0",
|
|
48
|
+
"vitest": "^4.0.17"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=14.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|