@dqcai/sqlite 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +434 -2522
- package/lib/adapters/base-adapter.d.ts.map +1 -1
- package/lib/adapters/index.d.ts +2 -0
- package/lib/adapters/index.d.ts.map +1 -0
- package/lib/core/base-service.d.ts +132 -0
- package/lib/core/base-service.d.ts.map +1 -0
- package/lib/core/database-factory.d.ts +98 -0
- package/lib/core/database-factory.d.ts.map +1 -0
- package/lib/core/database-manager.d.ts +208 -0
- package/lib/core/database-manager.d.ts.map +1 -0
- package/lib/core/index.d.ts +5 -0
- package/lib/core/index.d.ts.map +1 -0
- package/lib/core/universal-dao.d.ts +64 -0
- package/lib/core/universal-dao.d.ts.map +1 -0
- package/lib/index.d.ts +324 -14
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +45 -1
- package/lib/index.mjs.map +1 -1
- package/lib/index.umd.js +1 -1
- package/lib/index.umd.js.map +1 -1
- package/lib/query/query-builder.d.ts +120 -0
- package/lib/query/query-builder.d.ts.map +1 -0
- package/lib/types.d.ts +126 -4
- package/lib/types.d.ts.map +1 -1
- package/lib/utils/csv-import.d.ts +102 -0
- package/lib/utils/csv-import.d.ts.map +1 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/migration-manager.d.ts +184 -0
- package/lib/utils/migration-manager.d.ts.map +1 -0
- package/package.json +83 -63
- package/README-all-source.md +0 -1248
- package/README-ps-gemini.md +0 -1180
- package/lib/adapters/browser-adapter.d.ts +0 -17
- package/lib/adapters/browser-adapter.d.ts.map +0 -1
- package/lib/adapters/bun-adapter.d.ts +0 -7
- package/lib/adapters/bun-adapter.d.ts.map +0 -1
- package/lib/adapters/deno-adapter.d.ts +0 -7
- package/lib/adapters/deno-adapter.d.ts.map +0 -1
- package/lib/adapters/node-adapter.d.ts +0 -7
- package/lib/adapters/node-adapter.d.ts.map +0 -1
- package/lib/adapters/react-native-adapter.d.ts +0 -20
- package/lib/adapters/react-native-adapter.d.ts.map +0 -1
- package/lib/query-builder.d.ts +0 -19
- package/lib/query-builder.d.ts.map +0 -1
- package/lib/sqlite-manager.d.ts +0 -11
- package/lib/sqlite-manager.d.ts.map +0 -1
- package/scripts/obfuscate.mjs +0 -155
- package/scripts/version-manager.js +0 -317
package/README-ps-gemini.md
DELETED
|
@@ -1,1180 +0,0 @@
|
|
|
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"
|