chub-dev 0.1.0 → 0.1.2-beta.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.
Files changed (139) hide show
  1. package/README.md +55 -0
  2. package/bin/chub-mcp +2 -0
  3. package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
  4. package/dist/airtable/docs/database/python/DOC.md +1735 -0
  5. package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
  6. package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
  7. package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
  8. package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
  9. package/dist/asana/docs/tasks/DOC.md +1396 -0
  10. package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
  11. package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
  12. package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
  13. package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
  14. package/dist/auth0/docs/identity/python/DOC.md +1199 -0
  15. package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
  16. package/dist/aws/docs/s3/python/DOC.md +1807 -0
  17. package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
  18. package/dist/binance/docs/trading/python/DOC.md +1454 -0
  19. package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
  20. package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
  21. package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
  22. package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
  23. package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
  24. package/dist/clerk/docs/auth/python/DOC.md +274 -0
  25. package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
  26. package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
  27. package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
  28. package/dist/cohere/docs/llm/DOC.md +1335 -0
  29. package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
  30. package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
  31. package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
  32. package/dist/deepgram/docs/speech/python/DOC.md +685 -0
  33. package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
  34. package/dist/deepl/docs/translation/python/DOC.md +944 -0
  35. package/dist/deepseek/docs/llm/DOC.md +1220 -0
  36. package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
  37. package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
  38. package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
  39. package/dist/discord/docs/bot/python/DOC.md +1130 -0
  40. package/dist/elasticsearch/docs/search/DOC.md +1634 -0
  41. package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
  42. package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
  43. package/dist/firebase/docs/auth/DOC.md +1015 -0
  44. package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
  45. package/dist/gemini/docs/genai/python/DOC.md +555 -0
  46. package/dist/github/docs/octokit/DOC.md +1560 -0
  47. package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
  48. package/dist/google/docs/bigquery/python/DOC.md +1503 -0
  49. package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
  50. package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
  51. package/dist/huggingface/docs/transformers/DOC.md +948 -0
  52. package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
  53. package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
  54. package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
  55. package/dist/jira/docs/issues/python/DOC.md +1492 -0
  56. package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
  57. package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
  58. package/dist/landingai-ade/docs/api/DOC.md +620 -0
  59. package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
  60. package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
  61. package/dist/landingai-ade/skills/SKILL.md +489 -0
  62. package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
  63. package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
  64. package/dist/linear/docs/tracker/DOC.md +1554 -0
  65. package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
  66. package/dist/livekit/docs/realtime/python/DOC.md +163 -0
  67. package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
  68. package/dist/meilisearch/docs/search/DOC.md +1241 -0
  69. package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
  70. package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
  71. package/dist/mongodb/docs/atlas/DOC.md +2041 -0
  72. package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
  73. package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
  74. package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
  75. package/dist/okta/docs/identity/python/DOC.md +1401 -0
  76. package/dist/openai/docs/chat/javascript/DOC.md +407 -0
  77. package/dist/openai/docs/chat/python/DOC.md +568 -0
  78. package/dist/paypal/docs/checkout/DOC.md +278 -0
  79. package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
  80. package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
  81. package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
  82. package/dist/plaid/docs/banking/python/DOC.md +1203 -0
  83. package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
  84. package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
  85. package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
  86. package/dist/prisma/docs/orm/python/DOC.md +1317 -0
  87. package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
  88. package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
  89. package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
  90. package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
  91. package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
  92. package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
  93. package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
  94. package/dist/redis/docs/key-value/python/DOC.md +2054 -0
  95. package/dist/registry.json +2817 -0
  96. package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
  97. package/dist/resend/docs/email/DOC.md +1271 -0
  98. package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
  99. package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
  100. package/dist/search-index.json +1 -0
  101. package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
  102. package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
  103. package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
  104. package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
  105. package/dist/shopify/docs/storefront/DOC.md +457 -0
  106. package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
  107. package/dist/slack/docs/workspace/python/DOC.md +271 -0
  108. package/dist/square/docs/payments/javascript/DOC.md +1855 -0
  109. package/dist/square/docs/payments/python/DOC.md +1728 -0
  110. package/dist/stripe/docs/api/DOC.md +1727 -0
  111. package/dist/stripe/docs/payments/DOC.md +1726 -0
  112. package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
  113. package/dist/stytch/docs/auth/python/DOC.md +1962 -0
  114. package/dist/supabase/docs/client/DOC.md +1606 -0
  115. package/dist/twilio/docs/messaging/python/DOC.md +469 -0
  116. package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
  117. package/dist/vercel/docs/platform/DOC.md +1940 -0
  118. package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
  119. package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
  120. package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
  121. package/dist/zendesk/docs/support/python/DOC.md +2297 -0
  122. package/package.json +22 -6
  123. package/skills/get-api-docs/SKILL.md +84 -0
  124. package/src/commands/annotate.js +83 -0
  125. package/src/commands/build.js +12 -1
  126. package/src/commands/feedback.js +150 -0
  127. package/src/commands/get.js +83 -42
  128. package/src/commands/search.js +7 -0
  129. package/src/index.js +43 -17
  130. package/src/lib/analytics.js +90 -0
  131. package/src/lib/annotations.js +57 -0
  132. package/src/lib/bm25.js +170 -0
  133. package/src/lib/cache.js +69 -6
  134. package/src/lib/config.js +8 -3
  135. package/src/lib/identity.js +99 -0
  136. package/src/lib/registry.js +103 -20
  137. package/src/lib/telemetry.js +86 -0
  138. package/src/mcp/server.js +177 -0
  139. package/src/mcp/tools.js +251 -0
@@ -0,0 +1,1500 @@
1
+ ---
2
+ name: distributed-db
3
+ description: "CockroachDB with node-postgres (pg) - JavaScript guide for connecting to CockroachDB from Node.js applications"
4
+ metadata:
5
+ languages: "javascript"
6
+ versions: "8.16.3"
7
+ updated-on: "2026-03-02"
8
+ source: maintainer
9
+ tags: "cockroachdb,distributed-db,sql,postgres,database"
10
+ ---
11
+
12
+ # CockroachDB with node-postgres (pg) - JavaScript Guide
13
+
14
+ ## Golden Rule
15
+
16
+ **ALWAYS use the `pg` (node-postgres) package version 8.16.3 or later** to connect to CockroachDB from Node.js applications.
17
+
18
+ ```bash
19
+ npm install pg
20
+ ```
21
+
22
+ CockroachDB is PostgreSQL wire-compatible, meaning it uses the PostgreSQL protocol. The official recommendation is to use the standard PostgreSQL `pg` driver (node-postgres) for JavaScript/Node.js applications.
23
+
24
+ **DO NOT use:**
25
+ - Unofficial or deprecated CockroachDB-specific packages
26
+ - Old versions of `pg` that may not support modern features
27
+ - Random third-party wrappers without proper maintenance
28
+
29
+ **ALWAYS use `pg` (node-postgres)** - it is the officially supported and recommended driver.
30
+
31
+ ---
32
+
33
+ ## Installation
34
+
35
+ ### Basic Installation
36
+
37
+ ```bash
38
+ npm install pg
39
+ ```
40
+
41
+ ### With TypeScript Support
42
+
43
+ ```bash
44
+ npm install pg @types/pg
45
+ ```
46
+
47
+ ### Additional Dependencies (Optional)
48
+
49
+ For SSL connections with custom certificates:
50
+
51
+ ```bash
52
+ npm install pg fs
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Environment Variables
58
+
59
+ ### Basic Configuration
60
+
61
+ Create a `.env` file:
62
+
63
+ ```bash
64
+ # Database connection
65
+ DATABASE_URL=postgresql://root@localhost:26257/defaultdb?sslmode=disable
66
+
67
+ # Or separate variables
68
+ DB_USER=root
69
+ DB_HOST=localhost
70
+ DB_PORT=26257
71
+ DB_NAME=defaultdb
72
+ DB_PASSWORD=
73
+ ```
74
+
75
+ ### Secure Production Configuration
76
+
77
+ ```bash
78
+ # CockroachDB Serverless/Cloud
79
+ DATABASE_URL=postgresql://username:password@host.cockroachlabs.cloud:26257/database?sslmode=verify-full
80
+
81
+ # With certificate paths
82
+ DB_USER=maxroach
83
+ DB_HOST=blue-dog-147.6wr.cockroachlabs.cloud
84
+ DB_PORT=26257
85
+ DB_NAME=defaultdb
86
+ DB_PASSWORD=YourSecurePassword
87
+ DB_SSL_MODE=verify-full
88
+ DB_SSL_ROOT_CERT=/path/to/root.crt
89
+ DB_SSL_CERT=/path/to/client.crt
90
+ DB_SSL_KEY=/path/to/client.key
91
+ ```
92
+
93
+ ### Loading Environment Variables
94
+
95
+ ```javascript
96
+ require('dotenv').config();
97
+
98
+ const config = {
99
+ user: process.env.DB_USER || 'root',
100
+ host: process.env.DB_HOST || 'localhost',
101
+ database: process.env.DB_NAME || 'defaultdb',
102
+ password: process.env.DB_PASSWORD || '',
103
+ port: parseInt(process.env.DB_PORT) || 26257,
104
+ };
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Initialization
110
+
111
+ ### Basic Client Connection
112
+
113
+ ```javascript
114
+ const { Client } = require('pg');
115
+
116
+ const client = new Client({
117
+ user: 'root',
118
+ host: 'localhost',
119
+ database: 'defaultdb',
120
+ port: 26257,
121
+ ssl: false
122
+ });
123
+
124
+ client.connect()
125
+ .then(() => console.log('Connected to CockroachDB'))
126
+ .catch(err => console.error('Connection error', err.stack));
127
+ ```
128
+
129
+ ### Connection Pool (Recommended)
130
+
131
+ ```javascript
132
+ const { Pool } = require('pg');
133
+
134
+ const pool = new Pool({
135
+ user: 'root',
136
+ host: 'localhost',
137
+ database: 'defaultdb',
138
+ port: 26257,
139
+ max: 10, // Maximum pool size
140
+ min: 2, // Minimum pool size
141
+ idleTimeoutMillis: 30000, // Close idle clients after 30 seconds
142
+ connectionTimeoutMillis: 2000, // Return error after 2 seconds if connection unavailable
143
+ });
144
+
145
+ // Test the connection
146
+ pool.query('SELECT NOW()', (err, res) => {
147
+ if (err) {
148
+ console.error('Error executing query', err.stack);
149
+ } else {
150
+ console.log('Connected to CockroachDB:', res.rows[0]);
151
+ }
152
+ });
153
+ ```
154
+
155
+ ### Async/Await Pool Connection
156
+
157
+ ```javascript
158
+ const { Pool } = require('pg');
159
+
160
+ const pool = new Pool({
161
+ user: 'root',
162
+ host: 'localhost',
163
+ database: 'defaultdb',
164
+ port: 26257,
165
+ });
166
+
167
+ async function testConnection() {
168
+ try {
169
+ const client = await pool.connect();
170
+ const res = await client.query('SELECT version()');
171
+ console.log('CockroachDB version:', res.rows[0].version);
172
+ client.release();
173
+ } catch (err) {
174
+ console.error('Error:', err);
175
+ }
176
+ }
177
+
178
+ testConnection();
179
+ ```
180
+
181
+ ### SSL Configuration for Production
182
+
183
+ ```javascript
184
+ const { Pool } = require('pg');
185
+ const fs = require('fs');
186
+
187
+ const pool = new Pool({
188
+ user: 'maxroach',
189
+ host: 'blue-dog-147.cockroachlabs.cloud',
190
+ database: 'defaultdb',
191
+ password: 'YourPassword',
192
+ port: 26257,
193
+ ssl: {
194
+ rejectUnauthorized: true,
195
+ ca: fs.readFileSync('/path/to/root.crt').toString(),
196
+ cert: fs.readFileSync('/path/to/client.crt').toString(),
197
+ key: fs.readFileSync('/path/to/client.key').toString(),
198
+ },
199
+ });
200
+ ```
201
+
202
+ ### Connection String Format
203
+
204
+ ```javascript
205
+ const { Pool } = require('pg');
206
+
207
+ // Local insecure cluster
208
+ const pool = new Pool({
209
+ connectionString: 'postgresql://root@localhost:26257/defaultdb?sslmode=disable'
210
+ });
211
+
212
+ // Production secure cluster
213
+ const pool = new Pool({
214
+ connectionString: 'postgresql://user:password@host.cockroachlabs.cloud:26257/database?sslmode=verify-full'
215
+ });
216
+
217
+ // With environment variable
218
+ const pool = new Pool({
219
+ connectionString: process.env.DATABASE_URL
220
+ });
221
+ ```
222
+
223
+ ### Complete Initialization with Error Handling
224
+
225
+ ```javascript
226
+ const { Pool } = require('pg');
227
+
228
+ const pool = new Pool({
229
+ user: process.env.DB_USER || 'root',
230
+ host: process.env.DB_HOST || 'localhost',
231
+ database: process.env.DB_NAME || 'defaultdb',
232
+ password: process.env.DB_PASSWORD || '',
233
+ port: parseInt(process.env.DB_PORT) || 26257,
234
+ max: 20,
235
+ min: 5,
236
+ idleTimeoutMillis: 300000, // 5 minutes
237
+ connectionTimeoutMillis: 5000,
238
+ });
239
+
240
+ pool.on('error', (err, client) => {
241
+ console.error('Unexpected error on idle client', err);
242
+ process.exit(-1);
243
+ });
244
+
245
+ pool.on('connect', () => {
246
+ console.log('New client connected to CockroachDB pool');
247
+ });
248
+
249
+ pool.on('remove', () => {
250
+ console.log('Client removed from pool');
251
+ });
252
+
253
+ async function initializeDatabase() {
254
+ const client = await pool.connect();
255
+ try {
256
+ await client.query('SELECT 1');
257
+ console.log('Database connection established');
258
+ } catch (err) {
259
+ console.error('Failed to connect to database:', err);
260
+ throw err;
261
+ } finally {
262
+ client.release();
263
+ }
264
+ }
265
+
266
+ initializeDatabase();
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Core API Operations
272
+
273
+ ### Basic Queries
274
+
275
+ #### Simple SELECT Query
276
+
277
+ ```javascript
278
+ const { Pool } = require('pg');
279
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
280
+
281
+ async function getUsers() {
282
+ const client = await pool.connect();
283
+ try {
284
+ const result = await client.query('SELECT * FROM users');
285
+ console.log('Users:', result.rows);
286
+ return result.rows;
287
+ } catch (err) {
288
+ console.error('Query error:', err);
289
+ throw err;
290
+ } finally {
291
+ client.release();
292
+ }
293
+ }
294
+
295
+ getUsers();
296
+ ```
297
+
298
+ #### Parameterized Query
299
+
300
+ ```javascript
301
+ async function getUserById(id) {
302
+ const client = await pool.connect();
303
+ try {
304
+ const query = 'SELECT * FROM users WHERE id = $1';
305
+ const result = await client.query(query, [id]);
306
+ return result.rows[0];
307
+ } catch (err) {
308
+ console.error('Error fetching user:', err);
309
+ throw err;
310
+ } finally {
311
+ client.release();
312
+ }
313
+ }
314
+
315
+ getUserById('123e4567-e89b-12d3-a456-426614174000');
316
+ ```
317
+
318
+ #### Multiple Parameters
319
+
320
+ ```javascript
321
+ async function searchUsers(city, minAge) {
322
+ const client = await pool.connect();
323
+ try {
324
+ const query = 'SELECT * FROM users WHERE city = $1 AND age >= $2';
325
+ const result = await client.query(query, [city, minAge]);
326
+ return result.rows;
327
+ } finally {
328
+ client.release();
329
+ }
330
+ }
331
+
332
+ searchUsers('Seattle', 25);
333
+ ```
334
+
335
+ ### INSERT Operations
336
+
337
+ #### Single Row Insert
338
+
339
+ ```javascript
340
+ async function createUser(name, email, city) {
341
+ const client = await pool.connect();
342
+ try {
343
+ const query = `
344
+ INSERT INTO users (name, email, city)
345
+ VALUES ($1, $2, $3)
346
+ RETURNING *
347
+ `;
348
+ const result = await client.query(query, [name, email, city]);
349
+ console.log('Created user:', result.rows[0]);
350
+ return result.rows[0];
351
+ } finally {
352
+ client.release();
353
+ }
354
+ }
355
+
356
+ createUser('John Doe', 'john@example.com', 'New York');
357
+ ```
358
+
359
+ #### Multiple Row Insert
360
+
361
+ ```javascript
362
+ async function createMultipleUsers(users) {
363
+ const client = await pool.connect();
364
+ try {
365
+ const query = `
366
+ INSERT INTO users (name, email, city)
367
+ VALUES
368
+ ($1, $2, $3),
369
+ ($4, $5, $6),
370
+ ($7, $8, $9)
371
+ RETURNING id, name
372
+ `;
373
+ const values = users.flatMap(u => [u.name, u.email, u.city]);
374
+ const result = await client.query(query, values);
375
+ return result.rows;
376
+ } finally {
377
+ client.release();
378
+ }
379
+ }
380
+
381
+ createMultipleUsers([
382
+ { name: 'Alice', email: 'alice@example.com', city: 'Boston' },
383
+ { name: 'Bob', email: 'bob@example.com', city: 'Chicago' },
384
+ { name: 'Carol', email: 'carol@example.com', city: 'Denver' }
385
+ ]);
386
+ ```
387
+
388
+ #### Insert with Default Values
389
+
390
+ ```javascript
391
+ async function createDriver(city) {
392
+ const client = await pool.connect();
393
+ try {
394
+ const query = `
395
+ INSERT INTO drivers (id, city, created_at)
396
+ VALUES (gen_random_uuid(), $1, now())
397
+ RETURNING *
398
+ `;
399
+ const result = await client.query(query, [city]);
400
+ return result.rows[0];
401
+ } finally {
402
+ client.release();
403
+ }
404
+ }
405
+
406
+ createDriver('Seattle');
407
+ ```
408
+
409
+ #### Insert with ON CONFLICT
410
+
411
+ ```javascript
412
+ async function upsertUser(email, name, city) {
413
+ const client = await pool.connect();
414
+ try {
415
+ const query = `
416
+ INSERT INTO users (email, name, city)
417
+ VALUES ($1, $2, $3)
418
+ ON CONFLICT (email)
419
+ DO UPDATE SET
420
+ name = EXCLUDED.name,
421
+ city = EXCLUDED.city,
422
+ updated_at = now()
423
+ RETURNING *
424
+ `;
425
+ const result = await client.query(query, [email, name, city]);
426
+ return result.rows[0];
427
+ } finally {
428
+ client.release();
429
+ }
430
+ }
431
+
432
+ upsertUser('john@example.com', 'John Smith', 'Los Angeles');
433
+ ```
434
+
435
+ ### UPDATE Operations
436
+
437
+ #### Simple Update
438
+
439
+ ```javascript
440
+ async function updateUserCity(userId, newCity) {
441
+ const client = await pool.connect();
442
+ try {
443
+ const query = `
444
+ UPDATE users
445
+ SET city = $1, updated_at = now()
446
+ WHERE id = $2
447
+ RETURNING *
448
+ `;
449
+ const result = await client.query(query, [newCity, userId]);
450
+ return result.rows[0];
451
+ } finally {
452
+ client.release();
453
+ }
454
+ }
455
+
456
+ updateUserCity('123e4567-e89b-12d3-a456-426614174000', 'Portland');
457
+ ```
458
+
459
+ #### Conditional Update
460
+
461
+ ```javascript
462
+ async function activateUser(email) {
463
+ const client = await pool.connect();
464
+ try {
465
+ const query = `
466
+ UPDATE users
467
+ SET status = 'active', activated_at = now()
468
+ WHERE email = $1 AND status = 'pending'
469
+ RETURNING id, email, status
470
+ `;
471
+ const result = await client.query(query, [email]);
472
+ if (result.rowCount === 0) {
473
+ throw new Error('User not found or already active');
474
+ }
475
+ return result.rows[0];
476
+ } finally {
477
+ client.release();
478
+ }
479
+ }
480
+
481
+ activateUser('john@example.com');
482
+ ```
483
+
484
+ #### Bulk Update
485
+
486
+ ```javascript
487
+ async function updateVehicleStatus(city, newStatus) {
488
+ const client = await pool.connect();
489
+ try {
490
+ const query = `
491
+ UPDATE vehicles
492
+ SET status = $1
493
+ WHERE city = $2
494
+ RETURNING id, status
495
+ `;
496
+ const result = await client.query(query, [newStatus, city]);
497
+ console.log(`Updated ${result.rowCount} vehicles`);
498
+ return result.rows;
499
+ } finally {
500
+ client.release();
501
+ }
502
+ }
503
+
504
+ updateVehicleStatus('New York', 'available');
505
+ ```
506
+
507
+ ### DELETE Operations
508
+
509
+ #### Simple Delete
510
+
511
+ ```javascript
512
+ async function deleteUser(userId) {
513
+ const client = await pool.connect();
514
+ try {
515
+ const query = 'DELETE FROM users WHERE id = $1 RETURNING *';
516
+ const result = await client.query(query, [userId]);
517
+ return result.rows[0];
518
+ } finally {
519
+ client.release();
520
+ }
521
+ }
522
+
523
+ deleteUser('123e4567-e89b-12d3-a456-426614174000');
524
+ ```
525
+
526
+ #### Conditional Delete
527
+
528
+ ```javascript
529
+ async function deleteInactiveUsers(daysInactive) {
530
+ const client = await pool.connect();
531
+ try {
532
+ const result = await client.query(
533
+ `DELETE FROM users
534
+ WHERE last_login < now() - make_interval(days => $1)
535
+ RETURNING id, email`,
536
+ [daysInactive]
537
+ );
538
+ console.log(`Deleted ${result.rowCount} inactive users`);
539
+ return result.rows;
540
+ } finally {
541
+ client.release();
542
+ }
543
+ }
544
+
545
+ deleteInactiveUsers(90);
546
+ ```
547
+
548
+ #### Delete with JOIN
549
+
550
+ ```javascript
551
+ async function deleteUserOrders(userId) {
552
+ const client = await pool.connect();
553
+ try {
554
+ const query = `
555
+ DELETE FROM orders
556
+ WHERE user_id = $1
557
+ RETURNING id, total_amount
558
+ `;
559
+ const result = await client.query(query, [userId]);
560
+ return result.rows;
561
+ } finally {
562
+ client.release();
563
+ }
564
+ }
565
+
566
+ deleteUserOrders('123e4567-e89b-12d3-a456-426614174000');
567
+ ```
568
+
569
+ ---
570
+
571
+ ## Transactions
572
+
573
+ ### Basic Transaction
574
+
575
+ ```javascript
576
+ async function transferFunds(fromAccount, toAccount, amount) {
577
+ const client = await pool.connect();
578
+ try {
579
+ await client.query('BEGIN');
580
+
581
+ // Deduct from sender
582
+ await client.query(
583
+ 'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
584
+ [amount, fromAccount]
585
+ );
586
+
587
+ // Add to receiver
588
+ await client.query(
589
+ 'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
590
+ [amount, toAccount]
591
+ );
592
+
593
+ await client.query('COMMIT');
594
+ console.log('Transfer completed successfully');
595
+ } catch (err) {
596
+ await client.query('ROLLBACK');
597
+ console.error('Transfer failed, rolled back:', err);
598
+ throw err;
599
+ } finally {
600
+ client.release();
601
+ }
602
+ }
603
+
604
+ transferFunds('account-1', 'account-2', 100.50);
605
+ ```
606
+
607
+ ### Transaction with Savepoints
608
+
609
+ ```javascript
610
+ async function complexTransaction() {
611
+ const client = await pool.connect();
612
+ try {
613
+ await client.query('BEGIN');
614
+
615
+ // First operation
616
+ await client.query('INSERT INTO logs (message) VALUES ($1)', ['Started']);
617
+
618
+ // Create savepoint
619
+ await client.query('SAVEPOINT sp1');
620
+
621
+ try {
622
+ await client.query('INSERT INTO users (email) VALUES ($1)', ['test@example.com']);
623
+ } catch (err) {
624
+ // Rollback to savepoint on error
625
+ await client.query('ROLLBACK TO SAVEPOINT sp1');
626
+ console.log('User insert failed, rolled back to savepoint');
627
+ }
628
+
629
+ // Continue with transaction
630
+ await client.query('INSERT INTO logs (message) VALUES ($1)', ['Completed']);
631
+
632
+ await client.query('COMMIT');
633
+ } catch (err) {
634
+ await client.query('ROLLBACK');
635
+ throw err;
636
+ } finally {
637
+ client.release();
638
+ }
639
+ }
640
+
641
+ complexTransaction();
642
+ ```
643
+
644
+ ### Transaction with Retry Logic (CockroachDB Specific)
645
+
646
+ ```javascript
647
+ async function transferWithRetry(fromAccount, toAccount, amount, maxRetries = 3) {
648
+ let retries = 0;
649
+
650
+ while (retries < maxRetries) {
651
+ const client = await pool.connect();
652
+ try {
653
+ await client.query('BEGIN');
654
+
655
+ const fromResult = await client.query(
656
+ 'UPDATE accounts SET balance = balance - $1 WHERE id = $2 RETURNING balance',
657
+ [amount, fromAccount]
658
+ );
659
+
660
+ if (fromResult.rows[0].balance < 0) {
661
+ throw new Error('Insufficient funds');
662
+ }
663
+
664
+ await client.query(
665
+ 'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
666
+ [amount, toAccount]
667
+ );
668
+
669
+ await client.query('COMMIT');
670
+ client.release();
671
+ return { success: true, retries };
672
+
673
+ } catch (err) {
674
+ await client.query('ROLLBACK');
675
+ client.release();
676
+
677
+ // Check if it's a serialization error (40001)
678
+ if (err.code === '40001' && retries < maxRetries - 1) {
679
+ retries++;
680
+ console.log(`Retrying transaction (attempt ${retries + 1})`);
681
+ await new Promise(resolve => setTimeout(resolve, Math.random() * 100));
682
+ continue;
683
+ }
684
+
685
+ throw err;
686
+ }
687
+ }
688
+
689
+ throw new Error('Transaction failed after maximum retries');
690
+ }
691
+
692
+ transferWithRetry('account-1', 'account-2', 100.50);
693
+ ```
694
+
695
+ ### Read-Only Transaction
696
+
697
+ ```javascript
698
+ async function getAccountSummary(userId) {
699
+ const client = await pool.connect();
700
+ try {
701
+ await client.query('BEGIN TRANSACTION READ ONLY');
702
+
703
+ const userResult = await client.query(
704
+ 'SELECT * FROM users WHERE id = $1',
705
+ [userId]
706
+ );
707
+
708
+ const accountsResult = await client.query(
709
+ 'SELECT * FROM accounts WHERE user_id = $1',
710
+ [userId]
711
+ );
712
+
713
+ const transactionsResult = await client.query(
714
+ 'SELECT * FROM transactions WHERE user_id = $1 ORDER BY created_at DESC LIMIT 10',
715
+ [userId]
716
+ );
717
+
718
+ await client.query('COMMIT');
719
+
720
+ return {
721
+ user: userResult.rows[0],
722
+ accounts: accountsResult.rows,
723
+ recentTransactions: transactionsResult.rows
724
+ };
725
+ } catch (err) {
726
+ await client.query('ROLLBACK');
727
+ throw err;
728
+ } finally {
729
+ client.release();
730
+ }
731
+ }
732
+
733
+ getAccountSummary('user-123');
734
+ ```
735
+
736
+ ---
737
+
738
+ ## Advanced Features
739
+
740
+ ### JSON/JSONB Operations
741
+
742
+ #### Inserting JSON Data
743
+
744
+ ```javascript
745
+ async function createProduct(name, metadata) {
746
+ const client = await pool.connect();
747
+ try {
748
+ const query = `
749
+ INSERT INTO products (name, metadata)
750
+ VALUES ($1, $2::jsonb)
751
+ RETURNING *
752
+ `;
753
+ const result = await client.query(query, [name, JSON.stringify(metadata)]);
754
+ return result.rows[0];
755
+ } finally {
756
+ client.release();
757
+ }
758
+ }
759
+
760
+ createProduct('Laptop', {
761
+ brand: 'Dell',
762
+ specs: { ram: '16GB', storage: '512GB SSD' },
763
+ tags: ['electronics', 'computers']
764
+ });
765
+ ```
766
+
767
+ #### Querying JSON Fields
768
+
769
+ ```javascript
770
+ async function findProductsByBrand(brand) {
771
+ const client = await pool.connect();
772
+ try {
773
+ const query = `
774
+ SELECT * FROM products
775
+ WHERE metadata->>'brand' = $1
776
+ `;
777
+ const result = await client.query(query, [brand]);
778
+ return result.rows;
779
+ } finally {
780
+ client.release();
781
+ }
782
+ }
783
+
784
+ findProductsByBrand('Dell');
785
+ ```
786
+
787
+ #### Nested JSON Queries
788
+
789
+ ```javascript
790
+ async function findProductsByRAM(ram) {
791
+ const client = await pool.connect();
792
+ try {
793
+ const query = `
794
+ SELECT * FROM products
795
+ WHERE metadata->'specs'->>'ram' = $1
796
+ `;
797
+ const result = await client.query(query, [ram]);
798
+ return result.rows;
799
+ } finally {
800
+ client.release();
801
+ }
802
+ }
803
+
804
+ findProductsByRAM('16GB');
805
+ ```
806
+
807
+ #### Updating JSON Fields
808
+
809
+ ```javascript
810
+ async function updateProductPrice(productId, newPrice) {
811
+ const client = await pool.connect();
812
+ try {
813
+ const query = `
814
+ UPDATE products
815
+ SET metadata = jsonb_set(metadata, '{price}', $1::jsonb)
816
+ WHERE id = $2
817
+ RETURNING *
818
+ `;
819
+ const result = await client.query(query, [JSON.stringify(newPrice), productId]);
820
+ return result.rows[0];
821
+ } finally {
822
+ client.release();
823
+ }
824
+ }
825
+
826
+ updateProductPrice('product-123', 999.99);
827
+ ```
828
+
829
+ #### JSON Array Operations
830
+
831
+ ```javascript
832
+ async function addProductTag(productId, tag) {
833
+ const client = await pool.connect();
834
+ try {
835
+ const query = `
836
+ UPDATE products
837
+ SET metadata = jsonb_set(
838
+ metadata,
839
+ '{tags}',
840
+ (metadata->'tags')::jsonb || $1::jsonb
841
+ )
842
+ WHERE id = $2
843
+ RETURNING *
844
+ `;
845
+ const result = await client.query(query, [JSON.stringify([tag]), productId]);
846
+ return result.rows[0];
847
+ } finally {
848
+ client.release();
849
+ }
850
+ }
851
+
852
+ addProductTag('product-123', 'featured');
853
+ ```
854
+
855
+ ### Array Operations
856
+
857
+ #### Working with Arrays
858
+
859
+ ```javascript
860
+ async function createUserWithTags(email, tags) {
861
+ const client = await pool.connect();
862
+ try {
863
+ const query = `
864
+ INSERT INTO users (email, tags)
865
+ VALUES ($1, $2)
866
+ RETURNING *
867
+ `;
868
+ const result = await client.query(query, [email, tags]);
869
+ return result.rows[0];
870
+ } finally {
871
+ client.release();
872
+ }
873
+ }
874
+
875
+ createUserWithTags('john@example.com', ['premium', 'verified']);
876
+ ```
877
+
878
+ #### Querying Arrays
879
+
880
+ ```javascript
881
+ async function findUsersByTag(tag) {
882
+ const client = await pool.connect();
883
+ try {
884
+ const query = `
885
+ SELECT * FROM users
886
+ WHERE $1 = ANY(tags)
887
+ `;
888
+ const result = await client.query(query, [tag]);
889
+ return result.rows;
890
+ } finally {
891
+ client.release();
892
+ }
893
+ }
894
+
895
+ findUsersByTag('premium');
896
+ ```
897
+
898
+ #### Array Contains Query
899
+
900
+ ```javascript
901
+ async function findUsersWithAllTags(requiredTags) {
902
+ const client = await pool.connect();
903
+ try {
904
+ const query = `
905
+ SELECT * FROM users
906
+ WHERE tags @> $1::text[]
907
+ `;
908
+ const result = await client.query(query, [requiredTags]);
909
+ return result.rows;
910
+ } finally {
911
+ client.release();
912
+ }
913
+ }
914
+
915
+ findUsersWithAllTags(['premium', 'verified']);
916
+ ```
917
+
918
+ ### Full-Text Search
919
+
920
+ #### Creating a Text Search Query
921
+
922
+ ```javascript
923
+ async function searchArticles(searchTerm) {
924
+ const client = await pool.connect();
925
+ try {
926
+ const query = `
927
+ SELECT id, title, content,
928
+ ts_rank(to_tsvector('english', title || ' ' || content), query) AS rank
929
+ FROM articles,
930
+ to_tsquery('english', $1) query
931
+ WHERE to_tsvector('english', title || ' ' || content) @@ query
932
+ ORDER BY rank DESC
933
+ LIMIT 20
934
+ `;
935
+ const result = await client.query(query, [searchTerm]);
936
+ return result.rows;
937
+ } finally {
938
+ client.release();
939
+ }
940
+ }
941
+
942
+ searchArticles('database performance');
943
+ ```
944
+
945
+ ### Aggregations and Analytics
946
+
947
+ #### Group By and Aggregation
948
+
949
+ ```javascript
950
+ async function getUserStatsByCity() {
951
+ const client = await pool.connect();
952
+ try {
953
+ const query = `
954
+ SELECT
955
+ city,
956
+ COUNT(*) as user_count,
957
+ AVG(age) as avg_age,
958
+ MIN(created_at) as first_user,
959
+ MAX(created_at) as latest_user
960
+ FROM users
961
+ GROUP BY city
962
+ ORDER BY user_count DESC
963
+ `;
964
+ const result = await client.query(query);
965
+ return result.rows;
966
+ } finally {
967
+ client.release();
968
+ }
969
+ }
970
+
971
+ getUserStatsByCity();
972
+ ```
973
+
974
+ #### Window Functions
975
+
976
+ ```javascript
977
+ async function getRankedProducts() {
978
+ const client = await pool.connect();
979
+ try {
980
+ const query = `
981
+ SELECT
982
+ name,
983
+ category,
984
+ price,
985
+ RANK() OVER (PARTITION BY category ORDER BY price DESC) as price_rank,
986
+ AVG(price) OVER (PARTITION BY category) as category_avg_price
987
+ FROM products
988
+ `;
989
+ const result = await client.query(query);
990
+ return result.rows;
991
+ } finally {
992
+ client.release();
993
+ }
994
+ }
995
+
996
+ getRankedProducts();
997
+ ```
998
+
999
+ ### Common Table Expressions (CTEs)
1000
+
1001
+ ```javascript
1002
+ async function getTopSpenders(limit = 10) {
1003
+ const client = await pool.connect();
1004
+ try {
1005
+ const query = `
1006
+ WITH user_totals AS (
1007
+ SELECT
1008
+ user_id,
1009
+ SUM(amount) as total_spent,
1010
+ COUNT(*) as order_count
1011
+ FROM orders
1012
+ WHERE status = 'completed'
1013
+ GROUP BY user_id
1014
+ )
1015
+ SELECT
1016
+ u.id,
1017
+ u.email,
1018
+ ut.total_spent,
1019
+ ut.order_count
1020
+ FROM users u
1021
+ JOIN user_totals ut ON u.id = ut.user_id
1022
+ ORDER BY ut.total_spent DESC
1023
+ LIMIT $1
1024
+ `;
1025
+ const result = await client.query(query, [limit]);
1026
+ return result.rows;
1027
+ } finally {
1028
+ client.release();
1029
+ }
1030
+ }
1031
+
1032
+ getTopSpenders(20);
1033
+ ```
1034
+
1035
+ ### Batch Operations
1036
+
1037
+ #### Batch Insert
1038
+
1039
+ ```javascript
1040
+ async function batchInsertUsers(users) {
1041
+ const client = await pool.connect();
1042
+ try {
1043
+ await client.query('BEGIN');
1044
+
1045
+ for (const user of users) {
1046
+ await client.query(
1047
+ 'INSERT INTO users (name, email, city) VALUES ($1, $2, $3)',
1048
+ [user.name, user.email, user.city]
1049
+ );
1050
+ }
1051
+
1052
+ await client.query('COMMIT');
1053
+ console.log(`Inserted ${users.length} users`);
1054
+ } catch (err) {
1055
+ await client.query('ROLLBACK');
1056
+ throw err;
1057
+ } finally {
1058
+ client.release();
1059
+ }
1060
+ }
1061
+
1062
+ batchInsertUsers([
1063
+ { name: 'Alice', email: 'alice@example.com', city: 'NYC' },
1064
+ { name: 'Bob', email: 'bob@example.com', city: 'LA' }
1065
+ ]);
1066
+ ```
1067
+
1068
+ ---
1069
+
1070
+ ## Schema Management
1071
+
1072
+ ### Creating Tables
1073
+
1074
+ ```javascript
1075
+ async function createUsersTable() {
1076
+ const client = await pool.connect();
1077
+ try {
1078
+ const query = `
1079
+ CREATE TABLE IF NOT EXISTS users (
1080
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1081
+ email STRING UNIQUE NOT NULL,
1082
+ name STRING NOT NULL,
1083
+ city STRING,
1084
+ age INT,
1085
+ tags TEXT[],
1086
+ metadata JSONB,
1087
+ created_at TIMESTAMP DEFAULT now(),
1088
+ updated_at TIMESTAMP DEFAULT now()
1089
+ )
1090
+ `;
1091
+ await client.query(query);
1092
+ console.log('Users table created');
1093
+ } finally {
1094
+ client.release();
1095
+ }
1096
+ }
1097
+
1098
+ createUsersTable();
1099
+ ```
1100
+
1101
+ ### Creating Indexes
1102
+
1103
+ ```javascript
1104
+ async function createIndexes() {
1105
+ const client = await pool.connect();
1106
+ try {
1107
+ // Standard index
1108
+ await client.query('CREATE INDEX IF NOT EXISTS idx_users_city ON users (city)');
1109
+
1110
+ // Multi-column index
1111
+ await client.query('CREATE INDEX IF NOT EXISTS idx_users_city_age ON users (city, age)');
1112
+
1113
+ // Inverted index for JSONB
1114
+ await client.query('CREATE INVERTED INDEX IF NOT EXISTS idx_users_metadata ON users (metadata)');
1115
+
1116
+ // Inverted index for arrays
1117
+ await client.query('CREATE INVERTED INDEX IF NOT EXISTS idx_users_tags ON users (tags)');
1118
+
1119
+ console.log('Indexes created');
1120
+ } finally {
1121
+ client.release();
1122
+ }
1123
+ }
1124
+
1125
+ createIndexes();
1126
+ ```
1127
+
1128
+ ### Altering Tables
1129
+
1130
+ ```javascript
1131
+ async function alterUsersTable() {
1132
+ const client = await pool.connect();
1133
+ try {
1134
+ // Add column
1135
+ await client.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS status STRING DEFAULT \'active\'');
1136
+
1137
+ // Add constraint
1138
+ await client.query('ALTER TABLE users ADD CONSTRAINT check_age CHECK (age >= 0 AND age <= 150)');
1139
+
1140
+ console.log('Table altered successfully');
1141
+ } finally {
1142
+ client.release();
1143
+ }
1144
+ }
1145
+
1146
+ alterUsersTable();
1147
+ ```
1148
+
1149
+ ---
1150
+
1151
+ ## Error Handling
1152
+
1153
+ ### Handling Specific Errors
1154
+
1155
+ ```javascript
1156
+ async function createUserWithErrorHandling(email, name) {
1157
+ const client = await pool.connect();
1158
+ try {
1159
+ const query = 'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *';
1160
+ const result = await client.query(query, [email, name]);
1161
+ return result.rows[0];
1162
+ } catch (err) {
1163
+ if (err.code === '23505') {
1164
+ // Unique violation
1165
+ throw new Error(`User with email ${email} already exists`);
1166
+ } else if (err.code === '23502') {
1167
+ // Not null violation
1168
+ throw new Error('Required field is missing');
1169
+ } else if (err.code === '23503') {
1170
+ // Foreign key violation
1171
+ throw new Error('Referenced record does not exist');
1172
+ } else if (err.code === '40001') {
1173
+ // Serialization failure
1174
+ throw new Error('Transaction conflict, please retry');
1175
+ } else {
1176
+ throw err;
1177
+ }
1178
+ } finally {
1179
+ client.release();
1180
+ }
1181
+ }
1182
+
1183
+ createUserWithErrorHandling('john@example.com', 'John Doe');
1184
+ ```
1185
+
1186
+ ### Connection Error Handling
1187
+
1188
+ ```javascript
1189
+ async function queryWithRetry(query, params, maxRetries = 3) {
1190
+ let retries = 0;
1191
+
1192
+ while (retries < maxRetries) {
1193
+ let client;
1194
+ try {
1195
+ client = await pool.connect();
1196
+ const result = await client.query(query, params);
1197
+ client.release();
1198
+ return result;
1199
+ } catch (err) {
1200
+ if (client) client.release();
1201
+
1202
+ if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
1203
+ retries++;
1204
+ if (retries >= maxRetries) {
1205
+ throw new Error('Database connection failed after retries');
1206
+ }
1207
+ await new Promise(resolve => setTimeout(resolve, 1000 * retries));
1208
+ } else {
1209
+ throw err;
1210
+ }
1211
+ }
1212
+ }
1213
+ }
1214
+
1215
+ queryWithRetry('SELECT * FROM users WHERE id = $1', ['user-123']);
1216
+ ```
1217
+
1218
+ ---
1219
+
1220
+ ## Closing Connections
1221
+
1222
+ ### Graceful Shutdown
1223
+
1224
+ ```javascript
1225
+ async function gracefulShutdown() {
1226
+ console.log('Closing database connections...');
1227
+ await pool.end();
1228
+ console.log('Database connections closed');
1229
+ process.exit(0);
1230
+ }
1231
+
1232
+ process.on('SIGTERM', gracefulShutdown);
1233
+ process.on('SIGINT', gracefulShutdown);
1234
+ ```
1235
+
1236
+ ### Complete Application Example
1237
+
1238
+ ```javascript
1239
+ const { Pool } = require('pg');
1240
+
1241
+ const pool = new Pool({
1242
+ user: process.env.DB_USER || 'root',
1243
+ host: process.env.DB_HOST || 'localhost',
1244
+ database: process.env.DB_NAME || 'defaultdb',
1245
+ port: parseInt(process.env.DB_PORT) || 26257,
1246
+ max: 20,
1247
+ });
1248
+
1249
+ // Query functions
1250
+ async function getAllUsers() {
1251
+ const client = await pool.connect();
1252
+ try {
1253
+ const result = await client.query('SELECT * FROM users');
1254
+ return result.rows;
1255
+ } finally {
1256
+ client.release();
1257
+ }
1258
+ }
1259
+
1260
+ async function createUser(name, email) {
1261
+ const client = await pool.connect();
1262
+ try {
1263
+ const query = 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *';
1264
+ const result = await client.query(query, [name, email]);
1265
+ return result.rows[0];
1266
+ } finally {
1267
+ client.release();
1268
+ }
1269
+ }
1270
+
1271
+ // Graceful shutdown
1272
+ async function shutdown() {
1273
+ await pool.end();
1274
+ process.exit(0);
1275
+ }
1276
+
1277
+ process.on('SIGTERM', shutdown);
1278
+ process.on('SIGINT', shutdown);
1279
+
1280
+ // Export for use in application
1281
+ module.exports = {
1282
+ pool,
1283
+ getAllUsers,
1284
+ createUser,
1285
+ };
1286
+ ```
1287
+
1288
+ ---
1289
+
1290
+ ## Common Patterns
1291
+
1292
+ ### Repository Pattern
1293
+
1294
+ ```javascript
1295
+ class UserRepository {
1296
+ constructor(pool) {
1297
+ this.pool = pool;
1298
+ }
1299
+
1300
+ async findAll() {
1301
+ const client = await this.pool.connect();
1302
+ try {
1303
+ const result = await client.query('SELECT * FROM users');
1304
+ return result.rows;
1305
+ } finally {
1306
+ client.release();
1307
+ }
1308
+ }
1309
+
1310
+ async findById(id) {
1311
+ const client = await this.pool.connect();
1312
+ try {
1313
+ const result = await client.query('SELECT * FROM users WHERE id = $1', [id]);
1314
+ return result.rows[0];
1315
+ } finally {
1316
+ client.release();
1317
+ }
1318
+ }
1319
+
1320
+ async create(user) {
1321
+ const client = await this.pool.connect();
1322
+ try {
1323
+ const query = `
1324
+ INSERT INTO users (name, email, city)
1325
+ VALUES ($1, $2, $3)
1326
+ RETURNING *
1327
+ `;
1328
+ const result = await client.query(query, [user.name, user.email, user.city]);
1329
+ return result.rows[0];
1330
+ } finally {
1331
+ client.release();
1332
+ }
1333
+ }
1334
+
1335
+ async update(id, updates) {
1336
+ const client = await this.pool.connect();
1337
+ try {
1338
+ const query = `
1339
+ UPDATE users
1340
+ SET name = $1, city = $2, updated_at = now()
1341
+ WHERE id = $3
1342
+ RETURNING *
1343
+ `;
1344
+ const result = await client.query(query, [updates.name, updates.city, id]);
1345
+ return result.rows[0];
1346
+ } finally {
1347
+ client.release();
1348
+ }
1349
+ }
1350
+
1351
+ async delete(id) {
1352
+ const client = await this.pool.connect();
1353
+ try {
1354
+ const result = await client.query('DELETE FROM users WHERE id = $1 RETURNING *', [id]);
1355
+ return result.rows[0];
1356
+ } finally {
1357
+ client.release();
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ const userRepo = new UserRepository(pool);
1363
+ ```
1364
+
1365
+ ### Query Builder Pattern
1366
+
1367
+ ```javascript
1368
+ class QueryBuilder {
1369
+ constructor(pool, table) {
1370
+ this.pool = pool;
1371
+ this.table = table;
1372
+ this.whereConditions = [];
1373
+ this.parameters = [];
1374
+ this.orderByClause = '';
1375
+ this.limitClause = '';
1376
+ }
1377
+
1378
+ where(column, operator, value) {
1379
+ this.parameters.push(value);
1380
+ this.whereConditions.push(`${column} ${operator} $${this.parameters.length}`);
1381
+ return this;
1382
+ }
1383
+
1384
+ orderBy(column, direction = 'ASC') {
1385
+ this.orderByClause = `ORDER BY ${column} ${direction}`;
1386
+ return this;
1387
+ }
1388
+
1389
+ limit(count) {
1390
+ this.limitClause = `LIMIT ${count}`;
1391
+ return this;
1392
+ }
1393
+
1394
+ async execute() {
1395
+ const client = await this.pool.connect();
1396
+ try {
1397
+ let query = `SELECT * FROM ${this.table}`;
1398
+
1399
+ if (this.whereConditions.length > 0) {
1400
+ query += ` WHERE ${this.whereConditions.join(' AND ')}`;
1401
+ }
1402
+
1403
+ if (this.orderByClause) {
1404
+ query += ` ${this.orderByClause}`;
1405
+ }
1406
+
1407
+ if (this.limitClause) {
1408
+ query += ` ${this.limitClause}`;
1409
+ }
1410
+
1411
+ const result = await client.query(query, this.parameters);
1412
+ return result.rows;
1413
+ } finally {
1414
+ client.release();
1415
+ }
1416
+ }
1417
+ }
1418
+
1419
+ // Usage
1420
+ const users = await new QueryBuilder(pool, 'users')
1421
+ .where('city', '=', 'Seattle')
1422
+ .where('age', '>=', 25)
1423
+ .orderBy('created_at', 'DESC')
1424
+ .limit(10)
1425
+ .execute();
1426
+ ```
1427
+
1428
+ ---
1429
+
1430
+ ## Performance Optimization
1431
+
1432
+ ### Connection Pooling Configuration
1433
+
1434
+ ```javascript
1435
+ const { Pool } = require('pg');
1436
+
1437
+ const pool = new Pool({
1438
+ user: 'root',
1439
+ host: 'localhost',
1440
+ database: 'defaultdb',
1441
+ port: 26257,
1442
+ max: 20, // Maximum number of clients in the pool
1443
+ min: 5, // Minimum number of clients in the pool
1444
+ idleTimeoutMillis: 300000, // Close idle clients after 5 minutes (CockroachDB recommendation)
1445
+ connectionTimeoutMillis: 5000,
1446
+ allowExitOnIdle: false,
1447
+ });
1448
+ ```
1449
+
1450
+ ### Prepared Statements
1451
+
1452
+ ```javascript
1453
+ async function findUsersPrepared(city) {
1454
+ const client = await pool.connect();
1455
+ try {
1456
+ // Named prepared statement
1457
+ const queryName = 'find-users-by-city';
1458
+ const queryText = 'SELECT * FROM users WHERE city = $1';
1459
+
1460
+ const result = await client.query({
1461
+ name: queryName,
1462
+ text: queryText,
1463
+ values: [city]
1464
+ });
1465
+
1466
+ return result.rows;
1467
+ } finally {
1468
+ client.release();
1469
+ }
1470
+ }
1471
+
1472
+ findUsersPrepared('Seattle');
1473
+ ```
1474
+
1475
+ ### Cursor-Based Pagination
1476
+
1477
+ ```javascript
1478
+ async function paginateUsers(cursorId, pageSize = 20) {
1479
+ const client = await pool.connect();
1480
+ try {
1481
+ const query = `
1482
+ SELECT * FROM users
1483
+ WHERE id > $1
1484
+ ORDER BY id
1485
+ LIMIT $2
1486
+ `;
1487
+ const result = await client.query(query, [cursorId || '00000000-0000-0000-0000-000000000000', pageSize]);
1488
+
1489
+ return {
1490
+ data: result.rows,
1491
+ nextCursor: result.rows.length > 0 ? result.rows[result.rows.length - 1].id : null,
1492
+ hasMore: result.rows.length === pageSize
1493
+ };
1494
+ } finally {
1495
+ client.release();
1496
+ }
1497
+ }
1498
+
1499
+ paginateUsers(null, 20);
1500
+ ```