@dqcai/sqlite 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1180 @@
1
+ # Bước 1: Tạo các thư mục cần thiết
2
+ New-Item -ItemType Directory -Path "src"
3
+ New-Item -ItemType Directory -Path "src\adapters"
4
+
5
+ # Bước 2: Tạo file src/types.ts và thêm nội dung
6
+ "
7
+ export interface SQLiteRow {
8
+ [key: string]: any;
9
+ }
10
+
11
+ export interface SQLiteResult {
12
+ rows: SQLiteRow[];
13
+ rowsAffected: number;
14
+ lastInsertRowId?: number;
15
+ }
16
+
17
+ export interface SQLiteConnection {
18
+ execute(sql: string, params?: any[]): Promise<SQLiteResult>;
19
+ close(): Promise<void>;
20
+ }
21
+
22
+ export interface SQLiteAdapter {
23
+ connect(path: string): Promise<SQLiteConnection>;
24
+ isSupported(): boolean;
25
+ }
26
+
27
+ export interface SQLiteConfig {
28
+ path: string;
29
+ timeout?: number;
30
+ busyTimeout?: number;
31
+ }
32
+ " | Set-Content -Path "src/types.ts"
33
+
34
+ # Bước 3: Tạo file src/adapters/base-adapter.ts
35
+ "
36
+ export abstract class BaseAdapter implements SQLiteAdapter {
37
+ abstract connect(path: string): Promise<SQLiteConnection>;
38
+ abstract isSupported(): boolean;
39
+
40
+ protected sanitizeSQL(sql: string): string {
41
+ // Basic SQL injection prevention
42
+ return sql.trim();
43
+ }
44
+
45
+ protected bindParameters(sql: string, params?: any[]): string {
46
+ if (!params || params.length === 0) {
47
+ return sql;
48
+ }
49
+
50
+ let paramIndex = 0;
51
+ return sql.replace(/\?/g, () => {
52
+ if (paramIndex < params.length) {
53
+ const param = params[paramIndex++];
54
+ if (typeof param === 'string') {
55
+ return `'${param.replace(/'/g, ''')}'`;
56
+ }
57
+ if (param === null || param === undefined) {
58
+ return 'NULL';
59
+ }
60
+ return String(param);
61
+ }
62
+ return '?';
63
+ });
64
+ }
65
+ }
66
+ " | Set-Content -Path "src/adapters/base-adapter.ts"
67
+
68
+ # Bước 4: Tạo file src/adapters/node-adapter.ts
69
+ "
70
+ import { BaseAdapter } from './base-adapter';
71
+ import { SQLiteConnection, SQLiteResult, SQLiteRow } from '../types';
72
+
73
+ class NodeSQLiteConnection implements SQLiteConnection {
74
+ private db: any;
75
+
76
+ constructor(db: any) {
77
+ this.db = db;
78
+ }
79
+
80
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
81
+ return new Promise((resolve, reject) => {
82
+ const sanitizedSQL = this.bindParameters(sql, params);
83
+
84
+ if (sql.toLowerCase().trim().startsWith('select')) {
85
+ this.db.all(sanitizedSQL, (err: any, rows: SQLiteRow[]) => {
86
+ if (err) {
87
+ reject(new Error(`SQLite error: ${err.message}`));
88
+ } else {
89
+ resolve({
90
+ rows: rows || [],
91
+ rowsAffected: 0
92
+ });
93
+ }
94
+ });
95
+ } else {
96
+ this.db.run(sanitizedSQL, function(this: any, err: any) {
97
+ if (err) {
98
+ reject(new Error(`SQLite error: ${err.message}`));
99
+ } else {
100
+ resolve({
101
+ rows: [],
102
+ rowsAffected: this.changes || 0,
103
+ lastInsertRowId: this.lastID
104
+ });
105
+ }
106
+ });
107
+ }
108
+ });
109
+ }
110
+
111
+ private bindParameters(sql: string, params?: any[]): string {
112
+ if (!params || params.length === 0) {
113
+ return sql;
114
+ }
115
+
116
+ let paramIndex = 0;
117
+ return sql.replace(/\?/g, () => {
118
+ if (paramIndex < params.length) {
119
+ const param = params[paramIndex++];
120
+ if (typeof param === 'string') {
121
+ return `'${param.replace(/'/g, ''')}'`;
122
+ }
123
+ if (param === null || param === undefined) {
124
+ return 'NULL';
125
+ }
126
+ return String(param);
127
+ }
128
+ return '?';
129
+ });
130
+ }
131
+
132
+ async close(): Promise<void> {
133
+ return new Promise((resolve, reject) => {
134
+ this.db.close((err: any) => {
135
+ if (err) {
136
+ reject(new Error(`Error closing database: ${err.message}`));
137
+ } else {
138
+ resolve();
139
+ }
140
+ });
141
+ });
142
+ }
143
+ }
144
+
145
+ export class NodeAdapter extends BaseAdapter {
146
+ isSupported(): boolean {
147
+ try {
148
+ return typeof require !== 'undefined' && require('sqlite3') !== undefined;
149
+ } catch {
150
+ return false;
151
+ }
152
+ }
153
+
154
+ async connect(path: string): Promise<SQLiteConnection> {
155
+ return new Promise((resolve, reject) => {
156
+ try {
157
+ const sqlite3 = require('sqlite3').verbose();
158
+ const db = new sqlite3.Database(path, (err: any) => {
159
+ if (err) {
160
+ reject(new Error(`Cannot connect to database: ${err.message}`));
161
+ } else {
162
+ resolve(new NodeSQLiteConnection(db));
163
+ }
164
+ });
165
+ } catch (error) {
166
+ reject(new Error(`SQLite3 module not available: ${error}`));
167
+ }
168
+ });
169
+ }
170
+ }
171
+ " | Set-Content -Path "src/adapters/node-adapter.ts"
172
+
173
+ # Bước 5: Tạo file src/adapters/browser-adapter.ts
174
+ "
175
+ import { BaseAdapter } from './base-adapter';
176
+ import { SQLiteConnection, SQLiteResult, SQLiteRow } from '../types';
177
+
178
+ class BrowserSQLiteConnection implements SQLiteConnection {
179
+ private worker: Worker | null = null;
180
+ private db: any = null;
181
+
182
+ constructor(db: any, worker?: Worker) {
183
+ this.db = db;
184
+ this.worker = worker || null;
185
+ }
186
+
187
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
188
+ if (!this.db) {
189
+ throw new Error('Database connection not available');
190
+ }
191
+
192
+ try {
193
+ const boundSQL = super.bindParameters ? super.bindParameters(sql, params) : this.bindParameters(sql, params);
194
+
195
+ if (sql.toLowerCase().trim().startsWith('select')) {
196
+ const result = this.db.exec(boundSQL);
197
+ const rows: SQLiteRow[] = [];
198
+
199
+ if (result.length > 0 && result[0].values) {
200
+ const columns = result[0].columns;
201
+ const values = result[0].values;
202
+
203
+ for (const row of values) {
204
+ const rowObj: SQLiteRow = {};
205
+ columns.forEach((col: string, index: number) => {
206
+ rowObj[col] = row[index];
207
+ });
208
+ rows.push(rowObj);
209
+ }
210
+ }
211
+
212
+ return {
213
+ rows,
214
+ rowsAffected: 0
215
+ };
216
+ } else {
217
+ this.db.exec(boundSQL);
218
+ return {
219
+ rows: [],
220
+ rowsAffected: 1 // Browser doesn't provide exact count
221
+ };
222
+ }
223
+ } catch (error) {
224
+ throw new Error(`SQLite error: ${error}`);
225
+ }
226
+ }
227
+
228
+ private bindParameters(sql: string, params?: any[]): string {
229
+ if (!params || params.length === 0) {
230
+ return sql;
231
+ }
232
+
233
+ let paramIndex = 0;
234
+ return sql.replace(/\?/g, () => {
235
+ if (paramIndex < params.length) {
236
+ const param = params[paramIndex++];
237
+ if (typeof param === 'string') {
238
+ return `'${param.replace(/'/g, ''')}'`;
239
+ }
240
+ if (param === null || param === undefined) {
241
+ return 'NULL';
242
+ }
243
+ return String(param);
244
+ }
245
+ return '?';
246
+ });
247
+ }
248
+
249
+ async close(): Promise<void> {
250
+ if (this.db) {
251
+ this.db.close();
252
+ this.db = null;
253
+ }
254
+ if (this.worker) {
255
+ this.worker.terminate();
256
+ this.worker = null;
257
+ }
258
+ }
259
+ }
260
+
261
+ export class BrowserAdapter extends BaseAdapter {
262
+ private sqlJs: any = null;
263
+
264
+ isSupported(): boolean {
265
+ return typeof window !== 'undefined' &&
266
+ (typeof window.SQL !== 'undefined' || this.sqlJs !== null);
267
+ }
268
+
269
+ async connect(path: string): Promise<SQLiteConnection> {
270
+ try {
271
+ // Try to load sql.js if not already loaded
272
+ if (!this.sqlJs) {
273
+ if (typeof window.SQL !== 'undefined') {
274
+ this.sqlJs = window.SQL;
275
+ } else {
276
+ // Try to load from CDN
277
+ await this.loadSqlJs();
278
+ }
279
+ }
280
+
281
+ let db;
282
+
283
+ if (path === ':memory:') {
284
+ // In-memory database
285
+ db = new this.sqlJs.Database();
286
+ } else {
287
+ // Try to load from file or localStorage
288
+ const data = await this.loadDatabaseFile(path);
289
+ db = new this.sqlJs.Database(data);
290
+ }
291
+
292
+ return new BrowserSQLiteConnection(db);
293
+ } catch (error) {
294
+ throw new Error(`Cannot connect to browser database: ${error}`);
295
+ }
296
+ }
297
+
298
+ private async loadSqlJs(): Promise<void> {
299
+ return new Promise((resolve, reject) => {
300
+ const script = document.createElement('script');
301
+ script.src = 'https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js';
302
+ script.onload = async () => {
303
+ try {
304
+ this.sqlJs = await window.initSqlJs({
305
+ locateFile: (file: string) => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}`
306
+ });
307
+ resolve();
308
+ } catch (err) {
309
+ reject(err);
310
+ }
311
+ };
312
+ script.onerror = () => reject(new Error('Failed to load sql.js'));
313
+ document.head.appendChild(script);
314
+ });
315
+ }
316
+
317
+ private async loadDatabaseFile(path: string): Promise<Uint8Array | undefined> {
318
+ // Try localStorage first
319
+ const stored = localStorage.getItem(`sqlite_db_${path}`);
320
+ if (stored) {
321
+ return new Uint8Array(JSON.parse(stored));
322
+ }
323
+
324
+ // Try to fetch as a file
325
+ try {
326
+ const response = await fetch(path);
327
+ if (response.ok) {
328
+ const arrayBuffer = await response.arrayBuffer();
329
+ return new Uint8Array(arrayBuffer);
330
+ }
331
+ } catch {
332
+ // File doesn't exist, return undefined for new database
333
+ }
334
+
335
+ return undefined;
336
+ }
337
+ }
338
+ " | Set-Content -Path "src/adapters/browser-adapter.ts"
339
+
340
+ # Bước 6: Tạo file src/adapters/deno-adapter.ts
341
+ "
342
+ import { BaseAdapter } from './base-adapter';
343
+ import { SQLiteConnection, SQLiteResult, SQLiteRow } from '../types';
344
+
345
+ class DenoSQLiteConnection implements SQLiteConnection {
346
+ private db: any;
347
+
348
+ constructor(db: any) {
349
+ this.db = db;
350
+ }
351
+
352
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
353
+ try {
354
+ const boundSQL = this.bindParameters(sql, params);
355
+
356
+ if (sql.toLowerCase().trim().startsWith('select')) {
357
+ const result = this.db.queryEntries(boundSQL);
358
+ return {
359
+ rows: result,
360
+ rowsAffected: 0
361
+ };
362
+ } else {
363
+ const result = this.db.query(boundSQL);
364
+ return {
365
+ rows: [],
366
+ rowsAffected: result.length || 1
367
+ };
368
+ }
369
+ } catch (error) {
370
+ throw new Error(`SQLite error: ${error}`);
371
+ }
372
+ }
373
+
374
+ private bindParameters(sql: string, params?: any[]): string {
375
+ if (!params || params.length === 0) {
376
+ return sql;
377
+ }
378
+
379
+ let paramIndex = 0;
380
+ return sql.replace(/\?/g, () => {
381
+ if (paramIndex < params.length) {
382
+ const param = params[paramIndex++];
383
+ if (typeof param === 'string') {
384
+ return `'${param.replace(/'/g, ''')}'`;
385
+ }
386
+ if (param === null || param === undefined) {
387
+ return 'NULL';
388
+ }
389
+ return String(param);
390
+ }
391
+ return '?';
392
+ });
393
+ }
394
+
395
+ async close(): Promise<void> {
396
+ if (this.db) {
397
+ this.db.close();
398
+ }
399
+ }
400
+ }
401
+
402
+ export class DenoAdapter extends BaseAdapter {
403
+ isSupported(): boolean {
404
+ try {
405
+ return typeof Deno !== 'undefined' && Deno.env !== undefined;
406
+ } catch {
407
+ return false;
408
+ }
409
+ }
410
+
411
+ async connect(path: string): Promise<SQLiteConnection> {
412
+ try {
413
+ // Dynamic import for Deno SQLite
414
+ const { DB } = await import('https://deno.land/x/sqlite@v3.8.0/mod.ts');
415
+ const db = new DB(path);
416
+ return new DenoSQLiteConnection(db);
417
+ } catch (error) {
418
+ throw new Error(`Cannot connect to Deno database: ${error}`);
419
+ }
420
+ }
421
+ }
422
+ " | Set-Content -Path "src/adapters/deno-adapter.ts"
423
+
424
+ # Bước 7: Tạo file src/adapters/bun-adapter.ts
425
+ "
426
+ import { BaseAdapter } from './base-adapter';
427
+ import { SQLiteConnection, SQLiteResult, SQLiteRow } from '../types';
428
+
429
+ class BunSQLiteConnection implements SQLiteConnection {
430
+ private db: any;
431
+
432
+ constructor(db: any) {
433
+ this.db = db;
434
+ }
435
+
436
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
437
+ try {
438
+ if (sql.toLowerCase().trim().startsWith('select')) {
439
+ const result = this.db.query(sql).all(params || []);
440
+ return {
441
+ rows: result,
442
+ rowsAffected: 0
443
+ };
444
+ } else {
445
+ const result = this.db.query(sql).run(params || []);
446
+ return {
447
+ rows: [],
448
+ rowsAffected: result.changes || 0,
449
+ lastInsertRowId: result.lastInsertRowid
450
+ };
451
+ }
452
+ } catch (error) {
453
+ throw new Error(`SQLite error: ${error}`);
454
+ }
455
+ }
456
+
457
+ async close(): Promise<void> {
458
+ if (this.db) {
459
+ this.db.close();
460
+ }
461
+ }
462
+ }
463
+
464
+ export class BunAdapter extends BaseAdapter {
465
+ isSupported(): boolean {
466
+ try {
467
+ return typeof Bun !== 'undefined' && Bun.version !== undefined;
468
+ } catch {
469
+ return false;
470
+ }
471
+ }
472
+
473
+ async connect(path: string): Promise<SQLiteConnection> {
474
+ try {
475
+ const { Database } = require('bun:sqlite');
476
+ const db = new Database(path);
477
+ return new BunSQLiteConnection(db);
478
+ } catch (error) {
479
+ throw new Error(`Cannot connect to Bun database: ${error}`);
480
+ }
481
+ }
482
+ }
483
+ " | Set-Content -Path "src/adapters/bun-adapter.ts"
484
+
485
+ # Bước 8: Tạo file src/adapters/react-native-adapter.ts
486
+ "
487
+ import { BaseAdapter } from './base-adapter';
488
+ import { SQLiteConnection, SQLiteResult, SQLiteRow } from '../types';
489
+
490
+ class ReactNativeSQLiteConnection implements SQLiteConnection {
491
+ private db: any;
492
+ private dbName: string;
493
+
494
+ constructor(db: any, dbName: string) {
495
+ this.db = db;
496
+ this.dbName = dbName;
497
+ }
498
+
499
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
500
+ return new Promise((resolve, reject) => {
501
+ try {
502
+ const boundSQL = this.bindParameters(sql, params);
503
+
504
+ this.db.transaction((tx: any) => {
505
+ tx.executeSql(
506
+ boundSQL,
507
+ [],
508
+ (tx: any, results: any) => {
509
+ const rows: SQLiteRow[] = [];
510
+
511
+ if (results.rows) {
512
+ for (let i = 0; i < results.rows.length; i++) {
513
+ rows.push(results.rows.item(i));
514
+ }
515
+ }
516
+
517
+ resolve({
518
+ rows,
519
+ rowsAffected: results.rowsAffected || 0,
520
+ lastInsertRowId: results.insertId
521
+ });
522
+ },
523
+ (tx: any, error: any) => {
524
+ reject(new Error(`SQLite error: ${error.message}`));
525
+ return false;
526
+ }
527
+ );
528
+ });
529
+ } catch (error) {
530
+ reject(new Error(`SQLite execution error: ${error}`));
531
+ }
532
+ });
533
+ }
534
+
535
+ private bindParameters(sql: string, params?: any[]): string {
536
+ if (!params || params.length === 0) {
537
+ return sql;
538
+ }
539
+
540
+ let paramIndex = 0;
541
+ return sql.replace(/\?/g, () => {
542
+ if (paramIndex < params.length) {
543
+ const param = params[paramIndex++];
544
+ if (typeof param === 'string') {
545
+ return `'${param.replace(/'/g, ''')}'`;
546
+ }
547
+ if (param === null || param === undefined) {
548
+ return 'NULL';
549
+ }
550
+ return String(param);
551
+ }
552
+ return '?';
553
+ });
554
+ }
555
+
556
+ async close(): Promise<void> {
557
+ return new Promise((resolve, reject) => {
558
+ if (this.db && this.db.close) {
559
+ this.db.close(
560
+ () => resolve(),
561
+ (error: any) => reject(new Error(`Error closing database: ${error.message}`))
562
+ );
563
+ } else {
564
+ resolve();
565
+ }
566
+ });
567
+ }
568
+ }
569
+
570
+ // Adapter for react-native-sqlite-storage
571
+ class ReactNativeSQLiteStorageConnection implements SQLiteConnection {
572
+ private db: any;
573
+
574
+ constructor(db: any) {
575
+ this.db = db;
576
+ }
577
+
578
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
579
+ return new Promise((resolve, reject) => {
580
+ this.db.executeSql(
581
+ sql,
582
+ params || [],
583
+ (results: any) => {
584
+ const rows: SQLiteRow[] = [];
585
+
586
+ if (results.rows) {
587
+ for (let i = 0; i < results.rows.length; i++) {
588
+ rows.push(results.rows.item(i));
589
+ }
590
+ }
591
+
592
+ resolve({
593
+ rows,
594
+ rowsAffected: results.rowsAffected || 0,
595
+ lastInsertRowId: results.insertId
596
+ });
597
+ },
598
+ (error: any) => {
599
+ reject(new Error(`SQLite error: ${error.message}`));
600
+ }
601
+ );
602
+ });
603
+ }
604
+
605
+ async close(): Promise<void> {
606
+ return new Promise((resolve, reject) => {
607
+ if (this.db && this.db.close) {
608
+ this.db.close(resolve, reject);
609
+ } else {
610
+ resolve();
611
+ }
612
+ });
613
+ }
614
+ }
615
+
616
+ // Adapter for expo-sqlite
617
+ class ExpoSQLiteConnection implements SQLiteConnection {
618
+ private db: any;
619
+
620
+ constructor(db: any) {
621
+ this.db = db;
622
+ }
623
+
624
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
625
+ try {
626
+ const result = await this.db.execAsync([{
627
+ sql,
628
+ args: params || []
629
+ }]);
630
+
631
+ if (result && result[0]) {
632
+ const firstResult = result[0];
633
+ return {
634
+ rows: firstResult.rows || [],
635
+ rowsAffected: firstResult.rowsAffected || 0,
636
+ lastInsertRowId: firstResult.lastInsertRowId
637
+ };
638
+ }
639
+
640
+ return {
641
+ rows: [],
642
+ rowsAffected: 0
643
+ };
644
+ } catch (error) {
645
+ throw new Error(`SQLite error: ${error}`);
646
+ }
647
+ }
648
+
649
+ async close(): Promise<void> {
650
+ if (this.db && this.db.closeAsync) {
651
+ await this.db.closeAsync();
652
+ }
653
+ }
654
+ }
655
+
656
+ // Adapter for React Native Windows SQLite
657
+ class ReactNativeWindowsSQLiteConnection implements SQLiteConnection {
658
+ private db: any;
659
+
660
+ constructor(db: any) {
661
+ this.db = db;
662
+ }
663
+
664
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
665
+ try {
666
+ if (sql.toLowerCase().trim().startsWith('select')) {
667
+ const result = await this.db.all(sql, params || []);
668
+ return {
669
+ rows: result || [],
670
+ rowsAffected: 0
671
+ };
672
+ } else {
673
+ const result = await this.db.run(sql, params || []);
674
+ return {
675
+ rows: [],
676
+ rowsAffected: result.changes || 0,
677
+ lastInsertRowId: result.lastID
678
+ };
679
+ }
680
+ } catch (error) {
681
+ throw new Error(`SQLite error: ${error}`);
682
+ }
683
+ }
684
+
685
+ async close(): Promise<void> {
686
+ if (this.db && this.db.close) {
687
+ await this.db.close();
688
+ }
689
+ }
690
+ }
691
+
692
+ // Adapter for react-native-sqlite-2 (Windows specific)
693
+ class ReactNativeSQLite2Connection implements SQLiteConnection {
694
+ private db: any;
695
+
696
+ constructor(db: any) {
697
+ this.db = db;
698
+ }
699
+
700
+ async execute(sql: string, params?: any[]): Promise<SQLiteResult> {
701
+ return new Promise((resolve, reject) => {
702
+ this.db.exec(
703
+ [{ sql, args: params || [] }],
704
+ false,
705
+ (results: any) => {
706
+ if (results && results[0]) {
707
+ const result = results[0];
708
+ if (result.error) {
709
+ reject(new Error(`SQLite error: ${result.error.message}`));
710
+ } else {
711
+ resolve({
712
+ rows: result.rows || [],
713
+ rowsAffected: result.rowsAffected || 0,
714
+ lastInsertRowId: result.insertId
715
+ });
716
+ }
717
+ } else {
718
+ resolve({
719
+ rows: [],
720
+ rowsAffected: 0
721
+ });
722
+ }
723
+ }
724
+ );
725
+ });
726
+ }
727
+
728
+ async close(): Promise<void> {
729
+ return new Promise((resolve, reject) => {
730
+ if (this.db && this.db.close) {
731
+ this.db.close(resolve, reject);
732
+ } else {
733
+ resolve();
734
+ }
735
+ });
736
+ }
737
+ }
738
+
739
+ export class ReactNativeAdapter extends BaseAdapter {
740
+ private adapterType: 'webview' | 'storage' | 'expo' | 'windows' | 'sqlite2' | null = null;
741
+ private isWindows: boolean = false;
742
+
743
+ isSupported(): boolean {
744
+ try {
745
+ // Check for React Native environment
746
+ const isRN = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
747
+ if (!isRN) return false;
748
+
749
+ // Detect Windows platform
750
+ this.isWindows = this.isReactNativeWindows();
751
+
752
+ // Check for available SQLite libraries
753
+ if (this.isWindows) {
754
+ if (this.hasSQLite2()) {
755
+ this.adapterType = 'sqlite2';
756
+ return true;
757
+ }
758
+
759
+ if (this.hasWindowsSQLite()) {
760
+ this.adapterType = 'windows';
761
+ return true;
762
+ }
763
+ }
764
+
765
+ if (this.hasExpoSQLite()) {
766
+ this.adapterType = 'expo';
767
+ return true;
768
+ }
769
+
770
+ if (this.hasSQLiteStorage()) {
771
+ this.adapterType = 'storage';
772
+ return true;
773
+ }
774
+
775
+ if (this.hasWebViewSQLite()) {
776
+ this.adapterType = 'webview';
777
+ return true;
778
+ }
779
+
780
+ return false;
781
+ } catch {
782
+ return false;
783
+ }
784
+ }
785
+
786
+ private isReactNativeWindows(): boolean {
787
+ try {
788
+ // Check for Windows-specific APIs or platform detection
789
+ return (
790
+ typeof navigator !== 'undefined' &&
791
+ navigator.product === 'ReactNative' &&
792
+ (
793
+ // Windows-specific checks
794
+ typeof Windows !== 'undefined' ||
795
+ (typeof require !== 'undefined' &&
796
+ (() => {
797
+ try {
798
+ require('react-native-windows');
799
+ return true;
800
+ } catch {
801
+ return false;
802
+ }
803
+ })()) ||
804
+ // Platform module check
805
+ (typeof Platform !== 'undefined' && Platform.OS === 'windows')
806
+ )
807
+ );
808
+ } catch {
809
+ return false;
810
+ }
811
+ }
812
+
813
+ private hasExpoSQLite(): boolean {
814
+ try {
815
+ require('expo-sqlite');
816
+ return !this.isWindows; // Expo SQLite might not work on Windows
817
+ } catch {
818
+ return false;
819
+ }
820
+ }
821
+
822
+ private hasSQLiteStorage(): boolean {
823
+ try {
824
+ require('react-native-sqlite-storage');
825
+ return !this.isWindows; // Standard version might not work on Windows
826
+ } catch {
827
+ return false;
828
+ }
829
+ }
830
+
831
+ private hasWebViewSQLite(): boolean {
832
+ try {
833
+ return typeof window !== 'undefined' &&
834
+ typeof window.openDatabase === 'function' &&
835
+ !this.isWindows; // WebView SQLite not available on Windows
836
+ } catch {
837
+ return false;
838
+ }
839
+ }
840
+
841
+ private hasWindowsSQLite(): boolean {
842
+ try {
843
+ require('react-native-windows-sqlite');
844
+ return true;
845
+ } catch {
846
+ return false;
847
+ }
848
+ }
849
+
850
+ private hasSQLite2(): boolean {
851
+ try {
852
+ require('react-native-sqlite-2');
853
+ return true;
854
+ } catch {
855
+ return false;
856
+ }
857
+ }
858
+
859
+ async connect(path: string): Promise<SQLiteConnection> {
860
+ if (!this.isSupported()) {
861
+ throw new Error('React Native SQLite not supported in this environment');
862
+ }
863
+
864
+ switch (this.adapterType) {
865
+ case 'sqlite2':
866
+ return this.connectSQLite2(path);
867
+
868
+ case 'windows':
869
+ return this.connectWindows(path);
870
+
871
+ case 'expo':
872
+ return this.connectExpo(path);
873
+
874
+ case 'storage':
875
+ return this.connectStorage(path);
876
+
877
+ case 'webview':
878
+ return this.connectWebView(path);
879
+
880
+ default:
881
+ throw new Error('No React Native SQLite adapter available');
882
+ }
883
+ }
884
+
885
+ private async connectSQLite2(path: string): Promise<SQLiteConnection> {
886
+ return new Promise((resolve, reject) => {
887
+ try {
888
+ const SQLite = require('react-native-sqlite-2');
889
+
890
+ SQLite.openDatabase(
891
+ path,
892
+ '1.0',
893
+ 'Database',
894
+ 200000,
895
+ (db: any) => {
896
+ resolve(new ReactNativeSQLite2Connection(db));
897
+ },
898
+ (error: any) => {
899
+ reject(new Error(`Cannot connect to React Native SQLite-2: ${error.message}`));
900
+ }
901
+ );
902
+ } catch (error) {
903
+ reject(new Error(`React Native SQLite-2 not available: ${error}`));
904
+ }
905
+ });
906
+ }
907
+
908
+ private async connectWindows(path: string): Promise<SQLiteConnection> {
909
+ try {
910
+ const SQLite = require('react-native-windows-sqlite');
911
+ const db = await SQLite.openDatabase({
912
+ name: path,
913
+ location: 'default'
914
+ });
915
+ return new ReactNativeWindowsSQLiteConnection(db);
916
+ } catch (error) {
917
+ throw new Error(`Cannot connect to React Native Windows SQLite: ${error}`);
918
+ }
919
+ }
920
+
921
+ private async connectExpo(path: string): Promise<SQLiteConnection> {
922
+ try {
923
+ const SQLite = require('expo-sqlite');
924
+ const db = SQLite.openDatabaseSync(path);
925
+ return new ExpoSQLiteConnection(db);
926
+ } catch (error) {
927
+ throw new Error(`Cannot connect to Expo SQLite database: ${error}`);
928
+ }
929
+ }
930
+
931
+ private async connectStorage(path: string): Promise<SQLiteConnection> {
932
+ return new Promise((resolve, reject) => {
933
+ try {
934
+ const SQLite = require('react-native-sqlite-storage');
935
+
936
+ // Enable debugging (optional)
937
+ SQLite.DEBUG(false);
938
+ SQLite.enablePromise(true);
939
+
940
+ SQLite.openDatabase({
941
+ name: path,
942
+ location: 'default'
943
+ }).then((db: any) => {
944
+ resolve(new ReactNativeSQLiteStorageConnection(db));
945
+ }).catch((error: any) => {
946
+ reject(new Error(`Cannot connect to React Native SQLite database: ${error.message}`));
947
+ });
948
+ } catch (error) {
949
+ reject(new Error(`React Native SQLite Storage not available: ${error}`));
950
+ }
951
+ });
952
+ }
953
+
954
+ private async connectWebView(path: string): Promise<SQLiteConnection> {
955
+ try {
956
+ const db = window.openDatabase(path, '1.0', 'Database', 2 * 1024 * 1024);
957
+ return new ReactNativeSQLiteConnection(db, path);
958
+ } catch (error) {
959
+ throw new Error(`Cannot connect to WebView SQLite database: ${error}`);
960
+ }
961
+ }
962
+ }
963
+ " | Set-Content -Path "src/adapters/react-native-adapter.ts"
964
+
965
+ # Bước 9: Tạo file src/sqlite-manager.ts
966
+ "
967
+ import { SQLiteAdapter, SQLiteConnection, SQLiteConfig } from './types';
968
+ import { NodeAdapter } from './adapters/node-adapter';
969
+ import { BrowserAdapter } from './adapters/browser-adapter';
970
+ import { DenoAdapter } from './adapters/deno-adapter';
971
+ import { BunAdapter } from './adapters/bun-adapter';
972
+ import { ReactNativeAdapter } from './adapters/react-native-adapter';
973
+
974
+ export class SQLiteManager {
975
+ private adapters: SQLiteAdapter[] = [];
976
+ private currentAdapter: SQLiteAdapter | null = null;
977
+
978
+ constructor() {
979
+ this.adapters = [
980
+ new ReactNativeAdapter(), // Check React Native first
981
+ new BunAdapter(),
982
+ new DenoAdapter(),
983
+ new NodeAdapter(),
984
+ new BrowserAdapter()
985
+ ];
986
+ }
987
+
988
+ private detectEnvironment(): SQLiteAdapter {
989
+ for (const adapter of this.adapters) {
990
+ if (adapter.isSupported()) {
991
+ return adapter;
992
+ }
993
+ }
994
+ throw new Error('No supported SQLite adapter found for this environment');
995
+ }
996
+
997
+ async connect(config: string | SQLiteConfig): Promise<SQLiteConnection> {
998
+ const path = typeof config === 'string' ? config : config.path;
999
+
1000
+ if (!this.currentAdapter) {
1001
+ this.currentAdapter = this.detectEnvironment();
1002
+ }
1003
+
1004
+ try {
1005
+ return await this.currentAdapter.connect(path);
1006
+ } catch (error) {
1007
+ throw new Error(`Failed to connect to SQLite database: ${error}`);
1008
+ }
1009
+ }
1010
+
1011
+ getEnvironmentInfo(): string {
1012
+ if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
1013
+ // Detect specific React Native environment
1014
+ if (typeof Windows !== 'undefined') return 'React Native Windows';
1015
+ if (typeof Platform !== 'undefined' && Platform.OS === 'windows') return 'React Native Windows';
1016
+ return 'React Native';
1017
+ }
1018
+ if (typeof Bun !== 'undefined') return 'Bun';
1019
+ if (typeof Deno !== 'undefined') return 'Deno';
1020
+ if (typeof window !== 'undefined') return 'Browser';
1021
+ if (typeof process !== 'undefined') return 'Node.js';
1022
+ return 'Unknown';
1023
+ }
1024
+ }
1025
+ " | Set-Content -Path "src/sqlite-manager.ts"
1026
+
1027
+ # Bước 10: Tạo file src/query-builder.ts
1028
+ "
1029
+ export class QueryBuilder {
1030
+ private tableName = '';
1031
+ private selectFields: string[] = ['*'];
1032
+ private whereConditions: string[] = [];
1033
+ private orderByFields: string[] = [];
1034
+ private limitValue: number | null = null;
1035
+ private offsetValue: number | null = null;
1036
+
1037
+ static table(name: string): QueryBuilder {
1038
+ const builder = new QueryBuilder();
1039
+ builder.tableName = name;
1040
+ return builder;
1041
+ }
1042
+
1043
+ select(fields: string | string[]): QueryBuilder {
1044
+ this.selectFields = Array.isArray(fields) ? fields : [fields];
1045
+ return this;
1046
+ }
1047
+
1048
+ where(condition: string): QueryBuilder {
1049
+ this.whereConditions.push(condition);
1050
+ return this;
1051
+ }
1052
+
1053
+ orderBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): QueryBuilder {
1054
+ this.orderByFields.push(`${field} ${direction}`);
1055
+ return this;
1056
+ }
1057
+
1058
+ limit(count: number): QueryBuilder {
1059
+ this.limitValue = count;
1060
+ return this;
1061
+ }
1062
+
1063
+ offset(count: number): QueryBuilder {
1064
+ this.offsetValue = count;
1065
+ return this;
1066
+ }
1067
+
1068
+ toSQL(): string {
1069
+ let sql = `SELECT ${this.selectFields.join(', ')} FROM ${this.tableName}`;
1070
+
1071
+ if (this.whereConditions.length > 0) {
1072
+ sql += ` WHERE ${this.whereConditions.join(' AND ')}`;
1073
+ }
1074
+
1075
+ if (this.orderByFields.length > 0) {
1076
+ sql += ` ORDER BY ${this.orderByFields.join(', ')}`;
1077
+ }
1078
+
1079
+ if (this.limitValue !== null) {
1080
+ sql += ` LIMIT ${this.limitValue}`;
1081
+ }
1082
+
1083
+ if (this.offsetValue !== null) {
1084
+ sql += ` OFFSET ${this.offsetValue}`;
1085
+ }
1086
+
1087
+ return sql;
1088
+ }
1089
+
1090
+ // Insert methods
1091
+ static insert(tableName: string, data: Record<string, any>): string {
1092
+ const fields = Object.keys(data);
1093
+ const values = Object.values(data);
1094
+ const placeholders = values.map(() => '?').join(', ');
1095
+
1096
+ return `INSERT INTO ${tableName} (${fields.join(', ')}) VALUES (${placeholders})`;
1097
+ }
1098
+
1099
+ static update(tableName: string, data: Record<string, any>, where: string): string {
1100
+ const sets = Object.keys(data).map(key => `${key} = ?`).join(', ');
1101
+ return `UPDATE ${tableName} SET ${sets} WHERE ${where}`;
1102
+ }
1103
+
1104
+ static delete(tableName: string, where: string): string {
1105
+ return `DELETE FROM ${tableName} WHERE ${where}`;
1106
+ }
1107
+ }
1108
+ " | Set-Content -Path "src/query-builder.ts"
1109
+
1110
+ # Bước 11: Tạo file src/index.ts
1111
+ "
1112
+ export { SQLiteManager } from './sqlite-manager';
1113
+ export { QueryBuilder } from './query-builder';
1114
+ export * from './types';
1115
+
1116
+ // Example usage and main export
1117
+ export default class UniversalSqlite {
1118
+ private manager: SQLiteManager;
1119
+ private connection: SQLiteConnection | null = null;
1120
+
1121
+ constructor() {
1122
+ this.manager = new SQLiteManager();
1123
+ }
1124
+
1125
+ async connect(path: string): Promise<void> {
1126
+ this.connection = await this.manager.connect(path);
1127
+ }
1128
+
1129
+ async query(sql: string, params?: any[]) {
1130
+ if (!this.connection) {
1131
+ throw new Error('Database not connected');
1132
+ }
1133
+ return await this.connection.execute(sql, params);
1134
+ }
1135
+
1136
+ async close(): Promise<void> {
1137
+ if (this.connection) {
1138
+ await this.connection.close();
1139
+ this.connection = null;
1140
+ }
1141
+ }
1142
+
1143
+ getEnvironment(): string {
1144
+ return this.manager.getEnvironmentInfo();
1145
+ }
1146
+
1147
+ // Convenience methods
1148
+ async createTable(name: string, schema: Record<string, string>): Promise<void> {
1149
+ const fields = Object.entries(schema)
1150
+ .map(([field, type]) => `${field} ${type}`)
1151
+ .join(', ');
1152
+
1153
+ await this.query(`CREATE TABLE IF NOT EXISTS ${name} (${fields})`);
1154
+ }
1155
+
1156
+ async insert(table: string, data: Record<string, any>) {
1157
+ const sql = QueryBuilder.insert(table, data);
1158
+ return await this.query(sql, Object.values(data));
1159
+ }
1160
+
1161
+ async select(table: string, where?: string, params?: any[]) {
1162
+ let sql = `SELECT * FROM ${table}`;
1163
+ if (where) {
1164
+ sql += ` WHERE ${where}`;
1165
+ }
1166
+ return await this.query(sql, params);
1167
+ }
1168
+
1169
+ async update(table: string, data: Record<string, any>, where: string, whereParams?: any[]) {
1170
+ const sql = QueryBuilder.update(table, data, where);
1171
+ const params = [...Object.values(data), ...(whereParams || [])];
1172
+ return await this.query(sql, params);
1173
+ }
1174
+
1175
+ async delete(table: string, where: string, params?: any[]) {
1176
+ const sql = QueryBuilder.delete(table, where);
1177
+ return await this.query(sql, params);
1178
+ }
1179
+ }
1180
+ " | Set-Content -Path "src/index.ts"