@dockstat/sqlite-wrapper 1.2.8 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -373
- package/README.md +553 -99
- package/index.ts +1120 -858
- package/package.json +60 -54
- package/query-builder/base.ts +183 -221
- package/query-builder/delete.ts +441 -352
- package/query-builder/index.ts +409 -431
- package/query-builder/insert.ts +280 -249
- package/query-builder/select.ts +333 -358
- package/query-builder/update.ts +308 -278
- package/query-builder/where.ts +272 -307
- package/types.ts +608 -623
- package/utils/index.ts +44 -0
- package/utils/logger.ts +184 -0
- package/utils/sql.ts +241 -0
- package/utils/transformer.ts +256 -0
package/index.ts
CHANGED
|
@@ -1,858 +1,1120 @@
|
|
|
1
|
-
import { Database, type SQLQueryBindings } from "bun:sqlite"
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
DeleteResult,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
RegexCondition,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* -
|
|
56
|
-
* -
|
|
57
|
-
* -
|
|
58
|
-
* -
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* -
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
*
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
*
|
|
213
|
-
*/
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
*
|
|
295
|
-
*/
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
*
|
|
350
|
-
*/
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
*
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
return
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
`
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
*
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1
|
+
import { Database, type SQLQueryBindings } from "bun:sqlite"
|
|
2
|
+
import { QueryBuilder } from "./query-builder/index"
|
|
3
|
+
import type { ColumnDefinition, Parser, TableConstraints, TableOptions, TableSchema } from "./types"
|
|
4
|
+
import { addLoggerParents as addParents, createLogger, logger as sqliteLogger } from "./utils"
|
|
5
|
+
|
|
6
|
+
// Re-export logger utilities for external use
|
|
7
|
+
export const logger = sqliteLogger
|
|
8
|
+
export const addLoggerParents = addParents
|
|
9
|
+
|
|
10
|
+
// Internal loggers for different components
|
|
11
|
+
const dbLog = createLogger("db")
|
|
12
|
+
const backupLog = createLogger("backup")
|
|
13
|
+
const tableLog = createLogger("table")
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Re-export all types and utilities
|
|
17
|
+
*/
|
|
18
|
+
export { QueryBuilder }
|
|
19
|
+
export type {
|
|
20
|
+
ArrayKey,
|
|
21
|
+
ColumnConstraints,
|
|
22
|
+
ColumnDefinition,
|
|
23
|
+
ColumnNames,
|
|
24
|
+
DefaultExpression,
|
|
25
|
+
DeleteResult,
|
|
26
|
+
ForeignKeyAction,
|
|
27
|
+
InsertOptions,
|
|
28
|
+
InsertResult,
|
|
29
|
+
RegexCondition,
|
|
30
|
+
SQLiteType,
|
|
31
|
+
TableConstraints,
|
|
32
|
+
TableOptions,
|
|
33
|
+
TableSchema,
|
|
34
|
+
UpdateResult,
|
|
35
|
+
WhereCondition,
|
|
36
|
+
} from "./types"
|
|
37
|
+
|
|
38
|
+
// Re-export helper utilities
|
|
39
|
+
export {
|
|
40
|
+
column,
|
|
41
|
+
defaultExpr,
|
|
42
|
+
SQLiteFunctions,
|
|
43
|
+
SQLiteKeywords,
|
|
44
|
+
SQLiteTypes,
|
|
45
|
+
sql,
|
|
46
|
+
} from "./types"
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* TypedSQLite — comprehensive wrapper around bun:sqlite `Database`.
|
|
50
|
+
*
|
|
51
|
+
* This class provides full type safety for SQLite operations with support for:
|
|
52
|
+
* - All SQLite data types and variations
|
|
53
|
+
* - Built-in SQL functions
|
|
54
|
+
* - Complex constraints and relationships
|
|
55
|
+
* - Generated columns
|
|
56
|
+
* - Table-level constraints
|
|
57
|
+
* - JSON column support
|
|
58
|
+
* - And much more...
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* Auto-backup configuration options
|
|
62
|
+
*/
|
|
63
|
+
export interface AutoBackupOptions {
|
|
64
|
+
/** Enable automatic backups */
|
|
65
|
+
enabled: boolean
|
|
66
|
+
/** Directory to store backup files */
|
|
67
|
+
directory: string
|
|
68
|
+
/** Backup interval in milliseconds (default: 1 hour) */
|
|
69
|
+
intervalMs?: number
|
|
70
|
+
/** Maximum number of backups to retain (default: 10) */
|
|
71
|
+
maxBackups?: number
|
|
72
|
+
/** Prefix for backup filenames (default: 'backup') */
|
|
73
|
+
filenamePrefix?: string
|
|
74
|
+
/** Whether to compress backups using gzip (default: false) */
|
|
75
|
+
compress?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Database configuration options
|
|
80
|
+
*/
|
|
81
|
+
export interface DBOptions {
|
|
82
|
+
/** PRAGMA settings to apply on database open */
|
|
83
|
+
pragmas?: Array<[string, SQLQueryBindings]>
|
|
84
|
+
/** Paths to SQLite extensions to load */
|
|
85
|
+
loadExtensions?: string[]
|
|
86
|
+
/** Auto-backup configuration */
|
|
87
|
+
autoBackup?: AutoBackupOptions
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class DB {
|
|
91
|
+
protected db: Database
|
|
92
|
+
protected dbPath: string
|
|
93
|
+
private autoBackupTimer: ReturnType<typeof setInterval> | null = null
|
|
94
|
+
private autoBackupOptions: AutoBackupOptions | null = null
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Open or create a SQLite database at `path`.
|
|
98
|
+
*
|
|
99
|
+
* @param path - Path to the SQLite file (e.g. "app.db"). Use ":memory:" for in-memory DB.
|
|
100
|
+
* @param options - Optional database configuration
|
|
101
|
+
*/
|
|
102
|
+
constructor(path: string, options?: DBOptions) {
|
|
103
|
+
dbLog.connection(path, "open")
|
|
104
|
+
this.dbPath = path
|
|
105
|
+
this.db = new Database(path)
|
|
106
|
+
|
|
107
|
+
// Apply PRAGMA settings if provided
|
|
108
|
+
if (options?.pragmas) {
|
|
109
|
+
for (const [name, value] of options.pragmas) {
|
|
110
|
+
this.pragma(name, value)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Load extensions if provided
|
|
115
|
+
if (options?.loadExtensions) {
|
|
116
|
+
for (const extensionPath of options.loadExtensions) {
|
|
117
|
+
this.loadExtension(extensionPath)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Setup auto-backup if configured
|
|
122
|
+
if (options?.autoBackup?.enabled) {
|
|
123
|
+
this.setupAutoBackup(options.autoBackup)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Setup automatic backup with retention policy
|
|
129
|
+
*/
|
|
130
|
+
private setupAutoBackup(options: AutoBackupOptions): void {
|
|
131
|
+
if (this.dbPath === ":memory:") {
|
|
132
|
+
backupLog.warn("Auto-backup is not available for in-memory databases")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.autoBackupOptions = {
|
|
137
|
+
enabled: options.enabled,
|
|
138
|
+
directory: options.directory,
|
|
139
|
+
intervalMs: options.intervalMs ?? 60 * 60 * 1000, // Default: 1 hour
|
|
140
|
+
maxBackups: options.maxBackups ?? 10,
|
|
141
|
+
filenamePrefix: options.filenamePrefix ?? "backup",
|
|
142
|
+
compress: options.compress ?? false,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Ensure backup directory exists
|
|
146
|
+
const fs = require("node:fs")
|
|
147
|
+
if (!fs.existsSync(this.autoBackupOptions.directory)) {
|
|
148
|
+
fs.mkdirSync(this.autoBackupOptions.directory, { recursive: true })
|
|
149
|
+
backupLog.info(`Created backup directory: ${this.autoBackupOptions.directory}`)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Create initial backup
|
|
153
|
+
this.backup()
|
|
154
|
+
|
|
155
|
+
// Setup interval for periodic backups
|
|
156
|
+
this.autoBackupTimer = setInterval(() => {
|
|
157
|
+
this.backup()
|
|
158
|
+
}, this.autoBackupOptions.intervalMs)
|
|
159
|
+
|
|
160
|
+
backupLog.info(
|
|
161
|
+
`Auto-backup enabled: interval=${this.autoBackupOptions.intervalMs}ms, maxBackups=${this.autoBackupOptions.maxBackups}`
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Create a backup of the database
|
|
167
|
+
*
|
|
168
|
+
* @param customPath - Optional custom path for the backup file. If not provided, uses auto-backup settings or generates a timestamped filename.
|
|
169
|
+
* @returns The path to the created backup file
|
|
170
|
+
*/
|
|
171
|
+
backup(customPath?: string): string {
|
|
172
|
+
if (this.dbPath === ":memory:") {
|
|
173
|
+
throw new Error("Cannot backup an in-memory database")
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const path = require("node:path")
|
|
177
|
+
|
|
178
|
+
let backupPath: string
|
|
179
|
+
|
|
180
|
+
if (customPath) {
|
|
181
|
+
backupPath = customPath
|
|
182
|
+
} else if (this.autoBackupOptions) {
|
|
183
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
184
|
+
const filename = `${this.autoBackupOptions.filenamePrefix}_${timestamp}.db`
|
|
185
|
+
backupPath = path.join(this.autoBackupOptions.directory, filename)
|
|
186
|
+
} else {
|
|
187
|
+
// Generate a default backup path next to the database file
|
|
188
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
189
|
+
const dir = path.dirname(this.dbPath)
|
|
190
|
+
const basename = path.basename(this.dbPath, path.extname(this.dbPath))
|
|
191
|
+
backupPath = path.join(dir, `${basename}_backup_${timestamp}.db`)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Use SQLite's backup API via VACUUM INTO for a consistent backup
|
|
195
|
+
try {
|
|
196
|
+
this.db.run(`VACUUM INTO '${backupPath.replace(/'/g, "''")}'`)
|
|
197
|
+
backupLog.backup("create", backupPath)
|
|
198
|
+
|
|
199
|
+
// Apply retention policy if auto-backup is enabled
|
|
200
|
+
if (this.autoBackupOptions) {
|
|
201
|
+
this.applyRetentionPolicy()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return backupPath
|
|
205
|
+
} catch (error) {
|
|
206
|
+
backupLog.error(`Failed to create backup: ${error}`)
|
|
207
|
+
throw error
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Apply retention policy to remove old backups
|
|
213
|
+
*/
|
|
214
|
+
private applyRetentionPolicy(): void {
|
|
215
|
+
if (!this.autoBackupOptions) return
|
|
216
|
+
|
|
217
|
+
const fs = require("node:fs")
|
|
218
|
+
const path = require("node:path")
|
|
219
|
+
|
|
220
|
+
const backupDir = this.autoBackupOptions.directory
|
|
221
|
+
const prefix = this.autoBackupOptions.filenamePrefix || "backup"
|
|
222
|
+
const maxBackups = this.autoBackupOptions.maxBackups || 10
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Get all backup files matching the pattern
|
|
226
|
+
const files = fs
|
|
227
|
+
.readdirSync(backupDir)
|
|
228
|
+
.filter((file: string) => file.startsWith(prefix) && file.endsWith(".db"))
|
|
229
|
+
.map((file: string) => ({
|
|
230
|
+
name: file,
|
|
231
|
+
path: path.join(backupDir, file),
|
|
232
|
+
mtime: fs.statSync(path.join(backupDir, file)).mtime.getTime(),
|
|
233
|
+
}))
|
|
234
|
+
.sort((a: { mtime: number }, b: { mtime: number }) => b.mtime - a.mtime) // Sort by newest first
|
|
235
|
+
|
|
236
|
+
// Remove excess backups
|
|
237
|
+
if (files.length > maxBackups) {
|
|
238
|
+
const toDelete = files.slice(maxBackups)
|
|
239
|
+
for (const file of toDelete) {
|
|
240
|
+
fs.unlinkSync(file.path)
|
|
241
|
+
backupLog.debug(`Removed old backup: ${file.name}`)
|
|
242
|
+
}
|
|
243
|
+
backupLog.info(`Retention policy applied: removed ${toDelete.length} old backup(s)`)
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
backupLog.error(`Failed to apply retention policy: ${error}`)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* List all available backups
|
|
252
|
+
*
|
|
253
|
+
* @returns Array of backup file information
|
|
254
|
+
*/
|
|
255
|
+
listBackups(): Array<{ filename: string; path: string; size: number; created: Date }> {
|
|
256
|
+
if (!this.autoBackupOptions) {
|
|
257
|
+
backupLog.warn("Auto-backup is not configured. Use backup() with a custom path instead.")
|
|
258
|
+
return []
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const fs = require("node:fs")
|
|
262
|
+
const path = require("node:path")
|
|
263
|
+
|
|
264
|
+
const backupDir = this.autoBackupOptions.directory
|
|
265
|
+
const prefix = this.autoBackupOptions.filenamePrefix || "backup"
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
return fs
|
|
269
|
+
.readdirSync(backupDir)
|
|
270
|
+
.filter((file: string) => file.startsWith(prefix) && file.endsWith(".db"))
|
|
271
|
+
.map((file: string) => {
|
|
272
|
+
const filePath = path.join(backupDir, file)
|
|
273
|
+
const stats = fs.statSync(filePath)
|
|
274
|
+
return {
|
|
275
|
+
filename: file,
|
|
276
|
+
path: filePath,
|
|
277
|
+
size: stats.size,
|
|
278
|
+
created: stats.mtime,
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
.sort(
|
|
282
|
+
(a: { created: Date }, b: { created: Date }) => b.created.getTime() - a.created.getTime()
|
|
283
|
+
)
|
|
284
|
+
} catch (error) {
|
|
285
|
+
backupLog.error(`Failed to list backups: ${error}`)
|
|
286
|
+
return []
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Restore database from a backup file
|
|
292
|
+
*
|
|
293
|
+
* @param backupPath - Path to the backup file to restore from
|
|
294
|
+
* @param targetPath - Optional target path. If not provided, restores to the original database path.
|
|
295
|
+
*/
|
|
296
|
+
restore(backupPath: string, targetPath?: string): void {
|
|
297
|
+
const fs = require("node:fs")
|
|
298
|
+
|
|
299
|
+
if (!fs.existsSync(backupPath)) {
|
|
300
|
+
throw new Error(`Backup file not found: ${backupPath}`)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const restorePath = targetPath || this.dbPath
|
|
304
|
+
|
|
305
|
+
if (restorePath === ":memory:") {
|
|
306
|
+
throw new Error("Cannot restore to an in-memory database path")
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Close current connection if restoring to the same path
|
|
310
|
+
if (restorePath === this.dbPath) {
|
|
311
|
+
this.db.close()
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
fs.copyFileSync(backupPath, restorePath)
|
|
316
|
+
backupLog.backup("restore", backupPath)
|
|
317
|
+
|
|
318
|
+
// Reopen database if we closed it
|
|
319
|
+
if (restorePath === this.dbPath) {
|
|
320
|
+
this.db = new Database(this.dbPath)
|
|
321
|
+
dbLog.info("Database connection reopened after restore")
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
backupLog.error(`Failed to restore backup: ${error}`)
|
|
325
|
+
throw error
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Stop auto-backup if it's running
|
|
331
|
+
*/
|
|
332
|
+
stopAutoBackup(): void {
|
|
333
|
+
if (this.autoBackupTimer) {
|
|
334
|
+
clearInterval(this.autoBackupTimer)
|
|
335
|
+
this.autoBackupTimer = null
|
|
336
|
+
backupLog.info("Auto-backup stopped")
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get the database file path
|
|
342
|
+
*/
|
|
343
|
+
getPath(): string {
|
|
344
|
+
return this.dbPath
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Get a typed QueryBuilder for a given table name.
|
|
349
|
+
* (Documentation remains the same as before...)
|
|
350
|
+
*/
|
|
351
|
+
table<T extends Record<string, unknown>>(
|
|
352
|
+
tableName: string,
|
|
353
|
+
parser: Partial<Parser<T>>
|
|
354
|
+
): QueryBuilder<T> {
|
|
355
|
+
const pObj: Parser<T> = {
|
|
356
|
+
JSON: parser.JSON || [],
|
|
357
|
+
MODULE: parser.MODULE || {},
|
|
358
|
+
BOOLEAN: parser.BOOLEAN || [],
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
tableLog.debug(`Creating QueryBuilder for: ${tableName}`)
|
|
362
|
+
return new QueryBuilder<T>(this.db, tableName, pObj)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Close the underlying SQLite database handle.
|
|
367
|
+
* Also stops auto-backup if it's running.
|
|
368
|
+
*/
|
|
369
|
+
close(): void {
|
|
370
|
+
dbLog.connection(this.dbPath, "close")
|
|
371
|
+
this.stopAutoBackup()
|
|
372
|
+
this.db.close()
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Create a table with comprehensive type safety and feature support.
|
|
377
|
+
*
|
|
378
|
+
* Now supports all SQLite features:
|
|
379
|
+
*
|
|
380
|
+
* **Basic Usage:**
|
|
381
|
+
* ```ts
|
|
382
|
+
* import { column, sql } from "./db";
|
|
383
|
+
*
|
|
384
|
+
* db.createTable("users", {
|
|
385
|
+
* id: column.id(), // Auto-incrementing primary key
|
|
386
|
+
* email: column.varchar(255, { unique: true, notNull: true }),
|
|
387
|
+
* name: column.text({ notNull: true }),
|
|
388
|
+
* age: column.integer({ check: 'age >= 0 AND age <= 150' }),
|
|
389
|
+
* balance: column.numeric({ precision: 10, scale: 2, default: 0 }),
|
|
390
|
+
* is_active: column.boolean({ default: sql.true() }),
|
|
391
|
+
* metadata: column.json({ validateJson: true }),
|
|
392
|
+
* created_at: column.createdAt(),
|
|
393
|
+
* updated_at: column.updatedAt(),
|
|
394
|
+
* });
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* **Advanced Features:**
|
|
398
|
+
* ```ts
|
|
399
|
+
* db.createTable("orders", {
|
|
400
|
+
* id: column.id(),
|
|
401
|
+
* order_number: column.varchar(50, {
|
|
402
|
+
* unique: true,
|
|
403
|
+
* default: sql.raw("'ORD-' || strftime('%Y%m%d', 'now') || '-' || substr(hex(randomblob(4)), 1, 8)")
|
|
404
|
+
* }),
|
|
405
|
+
* customer_id: column.foreignKey('users', 'id', {
|
|
406
|
+
* onDelete: 'CASCADE',
|
|
407
|
+
* onUpdate: 'RESTRICT'
|
|
408
|
+
* }),
|
|
409
|
+
* status: column.enum(['pending', 'paid', 'shipped', 'delivered'], {
|
|
410
|
+
* default: 'pending'
|
|
411
|
+
* }),
|
|
412
|
+
* total: column.numeric({ precision: 10, scale: 2, notNull: true }),
|
|
413
|
+
* // Generated column
|
|
414
|
+
* display_total: {
|
|
415
|
+
* type: 'TEXT',
|
|
416
|
+
* generated: {
|
|
417
|
+
* expression: "printf('$%.2f', total)",
|
|
418
|
+
* stored: false // VIRTUAL column
|
|
419
|
+
* }
|
|
420
|
+
* },
|
|
421
|
+
* }, {
|
|
422
|
+
* constraints: {
|
|
423
|
+
* check: ['total >= 0'],
|
|
424
|
+
* unique: [['customer_id', 'order_number']]
|
|
425
|
+
* }
|
|
426
|
+
* });
|
|
427
|
+
* ```
|
|
428
|
+
*
|
|
429
|
+
* **Date/Time Columns:**
|
|
430
|
+
* ```ts
|
|
431
|
+
* db.createTable("events", {
|
|
432
|
+
* id: column.id(),
|
|
433
|
+
* name: column.text({ notNull: true }),
|
|
434
|
+
* event_date: column.date({ notNull: true }),
|
|
435
|
+
* start_time: column.time(),
|
|
436
|
+
* created_at: column.timestamp({ default: sql.unixTimestamp() }),
|
|
437
|
+
* expires_at: column.datetime({
|
|
438
|
+
* default: sql.raw("datetime('now', '+1 year')")
|
|
439
|
+
* }),
|
|
440
|
+
* });
|
|
441
|
+
* ```
|
|
442
|
+
*
|
|
443
|
+
* **JSON and Advanced Types:**
|
|
444
|
+
* ```ts
|
|
445
|
+
* db.createTable("products", {
|
|
446
|
+
* id: column.uuid({ generateDefault: true }), // UUID primary key
|
|
447
|
+
* name: column.text({ notNull: true }),
|
|
448
|
+
* price: column.real({ check: 'price > 0' }),
|
|
449
|
+
* specifications: column.json({ validateJson: true }),
|
|
450
|
+
* tags: column.text(), // JSON array
|
|
451
|
+
* image_data: column.blob(),
|
|
452
|
+
* search_vector: {
|
|
453
|
+
* type: 'TEXT',
|
|
454
|
+
* generated: {
|
|
455
|
+
* expression: "lower(name || ' ' || coalesce(json_extract(specifications, '$.description'), ''))",
|
|
456
|
+
* stored: true // STORED for indexing
|
|
457
|
+
* }
|
|
458
|
+
* }
|
|
459
|
+
* });
|
|
460
|
+
* ```
|
|
461
|
+
*
|
|
462
|
+
* @param tableName - Table name to create.
|
|
463
|
+
* @param columns - Column definitions (string, legacy object, or type-safe schema).
|
|
464
|
+
* @param options - Table options including constraints and metadata.
|
|
465
|
+
*
|
|
466
|
+
* @throws {Error} If column definitions are invalid or constraints conflict.
|
|
467
|
+
*/
|
|
468
|
+
createTable<_T extends Record<string, unknown> = Record<string, unknown>>(
|
|
469
|
+
tableName: string,
|
|
470
|
+
columns: Record<keyof _T, ColumnDefinition>,
|
|
471
|
+
options?: TableOptions<_T>
|
|
472
|
+
): QueryBuilder<_T> {
|
|
473
|
+
const temp = options?.temporary ? "TEMPORARY " : tableName === ":memory" ? "TEMPORARY " : ""
|
|
474
|
+
const ifNot = options?.ifNotExists ? "IF NOT EXISTS " : ""
|
|
475
|
+
const withoutRowId = options?.withoutRowId ? " WITHOUT ROWID" : ""
|
|
476
|
+
|
|
477
|
+
const quoteIdent = (s: string) => `"${s.replace(/"/g, '""')}"`
|
|
478
|
+
|
|
479
|
+
let columnDefs: string
|
|
480
|
+
let tableConstraints: string[] = []
|
|
481
|
+
|
|
482
|
+
if (this.isTableSchema(columns)) {
|
|
483
|
+
// comprehensive type-safe approach
|
|
484
|
+
const parts: string[] = []
|
|
485
|
+
for (const [colName, colDef] of Object.entries(columns)) {
|
|
486
|
+
if (!colName) continue
|
|
487
|
+
|
|
488
|
+
const sqlDef = this.buildColumnSQL(colName, colDef)
|
|
489
|
+
parts.push(`${quoteIdent(colName)} ${sqlDef}`)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (parts.length === 0) {
|
|
493
|
+
throw new Error("No columns provided")
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
columnDefs = parts.join(", ")
|
|
497
|
+
|
|
498
|
+
// Add table-level constraints
|
|
499
|
+
if (options?.constraints) {
|
|
500
|
+
tableConstraints = this.buildTableConstraints(options.constraints)
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
// Original object-based approach
|
|
504
|
+
const parts: string[] = []
|
|
505
|
+
for (const [col, def] of Object.entries(columns as Record<string, string>)) {
|
|
506
|
+
if (!col) continue
|
|
507
|
+
|
|
508
|
+
const defTrim = (def || "").trim()
|
|
509
|
+
if (!defTrim) {
|
|
510
|
+
throw new Error(`Missing SQL type/constraints for column "${col}"`)
|
|
511
|
+
}
|
|
512
|
+
parts.push(`${quoteIdent(col)} ${defTrim}`)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (parts.length === 0) {
|
|
516
|
+
throw new Error("No columns provided")
|
|
517
|
+
}
|
|
518
|
+
columnDefs = parts.join(", ")
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const allDefinitions = [columnDefs, ...tableConstraints].join(", ")
|
|
522
|
+
|
|
523
|
+
const columnNames = Object.keys(columns)
|
|
524
|
+
tableLog.tableCreate(tableName, columnNames)
|
|
525
|
+
|
|
526
|
+
const sql = `CREATE ${temp}TABLE ${ifNot}${quoteIdent(
|
|
527
|
+
tableName
|
|
528
|
+
)} (${allDefinitions})${withoutRowId};`
|
|
529
|
+
|
|
530
|
+
this.db.run(sql)
|
|
531
|
+
|
|
532
|
+
// Store table comment as metadata if provided
|
|
533
|
+
if (options?.comment) {
|
|
534
|
+
this.setTableComment(tableName, options.comment)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Auto-detect JSON and BOOLEAN columns from schema
|
|
538
|
+
const autoDetectedJson: Array<keyof _T> = []
|
|
539
|
+
const autoDetectedBoolean: Array<keyof _T> = []
|
|
540
|
+
|
|
541
|
+
if (this.isTableSchema(columns)) {
|
|
542
|
+
for (const [colName, colDef] of Object.entries(columns)) {
|
|
543
|
+
if (colDef.type === "JSON") {
|
|
544
|
+
autoDetectedJson.push(colName as keyof _T)
|
|
545
|
+
}
|
|
546
|
+
if (colDef.type === "BOOLEAN") {
|
|
547
|
+
autoDetectedBoolean.push(colName as keyof _T)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Merge auto-detected columns with user-provided parser options
|
|
553
|
+
const userJson = options?.parser?.JSON || []
|
|
554
|
+
const userBoolean = options?.parser?.BOOLEAN || []
|
|
555
|
+
const userModule = options?.parser?.MODULE || {}
|
|
556
|
+
|
|
557
|
+
// Combine and deduplicate
|
|
558
|
+
const mergedJson = [...new Set([...autoDetectedJson, ...userJson])] as Array<keyof _T>
|
|
559
|
+
const mergedBoolean = [...new Set([...autoDetectedBoolean, ...userBoolean])] as Array<keyof _T>
|
|
560
|
+
|
|
561
|
+
const pObj = {
|
|
562
|
+
JSON: mergedJson,
|
|
563
|
+
MODULE: userModule,
|
|
564
|
+
BOOLEAN: mergedBoolean,
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
tableLog.parserConfig(pObj.JSON.map(String), pObj.BOOLEAN.map(String), Object.keys(pObj.MODULE))
|
|
568
|
+
|
|
569
|
+
return this.table<_T>(tableName, pObj)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Create an index on a table
|
|
574
|
+
*/
|
|
575
|
+
createIndex(
|
|
576
|
+
indexName: string,
|
|
577
|
+
tableName: string,
|
|
578
|
+
columns: string | string[],
|
|
579
|
+
options?: {
|
|
580
|
+
unique?: boolean
|
|
581
|
+
ifNotExists?: boolean
|
|
582
|
+
where?: string
|
|
583
|
+
partial?: string
|
|
584
|
+
}
|
|
585
|
+
): void {
|
|
586
|
+
const unique = options?.unique ? "UNIQUE " : ""
|
|
587
|
+
const ifNot = options?.ifNotExists ? "IF NOT EXISTS " : ""
|
|
588
|
+
const quoteIdent = (s: string) => `"${s.replace(/"/g, '""')}"`
|
|
589
|
+
|
|
590
|
+
const columnList = Array.isArray(columns)
|
|
591
|
+
? columns.map(quoteIdent).join(", ")
|
|
592
|
+
: quoteIdent(columns)
|
|
593
|
+
|
|
594
|
+
let sql = `CREATE ${unique}INDEX ${ifNot}${quoteIdent(
|
|
595
|
+
indexName
|
|
596
|
+
)} ON ${quoteIdent(tableName)} (${columnList})`
|
|
597
|
+
|
|
598
|
+
if (options?.where) {
|
|
599
|
+
sql += ` WHERE ${options.where}`
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
this.db.run(`${sql};`)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Drop a table
|
|
607
|
+
*/
|
|
608
|
+
dropTable(tableName: string, options?: { ifExists?: boolean }): void {
|
|
609
|
+
const ifExists = options?.ifExists ? "IF EXISTS " : ""
|
|
610
|
+
const quoteIdent = (s: string) => `"${s.replace(/"/g, '""')}"`
|
|
611
|
+
|
|
612
|
+
const sql = `DROP TABLE ${ifExists}${quoteIdent(tableName)};`
|
|
613
|
+
this.db.run(sql)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Drop an index
|
|
618
|
+
*/
|
|
619
|
+
dropIndex(indexName: string, options?: { ifExists?: boolean }): void {
|
|
620
|
+
const ifExists = options?.ifExists ? "IF EXISTS " : ""
|
|
621
|
+
const quoteIdent = (s: string) => `"${s.replace(/"/g, '""')}"`
|
|
622
|
+
|
|
623
|
+
const sql = `DROP INDEX ${ifExists}${quoteIdent(indexName)};`
|
|
624
|
+
this.db.run(sql)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Type guard to check if columns definition is a TableSchema
|
|
629
|
+
*/
|
|
630
|
+
private isTableSchema(columns: unknown): columns is TableSchema {
|
|
631
|
+
if (typeof columns !== "object" || columns === null) {
|
|
632
|
+
return false
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Check if any value has a 'type' property with a valid SQLite type
|
|
636
|
+
for (const [_key, value] of Object.entries(columns)) {
|
|
637
|
+
if (typeof value === "object" && value !== null && "type" in value) {
|
|
638
|
+
const type = (value as { type: string }).type
|
|
639
|
+
const validTypes = [
|
|
640
|
+
"INTEGER",
|
|
641
|
+
"TEXT",
|
|
642
|
+
"REAL",
|
|
643
|
+
"BLOB",
|
|
644
|
+
"NUMERIC",
|
|
645
|
+
"INT",
|
|
646
|
+
"TINYINT",
|
|
647
|
+
"SMALLINT",
|
|
648
|
+
"MEDIUMINT",
|
|
649
|
+
"BIGINT",
|
|
650
|
+
"VARCHAR",
|
|
651
|
+
"CHAR",
|
|
652
|
+
"CHARACTER",
|
|
653
|
+
"NCHAR",
|
|
654
|
+
"NVARCHAR",
|
|
655
|
+
"CLOB",
|
|
656
|
+
"DOUBLE",
|
|
657
|
+
"FLOAT",
|
|
658
|
+
"DECIMAL",
|
|
659
|
+
"DATE",
|
|
660
|
+
"DATETIME",
|
|
661
|
+
"TIMESTAMP",
|
|
662
|
+
"TIME",
|
|
663
|
+
"BOOLEAN",
|
|
664
|
+
"JSON",
|
|
665
|
+
]
|
|
666
|
+
if (validTypes.includes(type)) {
|
|
667
|
+
return true
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return false
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Build SQL column definition from ColumnDefinition object
|
|
677
|
+
*/
|
|
678
|
+
private buildColumnSQL(columnName: string, colDef: ColumnDefinition): string {
|
|
679
|
+
const parts: string[] = []
|
|
680
|
+
|
|
681
|
+
// Add type with optional parameters
|
|
682
|
+
let typeStr = colDef.type
|
|
683
|
+
if (colDef.length) {
|
|
684
|
+
typeStr += `(${colDef.length})`
|
|
685
|
+
} else if (colDef.precision !== undefined) {
|
|
686
|
+
if (colDef.scale !== undefined) {
|
|
687
|
+
typeStr += `(${colDef.precision}, ${colDef.scale})`
|
|
688
|
+
} else {
|
|
689
|
+
typeStr += `(${colDef.precision})`
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
parts.push(typeStr)
|
|
693
|
+
|
|
694
|
+
// Add PRIMARY KEY (must come before AUTOINCREMENT)
|
|
695
|
+
if (colDef.primaryKey) {
|
|
696
|
+
parts.push("PRIMARY KEY")
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Add AUTOINCREMENT (only valid with INTEGER PRIMARY KEY)
|
|
700
|
+
if (colDef.autoincrement) {
|
|
701
|
+
if (!colDef.type.includes("INT") || !colDef.primaryKey) {
|
|
702
|
+
throw new Error(
|
|
703
|
+
`AUTOINCREMENT can only be used with INTEGER PRIMARY KEY columns (column: ${columnName})`
|
|
704
|
+
)
|
|
705
|
+
}
|
|
706
|
+
parts.push("AUTOINCREMENT")
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Add NOT NULL (but skip if PRIMARY KEY is already specified, as it's implicit)
|
|
710
|
+
if (colDef.notNull && !colDef.primaryKey) {
|
|
711
|
+
parts.push("NOT NULL")
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Add UNIQUE
|
|
715
|
+
if (colDef.unique) {
|
|
716
|
+
parts.push("UNIQUE")
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Add DEFAULT
|
|
720
|
+
if (colDef.default !== undefined) {
|
|
721
|
+
if (colDef.default === null) {
|
|
722
|
+
parts.push("DEFAULT NULL")
|
|
723
|
+
} else if (typeof colDef.default === "object" && colDef.default._type === "expression") {
|
|
724
|
+
// Handle DefaultExpression
|
|
725
|
+
parts.push(`DEFAULT (${colDef.default.expression})`)
|
|
726
|
+
} else if (typeof colDef.default === "string") {
|
|
727
|
+
// Handle string defaults - check if it's a function call or literal
|
|
728
|
+
if (this.isSQLFunction(colDef.default)) {
|
|
729
|
+
parts.push(`DEFAULT (${colDef.default})`)
|
|
730
|
+
} else {
|
|
731
|
+
// Literal string value
|
|
732
|
+
parts.push(`DEFAULT '${colDef.default.replace(/'/g, "''")}'`)
|
|
733
|
+
}
|
|
734
|
+
} else if (typeof colDef.default === "boolean") {
|
|
735
|
+
parts.push(`DEFAULT ${colDef.default ? 1 : 0}`)
|
|
736
|
+
} else {
|
|
737
|
+
parts.push(`DEFAULT ${colDef.default}`)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Add COLLATE
|
|
742
|
+
if (colDef.collate) {
|
|
743
|
+
parts.push(`COLLATE ${colDef.collate}`)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Add CHECK constraint (replace placeholder with actual column name)
|
|
747
|
+
if (colDef.check) {
|
|
748
|
+
const checkConstraint = colDef.check.replace(
|
|
749
|
+
/\{\{COLUMN\}\}/g,
|
|
750
|
+
`"${columnName.replace(/"/g, '""')}"`
|
|
751
|
+
)
|
|
752
|
+
parts.push(`CHECK (${checkConstraint})`)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Add REFERENCES (foreign key)
|
|
756
|
+
if (colDef.references) {
|
|
757
|
+
const ref = colDef.references
|
|
758
|
+
let refClause = `REFERENCES "${ref.table.replace(
|
|
759
|
+
/"/g,
|
|
760
|
+
'""'
|
|
761
|
+
)}"("${ref.column.replace(/"/g, '""')}")`
|
|
762
|
+
|
|
763
|
+
if (ref.onDelete) {
|
|
764
|
+
refClause += ` ON DELETE ${ref.onDelete}`
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (ref.onUpdate) {
|
|
768
|
+
refClause += ` ON UPDATE ${ref.onUpdate}`
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
parts.push(refClause)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Add GENERATED column
|
|
775
|
+
if (colDef.generated) {
|
|
776
|
+
const storageType = colDef.generated.stored ? "STORED" : "VIRTUAL"
|
|
777
|
+
parts.push(`GENERATED ALWAYS AS (${colDef.generated.expression}) ${storageType}`)
|
|
778
|
+
}
|
|
779
|
+
return parts.join(" ")
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Build table-level constraints
|
|
784
|
+
*/
|
|
785
|
+
private buildTableConstraints<T>(constraints: TableConstraints<T>): string[] {
|
|
786
|
+
const parts: string[] = []
|
|
787
|
+
|
|
788
|
+
// PRIMARY KEY constraint
|
|
789
|
+
if (constraints.primaryKey && constraints.primaryKey.length > 0) {
|
|
790
|
+
const columns = constraints.primaryKey
|
|
791
|
+
.map((col) => `"${String(col).replace(/"/g, '""')}"`)
|
|
792
|
+
.join(", ")
|
|
793
|
+
parts.push(`PRIMARY KEY (${columns})`)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// UNIQUE constraints
|
|
797
|
+
if (constraints.unique) {
|
|
798
|
+
if (Array.isArray(constraints.unique[0])) {
|
|
799
|
+
// Multiple composite unique constraints
|
|
800
|
+
for (const uniqueGroup of constraints.unique as string[][]) {
|
|
801
|
+
const columns = uniqueGroup.map((col) => `"${col.replace(/"/g, '""')}"`).join(", ")
|
|
802
|
+
parts.push(`UNIQUE (${columns})`)
|
|
803
|
+
}
|
|
804
|
+
} else {
|
|
805
|
+
// Single unique constraint
|
|
806
|
+
const columns = (constraints.unique as string[])
|
|
807
|
+
.map((col) => `"${col.replace(/"/g, '""')}"`)
|
|
808
|
+
.join(", ")
|
|
809
|
+
parts.push(`UNIQUE (${columns})`)
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// CHECK constraints
|
|
814
|
+
if (constraints.check) {
|
|
815
|
+
for (const checkExpr of constraints.check) {
|
|
816
|
+
parts.push(`CHECK (${checkExpr})`)
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// FOREIGN KEY constraints
|
|
821
|
+
if (constraints.foreignKeys) {
|
|
822
|
+
for (const fk of constraints.foreignKeys) {
|
|
823
|
+
const columns = fk.columns.map((col) => `"${String(col).replace(/"/g, '""')}"`).join(", ")
|
|
824
|
+
const refColumns = fk.references.columns
|
|
825
|
+
.map((col) => `"${col.replace(/"/g, '""')}"`)
|
|
826
|
+
.join(", ")
|
|
827
|
+
|
|
828
|
+
let fkClause = `FOREIGN KEY (${columns}) REFERENCES "${fk.references.table.replace(
|
|
829
|
+
/"/g,
|
|
830
|
+
'""'
|
|
831
|
+
)}" (${refColumns})`
|
|
832
|
+
|
|
833
|
+
if (fk.references.onDelete) {
|
|
834
|
+
fkClause += ` ON DELETE ${fk.references.onDelete}`
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (fk.references.onUpdate) {
|
|
838
|
+
fkClause += ` ON UPDATE ${fk.references.onUpdate}`
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
parts.push(fkClause)
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return parts
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Check if a string looks like a SQL function call
|
|
850
|
+
*/
|
|
851
|
+
private isSQLFunction(str: string): boolean {
|
|
852
|
+
// Simple heuristic: contains parentheses and common SQL function patterns
|
|
853
|
+
const functionPatterns = [
|
|
854
|
+
/^\w+\s*\(/, // Function name followed by (
|
|
855
|
+
/^(datetime|date|time|strftime|current_timestamp|current_date|current_time)/i,
|
|
856
|
+
/^(random|abs|length|upper|lower|trim)/i,
|
|
857
|
+
/^(coalesce|ifnull|nullif|iif)/i,
|
|
858
|
+
/^(json|json_extract|json_valid)/i,
|
|
859
|
+
]
|
|
860
|
+
|
|
861
|
+
return functionPatterns.some((pattern) => pattern.test(str.trim()))
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Store table comment as metadata (using a system table if needed)
|
|
866
|
+
*/
|
|
867
|
+
private setTableComment(tableName: string, comment: string): void {
|
|
868
|
+
// Create metadata table if it doesn't exist
|
|
869
|
+
try {
|
|
870
|
+
this.db.run(`
|
|
871
|
+
CREATE TABLE IF NOT EXISTS __table_metadata__ (
|
|
872
|
+
table_name TEXT PRIMARY KEY,
|
|
873
|
+
comment TEXT,
|
|
874
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
875
|
+
)
|
|
876
|
+
`)
|
|
877
|
+
|
|
878
|
+
// Insert or replace comment
|
|
879
|
+
const stmt = this.db.prepare(`
|
|
880
|
+
INSERT OR REPLACE INTO __table_metadata__ (table_name, comment, created_at)
|
|
881
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
882
|
+
`)
|
|
883
|
+
stmt.run(tableName, comment)
|
|
884
|
+
} catch (error) {
|
|
885
|
+
// Silently ignore if we can't create metadata table
|
|
886
|
+
logger.warn(`Could not store table comment for ${tableName}: ${error}`)
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Get table comment from metadata
|
|
892
|
+
*/
|
|
893
|
+
getTableComment(tableName: string): string | null {
|
|
894
|
+
try {
|
|
895
|
+
const stmt = this.db.prepare(`
|
|
896
|
+
SELECT comment FROM __table_metadata__ WHERE table_name = ?
|
|
897
|
+
`)
|
|
898
|
+
const result = stmt.get(tableName) as { comment: string } | undefined
|
|
899
|
+
return result?.comment || null
|
|
900
|
+
} catch (_error) {
|
|
901
|
+
return null
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* runute a raw SQL statement
|
|
907
|
+
*/
|
|
908
|
+
run(sql: string): void {
|
|
909
|
+
logger.debug(`runuting SQL: ${sql}`)
|
|
910
|
+
this.db.run(sql)
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Prepare a SQL statement for repeated runution
|
|
915
|
+
*/
|
|
916
|
+
prepare(sql: string) {
|
|
917
|
+
return this.db.prepare(sql)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* runute a transaction
|
|
922
|
+
*/
|
|
923
|
+
transaction<T>(fn: () => T): T {
|
|
924
|
+
return this.db.transaction(fn)()
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Begin a transaction manually
|
|
929
|
+
*/
|
|
930
|
+
begin(mode?: "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"): void {
|
|
931
|
+
const modeStr = mode ? ` ${mode}` : ""
|
|
932
|
+
this.db.run(`BEGIN${modeStr}`)
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Commit a transaction
|
|
937
|
+
*/
|
|
938
|
+
commit(): void {
|
|
939
|
+
dbLog.transaction("commit")
|
|
940
|
+
this.run("COMMIT")
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Rollback a transaction
|
|
945
|
+
*/
|
|
946
|
+
rollback(): void {
|
|
947
|
+
dbLog.transaction("rollback")
|
|
948
|
+
this.run("ROLLBACK")
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Create a savepoint
|
|
953
|
+
*/
|
|
954
|
+
savepoint(name: string): void {
|
|
955
|
+
const quotedName = `"${name.replace(/"/g, '""')}"`
|
|
956
|
+
this.db.run(`SAVEPOINT ${quotedName}`)
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Release a savepoint
|
|
961
|
+
*/
|
|
962
|
+
releaseSavepoint(name: string): void {
|
|
963
|
+
const quotedName = `"${name.replace(/"/g, '""')}"`
|
|
964
|
+
this.db.run(`RELEASE SAVEPOINT ${quotedName}`)
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Rollback to a savepoint
|
|
969
|
+
*/
|
|
970
|
+
rollbackToSavepoint(name: string): void {
|
|
971
|
+
const quotedName = `"${name.replace(/"/g, '""')}"`
|
|
972
|
+
this.db.run(`ROLLBACK TO SAVEPOINT ${quotedName}`)
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Vacuum the database (reclaim space and optimize)
|
|
977
|
+
*/
|
|
978
|
+
vacuum() {
|
|
979
|
+
const result = this.db.run("VACUUM")
|
|
980
|
+
dbLog.debug("Vacuum completed")
|
|
981
|
+
return result
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Analyze the database (update statistics for query optimizer)
|
|
986
|
+
*/
|
|
987
|
+
analyze(tableName?: string): void {
|
|
988
|
+
if (tableName) {
|
|
989
|
+
const quotedName = `"${tableName.replace(/"/g, '""')}"`
|
|
990
|
+
this.db.run(`ANALYZE ${quotedName}`)
|
|
991
|
+
} else {
|
|
992
|
+
this.db.run("ANALYZE")
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Check database integrity
|
|
998
|
+
*/
|
|
999
|
+
integrityCheck(): Array<{ integrity_check: string }> {
|
|
1000
|
+
const stmt = this.db.prepare("PRAGMA integrity_check")
|
|
1001
|
+
return stmt.all() as Array<{ integrity_check: string }>
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Get database schema information
|
|
1006
|
+
*/
|
|
1007
|
+
getSchema(): Array<{ name: string; type: string; sql: string }> {
|
|
1008
|
+
const stmt = this.db.prepare(`
|
|
1009
|
+
SELECT name, type, sql
|
|
1010
|
+
FROM sqlite_master
|
|
1011
|
+
WHERE type IN ('table', 'index', 'view', 'trigger')
|
|
1012
|
+
ORDER BY type, name
|
|
1013
|
+
`)
|
|
1014
|
+
return stmt.all() as Array<{ name: string; type: string; sql: string }>
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Get table info (columns, types, constraints)
|
|
1019
|
+
*/
|
|
1020
|
+
getTableInfo(tableName: string): Array<{
|
|
1021
|
+
cid: number
|
|
1022
|
+
name: string
|
|
1023
|
+
type: string
|
|
1024
|
+
notnull: number
|
|
1025
|
+
dflt_value: SQLQueryBindings
|
|
1026
|
+
pk: number
|
|
1027
|
+
}> {
|
|
1028
|
+
const stmt = this.db.prepare(`PRAGMA table_info("${tableName.replace(/"/g, '""')}")`)
|
|
1029
|
+
return stmt.all() as Array<{
|
|
1030
|
+
cid: number
|
|
1031
|
+
name: string
|
|
1032
|
+
type: string
|
|
1033
|
+
notnull: number
|
|
1034
|
+
dflt_value: SQLQueryBindings
|
|
1035
|
+
pk: number
|
|
1036
|
+
}>
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Get foreign key information for a table
|
|
1041
|
+
*/
|
|
1042
|
+
getForeignKeys(tableName: string): Array<{
|
|
1043
|
+
id: number
|
|
1044
|
+
seq: number
|
|
1045
|
+
table: string
|
|
1046
|
+
from: string
|
|
1047
|
+
to: string
|
|
1048
|
+
on_update: string
|
|
1049
|
+
on_delete: string
|
|
1050
|
+
match: string
|
|
1051
|
+
}> {
|
|
1052
|
+
const stmt = this.db.prepare(`PRAGMA foreign_key_list("${tableName.replace(/"/g, '""')}")`)
|
|
1053
|
+
return stmt.all() as Array<{
|
|
1054
|
+
id: number
|
|
1055
|
+
seq: number
|
|
1056
|
+
table: string
|
|
1057
|
+
from: string
|
|
1058
|
+
to: string
|
|
1059
|
+
on_update: string
|
|
1060
|
+
on_delete: string
|
|
1061
|
+
match: string
|
|
1062
|
+
}>
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Get index information for a table
|
|
1067
|
+
*/
|
|
1068
|
+
getIndexes(tableName: string): Array<{
|
|
1069
|
+
name: string
|
|
1070
|
+
unique: number
|
|
1071
|
+
origin: string
|
|
1072
|
+
partial: number
|
|
1073
|
+
}> {
|
|
1074
|
+
const stmt = this.db.prepare(`PRAGMA index_list("${tableName.replace(/"/g, '""')}")`)
|
|
1075
|
+
return stmt.all() as Array<{
|
|
1076
|
+
name: string
|
|
1077
|
+
unique: number
|
|
1078
|
+
origin: string
|
|
1079
|
+
partial: number
|
|
1080
|
+
}>
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Set or get a PRAGMA value.
|
|
1085
|
+
*
|
|
1086
|
+
* @param name - PRAGMA name (e.g., "foreign_keys", "journal_mode")
|
|
1087
|
+
* @param value - Value to set (omit to get current value)
|
|
1088
|
+
* @returns Current value when getting, undefined when setting
|
|
1089
|
+
*/
|
|
1090
|
+
pragma(name: string, value?: SQLQueryBindings): SQLQueryBindings | undefined {
|
|
1091
|
+
if (value !== undefined) {
|
|
1092
|
+
this.db.run(`PRAGMA ${name} = ${value}`)
|
|
1093
|
+
return undefined
|
|
1094
|
+
}
|
|
1095
|
+
const result = this.db.prepare(`PRAGMA ${name}`).get() as Record<string, SQLQueryBindings>
|
|
1096
|
+
return Object.values(result)[0]
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Load a SQLite extension.
|
|
1101
|
+
*
|
|
1102
|
+
* @param path - Absolute path to the compiled SQLite extension
|
|
1103
|
+
*/
|
|
1104
|
+
loadExtension(path: string): void {
|
|
1105
|
+
this.db.loadExtension(path)
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Get direct access to the underlying SQLite database instance.
|
|
1110
|
+
* Use this for advanced operations not covered by the wrapper.
|
|
1111
|
+
*
|
|
1112
|
+
* @returns The underlying Database instance
|
|
1113
|
+
*/
|
|
1114
|
+
getDb(): Database {
|
|
1115
|
+
return this.db
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
export { DB }
|
|
1120
|
+
export default DB
|