@starbemtech/star-db-query-builder 1.3.1 → 1.4.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 (75) hide show
  1. package/.claude/skills/star-db-query-builder/SKILL.md +104 -0
  2. package/CHANGELOG.md +82 -45
  3. package/LICENSE +21 -0
  4. package/README.md +194 -94
  5. package/bin/install-skill.js +53 -0
  6. package/dist/src/core/repository.d.ts +92 -9
  7. package/dist/src/core/repository.js +275 -27
  8. package/dist/src/core/repository.js.map +1 -1
  9. package/dist/src/core/types.d.ts +16 -1
  10. package/dist/src/core/utils.d.ts +102 -0
  11. package/dist/src/core/utils.js +287 -70
  12. package/dist/src/core/utils.js.map +1 -1
  13. package/dist/src/db/initDb.d.ts +60 -50
  14. package/dist/src/db/initDb.js +96 -64
  15. package/dist/src/db/initDb.js.map +1 -1
  16. package/dist/src/db/mysqlClient.d.ts +3 -8
  17. package/dist/src/db/mysqlClient.js +9 -11
  18. package/dist/src/db/mysqlClient.js.map +1 -1
  19. package/dist/src/db/pgClient.js +0 -2
  20. package/dist/src/db/pgClient.js.map +1 -1
  21. package/dist/src/monitor/monitor.js +7 -0
  22. package/dist/src/monitor/monitor.js.map +1 -1
  23. package/package.json +27 -19
  24. package/.github/workflows/publish.yml +0 -118
  25. package/.prettierignore +0 -3
  26. package/.prettierrc +0 -5
  27. package/ARCHITECTURE.md +0 -313
  28. package/coverage/base.css +0 -224
  29. package/coverage/block-navigation.js +0 -87
  30. package/coverage/favicon.png +0 -0
  31. package/coverage/index.html +0 -131
  32. package/coverage/lcov-report/base.css +0 -224
  33. package/coverage/lcov-report/block-navigation.js +0 -87
  34. package/coverage/lcov-report/favicon.png +0 -0
  35. package/coverage/lcov-report/index.html +0 -131
  36. package/coverage/lcov-report/mysqlClient.ts.html +0 -685
  37. package/coverage/lcov-report/pgClient.ts.html +0 -823
  38. package/coverage/lcov-report/prettify.css +0 -1
  39. package/coverage/lcov-report/prettify.js +0 -2
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +0 -210
  42. package/coverage/lcov.info +0 -533
  43. package/coverage/mysqlClient.ts.html +0 -685
  44. package/coverage/pgClient.ts.html +0 -823
  45. package/coverage/prettify.css +0 -1
  46. package/coverage/prettify.js +0 -2
  47. package/coverage/sort-arrow-sprite.png +0 -0
  48. package/coverage/sorter.js +0 -210
  49. package/dist/src/setupTests.d.ts +0 -26
  50. package/dist/src/setupTests.js +0 -43
  51. package/dist/src/setupTests.js.map +0 -1
  52. package/docs/INDEX.md +0 -145
  53. package/docs/methods/findFirst.md +0 -394
  54. package/docs/methods/findMany.md +0 -587
  55. package/docs/methods/insert.md +0 -536
  56. package/docs/methods/insertMany.md +0 -627
  57. package/docs/methods/joins.md +0 -781
  58. package/docs/methods/rawQuery.md +0 -284
  59. package/docs/methods/transactions.md +0 -737
  60. package/eslint.config.mjs +0 -77
  61. package/index.ts +0 -16
  62. package/jest.config.ts +0 -194
  63. package/scripts/release.sh +0 -123
  64. package/src/core/repository.ts +0 -865
  65. package/src/core/types.ts +0 -97
  66. package/src/core/utils.ts +0 -357
  67. package/src/db/IDatabaseClient.ts +0 -16
  68. package/src/db/__tests__/mysqlClient.test.ts +0 -262
  69. package/src/db/__tests__/pgClient.test.ts +0 -260
  70. package/src/db/initDb.ts +0 -181
  71. package/src/db/mysqlClient.ts +0 -200
  72. package/src/db/pgClient.ts +0 -246
  73. package/src/monitor/monitor.ts +0 -16
  74. package/src/setupTests.ts +0 -45
  75. package/tsconfig.test.json +0 -21
@@ -1,737 +0,0 @@
1
- # Transactions
2
-
3
- Execute multiple database operations within a single transaction to ensure data consistency and atomicity.
4
-
5
- ## Overview
6
-
7
- Transactions ensure that a series of database operations either all succeed or all fail together. This is crucial for maintaining data integrity when performing complex operations that involve multiple tables or records.
8
-
9
- ## Available Methods
10
-
11
- ### withTransaction
12
-
13
- Executes a function within a database transaction with automatic commit/rollback handling.
14
-
15
- ```typescript
16
- withTransaction<T>(
17
- dbClient: IDatabaseClient,
18
- transactionFn: (tx: ITransactionClient) => Promise<T>
19
- ): Promise<T>
20
- ```
21
-
22
- ### beginTransaction
23
-
24
- Creates a transaction client for manual transaction management.
25
-
26
- ```typescript
27
- beginTransaction(dbClient: IDatabaseClient): Promise<ITransactionClient>
28
- ```
29
-
30
- ## ITransactionClient Interface
31
-
32
- ```typescript
33
- interface ITransactionClient {
34
- query: <T>(sql: string, params?: any[]) => Promise<T>
35
- commit: () => Promise<void>
36
- rollback: () => Promise<void>
37
- }
38
- ```
39
-
40
- ## Examples
41
-
42
- ### Basic Transaction with withTransaction
43
-
44
- ```typescript
45
- import {
46
- withTransaction,
47
- insert,
48
- update,
49
- } from '@starbemtech/star-db-query-builder'
50
-
51
- // Create user with profile in a single transaction
52
- const createUserWithProfile = async (userData: any, profileData: any) => {
53
- return withTransaction(dbClient, async (tx) => {
54
- // Create user
55
- const user = await insert({
56
- tableName: 'users',
57
- dbClient: tx,
58
- data: userData,
59
- })
60
-
61
- // Create user profile
62
- const profile = await insert({
63
- tableName: 'user_profiles',
64
- dbClient: tx,
65
- data: {
66
- ...profileData,
67
- user_id: user.id,
68
- },
69
- })
70
-
71
- return { user, profile }
72
- })
73
- }
74
-
75
- // Usage
76
- try {
77
- const result = await createUserWithProfile(
78
- { name: 'John Doe', email: 'john@example.com' },
79
- { bio: 'Software developer', location: 'New York' }
80
- )
81
- console.log('User and profile created successfully:', result)
82
- } catch (error) {
83
- console.error('Transaction failed:', error.message)
84
- // Both user and profile creation were rolled back
85
- }
86
- ```
87
-
88
- ### E-commerce Order Processing
89
-
90
- ```typescript
91
- const processOrder = async (orderData: any, orderItems: any[]) => {
92
- return withTransaction(dbClient, async (tx) => {
93
- // Create order
94
- const order = await insert({
95
- tableName: 'orders',
96
- dbClient: tx,
97
- data: {
98
- ...orderData,
99
- status: 'pending',
100
- total: 0, // Will be calculated
101
- },
102
- })
103
-
104
- let totalAmount = 0
105
-
106
- // Create order items and calculate total
107
- for (const item of orderItems) {
108
- const orderItem = await insert({
109
- tableName: 'order_items',
110
- dbClient: tx,
111
- data: {
112
- ...item,
113
- order_id: order.id,
114
- },
115
- })
116
-
117
- totalAmount += item.price * item.quantity
118
-
119
- // Update product stock
120
- await update({
121
- tableName: 'products',
122
- dbClient: tx,
123
- id: item.product_id,
124
- data: {
125
- stock: { operator: '-', value: item.quantity },
126
- },
127
- })
128
- }
129
-
130
- // Update order total
131
- await update({
132
- tableName: 'orders',
133
- dbClient: tx,
134
- id: order.id,
135
- data: {
136
- total: totalAmount,
137
- status: 'confirmed',
138
- },
139
- })
140
-
141
- return { order, totalAmount }
142
- })
143
- }
144
- ```
145
-
146
- ### Account Transfer
147
-
148
- ```typescript
149
- const transferMoney = async (
150
- fromAccountId: string,
151
- toAccountId: string,
152
- amount: number
153
- ) => {
154
- return withTransaction(dbClient, async (tx) => {
155
- // Check sender balance
156
- const fromAccount = await findFirst({
157
- tableName: 'accounts',
158
- dbClient: tx,
159
- where: { id: { operator: '=', value: fromAccountId } },
160
- })
161
-
162
- if (!fromAccount || fromAccount.balance < amount) {
163
- throw new Error('Insufficient funds')
164
- }
165
-
166
- // Debit from sender
167
- await update({
168
- tableName: 'accounts',
169
- dbClient: tx,
170
- id: fromAccountId,
171
- data: {
172
- balance: { operator: '-', value: amount },
173
- },
174
- })
175
-
176
- // Credit to receiver
177
- await update({
178
- tableName: 'accounts',
179
- dbClient: tx,
180
- id: toAccountId,
181
- data: {
182
- balance: { operator: '+', value: amount },
183
- },
184
- })
185
-
186
- // Create transaction record
187
- const transaction = await insert({
188
- tableName: 'transactions',
189
- dbClient: tx,
190
- data: {
191
- from_account_id: fromAccountId,
192
- to_account_id: toAccountId,
193
- amount,
194
- type: 'transfer',
195
- status: 'completed',
196
- },
197
- })
198
-
199
- return transaction
200
- })
201
- }
202
- ```
203
-
204
- ### Manual Transaction Management
205
-
206
- ```typescript
207
- import {
208
- beginTransaction,
209
- insert,
210
- update,
211
- } from '@starbemtech/star-db-query-builder'
212
-
213
- const complexOperation = async () => {
214
- const transaction = await beginTransaction(dbClient)
215
-
216
- try {
217
- // First operation
218
- const user = await insert({
219
- tableName: 'users',
220
- dbClient: transaction,
221
- data: { name: 'John Doe', email: 'john@example.com' },
222
- })
223
-
224
- // Second operation
225
- const profile = await insert({
226
- tableName: 'user_profiles',
227
- dbClient: transaction,
228
- data: { user_id: user.id, bio: 'Hello world' },
229
- })
230
-
231
- // Third operation
232
- await update({
233
- tableName: 'users',
234
- dbClient: transaction,
235
- id: user.id,
236
- data: { profile_created: true },
237
- })
238
-
239
- // Commit all changes
240
- await transaction.commit()
241
- return { user, profile }
242
- } catch (error) {
243
- // Rollback on any error
244
- await transaction.rollback()
245
- throw error
246
- }
247
- }
248
- ```
249
-
250
- ### Nested Operations with Error Handling
251
-
252
- ```typescript
253
- const createUserWithMultipleRelations = async (userData: any) => {
254
- return withTransaction(dbClient, async (tx) => {
255
- try {
256
- // Create user
257
- const user = await insert({
258
- tableName: 'users',
259
- dbClient: tx,
260
- data: userData,
261
- })
262
-
263
- // Create user preferences
264
- const preferences = await insert({
265
- tableName: 'user_preferences',
266
- dbClient: tx,
267
- data: {
268
- user_id: user.id,
269
- theme: 'dark',
270
- notifications: true,
271
- },
272
- })
273
-
274
- // Create user settings
275
- const settings = await insert({
276
- tableName: 'user_settings',
277
- dbClient: tx,
278
- data: {
279
- user_id: user.id,
280
- language: 'en',
281
- timezone: 'UTC',
282
- },
283
- })
284
-
285
- // Create audit log
286
- await insert({
287
- tableName: 'audit_logs',
288
- dbClient: tx,
289
- data: {
290
- user_id: user.id,
291
- action: 'user_created',
292
- details: JSON.stringify({ email: user.email }),
293
- },
294
- })
295
-
296
- return { user, preferences, settings }
297
- } catch (error) {
298
- // Transaction will be automatically rolled back
299
- console.error('Failed to create user with relations:', error)
300
- throw error
301
- }
302
- })
303
- }
304
- ```
305
-
306
- ### Batch Operations with Transactions
307
-
308
- ```typescript
309
- const bulkUserCreation = async (usersData: any[]) => {
310
- return withTransaction(dbClient, async (tx) => {
311
- const results = []
312
-
313
- for (const userData of usersData) {
314
- // Create user
315
- const user = await insert({
316
- tableName: 'users',
317
- dbClient: tx,
318
- data: userData,
319
- })
320
-
321
- // Create default profile
322
- const profile = await insert({
323
- tableName: 'user_profiles',
324
- dbClient: tx,
325
- data: {
326
- user_id: user.id,
327
- bio: 'New user',
328
- created_at: new Date(),
329
- },
330
- })
331
-
332
- results.push({ user, profile })
333
- }
334
-
335
- // Create batch audit log
336
- await insert({
337
- tableName: 'audit_logs',
338
- dbClient: tx,
339
- data: {
340
- action: 'bulk_user_creation',
341
- details: JSON.stringify({
342
- count: usersData.length,
343
- user_ids: results.map((r) => r.user.id),
344
- }),
345
- },
346
- })
347
-
348
- return results
349
- })
350
- }
351
- ```
352
-
353
- ### Conditional Transaction Logic
354
-
355
- ```typescript
356
- const processPayment = async (paymentData: any) => {
357
- return withTransaction(dbClient, async (tx) => {
358
- // Create payment record
359
- const payment = await insert({
360
- tableName: 'payments',
361
- dbClient: tx,
362
- data: {
363
- ...paymentData,
364
- status: 'processing',
365
- },
366
- })
367
-
368
- // Check if payment amount is above threshold
369
- if (paymentData.amount > 1000) {
370
- // Require manual approval for large payments
371
- await insert({
372
- tableName: 'payment_approvals',
373
- dbClient: tx,
374
- data: {
375
- payment_id: payment.id,
376
- status: 'pending',
377
- requires_approval: true,
378
- },
379
- })
380
-
381
- // Update payment status
382
- await update({
383
- tableName: 'payments',
384
- dbClient: tx,
385
- id: payment.id,
386
- data: { status: 'pending_approval' },
387
- })
388
- } else {
389
- // Auto-approve small payments
390
- await update({
391
- tableName: 'payments',
392
- dbClient: tx,
393
- id: payment.id,
394
- data: { status: 'approved' },
395
- })
396
-
397
- // Process the payment
398
- await processApprovedPayment(payment.id, tx)
399
- }
400
-
401
- return payment
402
- })
403
- }
404
-
405
- const processApprovedPayment = async (
406
- paymentId: string,
407
- tx: ITransactionClient
408
- ) => {
409
- // Additional payment processing logic
410
- await update({
411
- tableName: 'payments',
412
- dbClient: tx,
413
- id: paymentId,
414
- data: { status: 'completed', processed_at: new Date() },
415
- })
416
- }
417
- ```
418
-
419
- ## Best Practices
420
-
421
- ### 1. Keep Transactions Short
422
-
423
- ```typescript
424
- // Good: Short, focused transaction
425
- const updateUserStatus = async (userId: string, status: string) => {
426
- return withTransaction(dbClient, async (tx) => {
427
- await update({
428
- tableName: 'users',
429
- dbClient: tx,
430
- id: userId,
431
- data: { status },
432
- })
433
-
434
- await insert({
435
- tableName: 'user_status_history',
436
- dbClient: tx,
437
- data: { user_id: userId, status, changed_at: new Date() },
438
- })
439
- })
440
- }
441
-
442
- // Avoid: Long-running transactions
443
- const badTransaction = async () => {
444
- return withTransaction(dbClient, async (tx) => {
445
- // ... many operations
446
- await someSlowOperation() // This could timeout
447
- // ... more operations
448
- })
449
- }
450
- ```
451
-
452
- ### 2. Handle Errors Properly
453
-
454
- ```typescript
455
- const safeTransaction = async () => {
456
- try {
457
- return await withTransaction(dbClient, async (tx) => {
458
- // Transaction operations
459
- const result = await someOperation(tx)
460
- return result
461
- })
462
- } catch (error) {
463
- // Transaction was automatically rolled back
464
- console.error('Transaction failed:', error.message)
465
-
466
- // Handle specific error types
467
- if (error.message.includes('duplicate key')) {
468
- throw new Error('Record already exists')
469
- }
470
-
471
- throw error
472
- }
473
- }
474
- ```
475
-
476
- ### 3. Use Appropriate Isolation Levels
477
-
478
- ```typescript
479
- // For read-heavy operations, consider using read-only transactions
480
- const getReportData = async () => {
481
- return withTransaction(dbClient, async (tx) => {
482
- // Set transaction to read-only (database-specific)
483
- await tx.query('SET TRANSACTION READ ONLY')
484
-
485
- const users = await findMany({
486
- tableName: 'users',
487
- dbClient: tx,
488
- where: { status: { operator: '=', value: 'active' } },
489
- })
490
-
491
- const orders = await findMany({
492
- tableName: 'orders',
493
- dbClient: tx,
494
- where: { status: { operator: '=', value: 'completed' } },
495
- })
496
-
497
- return { users, orders }
498
- })
499
- }
500
- ```
501
-
502
- ### 4. Avoid Nested Transactions
503
-
504
- ```typescript
505
- // Good: Single transaction for related operations
506
- const createOrderWithItems = async (orderData: any, items: any[]) => {
507
- return withTransaction(dbClient, async (tx) => {
508
- const order = await insert({
509
- tableName: 'orders',
510
- dbClient: tx,
511
- data: orderData,
512
- })
513
-
514
- for (const item of items) {
515
- await insert({
516
- tableName: 'order_items',
517
- dbClient: tx,
518
- data: { ...item, order_id: order.id },
519
- })
520
- }
521
-
522
- return order
523
- })
524
- }
525
-
526
- // Avoid: Nested transactions (not supported by most databases)
527
- const badNestedTransaction = async () => {
528
- return withTransaction(dbClient, async (tx1) => {
529
- // ... operations
530
-
531
- return withTransaction(dbClient, async (tx2) => {
532
- // This won't work as expected
533
- })
534
- })
535
- }
536
- ```
537
-
538
- ## Error Handling
539
-
540
- ### Common Transaction Errors
541
-
542
- ```typescript
543
- const handleTransactionErrors = async () => {
544
- try {
545
- return await withTransaction(dbClient, async (tx) => {
546
- // Your transaction logic
547
- })
548
- } catch (error) {
549
- if (error.message.includes('deadlock detected')) {
550
- // Handle deadlock - you might want to retry
551
- console.warn('Deadlock detected, retrying...')
552
- // Implement retry logic
553
- } else if (error.message.includes('serialization failure')) {
554
- // Handle serialization failure
555
- console.warn('Serialization failure, retrying...')
556
- // Implement retry logic
557
- } else if (error.message.includes('connection lost')) {
558
- // Handle connection issues
559
- console.error('Database connection lost')
560
- // Implement reconnection logic
561
- } else {
562
- // Handle other errors
563
- console.error('Transaction error:', error.message)
564
- }
565
-
566
- throw error
567
- }
568
- }
569
- ```
570
-
571
- ### Retry Logic for Transient Errors
572
-
573
- ```typescript
574
- const retryTransaction = async <T>(
575
- transactionFn: (tx: ITransactionClient) => Promise<T>,
576
- maxRetries: number = 3
577
- ): Promise<T> => {
578
- let lastError: Error
579
-
580
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
581
- try {
582
- return await withTransaction(dbClient, transactionFn)
583
- } catch (error) {
584
- lastError = error as Error
585
-
586
- // Check if error is retryable
587
- if (isRetryableError(error) && attempt < maxRetries) {
588
- const delay = Math.pow(2, attempt) * 1000 // Exponential backoff
589
- console.warn(
590
- `Transaction attempt ${attempt} failed, retrying in ${delay}ms...`
591
- )
592
- await new Promise((resolve) => setTimeout(resolve, delay))
593
- continue
594
- }
595
-
596
- throw error
597
- }
598
- }
599
-
600
- throw lastError!
601
- }
602
-
603
- const isRetryableError = (error: any): boolean => {
604
- const retryableErrors = [
605
- 'deadlock detected',
606
- 'serialization failure',
607
- 'connection lost',
608
- 'timeout',
609
- ]
610
-
611
- return retryableErrors.some((msg) =>
612
- error.message?.toLowerCase().includes(msg)
613
- )
614
- }
615
- ```
616
-
617
- ## Performance Considerations
618
-
619
- ### 1. Connection Pooling
620
-
621
- ```typescript
622
- // Ensure your database client is configured with proper connection pooling
623
- await initDb({
624
- type: 'pg',
625
- options: {
626
- host: 'localhost',
627
- port: 5432,
628
- database: 'myapp',
629
- user: 'username',
630
- password: 'password',
631
- max: 20, // Maximum connections in pool
632
- idleTimeoutMillis: 30000,
633
- connectionTimeoutMillis: 2000,
634
- },
635
- })
636
- ```
637
-
638
- ### 2. Transaction Timeout
639
-
640
- ```typescript
641
- // Set appropriate timeouts for transactions
642
- const quickTransaction = async () => {
643
- return withTransaction(dbClient, async (tx) => {
644
- // Set a timeout for this transaction
645
- const timeoutPromise = new Promise((_, reject) => {
646
- setTimeout(() => reject(new Error('Transaction timeout')), 5000)
647
- })
648
-
649
- const transactionPromise = (async () => {
650
- // Your transaction logic
651
- return await someOperation(tx)
652
- })()
653
-
654
- return Promise.race([transactionPromise, timeoutPromise])
655
- })
656
- }
657
- ```
658
-
659
- ### 3. Batch Operations
660
-
661
- ```typescript
662
- // For large batch operations, consider processing in chunks
663
- const bulkUpdateWithTransactions = async (
664
- records: any[],
665
- chunkSize: number = 100
666
- ) => {
667
- const results = []
668
-
669
- for (let i = 0; i < records.length; i += chunkSize) {
670
- const chunk = records.slice(i, i + chunkSize)
671
-
672
- const chunkResult = await withTransaction(dbClient, async (tx) => {
673
- const chunkResults = []
674
-
675
- for (const record of chunk) {
676
- const result = await update({
677
- tableName: 'records',
678
- dbClient: tx,
679
- id: record.id,
680
- data: record.data,
681
- })
682
- chunkResults.push(result)
683
- }
684
-
685
- return chunkResults
686
- })
687
-
688
- results.push(...chunkResult)
689
- }
690
-
691
- return results
692
- }
693
- ```
694
-
695
- ## Database-Specific Considerations
696
-
697
- ### PostgreSQL
698
-
699
- - Supports nested transactions (savepoints)
700
- - Has excellent transaction isolation
701
- - Supports advisory locks for complex scenarios
702
-
703
- ### MySQL
704
-
705
- - Uses autocommit mode by default
706
- - Supports different isolation levels
707
- - Has limitations with nested transactions
708
-
709
- ## Monitoring and Logging
710
-
711
- ```typescript
712
- // Monitor transaction events
713
- import { monitor, MonitorEvents } from '@starbemtech/star-db-query-builder'
714
-
715
- monitor.on(MonitorEvents.TRANSACTION_COMMIT, (data) => {
716
- console.log('Transaction committed:', data)
717
- })
718
-
719
- monitor.on(MonitorEvents.TRANSACTION_ROLLBACK, (data) => {
720
- console.log('Transaction rolled back:', data)
721
- })
722
-
723
- monitor.on(MonitorEvents.QUERY_START, (data) => {
724
- if (data.inTransaction) {
725
- console.log('Query in transaction:', data.sql)
726
- }
727
- })
728
- ```
729
-
730
- ## Summary
731
-
732
- Transactions are essential for maintaining data consistency in complex operations. The library provides two main approaches:
733
-
734
- 1. **`withTransaction`**: Automatic transaction management with commit/rollback
735
- 2. **`beginTransaction`**: Manual transaction control for advanced scenarios
736
-
737
- Always handle errors properly and keep transactions as short as possible to avoid performance issues and deadlocks.