@dockstat/sqlite-wrapper 1.2.6 → 1.2.8
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 +99 -66
- package/index.ts +858 -840
- package/package.json +54 -54
- package/query-builder/base.ts +221 -221
- package/query-builder/delete.ts +352 -352
- package/query-builder/index.ts +431 -431
- package/query-builder/insert.ts +249 -249
- package/query-builder/select.ts +358 -358
- package/query-builder/update.ts +278 -278
- package/query-builder/where.ts +307 -307
- package/types.ts +623 -623
package/index.ts
CHANGED
|
@@ -1,840 +1,858 @@
|
|
|
1
|
-
import { Database, type SQLQueryBindings } from
|
|
2
|
-
import { createLogger } from
|
|
3
|
-
import { QueryBuilder } from
|
|
4
|
-
import type {
|
|
5
|
-
ColumnConstraints,
|
|
6
|
-
ColumnDefinition,
|
|
7
|
-
DefaultExpression,
|
|
8
|
-
ForeignKeyAction,
|
|
9
|
-
JsonColumnConfig,
|
|
10
|
-
SQLiteType,
|
|
11
|
-
TableConstraints,
|
|
12
|
-
TableOptions,
|
|
13
|
-
TableSchema,
|
|
14
|
-
} from
|
|
15
|
-
|
|
16
|
-
const logger = createLogger(
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Re-export all types and utilities
|
|
20
|
-
*/
|
|
21
|
-
export { QueryBuilder }
|
|
22
|
-
export type {
|
|
23
|
-
InsertResult,
|
|
24
|
-
UpdateResult,
|
|
25
|
-
DeleteResult,
|
|
26
|
-
InsertOptions,
|
|
27
|
-
ColumnNames,
|
|
28
|
-
WhereCondition,
|
|
29
|
-
RegexCondition,
|
|
30
|
-
JsonColumnConfig,
|
|
31
|
-
TableSchema,
|
|
32
|
-
ColumnDefinition,
|
|
33
|
-
SQLiteType,
|
|
34
|
-
ColumnConstraints,
|
|
35
|
-
TableOptions,
|
|
36
|
-
DefaultExpression,
|
|
37
|
-
ForeignKeyAction,
|
|
38
|
-
TableConstraints,
|
|
39
|
-
} from
|
|
40
|
-
|
|
41
|
-
// Re-export helper utilities
|
|
42
|
-
export {
|
|
43
|
-
column,
|
|
44
|
-
sql,
|
|
45
|
-
SQLiteTypes,
|
|
46
|
-
SQLiteFunctions,
|
|
47
|
-
SQLiteKeywords,
|
|
48
|
-
defaultExpr,
|
|
49
|
-
} from
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* TypedSQLite — comprehensive wrapper around bun:sqlite `Database`.
|
|
53
|
-
*
|
|
54
|
-
* This class provides full type safety for SQLite operations with support for:
|
|
55
|
-
* - All SQLite data types and variations
|
|
56
|
-
* - Built-in SQL functions
|
|
57
|
-
* - Complex constraints and relationships
|
|
58
|
-
* - Generated columns
|
|
59
|
-
* - Table-level constraints
|
|
60
|
-
* - JSON column support
|
|
61
|
-
* - And much more...
|
|
62
|
-
*/
|
|
63
|
-
class DB {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Open or create a SQLite database at `path`.
|
|
68
|
-
*
|
|
69
|
-
* @param path - Path to the SQLite file (e.g. "app.db"). Use ":memory:" for in-memory DB.
|
|
70
|
-
* @param options - Optional database configuration
|
|
71
|
-
*/
|
|
72
|
-
constructor(
|
|
73
|
-
path: string,
|
|
74
|
-
options?: {
|
|
75
|
-
pragmas?: Array<[string, SQLQueryBindings]
|
|
76
|
-
loadExtensions?: string[]
|
|
77
|
-
}
|
|
78
|
-
) {
|
|
79
|
-
logger.info(`Opening database: ${path}`)
|
|
80
|
-
this.db = new Database(path)
|
|
81
|
-
|
|
82
|
-
// Apply PRAGMA settings if provided
|
|
83
|
-
if (options?.pragmas) {
|
|
84
|
-
for (const [name, value] of options.pragmas) {
|
|
85
|
-
this.pragma(name, value)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Load extensions if provided
|
|
90
|
-
if (options?.loadExtensions) {
|
|
91
|
-
for (const extensionPath of options.loadExtensions) {
|
|
92
|
-
this.loadExtension(extensionPath)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Get a typed QueryBuilder for a given table name.
|
|
99
|
-
* (Documentation remains the same as before...)
|
|
100
|
-
*/
|
|
101
|
-
table<T extends Record<string, unknown>>(
|
|
102
|
-
tableName: string,
|
|
103
|
-
jsonConfig?: JsonColumnConfig<T>
|
|
104
|
-
): QueryBuilder<T> {
|
|
105
|
-
logger.debug(
|
|
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
|
-
options
|
|
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
|
-
const
|
|
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
|
-
if (colDef.
|
|
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
|
-
parts.push(`DEFAULT ${colDef.default}`)
|
|
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
|
-
|
|
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
|
-
string
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
*
|
|
821
|
-
*
|
|
822
|
-
* @param
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1
|
+
import { Database, type SQLQueryBindings } from "bun:sqlite";
|
|
2
|
+
import { createLogger } from "@dockstat/logger";
|
|
3
|
+
import { QueryBuilder } from "./query-builder/index";
|
|
4
|
+
import type {
|
|
5
|
+
ColumnConstraints,
|
|
6
|
+
ColumnDefinition,
|
|
7
|
+
DefaultExpression,
|
|
8
|
+
ForeignKeyAction,
|
|
9
|
+
JsonColumnConfig,
|
|
10
|
+
SQLiteType,
|
|
11
|
+
TableConstraints,
|
|
12
|
+
TableOptions,
|
|
13
|
+
TableSchema,
|
|
14
|
+
} from "./types";
|
|
15
|
+
|
|
16
|
+
const logger = createLogger("sqlite-wrapper");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Re-export all types and utilities
|
|
20
|
+
*/
|
|
21
|
+
export { QueryBuilder };
|
|
22
|
+
export type {
|
|
23
|
+
InsertResult,
|
|
24
|
+
UpdateResult,
|
|
25
|
+
DeleteResult,
|
|
26
|
+
InsertOptions,
|
|
27
|
+
ColumnNames,
|
|
28
|
+
WhereCondition,
|
|
29
|
+
RegexCondition,
|
|
30
|
+
JsonColumnConfig,
|
|
31
|
+
TableSchema,
|
|
32
|
+
ColumnDefinition,
|
|
33
|
+
SQLiteType,
|
|
34
|
+
ColumnConstraints,
|
|
35
|
+
TableOptions,
|
|
36
|
+
DefaultExpression,
|
|
37
|
+
ForeignKeyAction,
|
|
38
|
+
TableConstraints,
|
|
39
|
+
} from "./types";
|
|
40
|
+
|
|
41
|
+
// Re-export helper utilities
|
|
42
|
+
export {
|
|
43
|
+
column,
|
|
44
|
+
sql,
|
|
45
|
+
SQLiteTypes,
|
|
46
|
+
SQLiteFunctions,
|
|
47
|
+
SQLiteKeywords,
|
|
48
|
+
defaultExpr,
|
|
49
|
+
} from "./types";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* TypedSQLite — comprehensive wrapper around bun:sqlite `Database`.
|
|
53
|
+
*
|
|
54
|
+
* This class provides full type safety for SQLite operations with support for:
|
|
55
|
+
* - All SQLite data types and variations
|
|
56
|
+
* - Built-in SQL functions
|
|
57
|
+
* - Complex constraints and relationships
|
|
58
|
+
* - Generated columns
|
|
59
|
+
* - Table-level constraints
|
|
60
|
+
* - JSON column support
|
|
61
|
+
* - And much more...
|
|
62
|
+
*/
|
|
63
|
+
class DB {
|
|
64
|
+
protected db: Database;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Open or create a SQLite database at `path`.
|
|
68
|
+
*
|
|
69
|
+
* @param path - Path to the SQLite file (e.g. "app.db"). Use ":memory:" for in-memory DB.
|
|
70
|
+
* @param options - Optional database configuration
|
|
71
|
+
*/
|
|
72
|
+
constructor(
|
|
73
|
+
path: string,
|
|
74
|
+
options?: {
|
|
75
|
+
pragmas?: Array<[string, SQLQueryBindings]>;
|
|
76
|
+
loadExtensions?: string[];
|
|
77
|
+
}
|
|
78
|
+
) {
|
|
79
|
+
logger.info(`Opening database: ${path}`);
|
|
80
|
+
this.db = new Database(path);
|
|
81
|
+
|
|
82
|
+
// Apply PRAGMA settings if provided
|
|
83
|
+
if (options?.pragmas) {
|
|
84
|
+
for (const [name, value] of options.pragmas) {
|
|
85
|
+
this.pragma(name, value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Load extensions if provided
|
|
90
|
+
if (options?.loadExtensions) {
|
|
91
|
+
for (const extensionPath of options.loadExtensions) {
|
|
92
|
+
this.loadExtension(extensionPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get a typed QueryBuilder for a given table name.
|
|
99
|
+
* (Documentation remains the same as before...)
|
|
100
|
+
*/
|
|
101
|
+
table<T extends Record<string, unknown>>(
|
|
102
|
+
tableName: string,
|
|
103
|
+
jsonConfig?: JsonColumnConfig<T>
|
|
104
|
+
): QueryBuilder<T> {
|
|
105
|
+
logger.debug(
|
|
106
|
+
`Creating QueryBuilder for table: ${tableName} - JSONConfig: ${JSON.stringify(
|
|
107
|
+
jsonConfig
|
|
108
|
+
)}`
|
|
109
|
+
);
|
|
110
|
+
return new QueryBuilder<T>(this.db, tableName, jsonConfig);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Close the underlying SQLite database handle.
|
|
115
|
+
*/
|
|
116
|
+
close(): void {
|
|
117
|
+
logger.info("Closing database connection");
|
|
118
|
+
this.db.close();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a table with comprehensive type safety and feature support.
|
|
123
|
+
*
|
|
124
|
+
* Now supports all SQLite features:
|
|
125
|
+
*
|
|
126
|
+
* **Basic Usage:**
|
|
127
|
+
* ```ts
|
|
128
|
+
* import { column, sql } from "./db";
|
|
129
|
+
*
|
|
130
|
+
* db.createTable("users", {
|
|
131
|
+
* id: column.id(), // Auto-incrementing primary key
|
|
132
|
+
* email: column.varchar(255, { unique: true, notNull: true }),
|
|
133
|
+
* name: column.text({ notNull: true }),
|
|
134
|
+
* age: column.integer({ check: 'age >= 0 AND age <= 150' }),
|
|
135
|
+
* balance: column.numeric({ precision: 10, scale: 2, default: 0 }),
|
|
136
|
+
* is_active: column.boolean({ default: sql.true() }),
|
|
137
|
+
* metadata: column.json({ validateJson: true }),
|
|
138
|
+
* created_at: column.createdAt(),
|
|
139
|
+
* updated_at: column.updatedAt(),
|
|
140
|
+
* });
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* **Advanced Features:**
|
|
144
|
+
* ```ts
|
|
145
|
+
* db.createTable("orders", {
|
|
146
|
+
* id: column.id(),
|
|
147
|
+
* order_number: column.varchar(50, {
|
|
148
|
+
* unique: true,
|
|
149
|
+
* default: sql.raw("'ORD-' || strftime('%Y%m%d', 'now') || '-' || substr(hex(randomblob(4)), 1, 8)")
|
|
150
|
+
* }),
|
|
151
|
+
* customer_id: column.foreignKey('users', 'id', {
|
|
152
|
+
* onDelete: 'CASCADE',
|
|
153
|
+
* onUpdate: 'RESTRICT'
|
|
154
|
+
* }),
|
|
155
|
+
* status: column.enum(['pending', 'paid', 'shipped', 'delivered'], {
|
|
156
|
+
* default: 'pending'
|
|
157
|
+
* }),
|
|
158
|
+
* total: column.numeric({ precision: 10, scale: 2, notNull: true }),
|
|
159
|
+
* // Generated column
|
|
160
|
+
* display_total: {
|
|
161
|
+
* type: 'TEXT',
|
|
162
|
+
* generated: {
|
|
163
|
+
* expression: "printf('$%.2f', total)",
|
|
164
|
+
* stored: false // VIRTUAL column
|
|
165
|
+
* }
|
|
166
|
+
* },
|
|
167
|
+
* }, {
|
|
168
|
+
* constraints: {
|
|
169
|
+
* check: ['total >= 0'],
|
|
170
|
+
* unique: [['customer_id', 'order_number']]
|
|
171
|
+
* }
|
|
172
|
+
* });
|
|
173
|
+
* ```
|
|
174
|
+
*
|
|
175
|
+
* **Date/Time Columns:**
|
|
176
|
+
* ```ts
|
|
177
|
+
* db.createTable("events", {
|
|
178
|
+
* id: column.id(),
|
|
179
|
+
* name: column.text({ notNull: true }),
|
|
180
|
+
* event_date: column.date({ notNull: true }),
|
|
181
|
+
* start_time: column.time(),
|
|
182
|
+
* created_at: column.timestamp({ default: sql.unixTimestamp() }),
|
|
183
|
+
* expires_at: column.datetime({
|
|
184
|
+
* default: sql.raw("datetime('now', '+1 year')")
|
|
185
|
+
* }),
|
|
186
|
+
* });
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* **JSON and Advanced Types:**
|
|
190
|
+
* ```ts
|
|
191
|
+
* db.createTable("products", {
|
|
192
|
+
* id: column.uuid({ generateDefault: true }), // UUID primary key
|
|
193
|
+
* name: column.text({ notNull: true }),
|
|
194
|
+
* price: column.real({ check: 'price > 0' }),
|
|
195
|
+
* specifications: column.json({ validateJson: true }),
|
|
196
|
+
* tags: column.text(), // JSON array
|
|
197
|
+
* image_data: column.blob(),
|
|
198
|
+
* search_vector: {
|
|
199
|
+
* type: 'TEXT',
|
|
200
|
+
* generated: {
|
|
201
|
+
* expression: "lower(name || ' ' || coalesce(json_extract(specifications, '$.description'), ''))",
|
|
202
|
+
* stored: true // STORED for indexing
|
|
203
|
+
* }
|
|
204
|
+
* }
|
|
205
|
+
* });
|
|
206
|
+
* ```
|
|
207
|
+
*
|
|
208
|
+
* @param tableName - Table name to create.
|
|
209
|
+
* @param columns - Column definitions (string, legacy object, or type-safe schema).
|
|
210
|
+
* @param options - Table options including constraints and metadata.
|
|
211
|
+
*
|
|
212
|
+
* @throws {Error} If column definitions are invalid or constraints conflict.
|
|
213
|
+
*/
|
|
214
|
+
createTable<_T extends Record<string, unknown>>(
|
|
215
|
+
tableName: string,
|
|
216
|
+
columns:
|
|
217
|
+
| string
|
|
218
|
+
| Record<string, string>
|
|
219
|
+
| Partial<Record<Extract<keyof _T, string>, ColumnDefinition>>
|
|
220
|
+
| TableSchema,
|
|
221
|
+
options?: TableOptions<_T>
|
|
222
|
+
): QueryBuilder<_T> {
|
|
223
|
+
const temp = options?.temporary ? "TEMPORARY " : "";
|
|
224
|
+
const ifNot = options?.ifNotExists ? "IF NOT EXISTS " : "";
|
|
225
|
+
const withoutRowId = options?.withoutRowId ? " WITHOUT ROWID" : "";
|
|
226
|
+
|
|
227
|
+
const quoteIdent = (s: string) => `"${s.replace(/"/g, '""')}"`;
|
|
228
|
+
|
|
229
|
+
let columnDefs: string;
|
|
230
|
+
let tableConstraints: string[] = [];
|
|
231
|
+
|
|
232
|
+
if (typeof columns === "string") {
|
|
233
|
+
// Original string-based approach
|
|
234
|
+
columnDefs = columns.trim();
|
|
235
|
+
if (!columnDefs) {
|
|
236
|
+
throw new Error("Empty column definition string");
|
|
237
|
+
}
|
|
238
|
+
} else if (this.isTableSchema(columns)) {
|
|
239
|
+
// New comprehensive type-safe approach
|
|
240
|
+
const parts: string[] = [];
|
|
241
|
+
for (const [colName, colDef] of Object.entries(columns)) {
|
|
242
|
+
if (!colName) continue;
|
|
243
|
+
|
|
244
|
+
const sqlDef = this.buildColumnSQL(colName, colDef);
|
|
245
|
+
parts.push(`${quoteIdent(colName)} ${sqlDef}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (parts.length === 0) {
|
|
249
|
+
throw new Error("No columns provided");
|
|
250
|
+
}
|
|
251
|
+
columnDefs = parts.join(", ");
|
|
252
|
+
|
|
253
|
+
// Add table-level constraints
|
|
254
|
+
if (options?.constraints) {
|
|
255
|
+
tableConstraints = this.buildTableConstraints(options.constraints);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
// Original object-based approach
|
|
259
|
+
const parts: string[] = [];
|
|
260
|
+
for (const [col, def] of Object.entries(columns)) {
|
|
261
|
+
if (!col) continue;
|
|
262
|
+
|
|
263
|
+
const defTrim = (def ?? "").trim();
|
|
264
|
+
if (!defTrim) {
|
|
265
|
+
throw new Error(`Missing SQL type/constraints for column "${col}"`);
|
|
266
|
+
}
|
|
267
|
+
parts.push(`${quoteIdent(col)} ${defTrim}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (parts.length === 0) {
|
|
271
|
+
throw new Error("No columns provided");
|
|
272
|
+
}
|
|
273
|
+
columnDefs = parts.join(", ");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Combine column definitions and table constraints
|
|
277
|
+
const allDefinitions = [columnDefs, ...tableConstraints].join(", ");
|
|
278
|
+
|
|
279
|
+
const sql = `CREATE ${temp}TABLE ${ifNot}${quoteIdent(
|
|
280
|
+
tableName
|
|
281
|
+
)} (${allDefinitions})${withoutRowId};`;
|
|
282
|
+
|
|
283
|
+
this.db.run(sql);
|
|
284
|
+
|
|
285
|
+
// Store table comment as metadata if provided
|
|
286
|
+
if (options?.comment) {
|
|
287
|
+
this.setTableComment(tableName, options.comment);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return this.table<_T>(tableName, options?.jsonConfig);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Create an index on a table
|
|
295
|
+
*/
|
|
296
|
+
createIndex(
|
|
297
|
+
indexName: string,
|
|
298
|
+
tableName: string,
|
|
299
|
+
columns: string | string[],
|
|
300
|
+
options?: {
|
|
301
|
+
unique?: boolean;
|
|
302
|
+
ifNotExists?: boolean;
|
|
303
|
+
where?: string;
|
|
304
|
+
partial?: string;
|
|
305
|
+
}
|
|
306
|
+
): void {
|
|
307
|
+
const unique = options?.unique ? "UNIQUE " : "";
|
|
308
|
+
const ifNot = options?.ifNotExists ? "IF NOT EXISTS " : "";
|
|
309
|
+
const quoteIdent = (s: string) => `"${s.replace(/"/g, '""')}"`;
|
|
310
|
+
|
|
311
|
+
const columnList = Array.isArray(columns)
|
|
312
|
+
? columns.map(quoteIdent).join(", ")
|
|
313
|
+
: quoteIdent(columns);
|
|
314
|
+
|
|
315
|
+
let sql = `CREATE ${unique}INDEX ${ifNot}${quoteIdent(
|
|
316
|
+
indexName
|
|
317
|
+
)} ON ${quoteIdent(tableName)} (${columnList})`;
|
|
318
|
+
|
|
319
|
+
if (options?.where) {
|
|
320
|
+
sql += ` WHERE ${options.where}`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
this.db.run(`${sql};`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Drop a table
|
|
328
|
+
*/
|
|
329
|
+
dropTable(tableName: string, options?: { ifExists?: boolean }): void {
|
|
330
|
+
const ifExists = options?.ifExists ? "IF EXISTS " : "";
|
|
331
|
+
const quoteIdent = (s: string) => `"${s.replace(/"/g, '""')}"`;
|
|
332
|
+
|
|
333
|
+
const sql = `DROP TABLE ${ifExists}${quoteIdent(tableName)};`;
|
|
334
|
+
this.db.run(sql);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Drop an index
|
|
339
|
+
*/
|
|
340
|
+
dropIndex(indexName: string, options?: { ifExists?: boolean }): void {
|
|
341
|
+
const ifExists = options?.ifExists ? "IF EXISTS " : "";
|
|
342
|
+
const quoteIdent = (s: string) => `"${s.replace(/"/g, '""')}"`;
|
|
343
|
+
|
|
344
|
+
const sql = `DROP INDEX ${ifExists}${quoteIdent(indexName)};`;
|
|
345
|
+
this.db.run(sql);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Type guard to check if columns definition is a TableSchema
|
|
350
|
+
*/
|
|
351
|
+
private isTableSchema(columns: unknown): columns is TableSchema {
|
|
352
|
+
if (typeof columns !== "object" || columns === null) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check if any value has a 'type' property with a valid SQLite type
|
|
357
|
+
for (const [_key, value] of Object.entries(columns)) {
|
|
358
|
+
if (typeof value === "object" && value !== null && "type" in value) {
|
|
359
|
+
const type = (value as { type: string }).type;
|
|
360
|
+
const validTypes = [
|
|
361
|
+
"INTEGER",
|
|
362
|
+
"TEXT",
|
|
363
|
+
"REAL",
|
|
364
|
+
"BLOB",
|
|
365
|
+
"NUMERIC",
|
|
366
|
+
"INT",
|
|
367
|
+
"TINYINT",
|
|
368
|
+
"SMALLINT",
|
|
369
|
+
"MEDIUMINT",
|
|
370
|
+
"BIGINT",
|
|
371
|
+
"VARCHAR",
|
|
372
|
+
"CHAR",
|
|
373
|
+
"CHARACTER",
|
|
374
|
+
"NCHAR",
|
|
375
|
+
"NVARCHAR",
|
|
376
|
+
"CLOB",
|
|
377
|
+
"DOUBLE",
|
|
378
|
+
"FLOAT",
|
|
379
|
+
"DECIMAL",
|
|
380
|
+
"DATE",
|
|
381
|
+
"DATETIME",
|
|
382
|
+
"TIMESTAMP",
|
|
383
|
+
"TIME",
|
|
384
|
+
"BOOLEAN",
|
|
385
|
+
"JSON",
|
|
386
|
+
];
|
|
387
|
+
if (validTypes.includes(type)) {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Build SQL column definition from ColumnDefinition object
|
|
398
|
+
*/
|
|
399
|
+
private buildColumnSQL(columnName: string, colDef: ColumnDefinition): string {
|
|
400
|
+
const parts: string[] = [];
|
|
401
|
+
|
|
402
|
+
// Add type with optional parameters
|
|
403
|
+
let typeStr = colDef.type;
|
|
404
|
+
if (colDef.length) {
|
|
405
|
+
typeStr += `(${colDef.length})`;
|
|
406
|
+
} else if (colDef.precision !== undefined) {
|
|
407
|
+
if (colDef.scale !== undefined) {
|
|
408
|
+
typeStr += `(${colDef.precision}, ${colDef.scale})`;
|
|
409
|
+
} else {
|
|
410
|
+
typeStr += `(${colDef.precision})`;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
parts.push(typeStr);
|
|
414
|
+
|
|
415
|
+
// Add PRIMARY KEY (must come before AUTOINCREMENT)
|
|
416
|
+
if (colDef.primaryKey) {
|
|
417
|
+
parts.push("PRIMARY KEY");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Add AUTOINCREMENT (only valid with INTEGER PRIMARY KEY)
|
|
421
|
+
if (colDef.autoincrement) {
|
|
422
|
+
if (!colDef.type.includes("INT") || !colDef.primaryKey) {
|
|
423
|
+
throw new Error(
|
|
424
|
+
`AUTOINCREMENT can only be used with INTEGER PRIMARY KEY columns (column: ${columnName})`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
parts.push("AUTOINCREMENT");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Add NOT NULL (but skip if PRIMARY KEY is already specified, as it's implicit)
|
|
431
|
+
if (colDef.notNull && !colDef.primaryKey) {
|
|
432
|
+
parts.push("NOT NULL");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Add UNIQUE
|
|
436
|
+
if (colDef.unique) {
|
|
437
|
+
parts.push("UNIQUE");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Add DEFAULT
|
|
441
|
+
if (colDef.default !== undefined) {
|
|
442
|
+
if (colDef.default === null) {
|
|
443
|
+
parts.push("DEFAULT NULL");
|
|
444
|
+
} else if (
|
|
445
|
+
typeof colDef.default === "object" &&
|
|
446
|
+
colDef.default._type === "expression"
|
|
447
|
+
) {
|
|
448
|
+
// Handle DefaultExpression
|
|
449
|
+
parts.push(`DEFAULT (${colDef.default.expression})`);
|
|
450
|
+
} else if (typeof colDef.default === "string") {
|
|
451
|
+
// Handle string defaults - check if it's a function call or literal
|
|
452
|
+
if (this.isSQLFunction(colDef.default)) {
|
|
453
|
+
parts.push(`DEFAULT (${colDef.default})`);
|
|
454
|
+
} else {
|
|
455
|
+
// Literal string value
|
|
456
|
+
parts.push(`DEFAULT '${colDef.default.replace(/'/g, "''")}'`);
|
|
457
|
+
}
|
|
458
|
+
} else if (typeof colDef.default === "boolean") {
|
|
459
|
+
parts.push(`DEFAULT ${colDef.default ? 1 : 0}`);
|
|
460
|
+
} else {
|
|
461
|
+
parts.push(`DEFAULT ${colDef.default}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Add COLLATE
|
|
466
|
+
if (colDef.collate) {
|
|
467
|
+
parts.push(`COLLATE ${colDef.collate}`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Add CHECK constraint (replace placeholder with actual column name)
|
|
471
|
+
if (colDef.check) {
|
|
472
|
+
const checkConstraint = colDef.check.replace(
|
|
473
|
+
/\{\{COLUMN\}\}/g,
|
|
474
|
+
`"${columnName.replace(/"/g, '""')}"`
|
|
475
|
+
);
|
|
476
|
+
parts.push(`CHECK (${checkConstraint})`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Add REFERENCES (foreign key)
|
|
480
|
+
if (colDef.references) {
|
|
481
|
+
const ref = colDef.references;
|
|
482
|
+
let refClause = `REFERENCES "${ref.table.replace(
|
|
483
|
+
/"/g,
|
|
484
|
+
'""'
|
|
485
|
+
)}"("${ref.column.replace(/"/g, '""')}")`;
|
|
486
|
+
|
|
487
|
+
if (ref.onDelete) {
|
|
488
|
+
refClause += ` ON DELETE ${ref.onDelete}`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (ref.onUpdate) {
|
|
492
|
+
refClause += ` ON UPDATE ${ref.onUpdate}`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
parts.push(refClause);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Add GENERATED column
|
|
499
|
+
if (colDef.generated) {
|
|
500
|
+
const storageType = colDef.generated.stored ? "STORED" : "VIRTUAL";
|
|
501
|
+
parts.push(
|
|
502
|
+
`GENERATED ALWAYS AS (${colDef.generated.expression}) ${storageType}`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return parts.join(" ");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Build table-level constraints
|
|
511
|
+
*/
|
|
512
|
+
private buildTableConstraints(constraints: TableConstraints): string[] {
|
|
513
|
+
const parts: string[] = [];
|
|
514
|
+
|
|
515
|
+
// PRIMARY KEY constraint
|
|
516
|
+
if (constraints.primaryKey && constraints.primaryKey.length > 0) {
|
|
517
|
+
const columns = constraints.primaryKey
|
|
518
|
+
.map((col) => `"${col.replace(/"/g, '""')}"`)
|
|
519
|
+
.join(", ");
|
|
520
|
+
parts.push(`PRIMARY KEY (${columns})`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// UNIQUE constraints
|
|
524
|
+
if (constraints.unique) {
|
|
525
|
+
if (Array.isArray(constraints.unique[0])) {
|
|
526
|
+
// Multiple composite unique constraints
|
|
527
|
+
for (const uniqueGroup of constraints.unique as string[][]) {
|
|
528
|
+
const columns = uniqueGroup
|
|
529
|
+
.map((col) => `"${col.replace(/"/g, '""')}"`)
|
|
530
|
+
.join(", ");
|
|
531
|
+
parts.push(`UNIQUE (${columns})`);
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
// Single unique constraint
|
|
535
|
+
const columns = (constraints.unique as string[])
|
|
536
|
+
.map((col) => `"${col.replace(/"/g, '""')}"`)
|
|
537
|
+
.join(", ");
|
|
538
|
+
parts.push(`UNIQUE (${columns})`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// CHECK constraints
|
|
543
|
+
if (constraints.check) {
|
|
544
|
+
for (const checkExpr of constraints.check) {
|
|
545
|
+
parts.push(`CHECK (${checkExpr})`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// FOREIGN KEY constraints
|
|
550
|
+
if (constraints.foreignKeys) {
|
|
551
|
+
for (const fk of constraints.foreignKeys) {
|
|
552
|
+
const columns = fk.columns
|
|
553
|
+
.map((col) => `"${col.replace(/"/g, '""')}"`)
|
|
554
|
+
.join(", ");
|
|
555
|
+
const refColumns = fk.references.columns
|
|
556
|
+
.map((col) => `"${col.replace(/"/g, '""')}"`)
|
|
557
|
+
.join(", ");
|
|
558
|
+
|
|
559
|
+
let fkClause = `FOREIGN KEY (${columns}) REFERENCES "${fk.references.table.replace(
|
|
560
|
+
/"/g,
|
|
561
|
+
'""'
|
|
562
|
+
)}" (${refColumns})`;
|
|
563
|
+
|
|
564
|
+
if (fk.references.onDelete) {
|
|
565
|
+
fkClause += ` ON DELETE ${fk.references.onDelete}`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (fk.references.onUpdate) {
|
|
569
|
+
fkClause += ` ON UPDATE ${fk.references.onUpdate}`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
parts.push(fkClause);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return parts;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Check if a string looks like a SQL function call
|
|
581
|
+
*/
|
|
582
|
+
private isSQLFunction(str: string): boolean {
|
|
583
|
+
// Simple heuristic: contains parentheses and common SQL function patterns
|
|
584
|
+
const functionPatterns = [
|
|
585
|
+
/^\w+\s*\(/, // Function name followed by (
|
|
586
|
+
/^(datetime|date|time|strftime|current_timestamp|current_date|current_time)/i,
|
|
587
|
+
/^(random|abs|length|upper|lower|trim)/i,
|
|
588
|
+
/^(coalesce|ifnull|nullif|iif)/i,
|
|
589
|
+
/^(json|json_extract|json_valid)/i,
|
|
590
|
+
];
|
|
591
|
+
|
|
592
|
+
return functionPatterns.some((pattern) => pattern.test(str.trim()));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Store table comment as metadata (using a system table if needed)
|
|
597
|
+
*/
|
|
598
|
+
private setTableComment(tableName: string, comment: string): void {
|
|
599
|
+
// Create metadata table if it doesn't exist
|
|
600
|
+
try {
|
|
601
|
+
this.db.run(`
|
|
602
|
+
CREATE TABLE IF NOT EXISTS __table_metadata__ (
|
|
603
|
+
table_name TEXT PRIMARY KEY,
|
|
604
|
+
comment TEXT,
|
|
605
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
606
|
+
)
|
|
607
|
+
`);
|
|
608
|
+
|
|
609
|
+
// Insert or replace comment
|
|
610
|
+
const stmt = this.db.prepare(`
|
|
611
|
+
INSERT OR REPLACE INTO __table_metadata__ (table_name, comment, created_at)
|
|
612
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
613
|
+
`);
|
|
614
|
+
stmt.run(tableName, comment);
|
|
615
|
+
} catch (error) {
|
|
616
|
+
// Silently ignore if we can't create metadata table
|
|
617
|
+
console.warn(`Could not store table comment for ${tableName}:`, error);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Get table comment from metadata
|
|
623
|
+
*/
|
|
624
|
+
getTableComment(tableName: string): string | null {
|
|
625
|
+
try {
|
|
626
|
+
const stmt = this.db.prepare(`
|
|
627
|
+
SELECT comment FROM __table_metadata__ WHERE table_name = ?
|
|
628
|
+
`);
|
|
629
|
+
const result = stmt.get(tableName) as { comment: string } | undefined;
|
|
630
|
+
return result?.comment || null;
|
|
631
|
+
} catch (_error) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* runute a raw SQL statement
|
|
638
|
+
*/
|
|
639
|
+
run(sql: string): void {
|
|
640
|
+
logger.debug(`runuting SQL: ${sql}`);
|
|
641
|
+
this.db.run(sql);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Prepare a SQL statement for repeated runution
|
|
646
|
+
*/
|
|
647
|
+
prepare(sql: string) {
|
|
648
|
+
return this.db.prepare(sql);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* runute a transaction
|
|
653
|
+
*/
|
|
654
|
+
transaction<T>(fn: () => T): T {
|
|
655
|
+
return this.db.transaction(fn)();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Begin a transaction manually
|
|
660
|
+
*/
|
|
661
|
+
begin(mode?: "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"): void {
|
|
662
|
+
const modeStr = mode ? ` ${mode}` : "";
|
|
663
|
+
this.db.run(`BEGIN${modeStr}`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Commit a transaction
|
|
668
|
+
*/
|
|
669
|
+
commit(): void {
|
|
670
|
+
logger.debug("Committing transaction");
|
|
671
|
+
this.run("COMMIT");
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Rollback a transaction
|
|
676
|
+
*/
|
|
677
|
+
rollback(): void {
|
|
678
|
+
logger.warn("Rolling back transaction");
|
|
679
|
+
this.run("ROLLBACK");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Create a savepoint
|
|
684
|
+
*/
|
|
685
|
+
savepoint(name: string): void {
|
|
686
|
+
const quotedName = `"${name.replace(/"/g, '""')}"`;
|
|
687
|
+
this.db.run(`SAVEPOINT ${quotedName}`);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Release a savepoint
|
|
692
|
+
*/
|
|
693
|
+
releaseSavepoint(name: string): void {
|
|
694
|
+
const quotedName = `"${name.replace(/"/g, '""')}"`;
|
|
695
|
+
this.db.run(`RELEASE SAVEPOINT ${quotedName}`);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Rollback to a savepoint
|
|
700
|
+
*/
|
|
701
|
+
rollbackToSavepoint(name: string): void {
|
|
702
|
+
const quotedName = `"${name.replace(/"/g, '""')}"`;
|
|
703
|
+
this.db.run(`ROLLBACK TO SAVEPOINT ${quotedName}`);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Vacuum the database (reclaim space and optimize)
|
|
708
|
+
*/
|
|
709
|
+
vacuum(): void {
|
|
710
|
+
this.db.run("VACUUM");
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Analyze the database (update statistics for query optimizer)
|
|
715
|
+
*/
|
|
716
|
+
analyze(tableName?: string): void {
|
|
717
|
+
if (tableName) {
|
|
718
|
+
const quotedName = `"${tableName.replace(/"/g, '""')}"`;
|
|
719
|
+
this.db.run(`ANALYZE ${quotedName}`);
|
|
720
|
+
} else {
|
|
721
|
+
this.db.run("ANALYZE");
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Check database integrity
|
|
727
|
+
*/
|
|
728
|
+
integrityCheck(): Array<{ integrity_check: string }> {
|
|
729
|
+
const stmt = this.db.prepare("PRAGMA integrity_check");
|
|
730
|
+
return stmt.all() as Array<{ integrity_check: string }>;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Get database schema information
|
|
735
|
+
*/
|
|
736
|
+
getSchema(): Array<{ name: string; type: string; sql: string }> {
|
|
737
|
+
const stmt = this.db.prepare(`
|
|
738
|
+
SELECT name, type, sql
|
|
739
|
+
FROM sqlite_master
|
|
740
|
+
WHERE type IN ('table', 'index', 'view', 'trigger')
|
|
741
|
+
ORDER BY type, name
|
|
742
|
+
`);
|
|
743
|
+
return stmt.all() as Array<{ name: string; type: string; sql: string }>;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Get table info (columns, types, constraints)
|
|
748
|
+
*/
|
|
749
|
+
getTableInfo(tableName: string): Array<{
|
|
750
|
+
cid: number;
|
|
751
|
+
name: string;
|
|
752
|
+
type: string;
|
|
753
|
+
notnull: number;
|
|
754
|
+
dflt_value: SQLQueryBindings;
|
|
755
|
+
pk: number;
|
|
756
|
+
}> {
|
|
757
|
+
const stmt = this.db.prepare(
|
|
758
|
+
`PRAGMA table_info("${tableName.replace(/"/g, '""')}")`
|
|
759
|
+
);
|
|
760
|
+
return stmt.all() as Array<{
|
|
761
|
+
cid: number;
|
|
762
|
+
name: string;
|
|
763
|
+
type: string;
|
|
764
|
+
notnull: number;
|
|
765
|
+
dflt_value: SQLQueryBindings;
|
|
766
|
+
pk: number;
|
|
767
|
+
}>;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Get foreign key information for a table
|
|
772
|
+
*/
|
|
773
|
+
getForeignKeys(tableName: string): Array<{
|
|
774
|
+
id: number;
|
|
775
|
+
seq: number;
|
|
776
|
+
table: string;
|
|
777
|
+
from: string;
|
|
778
|
+
to: string;
|
|
779
|
+
on_update: string;
|
|
780
|
+
on_delete: string;
|
|
781
|
+
match: string;
|
|
782
|
+
}> {
|
|
783
|
+
const stmt = this.db.prepare(
|
|
784
|
+
`PRAGMA foreign_key_list("${tableName.replace(/"/g, '""')}")`
|
|
785
|
+
);
|
|
786
|
+
return stmt.all() as Array<{
|
|
787
|
+
id: number;
|
|
788
|
+
seq: number;
|
|
789
|
+
table: string;
|
|
790
|
+
from: string;
|
|
791
|
+
to: string;
|
|
792
|
+
on_update: string;
|
|
793
|
+
on_delete: string;
|
|
794
|
+
match: string;
|
|
795
|
+
}>;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Get index information for a table
|
|
800
|
+
*/
|
|
801
|
+
getIndexes(tableName: string): Array<{
|
|
802
|
+
name: string;
|
|
803
|
+
unique: number;
|
|
804
|
+
origin: string;
|
|
805
|
+
partial: number;
|
|
806
|
+
}> {
|
|
807
|
+
const stmt = this.db.prepare(
|
|
808
|
+
`PRAGMA index_list("${tableName.replace(/"/g, '""')}")`
|
|
809
|
+
);
|
|
810
|
+
return stmt.all() as Array<{
|
|
811
|
+
name: string;
|
|
812
|
+
unique: number;
|
|
813
|
+
origin: string;
|
|
814
|
+
partial: number;
|
|
815
|
+
}>;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Set or get a PRAGMA value.
|
|
820
|
+
*
|
|
821
|
+
* @param name - PRAGMA name (e.g., "foreign_keys", "journal_mode")
|
|
822
|
+
* @param value - Value to set (omit to get current value)
|
|
823
|
+
* @returns Current value when getting, undefined when setting
|
|
824
|
+
*/
|
|
825
|
+
pragma(name: string, value?: SQLQueryBindings): SQLQueryBindings | undefined {
|
|
826
|
+
if (value !== undefined) {
|
|
827
|
+
this.db.run(`PRAGMA ${name} = ${value}`);
|
|
828
|
+
return undefined;
|
|
829
|
+
}
|
|
830
|
+
const result = this.db.prepare(`PRAGMA ${name}`).get() as Record<
|
|
831
|
+
string,
|
|
832
|
+
SQLQueryBindings
|
|
833
|
+
>;
|
|
834
|
+
return Object.values(result)[0];
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Load a SQLite extension.
|
|
839
|
+
*
|
|
840
|
+
* @param path - Absolute path to the compiled SQLite extension
|
|
841
|
+
*/
|
|
842
|
+
loadExtension(path: string): void {
|
|
843
|
+
this.db.loadExtension(path);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Get direct access to the underlying SQLite database instance.
|
|
848
|
+
* Use this for advanced operations not covered by the wrapper.
|
|
849
|
+
*
|
|
850
|
+
* @returns The underlying Database instance
|
|
851
|
+
*/
|
|
852
|
+
getDb(): Database {
|
|
853
|
+
return this.db;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export { DB };
|
|
858
|
+
export default DB;
|