@entergreat/pg-light 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/README.md ADDED
@@ -0,0 +1,928 @@
1
+ # @entergreat/pg-light ๐Ÿ˜
2
+
3
+ A lightweight PostgreSQL connection utility with one-line setup. Simple, clean, and production-ready.
4
+
5
+ ## Features โœจ
6
+
7
+ - โšก **One-line Connection** - Connect with a single function call
8
+ - ๐Ÿ”— **Connection String or Config** - Flexible connection options
9
+ - ๐ŸŠ **Connection Pooling** - Built-in connection pool management
10
+ - ๐Ÿ”„ **Transaction Support** - Easy transaction handling with auto-rollback
11
+ - ๐Ÿ“Š **Query Interface** - Simple query execution
12
+ - ๐ŸŽจ **Mongoose-like Models** - Friendly API for common CRUD operations
13
+ - ๐Ÿ”จ **Knex Query Builder** - Powerful chainable API for complex queries (NEW!)
14
+ - ๐Ÿ“ **Integrated Logging** - Uses `logger-standard` for consistent logging
15
+ - ๐Ÿงน **Clean Code** - All functions โ‰ค7 lines
16
+ - ๐ŸŽฏ **TypeScript Ready** - JSDoc types for better IDE support
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @entergreat/pg-light
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### Raw SQL Queries
27
+
28
+ ```js
29
+ import { connectDB } from '@entergreat/pg-light';
30
+
31
+ // One-liner connection with connection string
32
+ const db = await connectDB('postgresql://user:pass@localhost:5432/mydb');
33
+
34
+ // Query data
35
+ const result = await db.query('SELECT * FROM users WHERE id = $1', [123]);
36
+ console.log(result.rows);
37
+
38
+ // Close connection
39
+ await db.disconnect();
40
+ ```
41
+
42
+ ### Mongoose-like Models (For Simple CRUD)
43
+
44
+ ```js
45
+ import { connectDB } from '@entergreat/pg-light';
46
+
47
+ const db = await connectDB('postgresql://user:pass@localhost:5432/mydb');
48
+
49
+ // Create a model
50
+ const User = db.model('users');
51
+
52
+ // CRUD operations - No SQL needed!
53
+ const newUser = await User.create({ name: 'John', email: 'john@example.com' });
54
+ const users = await User.find({ active: true });
55
+ const user = await User.findById(1);
56
+ await User.updateById(1, { name: 'Jane' });
57
+ await User.deleteById(1);
58
+
59
+ await db.disconnect();
60
+ ```
61
+
62
+ ### Knex Query Builder (For Complex Queries)
63
+
64
+ ```js
65
+ import { connectDB } from '@entergreat/pg-light';
66
+
67
+ const db = await connectDB('postgresql://user:pass@localhost:5432/mydb');
68
+
69
+ // Complex queries with Knex chainable API
70
+ const popularPosts = await db.builder('posts')
71
+ .select('posts.*', 'users.name as author')
72
+ .join('users', 'posts.user_id', 'users.id')
73
+ .where('posts.views', '>', 1000)
74
+ .whereIn('posts.category', ['tech', 'science'])
75
+ .orderBy('posts.views', 'desc')
76
+ .limit(10);
77
+
78
+ // Aggregations
79
+ const stats = await db.builder('orders')
80
+ .count('* as total_orders')
81
+ .sum('amount as revenue')
82
+ .where('status', 'completed');
83
+
84
+ // Transactions
85
+ await db.knex.transaction(async (trx) => {
86
+ const [user] = await trx('users').insert({ name: 'Alice' }).returning('*');
87
+ await trx('posts').insert({ user_id: user.id, title: 'First Post' });
88
+ });
89
+
90
+ await db.disconnect();
91
+ ```
92
+
93
+ ## API
94
+
95
+ ### `connectDB(config, options?)`
96
+
97
+ Connects to PostgreSQL database and returns a `PgLight` instance.
98
+
99
+ **Parameters:**
100
+ - `config` - Connection string or config object
101
+ - `options.service` - Logger service name (default: `'pg-light'`)
102
+ - `options.logLevel` - Log level: `'debug'`, `'info'`, `'warn'`, `'error'` (default: `'info'`)
103
+
104
+ **Returns:** `Promise<PgLight>`
105
+
106
+ ### Connection Methods
107
+
108
+ #### With Connection String
109
+
110
+ ```js
111
+ const db = await connectDB('postgresql://user:pass@localhost:5432/mydb');
112
+ ```
113
+
114
+ #### With Config Object
115
+
116
+ ```js
117
+ const db = await connectDB({
118
+ host: 'localhost',
119
+ port: 5432,
120
+ database: 'mydb',
121
+ user: 'myuser',
122
+ password: 'mypassword',
123
+ max: 20, // Max connections in pool
124
+ idleTimeoutMillis: 30000,
125
+ connectionTimeoutMillis: 2000
126
+ });
127
+ ```
128
+
129
+ #### With Custom Logger Options
130
+
131
+ ```js
132
+ const db = await connectDB(config, {
133
+ service: 'my-api',
134
+ logLevel: 'debug'
135
+ });
136
+ ```
137
+
138
+ ### PgLight Methods
139
+
140
+ #### `query(text, params?)`
141
+
142
+ Execute a SQL query.
143
+
144
+ ```js
145
+ // Simple query
146
+ const result = await db.query('SELECT * FROM users');
147
+
148
+ // Parameterized query (prevents SQL injection)
149
+ const result = await db.query(
150
+ 'SELECT * FROM users WHERE email = $1',
151
+ ['john@example.com']
152
+ );
153
+
154
+ // Access results
155
+ console.log(result.rows); // Array of row objects
156
+ console.log(result.rowCount); // Number of rows
157
+ ```
158
+
159
+ #### `transaction(callback)`
160
+
161
+ Execute queries within a transaction. Automatically commits on success or rolls back on error.
162
+
163
+ ```js
164
+ await db.transaction(async (client) => {
165
+ await client.query('INSERT INTO users (name, email) VALUES ($1, $2)',
166
+ ['John Doe', 'john@example.com']);
167
+
168
+ await client.query('INSERT INTO logs (action, user_id) VALUES ($1, $2)',
169
+ ['user_created', 1]);
170
+
171
+ // If any query fails, all changes are rolled back
172
+ });
173
+ ```
174
+
175
+ **Error Handling:**
176
+
177
+ ```js
178
+ try {
179
+ await db.transaction(async (client) => {
180
+ await client.query('INSERT INTO users (name) VALUES ($1)', ['John']);
181
+ throw new Error('Something went wrong');
182
+ // Transaction automatically rolls back
183
+ });
184
+ } catch (error) {
185
+ console.error('Transaction failed:', error.message);
186
+ }
187
+ ```
188
+
189
+ #### `disconnect()`
190
+
191
+ Close all connections in the pool.
192
+
193
+ ```js
194
+ await db.disconnect();
195
+ ```
196
+
197
+ #### `getPool()`
198
+
199
+ Get the underlying `pg.Pool` instance for advanced usage.
200
+
201
+ ```js
202
+ const pool = db.getPool();
203
+
204
+ // Use native pg.Pool methods
205
+ pool.on('error', (err) => {
206
+ console.error('Unexpected pool error', err);
207
+ });
208
+ ```
209
+
210
+ #### `model(tableName)`
211
+
212
+ Create a Mongoose-like model for a database table. Returns a `Model` instance with CRUD methods.
213
+
214
+ ```js
215
+ const User = db.model('users');
216
+ const Post = db.model('posts');
217
+ ```
218
+
219
+ #### `builder(tableName)`
220
+
221
+ Create a Knex query builder for complex queries. Returns a Knex query builder instance.
222
+
223
+ ```js
224
+ // SELECT with WHERE and JOIN
225
+ const results = await db.builder('posts')
226
+ .select('posts.*', 'users.name as author')
227
+ .join('users', 'posts.user_id', 'users.id')
228
+ .where('posts.published', true)
229
+ .orderBy('posts.created_at', 'desc')
230
+ .limit(10);
231
+
232
+ // Aggregations
233
+ const stats = await db.builder('orders')
234
+ .select('user_id')
235
+ .count('* as order_count')
236
+ .sum('total as revenue')
237
+ .groupBy('user_id')
238
+ .having('order_count', '>', 5);
239
+
240
+ // INSERT
241
+ const [user] = await db.builder('users')
242
+ .insert({ name: 'Alice', email: 'alice@example.com' })
243
+ .returning('*');
244
+
245
+ // UPDATE
246
+ await db.builder('users')
247
+ .where({ id: 1 })
248
+ .update({ last_login: db.knex.fn.now() });
249
+
250
+ // DELETE
251
+ await db.builder('users')
252
+ .where('active', false)
253
+ .delete();
254
+ ```
255
+
256
+ See [Knex Query Builder Documentation](https://knexjs.org/guide/query-builder.html) for full API.
257
+
258
+ #### `knex`
259
+
260
+ Get the Knex instance for advanced usage, raw queries, and transactions.
261
+
262
+ ```js
263
+ // Raw queries
264
+ const result = await db.knex.raw('SELECT NOW()');
265
+
266
+ // Transactions
267
+ await db.knex.transaction(async (trx) => {
268
+ const [user] = await trx('users').insert({ name: 'Bob' }).returning('*');
269
+ await trx('logs').insert({ user_id: user.id, action: 'created' });
270
+ });
271
+
272
+ // Knex utility functions
273
+ const now = db.knex.fn.now();
274
+ const count = db.knex.count('* as total');
275
+ ```
276
+
277
+ ### Model Methods
278
+
279
+ The Model class provides a friendly API for common database operations without writing SQL.
280
+
281
+ #### `create(data)`
282
+
283
+ Insert a new record and return it.
284
+
285
+ ```js
286
+ const user = await User.create({
287
+ name: 'Alice',
288
+ email: 'alice@example.com',
289
+ age: 25
290
+ });
291
+ // Returns: { id: 1, name: 'Alice', email: 'alice@example.com', age: 25 }
292
+ ```
293
+
294
+ #### `find(conditions?, options?)`
295
+
296
+ Find multiple records matching conditions.
297
+
298
+ ```js
299
+ // Find all users
300
+ const allUsers = await User.find();
301
+
302
+ // Find with conditions
303
+ const activeUsers = await User.find({ active: true });
304
+
305
+ // Find with pagination
306
+ const users = await User.find({ role: 'admin' }, { limit: 10, offset: 20 });
307
+
308
+ // Find with limit only
309
+ const recentUsers = await User.find({}, { limit: 5 });
310
+ ```
311
+
312
+ #### `findOne(conditions)`
313
+
314
+ Find a single record matching conditions.
315
+
316
+ ```js
317
+ const user = await User.findOne({ email: 'alice@example.com' });
318
+ // Returns: { id: 1, name: 'Alice', ... } or null
319
+ ```
320
+
321
+ #### `findById(id)`
322
+
323
+ Find a record by its primary key (id).
324
+
325
+ ```js
326
+ const user = await User.findById(1);
327
+ // Returns: { id: 1, name: 'Alice', ... } or null
328
+ ```
329
+
330
+ #### `update(conditions, data)`
331
+
332
+ Update records matching conditions and return updated records.
333
+
334
+ ```js
335
+ const updatedUsers = await User.update(
336
+ { active: false },
337
+ { status: 'archived' }
338
+ );
339
+ // Returns: [{ id: 5, status: 'archived', ... }, ...]
340
+ ```
341
+
342
+ #### `updateById(id, data)`
343
+
344
+ Update a single record by id and return it.
345
+
346
+ ```js
347
+ const user = await User.updateById(1, { last_login: new Date() });
348
+ // Returns: { id: 1, last_login: '2026-04-12T...', ... } or null
349
+ ```
350
+
351
+ #### `delete(conditions)`
352
+
353
+ Delete records matching conditions and return deleted records.
354
+
355
+ ```js
356
+ const deletedUsers = await User.delete({ active: false });
357
+ // Returns: [{ id: 10, active: false, ... }, ...]
358
+ ```
359
+
360
+ #### `deleteById(id)`
361
+
362
+ Delete a single record by id and return it.
363
+
364
+ ```js
365
+ const user = await User.deleteById(1);
366
+ // Returns: { id: 1, ... } or null
367
+ ```
368
+
369
+ #### `count(conditions?)`
370
+
371
+ Count records matching conditions.
372
+
373
+ ```js
374
+ // Count all records
375
+ const totalUsers = await User.count();
376
+ // Returns: 42
377
+
378
+ // Count with conditions
379
+ const activeCount = await User.count({ active: true });
380
+ // Returns: 35
381
+ ```
382
+
383
+ ## Usage Examples
384
+
385
+ ### Model API (Mongoose-like) - Recommended
386
+
387
+ ```js
388
+ import { connectDB } from '@entergreat/pg-light';
389
+
390
+ const db = await connectDB(process.env.DATABASE_URL);
391
+
392
+ // Create a model
393
+ const User = db.model('users');
394
+
395
+ // CREATE - Insert records
396
+ const alice = await User.create({
397
+ name: 'Alice Smith',
398
+ email: 'alice@example.com',
399
+ active: true
400
+ });
401
+ console.log('Created:', alice);
402
+
403
+ // READ - Find records
404
+ const activeUsers = await User.find({ active: true });
405
+ console.log('Active users:', activeUsers);
406
+
407
+ const user = await User.findOne({ email: 'alice@example.com' });
408
+ console.log('Found user:', user);
409
+
410
+ const userById = await User.findById(1);
411
+ console.log('User #1:', userById);
412
+
413
+ // Pagination
414
+ const page1 = await User.find({}, { limit: 10, offset: 0 });
415
+ const page2 = await User.find({}, { limit: 10, offset: 10 });
416
+
417
+ // UPDATE - Modify records
418
+ await User.updateById(1, { last_login: new Date() });
419
+
420
+ const updated = await User.update(
421
+ { active: false },
422
+ { status: 'archived' }
423
+ );
424
+ console.log('Updated:', updated);
425
+
426
+ // DELETE - Remove records
427
+ await User.deleteById(999);
428
+
429
+ const deleted = await User.delete({ active: false });
430
+ console.log('Deleted:', deleted);
431
+
432
+ // COUNT - Count records
433
+ const total = await User.count();
434
+ const activeCount = await User.count({ active: true });
435
+ console.log(`${activeCount} active out of ${total} total users`);
436
+
437
+ await db.disconnect();
438
+ ```
439
+
440
+ ### Model with Relationships
441
+
442
+ ```js
443
+ import { connectDB } from '@entergreat/pg-light';
444
+
445
+ const db = await connectDB(process.env.DATABASE_URL);
446
+
447
+ const User = db.model('users');
448
+ const Post = db.model('posts');
449
+ const Comment = db.model('comments');
450
+
451
+ // Create a user
452
+ const john = await User.create({
453
+ name: 'John Doe',
454
+ email: 'john@example.com'
455
+ });
456
+
457
+ // Create posts for the user
458
+ const post1 = await Post.create({
459
+ user_id: john.id,
460
+ title: 'My First Post',
461
+ content: 'Hello World!',
462
+ published: true
463
+ });
464
+
465
+ const post2 = await Post.create({
466
+ user_id: john.id,
467
+ title: 'Another Post',
468
+ content: 'More content...',
469
+ published: false
470
+ });
471
+
472
+ // Create comments
473
+ await Comment.create({
474
+ post_id: post1.id,
475
+ user_id: john.id,
476
+ content: 'Great post!'
477
+ });
478
+
479
+ // Find all posts by user
480
+ const userPosts = await Post.find({ user_id: john.id });
481
+ console.log(`John has ${userPosts.length} posts`);
482
+
483
+ // Find published posts
484
+ const publishedPosts = await Post.find({ published: true });
485
+
486
+ // Complex queries still use raw SQL
487
+ const postsWithComments = await db.query(`
488
+ SELECT p.*, COUNT(c.id) as comment_count
489
+ FROM posts p
490
+ LEFT JOIN comments c ON p.id = c.post_id
491
+ WHERE p.user_id = $1
492
+ GROUP BY p.id
493
+ `, [john.id]);
494
+
495
+ console.log('Posts with comment counts:', postsWithComments.rows);
496
+
497
+ await db.disconnect();
498
+ ```
499
+
500
+ ### Knex Query Builder Examples
501
+
502
+ ```js
503
+ import { connectDB } from '@entergreat/pg-light';
504
+
505
+ const db = await connectDB(process.env.DATABASE_URL);
506
+
507
+ // SIMPLE QUERIES
508
+
509
+ // Select with WHERE
510
+ const activeUsers = await db.builder('users')
511
+ .select('*')
512
+ .where({ active: true })
513
+ .orderBy('created_at', 'desc');
514
+
515
+ // Select specific columns
516
+ const userEmails = await db.builder('users')
517
+ .select('id', 'name', 'email')
518
+ .where('age', '>', 25)
519
+ .limit(10);
520
+
521
+ // JOINS
522
+
523
+ const postsWithAuthors = await db.builder('posts')
524
+ .select('posts.*', 'users.name as author_name')
525
+ .join('users', 'posts.user_id', 'users.id')
526
+ .where('posts.published', true)
527
+ .orderBy('posts.views', 'desc')
528
+ .limit(20);
529
+
530
+ // Multiple JOINs with aggregation
531
+ const postsWithStats = await db.builder('posts')
532
+ .select('posts.*', 'users.name as author')
533
+ .count('comments.id as comment_count')
534
+ .leftJoin('users', 'posts.user_id', 'users.id')
535
+ .leftJoin('comments', 'posts.id', 'comments.post_id')
536
+ .groupBy('posts.id', 'users.name')
537
+ .having(db.knex.raw('COUNT(comments.id) > ?', [5]));
538
+
539
+ // COMPLEX WHERE CLAUSES
540
+
541
+ const filtered = await db.builder('users')
542
+ .select('*')
543
+ .where('active', true)
544
+ .andWhere('age', '>=', 18)
545
+ .whereIn('role', ['admin', 'moderator'])
546
+ .whereNotNull('email')
547
+ .where(function() {
548
+ this.where('name', 'like', '%John%')
549
+ .orWhere('email', 'like', '%john%');
550
+ });
551
+
552
+ // SUBQUERIES
553
+
554
+ const prolificAuthors = await db.builder('users')
555
+ .select('users.*')
556
+ .whereIn('users.id', function() {
557
+ this.select('user_id')
558
+ .from('posts')
559
+ .groupBy('user_id')
560
+ .havingRaw('COUNT(*) > ?', [10]);
561
+ });
562
+
563
+ // AGGREGATIONS
564
+
565
+ const orderStats = await db.builder('orders')
566
+ .select(
567
+ db.knex.raw('AVG(total) as avg_order'),
568
+ db.knex.raw('SUM(total) as revenue'),
569
+ db.knex.raw('MIN(total) as min_order'),
570
+ db.knex.raw('MAX(total) as max_order')
571
+ )
572
+ .where('status', 'completed')
573
+ .first();
574
+
575
+ // GROUP BY
576
+
577
+ const topSpenders = await db.builder('orders')
578
+ .select('user_id')
579
+ .count('* as order_count')
580
+ .sum('total as total_spent')
581
+ .groupBy('user_id')
582
+ .orderBy('total_spent', 'desc')
583
+ .limit(10);
584
+
585
+ // INSERT
586
+
587
+ const [newUser] = await db.builder('users')
588
+ .insert({
589
+ name: 'Alice',
590
+ email: 'alice@example.com',
591
+ created_at: db.knex.fn.now()
592
+ })
593
+ .returning('*');
594
+
595
+ // Bulk insert
596
+ const newUsers = await db.builder('users')
597
+ .insert([
598
+ { name: 'Bob', email: 'bob@example.com' },
599
+ { name: 'Charlie', email: 'charlie@example.com' }
600
+ ])
601
+ .returning('*');
602
+
603
+ // UPDATE
604
+
605
+ await db.builder('users')
606
+ .where({ email: 'alice@example.com' })
607
+ .update({
608
+ last_login: db.knex.fn.now(),
609
+ login_count: db.knex.raw('login_count + 1')
610
+ });
611
+
612
+ // DELETE
613
+
614
+ const deletedCount = await db.builder('users')
615
+ .where('active', false)
616
+ .andWhere('last_login', '<', db.knex.raw("NOW() - INTERVAL '1 year'"))
617
+ .delete();
618
+
619
+ // TRANSACTIONS
620
+
621
+ await db.knex.transaction(async (trx) => {
622
+ const [user] = await trx('users')
623
+ .insert({ name: 'David', email: 'david@example.com' })
624
+ .returning('*');
625
+
626
+ await trx('posts').insert([
627
+ { user_id: user.id, title: 'Post 1' },
628
+ { user_id: user.id, title: 'Post 2' }
629
+ ]);
630
+
631
+ await trx('users')
632
+ .where({ id: user.id })
633
+ .update({ post_count: 2 });
634
+ });
635
+
636
+ // PAGINATION
637
+
638
+ const page = 2;
639
+ const perPage = 10;
640
+
641
+ const results = await db.builder('users')
642
+ .select('*')
643
+ .where({ active: true })
644
+ .orderBy('created_at', 'desc')
645
+ .limit(perPage)
646
+ .offset((page - 1) * perPage);
647
+
648
+ const [{ total }] = await db.builder('users')
649
+ .count('* as total')
650
+ .where({ active: true });
651
+
652
+ console.log({
653
+ data: results,
654
+ page,
655
+ perPage,
656
+ total,
657
+ totalPages: Math.ceil(total / perPage)
658
+ });
659
+
660
+ await db.disconnect();
661
+ ```
662
+
663
+ ### Which API to Use?
664
+
665
+ **Use Model API when:**
666
+ - โœ… Simple CRUD operations (create, read, update, delete by ID)
667
+ - โœ… Single table queries with basic conditions
668
+ - โœ… You want the simplest, most readable code
669
+
670
+ **Use Knex Builder when:**
671
+ - โœ… Complex queries with JOINs
672
+ - โœ… Aggregations (COUNT, SUM, AVG, etc.)
673
+ - โœ… Subqueries
674
+ - โœ… GROUP BY / HAVING clauses
675
+ - โœ… Complex WHERE conditions
676
+ - โœ… You need chainable query building
677
+
678
+ **Use Raw SQL when:**
679
+ - โœ… PostgreSQL-specific features (window functions, CTEs, etc.)
680
+ - โœ… Highly optimized custom queries
681
+ - โœ… Database migrations
682
+ - โœ… You prefer writing SQL directly
683
+
684
+ **Mix and match as needed!** All three approaches work together seamlessly.
685
+
686
+ ### Basic CRUD Operations (Raw SQL)
687
+
688
+ ```js
689
+ import { connectDB } from '@entergreat/pg-light';
690
+
691
+ const db = await connectDB(process.env.DATABASE_URL);
692
+
693
+ // CREATE
694
+ const insertResult = await db.query(
695
+ 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
696
+ ['Alice Smith', 'alice@example.com']
697
+ );
698
+ console.log('Created user:', insertResult.rows[0]);
699
+
700
+ // READ
701
+ const users = await db.query('SELECT * FROM users WHERE active = $1', [true]);
702
+ console.log('Active users:', users.rows);
703
+
704
+ // UPDATE
705
+ const updateResult = await db.query(
706
+ 'UPDATE users SET last_login = NOW() WHERE id = $1 RETURNING *',
707
+ [123]
708
+ );
709
+ console.log('Updated user:', updateResult.rows[0]);
710
+
711
+ // DELETE
712
+ await db.query('DELETE FROM users WHERE id = $1', [456]);
713
+
714
+ await db.disconnect();
715
+ ```
716
+
717
+ ### Transaction Example
718
+
719
+ ```js
720
+ import { connectDB } from '@entergreat/pg-light';
721
+
722
+ const db = await connectDB(process.env.DATABASE_URL);
723
+
724
+ // Transfer money between accounts (atomic operation)
725
+ async function transferMoney(fromId, toId, amount) {
726
+ await db.transaction(async (client) => {
727
+ // Debit from account
728
+ await client.query(
729
+ 'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
730
+ [amount, fromId]
731
+ );
732
+
733
+ // Credit to account
734
+ await client.query(
735
+ 'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
736
+ [amount, toId]
737
+ );
738
+
739
+ // Log transaction
740
+ await client.query(
741
+ 'INSERT INTO transactions (from_id, to_id, amount) VALUES ($1, $2, $3)',
742
+ [fromId, toId, amount]
743
+ );
744
+ });
745
+ }
746
+
747
+ try {
748
+ await transferMoney(1, 2, 100.00);
749
+ console.log('Transfer successful');
750
+ } catch (error) {
751
+ console.error('Transfer failed:', error.message);
752
+ }
753
+
754
+ await db.disconnect();
755
+ ```
756
+
757
+ ### Connection Pooling
758
+
759
+ ```js
760
+ import { connectDB } from '@entergreat/pg-light';
761
+
762
+ // Configure connection pool
763
+ const db = await connectDB({
764
+ host: 'localhost',
765
+ database: 'mydb',
766
+ user: 'myuser',
767
+ password: 'mypass',
768
+ max: 20, // Max 20 connections
769
+ idleTimeoutMillis: 30000, // Close idle connections after 30s
770
+ connectionTimeoutMillis: 2000 // Fail if connection takes > 2s
771
+ });
772
+
773
+ // Pool automatically manages connections
774
+ // Multiple queries share the pool
775
+ const [users, orders, products] = await Promise.all([
776
+ db.query('SELECT * FROM users'),
777
+ db.query('SELECT * FROM orders'),
778
+ db.query('SELECT * FROM products')
779
+ ]);
780
+
781
+ await db.disconnect();
782
+ ```
783
+
784
+ ### Custom Logging
785
+
786
+ ```js
787
+ import { connectDB } from '@entergreat/pg-light';
788
+
789
+ // Enable debug logging
790
+ const db = await connectDB(process.env.DATABASE_URL, {
791
+ service: 'my-api-db',
792
+ logLevel: 'debug'
793
+ });
794
+
795
+ // Logs will show:
796
+ // [my-api-db] Database connected successfully
797
+ // [my-api-db] Executing query: SELECT * FROM users
798
+ // [my-api-db] Query returned 42 rows
799
+ ```
800
+
801
+ ### Environment Variables
802
+
803
+ ```js
804
+ import { connectDB } from '@entergreat/pg-light';
805
+
806
+ // .env file:
807
+ // DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
808
+
809
+ const db = await connectDB(process.env.DATABASE_URL);
810
+
811
+ // Or with separate vars:
812
+ const db = await connectDB({
813
+ host: process.env.DB_HOST,
814
+ port: process.env.DB_PORT,
815
+ database: process.env.DB_NAME,
816
+ user: process.env.DB_USER,
817
+ password: process.env.DB_PASSWORD
818
+ });
819
+ ```
820
+
821
+ ### Express.js Integration
822
+
823
+ ```js
824
+ import express from 'express';
825
+ import { connectDB } from '@entergreat/pg-light';
826
+
827
+ const app = express();
828
+ const db = await connectDB(process.env.DATABASE_URL);
829
+
830
+ app.get('/users', async (req, res) => {
831
+ try {
832
+ const result = await db.query('SELECT * FROM users');
833
+ res.json(result.rows);
834
+ } catch (error) {
835
+ res.status(500).json({ error: error.message });
836
+ }
837
+ });
838
+
839
+ app.post('/users', async (req, res) => {
840
+ const { name, email } = req.body;
841
+
842
+ try {
843
+ const result = await db.query(
844
+ 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
845
+ [name, email]
846
+ );
847
+ res.status(201).json(result.rows[0]);
848
+ } catch (error) {
849
+ res.status(500).json({ error: error.message });
850
+ }
851
+ });
852
+
853
+ // Graceful shutdown
854
+ process.on('SIGTERM', async () => {
855
+ await db.disconnect();
856
+ process.exit(0);
857
+ });
858
+
859
+ app.listen(3000, () => {
860
+ console.log('Server running on port 3000');
861
+ });
862
+ ```
863
+
864
+ ## Why pg-light?
865
+
866
+ - โœ… **Minimal Learning Curve** - One function to connect, simple API
867
+ - โœ… **Production Ready** - Built on battle-tested `pg` library
868
+ - โœ… **Clean Code** - All functions โ‰ค7 lines, easy to maintain
869
+ - โœ… **Integrated Logging** - Uses `logger-standard` for consistent logging
870
+ - โœ… **Transaction Safety** - Auto-rollback on errors
871
+ - โœ… **Connection Pooling** - Efficient resource management
872
+ - โœ… **Parameterized Queries** - SQL injection protection
873
+ - โœ… **Zero Magic** - Straightforward, predictable behavior
874
+
875
+ ## Comparison
876
+
877
+ ### Before (pg directly)
878
+
879
+ ```js
880
+ import pg from 'pg';
881
+
882
+ const pool = new pg.Pool({
883
+ connectionString: process.env.DATABASE_URL
884
+ });
885
+
886
+ pool.on('error', (err) => {
887
+ console.error('Unexpected error', err);
888
+ process.exit(-1);
889
+ });
890
+
891
+ const client = await pool.connect();
892
+ try {
893
+ const result = await client.query('SELECT * FROM users');
894
+ console.log(result.rows);
895
+ } finally {
896
+ client.release();
897
+ }
898
+
899
+ await pool.end();
900
+ ```
901
+
902
+ ### After (pg-light)
903
+
904
+ ```js
905
+ import { connectDB } from '@entergreat/pg-light';
906
+
907
+ const db = await connectDB(process.env.DATABASE_URL);
908
+ const result = await db.query('SELECT * FROM users');
909
+ console.log(result.rows);
910
+ await db.disconnect();
911
+ ```
912
+
913
+ ## License
914
+
915
+ ISC
916
+
917
+ ## Contributing
918
+
919
+ Contributions, issues, and feature requests are welcome!
920
+
921
+ ## Related Packages
922
+
923
+ - [logger-standard](https://www.npmjs.com/package/logger-standard) - Flexible logging utility (used internally)
924
+ - [pg](https://www.npmjs.com/package/pg) - PostgreSQL client (underlying library)
925
+
926
+ ## Repository
927
+
928
+ [https://github.com/nivkman/pg-light](https://github.com/nivkman/pg-light)