@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.
- package/README-all-source.md +1248 -0
- package/README-ps-gemini.md +1180 -0
- package/README.md +2653 -0
- package/lib/adapters/base-adapter.d.ts +8 -0
- package/lib/adapters/base-adapter.d.ts.map +1 -0
- package/lib/adapters/browser-adapter.d.ts +17 -0
- package/lib/adapters/browser-adapter.d.ts.map +1 -0
- package/lib/adapters/bun-adapter.d.ts +7 -0
- package/lib/adapters/bun-adapter.d.ts.map +1 -0
- package/lib/adapters/deno-adapter.d.ts +7 -0
- package/lib/adapters/deno-adapter.d.ts.map +1 -0
- package/lib/adapters/node-adapter.d.ts +7 -0
- package/lib/adapters/node-adapter.d.ts.map +1 -0
- package/lib/adapters/react-native-adapter.d.ts +20 -0
- package/lib/adapters/react-native-adapter.d.ts.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +2 -0
- package/lib/index.mjs.map +1 -0
- package/lib/index.umd.js +1 -0
- package/lib/index.umd.js.map +1 -0
- package/lib/query-builder.d.ts +19 -0
- package/lib/query-builder.d.ts.map +1 -0
- package/lib/sqlite-manager.d.ts +11 -0
- package/lib/sqlite-manager.d.ts.map +1 -0
- package/lib/types.d.ts +45 -0
- package/lib/types.d.ts.map +1 -0
- package/package.json +63 -0
- package/scripts/obfuscate.mjs +155 -0
- package/scripts/version-manager.js +317 -0
package/README.md
ADDED
|
@@ -0,0 +1,2653 @@
|
|
|
1
|
+
# @dqcai/sqlite - Universal SQLite Library
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Một thư viện SQLite đa nền tảng hỗ trợ **tất cả** các môi trường JavaScript/TypeScript phổ biến với API thống nhất.
|
|
8
|
+
|
|
9
|
+
## 🌟 Tính năng nổi bật
|
|
10
|
+
|
|
11
|
+
- ✅ **Universal**: Hoạt động trên Node.js, Browser, Deno, Bun, React Native
|
|
12
|
+
- ✅ **TypeScript**: Hỗ trợ đầy đủ TypeScript với type safety
|
|
13
|
+
- ✅ **Auto-detection**: Tự động phát hiện môi trường và chọn adapter phù hợp
|
|
14
|
+
- ✅ **Query Builder**: Công cụ xây dựng truy vấn mạnh mẽ
|
|
15
|
+
- ✅ **React Native Windows**: Hỗ trợ đặc biệt cho React Native Windows
|
|
16
|
+
- ✅ **Zero Configuration**: Không cần cấu hình phức tạp
|
|
17
|
+
|
|
18
|
+
## 🚀 Cài đặt
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @dqcai/sqlite
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Cài đặt dependencies theo môi trường
|
|
25
|
+
|
|
26
|
+
#### Node.js
|
|
27
|
+
```bash
|
|
28
|
+
npm install sqlite3
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### Browser
|
|
32
|
+
Không cần cài đặt thêm - sử dụng sql.js từ CDN
|
|
33
|
+
|
|
34
|
+
#### Deno
|
|
35
|
+
Không cần cài đặt - sử dụng Deno SQLite module
|
|
36
|
+
|
|
37
|
+
#### Bun
|
|
38
|
+
Sử dụng built-in SQLite của Bun
|
|
39
|
+
|
|
40
|
+
#### React Native
|
|
41
|
+
```bash
|
|
42
|
+
# Standard React Native
|
|
43
|
+
npm install react-native-sqlite-storage
|
|
44
|
+
# hoặc
|
|
45
|
+
npm install expo-sqlite
|
|
46
|
+
|
|
47
|
+
# React Native Windows
|
|
48
|
+
npm install react-native-sqlite-2
|
|
49
|
+
# hoặc
|
|
50
|
+
npm install react-native-windows-sqlite
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 📖 Sử dụng cơ bản
|
|
54
|
+
|
|
55
|
+
### Import thư viện
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import UniversalSQLite, { SQLiteManager, QueryBuilder } from '@dqcai/sqlite';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Kết nối và sử dụng đơn giản
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const db = new UniversalSQLite();
|
|
65
|
+
|
|
66
|
+
async function basicExample() {
|
|
67
|
+
try {
|
|
68
|
+
// Kết nối đến database
|
|
69
|
+
await db.connect('myapp.db');
|
|
70
|
+
|
|
71
|
+
// Tạo bảng
|
|
72
|
+
await db.createTable('users', {
|
|
73
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
74
|
+
name: 'TEXT NOT NULL',
|
|
75
|
+
email: 'TEXT UNIQUE',
|
|
76
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Thêm dữ liệu
|
|
80
|
+
await db.insert('users', {
|
|
81
|
+
name: 'John Doe',
|
|
82
|
+
email: 'john@example.com'
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Truy vấn dữ liệu
|
|
86
|
+
const users = await db.select('users');
|
|
87
|
+
console.log('All users:', users.rows);
|
|
88
|
+
|
|
89
|
+
// Tìm kiếm có điều kiện
|
|
90
|
+
const john = await db.select('users', 'name = ?', ['John Doe']);
|
|
91
|
+
console.log('John:', john.rows[0]);
|
|
92
|
+
|
|
93
|
+
// Cập nhật
|
|
94
|
+
await db.update('users',
|
|
95
|
+
{ email: 'john.doe@example.com' },
|
|
96
|
+
'name = ?',
|
|
97
|
+
['John Doe']
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Xóa
|
|
101
|
+
await db.delete('users', 'id = ?', [1]);
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Database error:', error);
|
|
105
|
+
} finally {
|
|
106
|
+
await db.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
basicExample();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 🔧 API Chi tiết
|
|
114
|
+
|
|
115
|
+
### UniversalSQLite Class
|
|
116
|
+
|
|
117
|
+
#### Khởi tạo
|
|
118
|
+
```typescript
|
|
119
|
+
const db = new UniversalSQLite();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Phương thức chính
|
|
123
|
+
|
|
124
|
+
##### `connect(path: string): Promise<void>`
|
|
125
|
+
```typescript
|
|
126
|
+
// In-memory database
|
|
127
|
+
await db.connect(':memory:');
|
|
128
|
+
|
|
129
|
+
// File database
|
|
130
|
+
await db.connect('database.db');
|
|
131
|
+
|
|
132
|
+
// Browser (file hoặc in-memory)
|
|
133
|
+
await db.connect('myapp.db');
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
##### `query(sql: string, params?: any[]): Promise<SQLiteResult>`
|
|
137
|
+
```typescript
|
|
138
|
+
// Truy vấn đơn giản
|
|
139
|
+
const result = await db.query('SELECT * FROM users');
|
|
140
|
+
|
|
141
|
+
// Truy vấn với parameters
|
|
142
|
+
const result = await db.query('SELECT * FROM users WHERE age > ?', [18]);
|
|
143
|
+
|
|
144
|
+
console.log(result.rows); // Dữ liệu trả về
|
|
145
|
+
console.log(result.rowsAffected); // Số dòng bị ảnh hưởng
|
|
146
|
+
console.log(result.lastInsertRowId); // ID của dòng mới insert
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
##### `createTable(name: string, schema: Record<string, string>): Promise<void>`
|
|
150
|
+
```typescript
|
|
151
|
+
await db.createTable('products', {
|
|
152
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
153
|
+
name: 'TEXT NOT NULL',
|
|
154
|
+
price: 'REAL',
|
|
155
|
+
category_id: 'INTEGER',
|
|
156
|
+
'FOREIGN KEY (category_id)': 'REFERENCES categories(id)'
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
##### `insert(table: string, data: Record<string, any>): Promise<SQLiteResult>`
|
|
161
|
+
```typescript
|
|
162
|
+
const result = await db.insert('users', {
|
|
163
|
+
name: 'Alice',
|
|
164
|
+
email: 'alice@example.com',
|
|
165
|
+
age: 25
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
console.log('New user ID:', result.lastInsertRowId);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
##### `select(table: string, where?: string, params?: any[]): Promise<SQLiteResult>`
|
|
172
|
+
```typescript
|
|
173
|
+
// Lấy tất cả
|
|
174
|
+
const allUsers = await db.select('users');
|
|
175
|
+
|
|
176
|
+
// Có điều kiện
|
|
177
|
+
const adults = await db.select('users', 'age >= ?', [18]);
|
|
178
|
+
|
|
179
|
+
// Điều kiện phức tạp
|
|
180
|
+
const result = await db.select('users', 'age BETWEEN ? AND ? AND name LIKE ?', [18, 65, 'A%']);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
##### `update(table: string, data: Record<string, any>, where: string, whereParams?: any[]): Promise<SQLiteResult>`
|
|
184
|
+
```typescript
|
|
185
|
+
const result = await db.update('users',
|
|
186
|
+
{ email: 'newemail@example.com', age: 26 },
|
|
187
|
+
'id = ?',
|
|
188
|
+
[1]
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
console.log('Updated rows:', result.rowsAffected);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
##### `delete(table: string, where: string, params?: any[]): Promise<SQLiteResult>`
|
|
195
|
+
```typescript
|
|
196
|
+
const result = await db.delete('users', 'age < ?', [18]);
|
|
197
|
+
console.log('Deleted rows:', result.rowsAffected);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### QueryBuilder - Xây dựng truy vấn nâng cao
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { QueryBuilder } from '@dqcai/sqlite';
|
|
204
|
+
|
|
205
|
+
// SELECT với QueryBuilder
|
|
206
|
+
const selectSQL = QueryBuilder
|
|
207
|
+
.table('users')
|
|
208
|
+
.select(['id', 'name', 'email'])
|
|
209
|
+
.where('age > 18')
|
|
210
|
+
.where('status = "active"')
|
|
211
|
+
.orderBy('name', 'ASC')
|
|
212
|
+
.limit(10)
|
|
213
|
+
.offset(0)
|
|
214
|
+
.toSQL();
|
|
215
|
+
|
|
216
|
+
console.log(selectSQL);
|
|
217
|
+
// Output: SELECT id, name, email FROM users WHERE age > 18 AND status = "active" ORDER BY name ASC LIMIT 10 OFFSET 0
|
|
218
|
+
|
|
219
|
+
const result = await db.query(selectSQL);
|
|
220
|
+
|
|
221
|
+
// INSERT với QueryBuilder
|
|
222
|
+
const insertSQL = QueryBuilder.insert('users', {
|
|
223
|
+
name: 'Bob',
|
|
224
|
+
email: 'bob@example.com',
|
|
225
|
+
age: 30
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await db.query(insertSQL, ['Bob', 'bob@example.com', 30]);
|
|
229
|
+
|
|
230
|
+
// UPDATE với QueryBuilder
|
|
231
|
+
const updateSQL = QueryBuilder.update('users',
|
|
232
|
+
{ email: 'bob.updated@example.com' },
|
|
233
|
+
'id = 1'
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
await db.query(updateSQL, ['bob.updated@example.com']);
|
|
237
|
+
|
|
238
|
+
// DELETE với QueryBuilder
|
|
239
|
+
const deleteSQL = QueryBuilder.delete('users', 'age < 18');
|
|
240
|
+
await db.query(deleteSQL);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### SQLiteManager - Quản lý kết nối thủ công
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { SQLiteManager } from '@dqcai/sqlite';
|
|
247
|
+
|
|
248
|
+
const manager = new SQLiteManager();
|
|
249
|
+
|
|
250
|
+
// Kiểm tra môi trường
|
|
251
|
+
console.log('Environment:', manager.getEnvironmentInfo());
|
|
252
|
+
|
|
253
|
+
// Kết nối thủ công
|
|
254
|
+
const connection = await manager.connect('database.db');
|
|
255
|
+
|
|
256
|
+
// Thực thi truy vấn
|
|
257
|
+
const result = await connection.execute('SELECT * FROM users');
|
|
258
|
+
|
|
259
|
+
// Đóng kết nối
|
|
260
|
+
await connection.close();
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## 🌍 Ví dụ theo môi trường
|
|
264
|
+
|
|
265
|
+
### Node.js Application
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// server.js
|
|
269
|
+
import UniversalSQLite from '@dqcai/sqlite';
|
|
270
|
+
|
|
271
|
+
const db = new UniversalSQLite();
|
|
272
|
+
|
|
273
|
+
async function setupUserSystem() {
|
|
274
|
+
await db.connect('./users.db');
|
|
275
|
+
|
|
276
|
+
// Tạo bảng users
|
|
277
|
+
await db.createTable('users', {
|
|
278
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
279
|
+
username: 'TEXT UNIQUE NOT NULL',
|
|
280
|
+
password_hash: 'TEXT NOT NULL',
|
|
281
|
+
email: 'TEXT UNIQUE',
|
|
282
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP',
|
|
283
|
+
last_login: 'DATETIME'
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Tạo index cho performance
|
|
287
|
+
await db.query('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)');
|
|
288
|
+
|
|
289
|
+
console.log('User system ready!');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function createUser(username: string, email: string, passwordHash: string) {
|
|
293
|
+
return await db.insert('users', {
|
|
294
|
+
username,
|
|
295
|
+
email,
|
|
296
|
+
password_hash: passwordHash
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function getUserByEmail(email: string) {
|
|
301
|
+
const result = await db.select('users', 'email = ?', [email]);
|
|
302
|
+
return result.rows[0] || null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
setupUserSystem();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Browser Application
|
|
309
|
+
|
|
310
|
+
```html
|
|
311
|
+
<!DOCTYPE html>
|
|
312
|
+
<html>
|
|
313
|
+
<head>
|
|
314
|
+
<title>Universal SQLite Browser Demo</title>
|
|
315
|
+
</head>
|
|
316
|
+
<body>
|
|
317
|
+
<div id="app"></div>
|
|
318
|
+
|
|
319
|
+
<script type="module">
|
|
320
|
+
import UniversalSQLite from './dist/index.js';
|
|
321
|
+
|
|
322
|
+
const db = new UniversalSQLite();
|
|
323
|
+
|
|
324
|
+
async function browserDemo() {
|
|
325
|
+
try {
|
|
326
|
+
// Kết nối in-memory database
|
|
327
|
+
await db.connect(':memory:');
|
|
328
|
+
|
|
329
|
+
// Tạo bảng todos
|
|
330
|
+
await db.createTable('todos', {
|
|
331
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
332
|
+
title: 'TEXT NOT NULL',
|
|
333
|
+
completed: 'BOOLEAN DEFAULT 0',
|
|
334
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Thêm một số todos
|
|
338
|
+
await db.insert('todos', { title: 'Learn SQLite', completed: 0 });
|
|
339
|
+
await db.insert('todos', { title: 'Build awesome app', completed: 0 });
|
|
340
|
+
|
|
341
|
+
// Hiển thị todos
|
|
342
|
+
const todos = await db.select('todos');
|
|
343
|
+
document.getElementById('app').innerHTML = `
|
|
344
|
+
<h1>My Todos</h1>
|
|
345
|
+
<ul>
|
|
346
|
+
${todos.rows.map(todo =>
|
|
347
|
+
`<li>${todo.title} - ${todo.completed ? '✅' : '⏳'}</li>`
|
|
348
|
+
).join('')}
|
|
349
|
+
</ul>
|
|
350
|
+
`;
|
|
351
|
+
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('Error:', error);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
browserDemo();
|
|
358
|
+
</script>
|
|
359
|
+
</body>
|
|
360
|
+
</html>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### React Native Application
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// App.tsx
|
|
367
|
+
import React, { useState, useEffect } from 'react';
|
|
368
|
+
import { View, Text, Button, FlatList } from 'react-native';
|
|
369
|
+
import UniversalSQLite from '@dqcai/sqlite';
|
|
370
|
+
|
|
371
|
+
const db = new UniversalSQLite();
|
|
372
|
+
|
|
373
|
+
interface User {
|
|
374
|
+
id: number;
|
|
375
|
+
name: string;
|
|
376
|
+
email: string;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export default function App() {
|
|
380
|
+
const [users, setUsers] = useState<User[]>([]);
|
|
381
|
+
const [environment, setEnvironment] = useState<string>('');
|
|
382
|
+
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
initDatabase();
|
|
385
|
+
}, []);
|
|
386
|
+
|
|
387
|
+
const initDatabase = async () => {
|
|
388
|
+
try {
|
|
389
|
+
// Kết nối database
|
|
390
|
+
await db.connect('myapp.db');
|
|
391
|
+
|
|
392
|
+
// Hiển thị môi trường
|
|
393
|
+
setEnvironment(db.getEnvironment());
|
|
394
|
+
|
|
395
|
+
// Tạo bảng
|
|
396
|
+
await db.createTable('users', {
|
|
397
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
398
|
+
name: 'TEXT NOT NULL',
|
|
399
|
+
email: 'TEXT UNIQUE'
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Load dữ liệu
|
|
403
|
+
loadUsers();
|
|
404
|
+
|
|
405
|
+
} catch (error) {
|
|
406
|
+
console.error('Database init error:', error);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const loadUsers = async () => {
|
|
411
|
+
try {
|
|
412
|
+
const result = await db.select('users');
|
|
413
|
+
setUsers(result.rows);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error('Load users error:', error);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const addUser = async () => {
|
|
420
|
+
try {
|
|
421
|
+
await db.insert('users', {
|
|
422
|
+
name: `User ${Date.now()}`,
|
|
423
|
+
email: `user${Date.now()}@example.com`
|
|
424
|
+
});
|
|
425
|
+
loadUsers();
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error('Add user error:', error);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<View style={{ flex: 1, padding: 20 }}>
|
|
433
|
+
<Text>Environment: {environment}</Text>
|
|
434
|
+
<Button title="Add User" onPress={addUser} />
|
|
435
|
+
|
|
436
|
+
<FlatList
|
|
437
|
+
data={users}
|
|
438
|
+
keyExtractor={item => item.id.toString()}
|
|
439
|
+
renderItem={({ item }) => (
|
|
440
|
+
<View style={{ padding: 10, borderBottomWidth: 1 }}>
|
|
441
|
+
<Text>{item.name}</Text>
|
|
442
|
+
<Text>{item.email}</Text>
|
|
443
|
+
</View>
|
|
444
|
+
)}
|
|
445
|
+
/>
|
|
446
|
+
</View>
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Deno Application
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// main.ts
|
|
455
|
+
import UniversalSQLite from "https://esm.sh/@dqcai/sqlite";
|
|
456
|
+
|
|
457
|
+
const db = new UniversalSQLite();
|
|
458
|
+
|
|
459
|
+
async function denoExample() {
|
|
460
|
+
try {
|
|
461
|
+
await db.connect('./deno_database.db');
|
|
462
|
+
|
|
463
|
+
// Tạo bảng logs
|
|
464
|
+
await db.createTable('logs', {
|
|
465
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
466
|
+
level: 'TEXT NOT NULL',
|
|
467
|
+
message: 'TEXT NOT NULL',
|
|
468
|
+
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// Thêm log entries
|
|
472
|
+
await db.insert('logs', { level: 'INFO', message: 'Application started' });
|
|
473
|
+
await db.insert('logs', { level: 'DEBUG', message: 'Database connected' });
|
|
474
|
+
await db.insert('logs', { level: 'ERROR', message: 'Something went wrong' });
|
|
475
|
+
|
|
476
|
+
// Truy vấn logs theo level
|
|
477
|
+
const errorLogs = await db.select('logs', 'level = ?', ['ERROR']);
|
|
478
|
+
console.log('Error logs:', errorLogs.rows);
|
|
479
|
+
|
|
480
|
+
// Sử dụng QueryBuilder
|
|
481
|
+
const recentLogs = QueryBuilder
|
|
482
|
+
.table('logs')
|
|
483
|
+
.select(['level', 'message', 'timestamp'])
|
|
484
|
+
.where('timestamp > datetime("now", "-1 hour")')
|
|
485
|
+
.orderBy('timestamp', 'DESC')
|
|
486
|
+
.limit(10)
|
|
487
|
+
.toSQL();
|
|
488
|
+
|
|
489
|
+
const result = await db.query(recentLogs);
|
|
490
|
+
console.log('Recent logs:', result.rows);
|
|
491
|
+
|
|
492
|
+
} finally {
|
|
493
|
+
await db.close();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Chạy với: deno run --allow-read --allow-write main.ts
|
|
498
|
+
denoExample();
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Bun Application
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// app.ts
|
|
505
|
+
import UniversalSQLite from '@dqcai/sqlite';
|
|
506
|
+
|
|
507
|
+
const db = new UniversalSQLite();
|
|
508
|
+
|
|
509
|
+
async function bunExample() {
|
|
510
|
+
console.log('Environment:', db.getEnvironment()); // "Bun"
|
|
511
|
+
|
|
512
|
+
await db.connect('./bun_database.db');
|
|
513
|
+
|
|
514
|
+
// Tạo bảng products
|
|
515
|
+
await db.createTable('products', {
|
|
516
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
517
|
+
name: 'TEXT NOT NULL',
|
|
518
|
+
price: 'REAL NOT NULL',
|
|
519
|
+
category: 'TEXT',
|
|
520
|
+
in_stock: 'BOOLEAN DEFAULT 1'
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// Bulk insert
|
|
524
|
+
const products = [
|
|
525
|
+
{ name: 'Laptop', price: 999.99, category: 'Electronics' },
|
|
526
|
+
{ name: 'Mouse', price: 29.99, category: 'Electronics' },
|
|
527
|
+
{ name: 'Desk', price: 199.99, category: 'Furniture' }
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
for (const product of products) {
|
|
531
|
+
await db.insert('products', product);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Truy vấn phức tạp
|
|
535
|
+
const expensiveProducts = await db.query(`
|
|
536
|
+
SELECT name, price, category
|
|
537
|
+
FROM products
|
|
538
|
+
WHERE price > ? AND in_stock = 1
|
|
539
|
+
ORDER BY price DESC
|
|
540
|
+
`, [100]);
|
|
541
|
+
|
|
542
|
+
console.log('Expensive products:', expensiveProducts.rows);
|
|
543
|
+
|
|
544
|
+
await db.close();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Chạy với: bun run app.ts
|
|
548
|
+
bunExample();
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## 🏗️ Ví dụ nâng cao
|
|
552
|
+
|
|
553
|
+
### 1. Transaction Management
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
import { SQLiteManager } from '@dqcai/sqlite';
|
|
557
|
+
|
|
558
|
+
async function transactionExample() {
|
|
559
|
+
const manager = new SQLiteManager();
|
|
560
|
+
const connection = await manager.connect('transactions.db');
|
|
561
|
+
|
|
562
|
+
try {
|
|
563
|
+
// Bắt đầu transaction
|
|
564
|
+
await connection.execute('BEGIN TRANSACTION');
|
|
565
|
+
|
|
566
|
+
// Tạo bảng accounts
|
|
567
|
+
await connection.execute(`
|
|
568
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
569
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
570
|
+
name TEXT NOT NULL,
|
|
571
|
+
balance REAL DEFAULT 0.00
|
|
572
|
+
)
|
|
573
|
+
`);
|
|
574
|
+
|
|
575
|
+
// Thêm accounts
|
|
576
|
+
await connection.execute('INSERT INTO accounts (name, balance) VALUES (?, ?)', ['Alice', 1000]);
|
|
577
|
+
await connection.execute('INSERT INTO accounts (name, balance) VALUES (?, ?)', ['Bob', 500]);
|
|
578
|
+
|
|
579
|
+
// Chuyển tiền từ Alice sang Bob
|
|
580
|
+
const transferAmount = 200;
|
|
581
|
+
|
|
582
|
+
// Trừ tiền Alice
|
|
583
|
+
await connection.execute(
|
|
584
|
+
'UPDATE accounts SET balance = balance - ? WHERE name = ?',
|
|
585
|
+
[transferAmount, 'Alice']
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
// Cộng tiền Bob
|
|
589
|
+
await connection.execute(
|
|
590
|
+
'UPDATE accounts SET balance = balance + ? WHERE name = ?',
|
|
591
|
+
[transferAmount, 'Bob']
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
// Kiểm tra balance không âm
|
|
595
|
+
const aliceBalance = await connection.execute('SELECT balance FROM accounts WHERE name = ?', ['Alice']);
|
|
596
|
+
if (aliceBalance.rows[0].balance < 0) {
|
|
597
|
+
throw new Error('Insufficient funds');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Commit transaction
|
|
601
|
+
await connection.execute('COMMIT');
|
|
602
|
+
console.log('Transfer successful!');
|
|
603
|
+
|
|
604
|
+
// Hiển thị kết quả
|
|
605
|
+
const allAccounts = await connection.execute('SELECT * FROM accounts');
|
|
606
|
+
console.log('Final balances:', allAccounts.rows);
|
|
607
|
+
|
|
608
|
+
} catch (error) {
|
|
609
|
+
// Rollback nếu có lỗi
|
|
610
|
+
await connection.execute('ROLLBACK');
|
|
611
|
+
console.error('Transaction failed:', error);
|
|
612
|
+
} finally {
|
|
613
|
+
await connection.close();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
transactionExample();
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### 2. Complex Queries với JOIN
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
async function complexQueryExample() {
|
|
624
|
+
const db = new UniversalSQLite();
|
|
625
|
+
await db.connect('ecommerce.db');
|
|
626
|
+
|
|
627
|
+
// Tạo schema phức tạp
|
|
628
|
+
await db.createTable('categories', {
|
|
629
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
630
|
+
name: 'TEXT NOT NULL UNIQUE',
|
|
631
|
+
description: 'TEXT'
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
await db.createTable('products', {
|
|
635
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
636
|
+
name: 'TEXT NOT NULL',
|
|
637
|
+
price: 'REAL NOT NULL',
|
|
638
|
+
category_id: 'INTEGER NOT NULL',
|
|
639
|
+
'FOREIGN KEY (category_id)': 'REFERENCES categories(id)'
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
await db.createTable('orders', {
|
|
643
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
644
|
+
product_id: 'INTEGER NOT NULL',
|
|
645
|
+
quantity: 'INTEGER NOT NULL',
|
|
646
|
+
order_date: 'DATETIME DEFAULT CURRENT_TIMESTAMP',
|
|
647
|
+
'FOREIGN KEY (product_id)': 'REFERENCES products(id)'
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// Thêm dữ liệu mẫu
|
|
651
|
+
await db.insert('categories', { name: 'Electronics', description: 'Electronic devices' });
|
|
652
|
+
await db.insert('categories', { name: 'Books', description: 'Books and literature' });
|
|
653
|
+
|
|
654
|
+
await db.insert('products', { name: 'iPhone 15', price: 999, category_id: 1 });
|
|
655
|
+
await db.insert('products', { name: 'MacBook Pro', price: 2499, category_id: 1 });
|
|
656
|
+
await db.insert('products', { name: 'JavaScript Guide', price: 29.99, category_id: 2 });
|
|
657
|
+
|
|
658
|
+
await db.insert('orders', { product_id: 1, quantity: 2 });
|
|
659
|
+
await db.insert('orders', { product_id: 2, quantity: 1 });
|
|
660
|
+
await db.insert('orders', { product_id: 1, quantity: 1 });
|
|
661
|
+
|
|
662
|
+
// Query phức tạp với JOIN
|
|
663
|
+
const salesReport = await db.query(`
|
|
664
|
+
SELECT
|
|
665
|
+
c.name as category,
|
|
666
|
+
p.name as product,
|
|
667
|
+
SUM(o.quantity) as total_sold,
|
|
668
|
+
SUM(o.quantity * p.price) as total_revenue,
|
|
669
|
+
AVG(p.price) as avg_price
|
|
670
|
+
FROM orders o
|
|
671
|
+
JOIN products p ON o.product_id = p.id
|
|
672
|
+
JOIN categories c ON p.category_id = c.id
|
|
673
|
+
GROUP BY c.id, p.id
|
|
674
|
+
ORDER BY total_revenue DESC
|
|
675
|
+
`);
|
|
676
|
+
|
|
677
|
+
console.log('Sales Report:', salesReport.rows);
|
|
678
|
+
|
|
679
|
+
// Subquery example
|
|
680
|
+
const topSellingCategory = await db.query(`
|
|
681
|
+
SELECT
|
|
682
|
+
c.name,
|
|
683
|
+
(SELECT SUM(quantity) FROM orders o
|
|
684
|
+
JOIN products p ON o.product_id = p.id
|
|
685
|
+
WHERE p.category_id = c.id) as total_quantity
|
|
686
|
+
FROM categories c
|
|
687
|
+
ORDER BY total_quantity DESC
|
|
688
|
+
LIMIT 1
|
|
689
|
+
`);
|
|
690
|
+
|
|
691
|
+
console.log('Top selling category:', topSellingCategory.rows[0]);
|
|
692
|
+
|
|
693
|
+
await db.close();
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
complexQueryExample();
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### 3. Real-time Data Synchronization
|
|
700
|
+
|
|
701
|
+
```typescript
|
|
702
|
+
async function realtimeExample() {
|
|
703
|
+
const db = new UniversalSQLite();
|
|
704
|
+
await db.connect('realtime.db');
|
|
705
|
+
|
|
706
|
+
// Tạo bảng cho sync
|
|
707
|
+
await db.createTable('sync_queue', {
|
|
708
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
709
|
+
table_name: 'TEXT NOT NULL',
|
|
710
|
+
operation: 'TEXT NOT NULL', // INSERT, UPDATE, DELETE
|
|
711
|
+
data: 'TEXT', // JSON data
|
|
712
|
+
synced: 'BOOLEAN DEFAULT 0',
|
|
713
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
await db.createTable('users', {
|
|
717
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
718
|
+
name: 'TEXT NOT NULL',
|
|
719
|
+
email: 'TEXT UNIQUE',
|
|
720
|
+
last_modified: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
// Helper function để track changes
|
|
724
|
+
async function trackChange(table: string, operation: string, data: any) {
|
|
725
|
+
await db.insert('sync_queue', {
|
|
726
|
+
table_name: table,
|
|
727
|
+
operation: operation,
|
|
728
|
+
data: JSON.stringify(data)
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Wrapper functions với tracking
|
|
733
|
+
async function insertUser(userData: any) {
|
|
734
|
+
const result = await db.insert('users', userData);
|
|
735
|
+
await trackChange('users', 'INSERT', { ...userData, id: result.lastInsertRowId });
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async function updateUser(id: number, userData: any) {
|
|
740
|
+
const result = await db.update('users',
|
|
741
|
+
{ ...userData, last_modified: new Date().toISOString() },
|
|
742
|
+
'id = ?',
|
|
743
|
+
[id]
|
|
744
|
+
);
|
|
745
|
+
await trackChange('users', 'UPDATE', { id, ...userData });
|
|
746
|
+
return result;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async function deleteUser(id: number) {
|
|
750
|
+
const result = await db.delete('users', 'id = ?', [id]);
|
|
751
|
+
await trackChange('users', 'DELETE', { id });
|
|
752
|
+
return result;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Sync function
|
|
756
|
+
async function syncToServer() {
|
|
757
|
+
const unsyncedChanges = await db.select('sync_queue', 'synced = 0');
|
|
758
|
+
|
|
759
|
+
for (const change of unsyncedChanges.rows) {
|
|
760
|
+
try {
|
|
761
|
+
// Giả lập API call
|
|
762
|
+
console.log(`Syncing ${change.operation} on ${change.table_name}:`,
|
|
763
|
+
JSON.parse(change.data));
|
|
764
|
+
|
|
765
|
+
// Đánh dấu đã sync
|
|
766
|
+
await db.update('sync_queue',
|
|
767
|
+
{ synced: 1 },
|
|
768
|
+
'id = ?',
|
|
769
|
+
[change.id]
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
} catch (error) {
|
|
773
|
+
console.error('Sync failed for change:', change.id, error);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Demo usage
|
|
779
|
+
await insertUser({ name: 'Alice', email: 'alice@example.com' });
|
|
780
|
+
await insertUser({ name: 'Bob', email: 'bob@example.com' });
|
|
781
|
+
await updateUser(1, { email: 'alice.updated@example.com' });
|
|
782
|
+
|
|
783
|
+
// Sync changes
|
|
784
|
+
await syncToServer();
|
|
785
|
+
|
|
786
|
+
await db.close();
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
realtimeExample();
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
### 4. Database Migration System
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
class DatabaseMigration {
|
|
796
|
+
private db: UniversalSQLite;
|
|
797
|
+
private currentVersion: number = 0;
|
|
798
|
+
|
|
799
|
+
constructor(db: UniversalSQLite) {
|
|
800
|
+
this.db = db;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
async getCurrentVersion(): Promise<number> {
|
|
804
|
+
try {
|
|
805
|
+
const result = await this.db.query('PRAGMA user_version');
|
|
806
|
+
return result.rows[0].user_version || 0;
|
|
807
|
+
} catch {
|
|
808
|
+
return 0;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async setVersion(version: number): Promise<void> {
|
|
813
|
+
await this.db.query(`PRAGMA user_version = ${version}`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async migrate() {
|
|
817
|
+
this.currentVersion = await this.getCurrentVersion();
|
|
818
|
+
console.log(`Current database version: ${this.currentVersion}`);
|
|
819
|
+
|
|
820
|
+
const migrations = [
|
|
821
|
+
this.migration1_initial,
|
|
822
|
+
this.migration2_addUserProfiles,
|
|
823
|
+
this.migration3_addIndexes,
|
|
824
|
+
this.migration4_addAuditLog
|
|
825
|
+
];
|
|
826
|
+
|
|
827
|
+
for (let i = this.currentVersion; i < migrations.length; i++) {
|
|
828
|
+
console.log(`Running migration ${i + 1}...`);
|
|
829
|
+
await migrations[i].call(this);
|
|
830
|
+
await this.setVersion(i + 1);
|
|
831
|
+
console.log(`Migration ${i + 1} completed`);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
console.log('All migrations completed!');
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Migration 1: Initial schema
|
|
838
|
+
private async migration1_initial() {
|
|
839
|
+
await this.db.createTable('users', {
|
|
840
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
841
|
+
username: 'TEXT UNIQUE NOT NULL',
|
|
842
|
+
email: 'TEXT UNIQUE NOT NULL',
|
|
843
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Migration 2: Add user profiles
|
|
848
|
+
private async migration2_addUserProfiles() {
|
|
849
|
+
await this.db.query('ALTER TABLE users ADD COLUMN first_name TEXT');
|
|
850
|
+
await this.db.query('ALTER TABLE users ADD COLUMN last_name TEXT');
|
|
851
|
+
await this.db.query('ALTER TABLE users ADD COLUMN avatar_url TEXT');
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Migration 3: Add performance indexes
|
|
855
|
+
private async migration3_addIndexes() {
|
|
856
|
+
await this.db.query('CREATE INDEX idx_users_email ON users(email)');
|
|
857
|
+
await this.db.query('CREATE INDEX idx_users_username ON users(username)');
|
|
858
|
+
await this.db.query('CREATE INDEX idx_users_created_at ON users(created_at)');
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Migration 4: Add audit log
|
|
862
|
+
private async migration4_addAuditLog() {
|
|
863
|
+
await this.db.createTable('audit_logs', {
|
|
864
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
865
|
+
table_name: 'TEXT NOT NULL',
|
|
866
|
+
record_id: 'INTEGER NOT NULL',
|
|
867
|
+
action: 'TEXT NOT NULL',
|
|
868
|
+
old_values: 'TEXT',
|
|
869
|
+
new_values: 'TEXT',
|
|
870
|
+
user_id: 'INTEGER',
|
|
871
|
+
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
// Tạo trigger cho audit
|
|
875
|
+
await this.db.query(`
|
|
876
|
+
CREATE TRIGGER audit_users_update
|
|
877
|
+
AFTER UPDATE ON users
|
|
878
|
+
FOR EACH ROW
|
|
879
|
+
BEGIN
|
|
880
|
+
INSERT INTO audit_logs (table_name, record_id, action, old_values, new_values)
|
|
881
|
+
VALUES ('users', NEW.id, 'UPDATE',
|
|
882
|
+
json_object('username', OLD.username, 'email', OLD.email),
|
|
883
|
+
json_object('username', NEW.username, 'email', NEW.email));
|
|
884
|
+
END
|
|
885
|
+
`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Sử dụng migration
|
|
890
|
+
async function migrationExample() {
|
|
891
|
+
const db = new UniversalSQLite();
|
|
892
|
+
await db.connect('app_with_migrations.db');
|
|
893
|
+
|
|
894
|
+
const migration = new DatabaseMigration(db);
|
|
895
|
+
await migration.migrate();
|
|
896
|
+
|
|
897
|
+
// Test data sau migration
|
|
898
|
+
await db.insert('users', {
|
|
899
|
+
username: 'testuser',
|
|
900
|
+
email: 'test@example.com',
|
|
901
|
+
first_name: 'Test',
|
|
902
|
+
last_name: 'User'
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// Update để trigger audit
|
|
906
|
+
await db.update('users',
|
|
907
|
+
{ email: 'test.updated@example.com' },
|
|
908
|
+
'username = ?',
|
|
909
|
+
['testuser']
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
// Kiểm tra audit log
|
|
913
|
+
const auditLogs = await db.select('audit_logs');
|
|
914
|
+
console.log('Audit logs:', auditLogs.rows);
|
|
915
|
+
|
|
916
|
+
await db.close();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
migrationExample();
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
### 5. Full-Text Search
|
|
923
|
+
|
|
924
|
+
```typescript
|
|
925
|
+
async function fullTextSearchExample() {
|
|
926
|
+
const db = new UniversalSQLite();
|
|
927
|
+
await db.connect('search.db');
|
|
928
|
+
|
|
929
|
+
// Tạo FTS table
|
|
930
|
+
await db.query(`
|
|
931
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts
|
|
932
|
+
USING fts5(id, title, content, author)
|
|
933
|
+
`);
|
|
934
|
+
|
|
935
|
+
// Tạo bảng articles thường
|
|
936
|
+
await db.createTable('articles', {
|
|
937
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
938
|
+
title: 'TEXT NOT NULL',
|
|
939
|
+
content: 'TEXT NOT NULL',
|
|
940
|
+
author: 'TEXT NOT NULL',
|
|
941
|
+
published_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
// Thêm dữ liệu mẫu
|
|
945
|
+
const articles = [
|
|
946
|
+
{
|
|
947
|
+
title: 'Introduction to SQLite',
|
|
948
|
+
content: 'SQLite is a lightweight database engine that is perfect for mobile and web applications. It provides ACID compliance and supports most of the SQL standard.',
|
|
949
|
+
author: 'John Doe'
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
title: 'Advanced TypeScript Patterns',
|
|
953
|
+
content: 'TypeScript provides powerful type system features including generics, conditional types, and mapped types that enable building robust applications.',
|
|
954
|
+
author: 'Jane Smith'
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
title: 'React Native Performance Tips',
|
|
958
|
+
content: 'Optimizing React Native apps requires understanding of the bridge, native modules, and proper state management patterns.',
|
|
959
|
+
author: 'Bob Johnson'
|
|
960
|
+
}
|
|
961
|
+
];
|
|
962
|
+
|
|
963
|
+
for (const article of articles) {
|
|
964
|
+
const result = await db.insert('articles', article);
|
|
965
|
+
// Thêm vào FTS index
|
|
966
|
+
await db.query(`
|
|
967
|
+
INSERT INTO articles_fts (id, title, content, author)
|
|
968
|
+
VALUES (?, ?, ?, ?)
|
|
969
|
+
`, [result.lastInsertRowId, article.title, article.content, article.author]);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Full-text search
|
|
973
|
+
const searchResults = await db.query(`
|
|
974
|
+
SELECT
|
|
975
|
+
a.id, a.title, a.author, a.published_at,
|
|
976
|
+
snippet(articles_fts, 1, '<b>', '</b>', '...', 50) as title_snippet,
|
|
977
|
+
snippet(articles_fts, 2, '<b>', '</b>', '...', 100) as content_snippet
|
|
978
|
+
FROM articles_fts
|
|
979
|
+
JOIN articles a ON articles_fts.id = a.id
|
|
980
|
+
WHERE articles_fts MATCH ?
|
|
981
|
+
ORDER BY rank
|
|
982
|
+
`, ['SQLite OR TypeScript']);
|
|
983
|
+
|
|
984
|
+
console.log('Search results:', searchResults.rows);
|
|
985
|
+
|
|
986
|
+
// Advanced search với bxm25 ranking
|
|
987
|
+
const advancedSearch = await db.query(`
|
|
988
|
+
SELECT
|
|
989
|
+
a.*,
|
|
990
|
+
bm25(articles_fts) as relevance_score
|
|
991
|
+
FROM articles_fts
|
|
992
|
+
JOIN articles a ON articles_fts.id = a.id
|
|
993
|
+
WHERE articles_fts MATCH ?
|
|
994
|
+
ORDER BY relevance_score
|
|
995
|
+
`, ['application AND (mobile OR web)']);
|
|
996
|
+
|
|
997
|
+
console.log('Advanced search:', advancedSearch.rows);
|
|
998
|
+
|
|
999
|
+
await db.close();
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
fullTextSearchExample();
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
### 6. Data Analytics và Reporting
|
|
1006
|
+
|
|
1007
|
+
```typescript
|
|
1008
|
+
async function analyticsExample() {
|
|
1009
|
+
const db = new UniversalSQLite();
|
|
1010
|
+
await db.connect('analytics.db');
|
|
1011
|
+
|
|
1012
|
+
// Tạo schema cho analytics
|
|
1013
|
+
await db.createTable('events', {
|
|
1014
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1015
|
+
user_id: 'INTEGER NOT NULL',
|
|
1016
|
+
event_type: 'TEXT NOT NULL',
|
|
1017
|
+
event_data: 'TEXT', // JSON
|
|
1018
|
+
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
await db.createTable('users_analytics', {
|
|
1022
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1023
|
+
user_id: 'INTEGER UNIQUE NOT NULL',
|
|
1024
|
+
total_events: 'INTEGER DEFAULT 0',
|
|
1025
|
+
first_seen: 'DATETIME',
|
|
1026
|
+
last_seen: 'DATETIME',
|
|
1027
|
+
session_count: 'INTEGER DEFAULT 0'
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
// Thêm dữ liệu events mẫu
|
|
1031
|
+
const sampleEvents = [
|
|
1032
|
+
{ user_id: 1, event_type: 'page_view', event_data: '{"page": "/home"}' },
|
|
1033
|
+
{ user_id: 1, event_type: 'click', event_data: '{"element": "signup_button"}' },
|
|
1034
|
+
{ user_id: 2, event_type: 'page_view', event_data: '{"page": "/products"}' },
|
|
1035
|
+
{ user_id: 1, event_type: 'purchase', event_data: '{"product_id": 123, "amount": 99.99}' },
|
|
1036
|
+
{ user_id: 3, event_type: 'page_view', event_data: '{"page": "/home"}' },
|
|
1037
|
+
{ user_id: 2, event_type: 'purchase', event_data: '{"product_id": 456, "amount": 149.99}' }
|
|
1038
|
+
];
|
|
1039
|
+
|
|
1040
|
+
for (const event of sampleEvents) {
|
|
1041
|
+
await db.insert('events', event);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Analytics queries
|
|
1045
|
+
|
|
1046
|
+
// 1. User engagement report
|
|
1047
|
+
const userEngagement = await db.query(`
|
|
1048
|
+
SELECT
|
|
1049
|
+
user_id,
|
|
1050
|
+
COUNT(*) as total_events,
|
|
1051
|
+
COUNT(CASE WHEN event_type = 'purchase' THEN 1 END) as purchases,
|
|
1052
|
+
MIN(timestamp) as first_seen,
|
|
1053
|
+
MAX(timestamp) as last_seen,
|
|
1054
|
+
ROUND(
|
|
1055
|
+
JULIANDAY(MAX(timestamp)) - JULIANDAY(MIN(timestamp))
|
|
1056
|
+
) as days_active
|
|
1057
|
+
FROM events
|
|
1058
|
+
GROUP BY user_id
|
|
1059
|
+
ORDER BY total_events DESC
|
|
1060
|
+
`);
|
|
1061
|
+
|
|
1062
|
+
console.log('User Engagement Report:', userEngagement.rows);
|
|
1063
|
+
|
|
1064
|
+
// 2. Revenue analytics
|
|
1065
|
+
const revenueReport = await db.query(`
|
|
1066
|
+
SELECT
|
|
1067
|
+
DATE(timestamp) as date,
|
|
1068
|
+
COUNT(*) as purchase_count,
|
|
1069
|
+
SUM(CAST(json_extract(event_data, '$.amount') AS REAL)) as total_revenue,
|
|
1070
|
+
AVG(CAST(json_extract(event_data, '$.amount') AS REAL)) as avg_order_value
|
|
1071
|
+
FROM events
|
|
1072
|
+
WHERE event_type = 'purchase'
|
|
1073
|
+
GROUP BY DATE(timestamp)
|
|
1074
|
+
ORDER BY date DESC
|
|
1075
|
+
`);
|
|
1076
|
+
|
|
1077
|
+
console.log('Revenue Report:', revenueReport.rows);
|
|
1078
|
+
|
|
1079
|
+
// 3. Funnel analysis
|
|
1080
|
+
const funnelAnalysis = await db.query(`
|
|
1081
|
+
WITH user_funnel AS (
|
|
1082
|
+
SELECT
|
|
1083
|
+
user_id,
|
|
1084
|
+
MAX(CASE WHEN event_type = 'page_view' THEN 1 ELSE 0 END) as viewed,
|
|
1085
|
+
MAX(CASE WHEN event_type = 'click' THEN 1 ELSE 0 END) as clicked,
|
|
1086
|
+
MAX(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) as purchased
|
|
1087
|
+
FROM events
|
|
1088
|
+
GROUP BY user_id
|
|
1089
|
+
)
|
|
1090
|
+
SELECT
|
|
1091
|
+
SUM(viewed) as total_users,
|
|
1092
|
+
SUM(clicked) as users_clicked,
|
|
1093
|
+
SUM(purchased) as users_purchased,
|
|
1094
|
+
ROUND(SUM(clicked) * 100.0 / SUM(viewed), 2) as click_rate_percent,
|
|
1095
|
+
ROUND(SUM(purchased) * 100.0 / SUM(clicked), 2) as conversion_rate_percent
|
|
1096
|
+
FROM user_funnel
|
|
1097
|
+
`);
|
|
1098
|
+
|
|
1099
|
+
console.log('Funnel Analysis:', funnelAnalysis.rows[0]);
|
|
1100
|
+
|
|
1101
|
+
await db.close();
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
analyticsExample();
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
## 🔍 Debugging và Monitoring
|
|
1108
|
+
|
|
1109
|
+
### 1. Query Performance Monitoring
|
|
1110
|
+
|
|
1111
|
+
```typescript
|
|
1112
|
+
class PerformanceMonitor {
|
|
1113
|
+
private db: UniversalSQLite;
|
|
1114
|
+
|
|
1115
|
+
constructor(db: UniversalSQLite) {
|
|
1116
|
+
this.db = db;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
async queryWithTiming(sql: string, params?: any[]): Promise<{result: any, duration: number}> {
|
|
1120
|
+
const startTime = performance.now();
|
|
1121
|
+
const result = await this.db.query(sql, params);
|
|
1122
|
+
const endTime = performance.now();
|
|
1123
|
+
const duration = endTime - startTime;
|
|
1124
|
+
|
|
1125
|
+
console.log(`Query executed in ${duration.toFixed(2)}ms: ${sql.substring(0, 100)}...`);
|
|
1126
|
+
|
|
1127
|
+
return { result, duration };
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
async explainQuery(sql: string): Promise<void> {
|
|
1131
|
+
const explanation = await this.db.query(`EXPLAIN QUERY PLAN ${sql}`);
|
|
1132
|
+
console.log('Query Plan:');
|
|
1133
|
+
explanation.rows.forEach(row => {
|
|
1134
|
+
console.log(` ${row.detail}`);
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
async function performanceExample() {
|
|
1140
|
+
const db = new UniversalSQLite();
|
|
1141
|
+
await db.connect('performance_test.db');
|
|
1142
|
+
const monitor = new PerformanceMonitor(db);
|
|
1143
|
+
|
|
1144
|
+
// Tạo bảng với nhiều dữ liệu
|
|
1145
|
+
await db.createTable('large_table', {
|
|
1146
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1147
|
+
name: 'TEXT',
|
|
1148
|
+
value: 'INTEGER',
|
|
1149
|
+
category: 'TEXT'
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
// Insert bulk data
|
|
1153
|
+
console.log('Inserting test data...');
|
|
1154
|
+
for (let i = 0; i < 1000; i++) {
|
|
1155
|
+
await db.insert('large_table', {
|
|
1156
|
+
name: `Item ${i}`,
|
|
1157
|
+
value: Math.floor(Math.random() * 1000),
|
|
1158
|
+
category: `Category ${i % 10}`
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Test query performance
|
|
1163
|
+
const { result, duration } = await monitor.queryWithTiming(`
|
|
1164
|
+
SELECT category, COUNT(*) as count, AVG(value) as avg_value
|
|
1165
|
+
FROM large_table
|
|
1166
|
+
GROUP BY category
|
|
1167
|
+
ORDER BY count DESC
|
|
1168
|
+
`);
|
|
1169
|
+
|
|
1170
|
+
console.log('Aggregation result:', result.rows);
|
|
1171
|
+
|
|
1172
|
+
// Explain query
|
|
1173
|
+
await monitor.explainQuery('SELECT * FROM large_table WHERE value > 500');
|
|
1174
|
+
|
|
1175
|
+
await db.close();
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
performanceExample();
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
### 2. Connection Pool (Advanced)
|
|
1182
|
+
|
|
1183
|
+
```typescript
|
|
1184
|
+
class ConnectionPool {
|
|
1185
|
+
private connections: Map<string, UniversalSQLite> = new Map();
|
|
1186
|
+
private maxConnections: number = 5;
|
|
1187
|
+
|
|
1188
|
+
async getConnection(dbPath: string): Promise<UniversalSQLite> {
|
|
1189
|
+
if (this.connections.has(dbPath)) {
|
|
1190
|
+
return this.connections.get(dbPath)!;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (this.connections.size >= this.maxConnections) {
|
|
1194
|
+
throw new Error('Connection pool exhausted');
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const db = new UniversalSQLite();
|
|
1198
|
+
await db.connect(dbPath);
|
|
1199
|
+
this.connections.set(dbPath, db);
|
|
1200
|
+
|
|
1201
|
+
return db;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
async closeAll(): Promise<void> {
|
|
1205
|
+
for (const [path, db] of this.connections) {
|
|
1206
|
+
await db.close();
|
|
1207
|
+
}
|
|
1208
|
+
this.connections.clear();
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
async function connectionPoolExample() {
|
|
1213
|
+
const pool = new ConnectionPool();
|
|
1214
|
+
|
|
1215
|
+
try {
|
|
1216
|
+
// Sử dụng multiple databases
|
|
1217
|
+
const mainDb = await pool.getConnection('main.db');
|
|
1218
|
+
const cacheDb = await pool.getConnection('cache.db');
|
|
1219
|
+
const logsDb = await pool.getConnection('logs.db');
|
|
1220
|
+
|
|
1221
|
+
// Setup schemas
|
|
1222
|
+
await mainDb.createTable('users', {
|
|
1223
|
+
id: 'INTEGER PRIMARY KEY',
|
|
1224
|
+
name: 'TEXT',
|
|
1225
|
+
email: 'TEXT'
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
await cacheDb.createTable('cache_entries', {
|
|
1229
|
+
key: 'TEXT PRIMARY KEY',
|
|
1230
|
+
value: 'TEXT',
|
|
1231
|
+
expires_at: 'DATETIME'
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
await logsDb.createTable('access_logs', {
|
|
1235
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1236
|
+
ip: 'TEXT',
|
|
1237
|
+
path: 'TEXT',
|
|
1238
|
+
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
// Concurrent operations
|
|
1242
|
+
await Promise.all([
|
|
1243
|
+
mainDb.insert('users', { name: 'Alice', email: 'alice@test.com' }),
|
|
1244
|
+
cacheDb.insert('cache_entries', { key: 'user:1', value: '{"name":"Alice"}', expires_at: '2024-12-31 23:59:59' }),
|
|
1245
|
+
logsDb.insert('access_logs', { ip: '192.168.1.1', path: '/api/users' })
|
|
1246
|
+
]);
|
|
1247
|
+
|
|
1248
|
+
console.log('All operations completed successfully');
|
|
1249
|
+
|
|
1250
|
+
} finally {
|
|
1251
|
+
await pool.closeAll();
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
connectionPoolExample();
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
## 🛠️ Configuration và Customization
|
|
1259
|
+
|
|
1260
|
+
### Environment Detection
|
|
1261
|
+
|
|
1262
|
+
```typescript
|
|
1263
|
+
import { SQLiteManager } from '@dqcai/sqlite';
|
|
1264
|
+
|
|
1265
|
+
const manager = new SQLiteManager();
|
|
1266
|
+
|
|
1267
|
+
// Kiểm tra môi trường hiện tại
|
|
1268
|
+
const env = manager.getEnvironmentInfo();
|
|
1269
|
+
console.log('Running on:', env);
|
|
1270
|
+
|
|
1271
|
+
// Conditional logic based on environment
|
|
1272
|
+
switch (env) {
|
|
1273
|
+
case 'Node.js':
|
|
1274
|
+
console.log('Server-side database with file system access');
|
|
1275
|
+
break;
|
|
1276
|
+
case 'Browser':
|
|
1277
|
+
console.log('Client-side database with local storage');
|
|
1278
|
+
break;
|
|
1279
|
+
case 'React Native':
|
|
1280
|
+
console.log('Mobile database with native SQLite');
|
|
1281
|
+
break;
|
|
1282
|
+
case 'React Native Windows':
|
|
1283
|
+
console.log('Windows mobile database');
|
|
1284
|
+
break;
|
|
1285
|
+
case 'Deno':
|
|
1286
|
+
console.log('Deno runtime with modern SQLite');
|
|
1287
|
+
break;
|
|
1288
|
+
case 'Bun':
|
|
1289
|
+
console.log('Bun runtime with built-in SQLite');
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
### Custom Error Handling
|
|
1295
|
+
|
|
1296
|
+
```typescript
|
|
1297
|
+
class DatabaseErrorHandler {
|
|
1298
|
+
static async safeExecute<T>(
|
|
1299
|
+
operation: () => Promise<T>,
|
|
1300
|
+
fallback?: T
|
|
1301
|
+
): Promise<T | null> {
|
|
1302
|
+
try {
|
|
1303
|
+
return await operation();
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
console.error('Database operation failed:', error);
|
|
1306
|
+
|
|
1307
|
+
if (error.message.includes('UNIQUE constraint failed')) {
|
|
1308
|
+
console.log('Duplicate entry detected');
|
|
1309
|
+
} else if (error.message.includes('no such table')) {
|
|
1310
|
+
console.log('Table does not exist');
|
|
1311
|
+
} else if (error.message.includes('database is locked')) {
|
|
1312
|
+
console.log('Database is busy, retrying...');
|
|
1313
|
+
// Implement retry logic
|
|
1314
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1315
|
+
return await operation();
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
return fallback || null;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
async function errorHandlingExample() {
|
|
1324
|
+
const db = new UniversalSQLite();
|
|
1325
|
+
await db.connect('error_test.db');
|
|
1326
|
+
|
|
1327
|
+
await db.createTable('users', {
|
|
1328
|
+
id: 'INTEGER PRIMARY KEY',
|
|
1329
|
+
email: 'TEXT UNIQUE NOT NULL'
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
// Safe insert với error handling
|
|
1333
|
+
const user1 = await DatabaseErrorHandler.safeExecute(async () => {
|
|
1334
|
+
return await db.insert('users', { email: 'test@example.com' });
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
// Duplicate insert sẽ fail gracefully
|
|
1338
|
+
const user2 = await DatabaseErrorHandler.safeExecute(async () => {
|
|
1339
|
+
return await db.insert('users', { email: 'test@example.com' });
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
console.log('User1 result:', user1);
|
|
1343
|
+
console.log('User2 result:', user2); // null due to unique constraint
|
|
1344
|
+
|
|
1345
|
+
await db.close();
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
errorHandlingExample();
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
## 📱 Platform-Specific Examples
|
|
1352
|
+
|
|
1353
|
+
### React Native với Expo
|
|
1354
|
+
|
|
1355
|
+
```typescript
|
|
1356
|
+
// expo-sqlite-example.ts
|
|
1357
|
+
import { StatusBar } from 'expo-status-bar';
|
|
1358
|
+
import { StyleSheet, Text, View, Button, FlatList } from 'react-native';
|
|
1359
|
+
import { useState, useEffect } from 'react';
|
|
1360
|
+
import UniversalSQLite from '@dqcai/sqlite';
|
|
1361
|
+
|
|
1362
|
+
const db = new UniversalSQLite();
|
|
1363
|
+
|
|
1364
|
+
export default function App() {
|
|
1365
|
+
const [todos, setTodos] = useState([]);
|
|
1366
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1367
|
+
|
|
1368
|
+
useEffect(() => {
|
|
1369
|
+
initDatabase();
|
|
1370
|
+
}, []);
|
|
1371
|
+
|
|
1372
|
+
const initDatabase = async () => {
|
|
1373
|
+
try {
|
|
1374
|
+
await db.connect('todos.db');
|
|
1375
|
+
|
|
1376
|
+
await db.createTable('todos', {
|
|
1377
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1378
|
+
title: 'TEXT NOT NULL',
|
|
1379
|
+
completed: 'BOOLEAN DEFAULT 0',
|
|
1380
|
+
priority: 'INTEGER DEFAULT 1',
|
|
1381
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
loadTodos();
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
console.error('Database setup error:', error);
|
|
1387
|
+
} finally {
|
|
1388
|
+
setIsLoading(false);
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
const loadTodos = async () => {
|
|
1393
|
+
try {
|
|
1394
|
+
const result = await db.select('todos', null, []);
|
|
1395
|
+
setTodos(result.rows);
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
console.error('Load todos error:', error);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
const addTodo = async () => {
|
|
1402
|
+
try {
|
|
1403
|
+
await db.insert('todos', {
|
|
1404
|
+
title: `Todo ${Date.now()}`,
|
|
1405
|
+
priority: Math.floor(Math.random() * 3) + 1
|
|
1406
|
+
});
|
|
1407
|
+
loadTodos();
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
console.error('Add todo error:', error);
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
const toggleTodo = async (id: number, completed: boolean) => {
|
|
1414
|
+
try {
|
|
1415
|
+
await db.update('todos',
|
|
1416
|
+
{ completed: completed ? 0 : 1 },
|
|
1417
|
+
'id = ?',
|
|
1418
|
+
[id]
|
|
1419
|
+
);
|
|
1420
|
+
loadTodos();
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
console.error('Toggle todo error:', error);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
if (isLoading) {
|
|
1427
|
+
return (
|
|
1428
|
+
<View style={styles.container}>
|
|
1429
|
+
<Text>Loading database...</Text>
|
|
1430
|
+
</View>
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
return (
|
|
1435
|
+
<View style={styles.container}>
|
|
1436
|
+
<Text style={styles.title}>Universal SQLite Todos</Text>
|
|
1437
|
+
<Text>Environment: {db.getEnvironment()}</Text>
|
|
1438
|
+
|
|
1439
|
+
<Button title="Add Todo" onPress={addTodo} />
|
|
1440
|
+
|
|
1441
|
+
<FlatList
|
|
1442
|
+
data={todos}
|
|
1443
|
+
keyExtractor={item => item.id.toString()}
|
|
1444
|
+
renderItem={({ item }) => (
|
|
1445
|
+
<View style={styles.todoItem}>
|
|
1446
|
+
<Text style={[styles.todoText, item.completed && styles.completed]}>
|
|
1447
|
+
{item.title}
|
|
1448
|
+
</Text>
|
|
1449
|
+
<Button
|
|
1450
|
+
title={item.completed ? "Undo" : "Done"}
|
|
1451
|
+
onPress={() => toggleTodo(item.id, item.completed)}
|
|
1452
|
+
/>
|
|
1453
|
+
</View>
|
|
1454
|
+
)}
|
|
1455
|
+
/>
|
|
1456
|
+
|
|
1457
|
+
<StatusBar style="auto" />
|
|
1458
|
+
</View>
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
const styles = StyleSheet.create({
|
|
1463
|
+
container: {
|
|
1464
|
+
flex: 1,
|
|
1465
|
+
backgroundColor: '#fff',
|
|
1466
|
+
alignItems: 'center',
|
|
1467
|
+
justifyContent: 'center',
|
|
1468
|
+
padding: 20,
|
|
1469
|
+
},
|
|
1470
|
+
title: {
|
|
1471
|
+
fontSize: 24,
|
|
1472
|
+
fontWeight: 'bold',
|
|
1473
|
+
marginBottom: 20,
|
|
1474
|
+
},
|
|
1475
|
+
todoItem: {
|
|
1476
|
+
flexDirection: 'row',
|
|
1477
|
+
alignItems: 'center',
|
|
1478
|
+
padding: 10,
|
|
1479
|
+
borderBottomWidth: 1,
|
|
1480
|
+
borderBottomColor: '#ccc',
|
|
1481
|
+
},
|
|
1482
|
+
todoText: {
|
|
1483
|
+
flex: 1,
|
|
1484
|
+
fontSize: 16,
|
|
1485
|
+
},
|
|
1486
|
+
completed: {
|
|
1487
|
+
textDecorationLine: 'line-through',
|
|
1488
|
+
color: '#888',
|
|
1489
|
+
},
|
|
1490
|
+
});
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
### Next.js API Routes
|
|
1494
|
+
|
|
1495
|
+
```typescript
|
|
1496
|
+
// pages/api/users/[id].ts
|
|
1497
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
|
1498
|
+
import UniversalSQLite from '@dqcai/sqlite';
|
|
1499
|
+
|
|
1500
|
+
const db = new UniversalSQLite();
|
|
1501
|
+
let isConnected = false;
|
|
1502
|
+
|
|
1503
|
+
async function ensureConnection() {
|
|
1504
|
+
if (!isConnected) {
|
|
1505
|
+
await db.connect(process.env.DATABASE_PATH || './api_database.db');
|
|
1506
|
+
|
|
1507
|
+
await db.createTable('users', {
|
|
1508
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1509
|
+
name: 'TEXT NOT NULL',
|
|
1510
|
+
email: 'TEXT UNIQUE NOT NULL',
|
|
1511
|
+
avatar: 'TEXT',
|
|
1512
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP',
|
|
1513
|
+
updated_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
isConnected = true;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
1521
|
+
const { id } = req.query;
|
|
1522
|
+
|
|
1523
|
+
try {
|
|
1524
|
+
await ensureConnection();
|
|
1525
|
+
|
|
1526
|
+
switch (req.method) {
|
|
1527
|
+
case 'GET':
|
|
1528
|
+
if (id === 'all') {
|
|
1529
|
+
const users = await db.select('users');
|
|
1530
|
+
res.status(200).json(users.rows);
|
|
1531
|
+
} else {
|
|
1532
|
+
const user = await db.select('users', 'id = ?', [Number(id)]);
|
|
1533
|
+
if (user.rows.length === 0) {
|
|
1534
|
+
res.status(404).json({ error: 'User not found' });
|
|
1535
|
+
} else {
|
|
1536
|
+
res.status(200).json(user.rows[0]);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
break;
|
|
1540
|
+
|
|
1541
|
+
case 'POST':
|
|
1542
|
+
const { name, email, avatar } = req.body;
|
|
1543
|
+
const newUser = await db.insert('users', { name, email, avatar });
|
|
1544
|
+
res.status(201).json({
|
|
1545
|
+
id: newUser.lastInsertRowId,
|
|
1546
|
+
message: 'User created successfully'
|
|
1547
|
+
});
|
|
1548
|
+
break;
|
|
1549
|
+
|
|
1550
|
+
case 'PUT':
|
|
1551
|
+
const updateData = { ...req.body, updated_at: new Date().toISOString() };
|
|
1552
|
+
const updateResult = await db.update('users', updateData, 'id = ?', [Number(id)]);
|
|
1553
|
+
|
|
1554
|
+
if (updateResult.rowsAffected === 0) {
|
|
1555
|
+
res.status(404).json({ error: 'User not found' });
|
|
1556
|
+
} else {
|
|
1557
|
+
res.status(200).json({ message: 'User updated successfully' });
|
|
1558
|
+
}
|
|
1559
|
+
break;
|
|
1560
|
+
|
|
1561
|
+
case 'DELETE':
|
|
1562
|
+
const deleteResult = await db.delete('users', 'id = ?', [Number(id)]);
|
|
1563
|
+
|
|
1564
|
+
if (deleteResult.rowsAffected === 0) {
|
|
1565
|
+
res.status(404).json({ error: 'User not found' });
|
|
1566
|
+
} else {
|
|
1567
|
+
res.status(200).json({ message: 'User deleted successfully' });
|
|
1568
|
+
}
|
|
1569
|
+
break;
|
|
1570
|
+
|
|
1571
|
+
default:
|
|
1572
|
+
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
|
|
1573
|
+
res.status(405).end(`Method ${req.method} Not Allowed`);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
console.error('API Error:', error);
|
|
1578
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
```
|
|
1582
|
+
|
|
1583
|
+
## 🧪 Testing
|
|
1584
|
+
|
|
1585
|
+
### Unit Tests với Jest
|
|
1586
|
+
|
|
1587
|
+
```typescript
|
|
1588
|
+
// __tests__/universal-sqlite.test.ts
|
|
1589
|
+
import UniversalSQLite from '../src/index';
|
|
1590
|
+
|
|
1591
|
+
describe('UniversalSQLite', () => {
|
|
1592
|
+
let db: UniversalSQLite;
|
|
1593
|
+
|
|
1594
|
+
beforeEach(async () => {
|
|
1595
|
+
db = new UniversalSQLite();
|
|
1596
|
+
await db.connect(':memory:');
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
afterEach(async () => {
|
|
1600
|
+
await db.close();
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
test('should create and query table', async () => {
|
|
1604
|
+
await db.createTable('test_table', {
|
|
1605
|
+
id: 'INTEGER PRIMARY KEY',
|
|
1606
|
+
name: 'TEXT'
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
await db.insert('test_table', { name: 'Test Item' });
|
|
1610
|
+
|
|
1611
|
+
const result = await db.select('test_table');
|
|
1612
|
+
expect(result.rows).toHaveLength(1);
|
|
1613
|
+
expect(result.rows[0].name).toBe('Test Item');
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
test('should handle parameters correctly', async () => {
|
|
1617
|
+
await db.createTable('users', {
|
|
1618
|
+
id: 'INTEGER PRIMARY KEY',
|
|
1619
|
+
email: 'TEXT UNIQUE'
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
await db.insert('users', { email: 'test@example.com' });
|
|
1623
|
+
|
|
1624
|
+
const user = await db.select('users', 'email = ?', ['test@example.com']);
|
|
1625
|
+
expect(user.rows).toHaveLength(1);
|
|
1626
|
+
|
|
1627
|
+
const noUser = await db.select('users', 'email = ?', ['nonexistent@example.com']);
|
|
1628
|
+
expect(noUser.rows).toHaveLength(0);
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
test('should handle SQL injection attempts', async () => {
|
|
1632
|
+
await db.createTable('users', {
|
|
1633
|
+
id: 'INTEGER PRIMARY KEY',
|
|
1634
|
+
name: 'TEXT'
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
await db.insert('users', { name: 'Regular User' });
|
|
1638
|
+
|
|
1639
|
+
// Attempt SQL injection
|
|
1640
|
+
const maliciousInput = "'; DROP TABLE users; --";
|
|
1641
|
+
await db.insert('users', { name: maliciousInput });
|
|
1642
|
+
|
|
1643
|
+
const users = await db.select('users');
|
|
1644
|
+
expect(users.rows).toHaveLength(2); // Table should still exist
|
|
1645
|
+
});
|
|
1646
|
+
});
|
|
1647
|
+
```
|
|
1648
|
+
|
|
1649
|
+
## 🔐 Best Practices
|
|
1650
|
+
|
|
1651
|
+
### 1. Security
|
|
1652
|
+
|
|
1653
|
+
```typescript
|
|
1654
|
+
class SecureDatabase {
|
|
1655
|
+
private db: UniversalSQLite;
|
|
1656
|
+
|
|
1657
|
+
constructor() {
|
|
1658
|
+
this.db = new UniversalSQLite();
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
async connect(path: string) {
|
|
1662
|
+
await this.db.connect(path);
|
|
1663
|
+
|
|
1664
|
+
// Enable foreign key constraints
|
|
1665
|
+
await this.db.query('PRAGMA foreign_keys = ON');
|
|
1666
|
+
|
|
1667
|
+
// Set secure defaults
|
|
1668
|
+
await this.db.query('PRAGMA journal_mode = WAL'); // Write-Ahead Logging
|
|
1669
|
+
await this.db.query('PRAGMA synchronous = NORMAL');
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// Validate input before queries
|
|
1673
|
+
validateEmail(email: string): boolean {
|
|
1674
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1675
|
+
return emailRegex.test(email);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
validateUsername(username: string): boolean {
|
|
1679
|
+
return /^[a-zA-Z0-9_]{3,20}$/.test(username);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
async createUser(userData: {name: string, email: string, username: string}) {
|
|
1683
|
+
// Validate input
|
|
1684
|
+
if (!this.validateEmail(userData.email)) {
|
|
1685
|
+
throw new Error('Invalid email format');
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
if (!this.validateUsername(userData.username)) {
|
|
1689
|
+
throw new Error('Invalid username format');
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// Sanitize input
|
|
1693
|
+
const sanitizedData = {
|
|
1694
|
+
name: userData.name.trim(),
|
|
1695
|
+
email: userData.email.toLowerCase().trim(),
|
|
1696
|
+
username: userData.username.trim()
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
return await this.db.insert('users', sanitizedData);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
```
|
|
1703
|
+
|
|
1704
|
+
### 2. Performance Optimization
|
|
1705
|
+
|
|
1706
|
+
```typescript
|
|
1707
|
+
class OptimizedDatabase {
|
|
1708
|
+
private db: UniversalSQLite;
|
|
1709
|
+
|
|
1710
|
+
constructor() {
|
|
1711
|
+
this.db = new UniversalSQLite();
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
async connect(path: string) {
|
|
1715
|
+
await this.db.connect(path);
|
|
1716
|
+
|
|
1717
|
+
// Performance tuning
|
|
1718
|
+
await this.db.query('PRAGMA cache_size = 10000');
|
|
1719
|
+
await this.db.query('PRAGMA temp_store = memory');
|
|
1720
|
+
await this.db.query('PRAGMA mmap_size = 268435456'); // 256MB
|
|
1721
|
+
await this.db.query('PRAGMA optimize');
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
async bulkInsert(table: string, records: Record<string, any>[]) {
|
|
1725
|
+
await this.db.query('BEGIN TRANSACTION');
|
|
1726
|
+
|
|
1727
|
+
try {
|
|
1728
|
+
for (const record of records) {
|
|
1729
|
+
await this.db.insert(table, record);
|
|
1730
|
+
}
|
|
1731
|
+
await this.db.query('COMMIT');
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
await this.db.query('ROLLBACK');
|
|
1734
|
+
throw error;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
async createOptimizedIndexes(table: string, columns: string[]) {
|
|
1739
|
+
for (const column of columns) {
|
|
1740
|
+
await this.db.query(`
|
|
1741
|
+
CREATE INDEX IF NOT EXISTS idx_${table}_${column}
|
|
1742
|
+
ON ${table}(${column})
|
|
1743
|
+
`);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
## 🚨 Troubleshooting
|
|
1750
|
+
|
|
1751
|
+
### Common Issues
|
|
1752
|
+
|
|
1753
|
+
#### 1. "No supported SQLite adapter found"
|
|
1754
|
+
```typescript
|
|
1755
|
+
// Kiểm tra adapter availability
|
|
1756
|
+
const manager = new SQLiteManager();
|
|
1757
|
+
const adapters = [
|
|
1758
|
+
'NodeAdapter',
|
|
1759
|
+
'BrowserAdapter',
|
|
1760
|
+
'DenoAdapter',
|
|
1761
|
+
'BunAdapter',
|
|
1762
|
+
'ReactNativeAdapter'
|
|
1763
|
+
];
|
|
1764
|
+
|
|
1765
|
+
for (const adapterName of adapters) {
|
|
1766
|
+
try {
|
|
1767
|
+
const adapter = new (require(`./adapters/${adapterName.toLowerCase()}`))();
|
|
1768
|
+
console.log(`${adapterName}: ${adapter.isSupported() ? '✅' : '❌'}`);
|
|
1769
|
+
} catch {
|
|
1770
|
+
console.log(`${adapterName}: ❌ (not available)`);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
```
|
|
1774
|
+
|
|
1775
|
+
#### 2. React Native Windows Setup
|
|
1776
|
+
|
|
1777
|
+
```typescript
|
|
1778
|
+
// Kiểm tra Windows environment
|
|
1779
|
+
async function checkReactNativeWindows() {
|
|
1780
|
+
const db = new UniversalSQLite();
|
|
1781
|
+
|
|
1782
|
+
console.log('Environment:', db.getEnvironment());
|
|
1783
|
+
|
|
1784
|
+
if (db.getEnvironment() === 'React Native Windows') {
|
|
1785
|
+
console.log('✅ React Native Windows detected');
|
|
1786
|
+
|
|
1787
|
+
try {
|
|
1788
|
+
await db.connect('windows_test.db');
|
|
1789
|
+
console.log('✅ Database connection successful');
|
|
1790
|
+
|
|
1791
|
+
await db.createTable('test', { id: 'INTEGER', name: 'TEXT' });
|
|
1792
|
+
console.log('✅ Table creation successful');
|
|
1793
|
+
|
|
1794
|
+
} catch (error) {
|
|
1795
|
+
console.error('❌ Windows SQLite error:', error);
|
|
1796
|
+
console.log('💡 Try installing: npm install react-native-sqlite-2');
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
```
|
|
1801
|
+
|
|
1802
|
+
#### 3. Browser CORS Issues
|
|
1803
|
+
|
|
1804
|
+
```typescript
|
|
1805
|
+
// browser-cors-fix.js
|
|
1806
|
+
async function setupBrowserDatabase() {
|
|
1807
|
+
const db = new UniversalSQLite();
|
|
1808
|
+
|
|
1809
|
+
try {
|
|
1810
|
+
// In-memory database (không cần file access)
|
|
1811
|
+
await db.connect(':memory:');
|
|
1812
|
+
console.log('✅ Browser in-memory database ready');
|
|
1813
|
+
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
console.error('❌ Browser setup failed:', error);
|
|
1816
|
+
console.log('💡 Fallback: Using in-memory database');
|
|
1817
|
+
|
|
1818
|
+
// Fallback strategy
|
|
1819
|
+
await db.connect(':memory:');
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
```
|
|
1823
|
+
|
|
1824
|
+
## 📊 Performance Benchmarks
|
|
1825
|
+
|
|
1826
|
+
```typescript
|
|
1827
|
+
async function benchmarkExample() {
|
|
1828
|
+
const db = new UniversalSQLite();
|
|
1829
|
+
await db.connect('benchmark.db');
|
|
1830
|
+
|
|
1831
|
+
await db.createTable('benchmark_table', {
|
|
1832
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1833
|
+
data: 'TEXT',
|
|
1834
|
+
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
// Benchmark bulk insert
|
|
1838
|
+
console.log('Starting bulk insert benchmark...');
|
|
1839
|
+
const insertStart = performance.now();
|
|
1840
|
+
|
|
1841
|
+
await db.query('BEGIN TRANSACTION');
|
|
1842
|
+
for (let i = 0; i < 10000; i++) {
|
|
1843
|
+
await db.insert('benchmark_table', {
|
|
1844
|
+
data: `Sample data ${i}`
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
await db.query('COMMIT');
|
|
1848
|
+
|
|
1849
|
+
const insertEnd = performance.now();
|
|
1850
|
+
console.log(`Bulk insert (10k records): ${(insertEnd - insertStart).toFixed(2)}ms`);
|
|
1851
|
+
|
|
1852
|
+
// Benchmark select
|
|
1853
|
+
const selectStart = performance.now();
|
|
1854
|
+
const allRecords = await db.select('benchmark_table');
|
|
1855
|
+
const selectEnd = performance.now();
|
|
1856
|
+
|
|
1857
|
+
console.log(`Select all (${allRecords.rows.length} records): ${(selectEnd - selectStart).toFixed(2)}ms`);
|
|
1858
|
+
|
|
1859
|
+
// Benchmark with index
|
|
1860
|
+
await db.query('CREATE INDEX idx_data ON benchmark_table(data)');
|
|
1861
|
+
|
|
1862
|
+
const indexedSelectStart = performance.now();
|
|
1863
|
+
const searchResult = await db.select('benchmark_table', 'data LIKE ?', ['Sample data 50%']);
|
|
1864
|
+
const indexedSelectEnd = performance.now();
|
|
1865
|
+
|
|
1866
|
+
console.log(`Indexed search (${searchResult.rows.length} results): ${(indexedSelectEnd - indexedSelectStart).toFixed(2)}ms`);
|
|
1867
|
+
|
|
1868
|
+
await db.close();
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
benchmarkExample();
|
|
1872
|
+
```
|
|
1873
|
+
|
|
1874
|
+
## 📚 Advanced Use Cases
|
|
1875
|
+
|
|
1876
|
+
### 1. Multi-Database Application
|
|
1877
|
+
|
|
1878
|
+
```typescript
|
|
1879
|
+
class MultiDatabaseApp {
|
|
1880
|
+
private userDb: UniversalSQLite;
|
|
1881
|
+
private logDb: UniversalSQLite;
|
|
1882
|
+
private cacheDb: UniversalSQLite;
|
|
1883
|
+
|
|
1884
|
+
constructor() {
|
|
1885
|
+
this.userDb = new UniversalSQLite();
|
|
1886
|
+
this.logDb = new UniversalSQLite();
|
|
1887
|
+
this.cacheDb = new UniversalSQLite();
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
async initialize() {
|
|
1891
|
+
// Separate databases for different concerns
|
|
1892
|
+
await this.userDb.connect('users.db');
|
|
1893
|
+
await this.logDb.connect('logs.db');
|
|
1894
|
+
await this.cacheDb.connect(':memory:'); // Cache in memory
|
|
1895
|
+
|
|
1896
|
+
// Setup schemas
|
|
1897
|
+
await this.setupUserDatabase();
|
|
1898
|
+
await this.setupLogDatabase();
|
|
1899
|
+
await this.setupCacheDatabase();
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
private async setupUserDatabase() {
|
|
1903
|
+
await this.userDb.createTable('users', {
|
|
1904
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1905
|
+
username: 'TEXT UNIQUE NOT NULL',
|
|
1906
|
+
email: 'TEXT UNIQUE NOT NULL',
|
|
1907
|
+
profile_data: 'TEXT', // JSON
|
|
1908
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1909
|
+
});
|
|
1910
|
+
|
|
1911
|
+
await this.userDb.createTable('user_sessions', {
|
|
1912
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1913
|
+
user_id: 'INTEGER NOT NULL',
|
|
1914
|
+
session_token: 'TEXT UNIQUE NOT NULL',
|
|
1915
|
+
expires_at: 'DATETIME NOT NULL',
|
|
1916
|
+
'FOREIGN KEY (user_id)': 'REFERENCES users(id) ON DELETE CASCADE'
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
private async setupLogDatabase() {
|
|
1921
|
+
await this.logDb.createTable('access_logs', {
|
|
1922
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1923
|
+
user_id: 'INTEGER',
|
|
1924
|
+
action: 'TEXT NOT NULL',
|
|
1925
|
+
ip_address: 'TEXT',
|
|
1926
|
+
user_agent: 'TEXT',
|
|
1927
|
+
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
await this.logDb.createTable('error_logs', {
|
|
1931
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1932
|
+
error_type: 'TEXT NOT NULL',
|
|
1933
|
+
error_message: 'TEXT NOT NULL',
|
|
1934
|
+
stack_trace: 'TEXT',
|
|
1935
|
+
context: 'TEXT', // JSON
|
|
1936
|
+
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
private async setupCacheDatabase() {
|
|
1941
|
+
await this.cacheDb.createTable('cache_entries', {
|
|
1942
|
+
key: 'TEXT PRIMARY KEY',
|
|
1943
|
+
value: 'TEXT NOT NULL',
|
|
1944
|
+
expires_at: 'DATETIME NOT NULL'
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// User operations
|
|
1949
|
+
async createUser(userData: any) {
|
|
1950
|
+
const result = await this.userDb.insert('users', userData);
|
|
1951
|
+
|
|
1952
|
+
// Log the action
|
|
1953
|
+
await this.logAction(result.lastInsertRowId!, 'USER_CREATED', 'User registration');
|
|
1954
|
+
|
|
1955
|
+
return result;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
async getUserById(id: number) {
|
|
1959
|
+
// Check cache first
|
|
1960
|
+
const cached = await this.getFromCache(`user:${id}`);
|
|
1961
|
+
if (cached) {
|
|
1962
|
+
return JSON.parse(cached);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// Query from database
|
|
1966
|
+
const result = await this.userDb.select('users', 'id = ?', [id]);
|
|
1967
|
+
if (result.rows.length > 0) {
|
|
1968
|
+
const user = result.rows[0];
|
|
1969
|
+
// Cache for 1 hour
|
|
1970
|
+
await this.setCache(`user:${id}`, JSON.stringify(user), 3600);
|
|
1971
|
+
return user;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
return null;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
// Logging operations
|
|
1978
|
+
async logAction(userId: number, action: string, details: string) {
|
|
1979
|
+
await this.logDb.insert('access_logs', {
|
|
1980
|
+
user_id: userId,
|
|
1981
|
+
action: action,
|
|
1982
|
+
ip_address: '127.0.0.1', // Would get from request
|
|
1983
|
+
user_agent: 'Universal SQLite App'
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
async logError(error: Error, context?: any) {
|
|
1988
|
+
await this.logDb.insert('error_logs', {
|
|
1989
|
+
error_type: error.name,
|
|
1990
|
+
error_message: error.message,
|
|
1991
|
+
stack_trace: error.stack,
|
|
1992
|
+
context: context ? JSON.stringify(context) : null
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// Cache operations
|
|
1997
|
+
async setCache(key: string, value: string, ttlSeconds: number) {
|
|
1998
|
+
const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
1999
|
+
|
|
2000
|
+
await this.cacheDb.query(`
|
|
2001
|
+
INSERT OR REPLACE INTO cache_entries (key, value, expires_at)
|
|
2002
|
+
VALUES (?, ?, ?)
|
|
2003
|
+
`, [key, value, expiresAt]);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
async getFromCache(key: string): Promise<string | null> {
|
|
2007
|
+
// Clean expired entries
|
|
2008
|
+
await this.cacheDb.delete('cache_entries', 'expires_at < ?', [new Date().toISOString()]);
|
|
2009
|
+
|
|
2010
|
+
const result = await this.cacheDb.select('cache_entries', 'key = ?', [key]);
|
|
2011
|
+
return result.rows.length > 0 ? result.rows[0].value : null;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// Analytics across databases
|
|
2015
|
+
async getUserAnalytics(userId: number) {
|
|
2016
|
+
const user = await this.getUserById(userId);
|
|
2017
|
+
if (!user) return null;
|
|
2018
|
+
|
|
2019
|
+
const logs = await this.logDb.query(`
|
|
2020
|
+
SELECT
|
|
2021
|
+
action,
|
|
2022
|
+
COUNT(*) as count,
|
|
2023
|
+
MAX(timestamp) as last_action
|
|
2024
|
+
FROM access_logs
|
|
2025
|
+
WHERE user_id = ?
|
|
2026
|
+
GROUP BY action
|
|
2027
|
+
ORDER BY count DESC
|
|
2028
|
+
`, [userId]);
|
|
2029
|
+
|
|
2030
|
+
return {
|
|
2031
|
+
user: user,
|
|
2032
|
+
activity: logs.rows,
|
|
2033
|
+
total_actions: logs.rows.reduce((sum, row) => sum + row.count, 0)
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
async cleanup() {
|
|
2038
|
+
await this.userDb.close();
|
|
2039
|
+
await this.logDb.close();
|
|
2040
|
+
await this.cacheDb.close();
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
// Usage
|
|
2045
|
+
async function multiDbExample() {
|
|
2046
|
+
const app = new MultiDatabaseApp();
|
|
2047
|
+
|
|
2048
|
+
try {
|
|
2049
|
+
await app.initialize();
|
|
2050
|
+
|
|
2051
|
+
// Create user
|
|
2052
|
+
const newUser = await app.createUser({
|
|
2053
|
+
username: 'johndoe',
|
|
2054
|
+
email: 'john@example.com',
|
|
2055
|
+
profile_data: JSON.stringify({ bio: 'Software developer' })
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
const userId = newUser.lastInsertRowId!;
|
|
2059
|
+
|
|
2060
|
+
// Simulate user activity
|
|
2061
|
+
await app.logAction(userId, 'LOGIN', 'User logged in');
|
|
2062
|
+
await app.logAction(userId, 'VIEW_PROFILE', 'Viewed profile page');
|
|
2063
|
+
await app.logAction(userId, 'UPDATE_PROFILE', 'Updated profile');
|
|
2064
|
+
|
|
2065
|
+
// Get analytics
|
|
2066
|
+
const analytics = await app.getUserAnalytics(userId);
|
|
2067
|
+
console.log('User Analytics:', analytics);
|
|
2068
|
+
|
|
2069
|
+
} catch (error) {
|
|
2070
|
+
console.error('Multi-db error:', error);
|
|
2071
|
+
} finally {
|
|
2072
|
+
await app.cleanup();
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
multiDbExample();
|
|
2077
|
+
```
|
|
2078
|
+
|
|
2079
|
+
### 2. Real-time Sync với WebSocket
|
|
2080
|
+
|
|
2081
|
+
```typescript
|
|
2082
|
+
// realtime-sync.ts
|
|
2083
|
+
import UniversalSQLite, { QueryBuilder } from '@dqcai/sqlite';
|
|
2084
|
+
import { WebSocket } from 'ws'; // Node.js example
|
|
2085
|
+
|
|
2086
|
+
class RealtimeSync {
|
|
2087
|
+
private db: UniversalSQLite;
|
|
2088
|
+
private ws: WebSocket | null = null;
|
|
2089
|
+
private syncQueue: any[] = [];
|
|
2090
|
+
|
|
2091
|
+
constructor() {
|
|
2092
|
+
this.db = new UniversalSQLite();
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
async initialize() {
|
|
2096
|
+
await this.db.connect('realtime.db');
|
|
2097
|
+
|
|
2098
|
+
// Setup sync tables
|
|
2099
|
+
await this.db.createTable('messages', {
|
|
2100
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
2101
|
+
content: 'TEXT NOT NULL',
|
|
2102
|
+
sender_id: 'INTEGER NOT NULL',
|
|
2103
|
+
room_id: 'INTEGER NOT NULL',
|
|
2104
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP',
|
|
2105
|
+
synced: 'BOOLEAN DEFAULT 0'
|
|
2106
|
+
});
|
|
2107
|
+
|
|
2108
|
+
await this.db.createTable('sync_log', {
|
|
2109
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
2110
|
+
operation: 'TEXT NOT NULL',
|
|
2111
|
+
table_name: 'TEXT NOT NULL',
|
|
2112
|
+
record_id: 'INTEGER',
|
|
2113
|
+
data: 'TEXT',
|
|
2114
|
+
sync_status: 'TEXT DEFAULT "pending"',
|
|
2115
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
2116
|
+
});
|
|
2117
|
+
|
|
2118
|
+
this.startSyncProcess();
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
async addMessage(content: string, senderId: number, roomId: number) {
|
|
2122
|
+
const result = await this.db.insert('messages', {
|
|
2123
|
+
content,
|
|
2124
|
+
sender_id: senderId,
|
|
2125
|
+
room_id: roomId
|
|
2126
|
+
});
|
|
2127
|
+
|
|
2128
|
+
// Queue for sync
|
|
2129
|
+
await this.queueSync('INSERT', 'messages', result.lastInsertRowId!, {
|
|
2130
|
+
id: result.lastInsertRowId,
|
|
2131
|
+
content,
|
|
2132
|
+
sender_id: senderId,
|
|
2133
|
+
room_id: roomId
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
return result;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
async getMessages(roomId: number, limit: number = 50) {
|
|
2140
|
+
return await this.db.query(`
|
|
2141
|
+
SELECT m.*, u.username
|
|
2142
|
+
FROM messages m
|
|
2143
|
+
LEFT JOIN users u ON m.sender_id = u.id
|
|
2144
|
+
WHERE m.room_id = ?
|
|
2145
|
+
ORDER BY m.created_at DESC
|
|
2146
|
+
LIMIT ?
|
|
2147
|
+
`, [roomId, limit]);
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
private async queueSync(operation: string, tableName: string, recordId: number, data: any) {
|
|
2151
|
+
await this.db.insert('sync_log', {
|
|
2152
|
+
operation,
|
|
2153
|
+
table_name: tableName,
|
|
2154
|
+
record_id: recordId,
|
|
2155
|
+
data: JSON.stringify(data)
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
private async startSyncProcess() {
|
|
2160
|
+
setInterval(async () => {
|
|
2161
|
+
await this.processSyncQueue();
|
|
2162
|
+
}, 5000); // Sync every 5 seconds
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
private async processSyncQueue() {
|
|
2166
|
+
const pendingSyncs = await this.db.select('sync_log', 'sync_status = ?', ['pending']);
|
|
2167
|
+
|
|
2168
|
+
for (const sync of pendingSyncs.rows) {
|
|
2169
|
+
try {
|
|
2170
|
+
// Simulate API call
|
|
2171
|
+
const response = await this.syncToServer(sync);
|
|
2172
|
+
|
|
2173
|
+
if (response.success) {
|
|
2174
|
+
await this.db.update('sync_log',
|
|
2175
|
+
{ sync_status: 'completed' },
|
|
2176
|
+
'id = ?',
|
|
2177
|
+
[sync.id]
|
|
2178
|
+
);
|
|
2179
|
+
|
|
2180
|
+
// Mark original record as synced
|
|
2181
|
+
if (sync.table_name === 'messages') {
|
|
2182
|
+
await this.db.update('messages',
|
|
2183
|
+
{ synced: 1 },
|
|
2184
|
+
'id = ?',
|
|
2185
|
+
[sync.record_id]
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
} catch (error) {
|
|
2190
|
+
await this.db.update('sync_log',
|
|
2191
|
+
{ sync_status: 'failed' },
|
|
2192
|
+
'id = ?',
|
|
2193
|
+
[sync.id]
|
|
2194
|
+
);
|
|
2195
|
+
console.error('Sync failed:', error);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
private async syncToServer(syncData: any): Promise<{success: boolean}> {
|
|
2201
|
+
// Simulate server sync
|
|
2202
|
+
return new Promise((resolve) => {
|
|
2203
|
+
setTimeout(() => {
|
|
2204
|
+
resolve({ success: Math.random() > 0.1 }); // 90% success rate
|
|
2205
|
+
}, 100);
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
// Usage
|
|
2211
|
+
async function realtimeSyncExample() {
|
|
2212
|
+
const sync = new RealtimeSync();
|
|
2213
|
+
await sync.initialize();
|
|
2214
|
+
|
|
2215
|
+
// Simulate adding messages
|
|
2216
|
+
await sync.addMessage('Hello World!', 1, 1);
|
|
2217
|
+
await sync.addMessage('How are you?', 2, 1);
|
|
2218
|
+
|
|
2219
|
+
// Get messages
|
|
2220
|
+
const messages = await sync.getMessages(1);
|
|
2221
|
+
console.log('Room messages:', messages.rows);
|
|
2222
|
+
|
|
2223
|
+
// Let sync process run
|
|
2224
|
+
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
realtimeSyncExample();
|
|
2228
|
+
```
|
|
2229
|
+
|
|
2230
|
+
### 3. Database Backup và Restore
|
|
2231
|
+
|
|
2232
|
+
```typescript
|
|
2233
|
+
class DatabaseBackup {
|
|
2234
|
+
private db: UniversalSQLite;
|
|
2235
|
+
|
|
2236
|
+
constructor() {
|
|
2237
|
+
this.db = new UniversalSQLite();
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
async connect(path: string) {
|
|
2241
|
+
await this.db.connect(path);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
async createBackup(): Promise<string> {
|
|
2245
|
+
// Get all table schemas
|
|
2246
|
+
const tables = await this.db.query(`
|
|
2247
|
+
SELECT name, sql FROM sqlite_master
|
|
2248
|
+
WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
|
|
2249
|
+
`);
|
|
2250
|
+
|
|
2251
|
+
let backupSQL = '';
|
|
2252
|
+
backupSQL += '-- Database Backup\n';
|
|
2253
|
+
backupSQL += `-- Generated: ${new Date().toISOString()}\n\n`;
|
|
2254
|
+
|
|
2255
|
+
// Add schema
|
|
2256
|
+
for (const table of tables.rows) {
|
|
2257
|
+
backupSQL += `${table.sql};\n\n`;
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// Add data
|
|
2261
|
+
for (const table of tables.rows) {
|
|
2262
|
+
const data = await this.db.select(table.name);
|
|
2263
|
+
|
|
2264
|
+
if (data.rows.length > 0) {
|
|
2265
|
+
// Get column names
|
|
2266
|
+
const columns = Object.keys(data.rows[0]);
|
|
2267
|
+
|
|
2268
|
+
backupSQL += `-- Data for ${table.name}\n`;
|
|
2269
|
+
for (const row of data.rows) {
|
|
2270
|
+
const values = columns.map(col => {
|
|
2271
|
+
const value = row[col];
|
|
2272
|
+
if (value === null) return 'NULL';
|
|
2273
|
+
if (typeof value === 'string') return `'${value.replace(/'/g, "''")}'`;
|
|
2274
|
+
return String(value);
|
|
2275
|
+
});
|
|
2276
|
+
|
|
2277
|
+
backupSQL += `INSERT INTO ${table.name} (${columns.join(', ')}) VALUES (${values.join(', ')});\n`;
|
|
2278
|
+
}
|
|
2279
|
+
backupSQL += '\n';
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
return backupSQL;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
async restoreFromBackup(backupSQL: string): Promise<void> {
|
|
2287
|
+
const statements = backupSQL
|
|
2288
|
+
.split(';')
|
|
2289
|
+
.map(stmt => stmt.trim())
|
|
2290
|
+
.filter(stmt => stmt && !stmt.startsWith('--'));
|
|
2291
|
+
|
|
2292
|
+
await this.db.query('BEGIN TRANSACTION');
|
|
2293
|
+
|
|
2294
|
+
try {
|
|
2295
|
+
for (const statement of statements) {
|
|
2296
|
+
if (statement) {
|
|
2297
|
+
await this.db.query(statement);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
await this.db.query('COMMIT');
|
|
2301
|
+
console.log('Backup restored successfully');
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
await this.db.query('ROLLBACK');
|
|
2304
|
+
throw new Error(`Restore failed: ${error}`);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
async scheduleBackups(intervalMinutes: number = 60) {
|
|
2309
|
+
setInterval(async () => {
|
|
2310
|
+
try {
|
|
2311
|
+
const backup = await this.createBackup();
|
|
2312
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
2313
|
+
const filename = `backup_${timestamp}.sql`;
|
|
2314
|
+
|
|
2315
|
+
// In Node.js environment, save to file
|
|
2316
|
+
if (typeof require !== 'undefined' && this.db.getEnvironment() === 'Node.js') {
|
|
2317
|
+
const fs = require('fs').promises;
|
|
2318
|
+
await fs.writeFile(filename, backup);
|
|
2319
|
+
console.log(`Backup saved: ${filename}`);
|
|
2320
|
+
} else {
|
|
2321
|
+
// In browser/other environments, log or send to server
|
|
2322
|
+
console.log('Backup created (implement save logic for your environment)');
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
console.error('Backup failed:', error);
|
|
2327
|
+
}
|
|
2328
|
+
}, intervalMinutes * 60 * 1000);
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
async close() {
|
|
2332
|
+
await this.db.close();
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// Usage
|
|
2337
|
+
async function backupExample() {
|
|
2338
|
+
const backup = new DatabaseBackup();
|
|
2339
|
+
await backup.connect('main_app.db');
|
|
2340
|
+
|
|
2341
|
+
// Create some test data
|
|
2342
|
+
const db = new UniversalSQLite();
|
|
2343
|
+
await db.connect('main_app.db');
|
|
2344
|
+
|
|
2345
|
+
await db.createTable('posts', {
|
|
2346
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
2347
|
+
title: 'TEXT NOT NULL',
|
|
2348
|
+
content: 'TEXT',
|
|
2349
|
+
author_id: 'INTEGER'
|
|
2350
|
+
});
|
|
2351
|
+
|
|
2352
|
+
await db.insert('posts', { title: 'First Post', content: 'Hello World!', author_id: 1 });
|
|
2353
|
+
await db.insert('posts', { title: 'Second Post', content: 'More content', author_id: 2 });
|
|
2354
|
+
|
|
2355
|
+
// Create backup
|
|
2356
|
+
const backupSQL = await backup.createBackup();
|
|
2357
|
+
console.log('Backup created, size:', backupSQL.length, 'characters');
|
|
2358
|
+
|
|
2359
|
+
// Test restore (to new database)
|
|
2360
|
+
const restoreDb = new UniversalSQLite();
|
|
2361
|
+
await restoreDb.connect(':memory:');
|
|
2362
|
+
|
|
2363
|
+
const restoreBackup = new DatabaseBackup();
|
|
2364
|
+
await restoreBackup.connect(':memory:');
|
|
2365
|
+
await restoreBackup.restoreFromBackup(backupSQL);
|
|
2366
|
+
|
|
2367
|
+
// Verify restore
|
|
2368
|
+
const restoredData = await restoreDb.select('posts');
|
|
2369
|
+
console.log('Restored data:', restoredData.rows);
|
|
2370
|
+
|
|
2371
|
+
await db.close();
|
|
2372
|
+
await restoreDb.close();
|
|
2373
|
+
await backup.close();
|
|
2374
|
+
await restoreBackup.close();
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
backupExample();
|
|
2378
|
+
```
|
|
2379
|
+
|
|
2380
|
+
## 🌐 Production Deployment
|
|
2381
|
+
|
|
2382
|
+
### Environment Variables
|
|
2383
|
+
|
|
2384
|
+
```typescript
|
|
2385
|
+
// config.ts
|
|
2386
|
+
interface DatabaseConfig {
|
|
2387
|
+
path: string;
|
|
2388
|
+
backupInterval?: number;
|
|
2389
|
+
maxConnections?: number;
|
|
2390
|
+
enableWAL?: boolean;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
function getDatabaseConfig(): DatabaseConfig {
|
|
2394
|
+
const env = process.env.NODE_ENV || 'development';
|
|
2395
|
+
|
|
2396
|
+
const configs: Record<string, DatabaseConfig> = {
|
|
2397
|
+
development: {
|
|
2398
|
+
path: './dev_database.db',
|
|
2399
|
+
backupInterval: 60, // 1 hour
|
|
2400
|
+
enableWAL: true
|
|
2401
|
+
},
|
|
2402
|
+
test: {
|
|
2403
|
+
path: ':memory:',
|
|
2404
|
+
enableWAL: false
|
|
2405
|
+
},
|
|
2406
|
+
production: {
|
|
2407
|
+
path: process.env.DATABASE_PATH || './prod_database.db',
|
|
2408
|
+
backupInterval: 15, // 15 minutes
|
|
2409
|
+
maxConnections: 10,
|
|
2410
|
+
enableWAL: true
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
|
|
2414
|
+
return configs[env] || configs.development;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// app.ts
|
|
2418
|
+
async function productionSetup() {
|
|
2419
|
+
const config = getDatabaseConfig();
|
|
2420
|
+
const db = new UniversalSQLite();
|
|
2421
|
+
|
|
2422
|
+
await db.connect(config.path);
|
|
2423
|
+
|
|
2424
|
+
if (config.enableWAL) {
|
|
2425
|
+
await db.query('PRAGMA journal_mode = WAL');
|
|
2426
|
+
await db.query('PRAGMA synchronous = NORMAL');
|
|
2427
|
+
await db.query('PRAGMA cache_size = 10000');
|
|
2428
|
+
await db.query('PRAGMA temp_store = memory');
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// Setup production tables
|
|
2432
|
+
await setupProductionSchema(db);
|
|
2433
|
+
|
|
2434
|
+
// Setup monitoring
|
|
2435
|
+
if (process.env.NODE_ENV === 'production') {
|
|
2436
|
+
setupProductionMonitoring(db);
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
return db;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
async function setupProductionSchema(db: UniversalSQLite) {
|
|
2443
|
+
// Users table với full audit trail
|
|
2444
|
+
await db.createTable('users', {
|
|
2445
|
+
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
2446
|
+
email: 'TEXT UNIQUE NOT NULL',
|
|
2447
|
+
password_hash: 'TEXT NOT NULL',
|
|
2448
|
+
status: 'TEXT DEFAULT "active" CHECK(status IN ("active", "inactive", "suspended"))',
|
|
2449
|
+
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP',
|
|
2450
|
+
updated_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP',
|
|
2451
|
+
last_login: 'DATETIME'
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2454
|
+
// Indexes for performance
|
|
2455
|
+
await db.query('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)');
|
|
2456
|
+
await db.query('CREATE INDEX IF NOT EXISTS idx_users_status ON users(status)');
|
|
2457
|
+
await db.query('CREATE INDEX IF NOT EXISTS idx_users_last_login ON users(last_login)');
|
|
2458
|
+
|
|
2459
|
+
// Trigger for updated_at
|
|
2460
|
+
await db.query(`
|
|
2461
|
+
CREATE TRIGGER IF NOT EXISTS update_users_timestamp
|
|
2462
|
+
AFTER UPDATE ON users
|
|
2463
|
+
FOR EACH ROW
|
|
2464
|
+
BEGIN
|
|
2465
|
+
UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
2466
|
+
END
|
|
2467
|
+
`);
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
function setupProductionMonitoring(db: UniversalSQLite) {
|
|
2471
|
+
// Monitor database size
|
|
2472
|
+
setInterval(async () => {
|
|
2473
|
+
const sizeInfo = await db.query('PRAGMA page_count; PRAGMA page_size;');
|
|
2474
|
+
const pages = sizeInfo.rows[0].page_count;
|
|
2475
|
+
const pageSize = sizeInfo.rows[1].page_size;
|
|
2476
|
+
const sizeBytes = pages * pageSize;
|
|
2477
|
+
const sizeMB = (sizeBytes / 1024 / 1024).toFixed(2);
|
|
2478
|
+
|
|
2479
|
+
console.log(`Database size: ${sizeMB} MB`);
|
|
2480
|
+
|
|
2481
|
+
if (sizeBytes > 500 * 1024 * 1024) { // 500MB warning
|
|
2482
|
+
console.warn('Database size exceeds 500MB, consider archiving old data');
|
|
2483
|
+
}
|
|
2484
|
+
}, 300000); // Check every 5 minutes
|
|
2485
|
+
|
|
2486
|
+
// Monitor query performance
|
|
2487
|
+
setInterval(async () => {
|
|
2488
|
+
const longQueries = await db.query(`
|
|
2489
|
+
SELECT * FROM pragma_stats
|
|
2490
|
+
WHERE name LIKE '%time%'
|
|
2491
|
+
`);
|
|
2492
|
+
|
|
2493
|
+
// Log slow queries for optimization
|
|
2494
|
+
console.log('Database stats:', longQueries.rows);
|
|
2495
|
+
}, 600000); // Check every 10 minutes
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
productionSetup();
|
|
2499
|
+
```
|
|
2500
|
+
|
|
2501
|
+
## 📖 API Reference
|
|
2502
|
+
|
|
2503
|
+
### Core Types
|
|
2504
|
+
|
|
2505
|
+
```typescript
|
|
2506
|
+
interface SQLiteRow {
|
|
2507
|
+
[key: string]: any;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
interface SQLiteResult {
|
|
2511
|
+
rows: SQLiteRow[]; // Dữ liệu trả về
|
|
2512
|
+
rowsAffected: number; // Số dòng bị ảnh hưởng
|
|
2513
|
+
lastInsertRowId?: number; // ID của record mới insert
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
interface SQLiteConnection {
|
|
2517
|
+
execute(sql: string, params?: any[]): Promise<SQLiteResult>;
|
|
2518
|
+
close(): Promise<void>;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
interface SQLiteConfig {
|
|
2522
|
+
path: string; // Đường dẫn database
|
|
2523
|
+
timeout?: number; // Timeout kết nối
|
|
2524
|
+
busyTimeout?: number; // Timeout khi database busy
|
|
2525
|
+
}
|
|
2526
|
+
```
|
|
2527
|
+
|
|
2528
|
+
### Error Codes
|
|
2529
|
+
|
|
2530
|
+
| Error | Description | Solution |
|
|
2531
|
+
|-------|-------------|----------|
|
|
2532
|
+
| `No supported SQLite adapter found` | Không tìm thấy adapter phù hợp | Cài đặt SQLite dependencies cho môi trường |
|
|
2533
|
+
| `Database connection not available` | Chưa kết nối database | Gọi `connect()` trước khi query |
|
|
2534
|
+
| `SQLite error: no such table` | Bảng không tồn tại | Tạo bảng bằng `createTable()` hoặc SQL |
|
|
2535
|
+
| `UNIQUE constraint failed` | Vi phạm ràng buộc unique | Kiểm tra dữ liệu trước khi insert |
|
|
2536
|
+
| `database is locked` | Database đang bị khóa | Retry sau một khoảng thời gian |
|
|
2537
|
+
|
|
2538
|
+
## 🤝 Contributing
|
|
2539
|
+
|
|
2540
|
+
### Development Setup
|
|
2541
|
+
|
|
2542
|
+
```bash
|
|
2543
|
+
# Clone repository
|
|
2544
|
+
git clone https://github.com/dqcai/sqlite
|
|
2545
|
+
cd sqlite
|
|
2546
|
+
|
|
2547
|
+
# Install dependencies
|
|
2548
|
+
npm install
|
|
2549
|
+
|
|
2550
|
+
# Run tests
|
|
2551
|
+
npm test
|
|
2552
|
+
|
|
2553
|
+
# Build
|
|
2554
|
+
npm run build
|
|
2555
|
+
|
|
2556
|
+
# Test in different environments
|
|
2557
|
+
npm run test:node
|
|
2558
|
+
npm run test:browser
|
|
2559
|
+
npm run test:deno
|
|
2560
|
+
npm run test:bun
|
|
2561
|
+
```
|
|
2562
|
+
|
|
2563
|
+
### Testing New Adapters
|
|
2564
|
+
|
|
2565
|
+
```typescript
|
|
2566
|
+
// tests/adapter-test.ts
|
|
2567
|
+
import { SQLiteAdapter } from '../src/types';
|
|
2568
|
+
|
|
2569
|
+
export async function testAdapter(adapter: SQLiteAdapter, testDbPath: string) {
|
|
2570
|
+
console.log(`Testing adapter: ${adapter.constructor.name}`);
|
|
2571
|
+
|
|
2572
|
+
// Test 1: Support detection
|
|
2573
|
+
console.log(`✓ isSupported(): ${adapter.isSupported()}`);
|
|
2574
|
+
|
|
2575
|
+
if (!adapter.isSupported()) {
|
|
2576
|
+
console.log('❌ Adapter not supported in current environment');
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
try {
|
|
2581
|
+
// Test 2: Connection
|
|
2582
|
+
const connection = await adapter.connect(testDbPath);
|
|
2583
|
+
console.log('✓ Connection established');
|
|
2584
|
+
|
|
2585
|
+
// Test 3: Table creation
|
|
2586
|
+
await connection.execute(`
|
|
2587
|
+
CREATE TABLE IF NOT EXISTS test_table (
|
|
2588
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2589
|
+
name TEXT NOT NULL,
|
|
2590
|
+
value INTEGER
|
|
2591
|
+
)
|
|
2592
|
+
`);
|
|
2593
|
+
console.log('✓ Table created');
|
|
2594
|
+
|
|
2595
|
+
// Test 4: Insert
|
|
2596
|
+
const insertResult = await connection.execute(
|
|
2597
|
+
'INSERT INTO test_table (name, value) VALUES (?, ?)',
|
|
2598
|
+
['Test Item', 42]
|
|
2599
|
+
);
|
|
2600
|
+
console.log('✓ Insert successful, ID:', insertResult.lastInsertRowId);
|
|
2601
|
+
|
|
2602
|
+
// Test 5: Select
|
|
2603
|
+
const selectResult = await connection.execute('SELECT * FROM test_table');
|
|
2604
|
+
console.log('✓ Select successful, rows:', selectResult.rows.length);
|
|
2605
|
+
|
|
2606
|
+
// Test 6: Update
|
|
2607
|
+
const updateResult = await connection.execute(
|
|
2608
|
+
'UPDATE test_table SET value = ? WHERE id = ?',
|
|
2609
|
+
[100, insertResult.lastInsertRowId]
|
|
2610
|
+
);
|
|
2611
|
+
console.log('✓ Update successful, affected:', updateResult.rowsAffected);
|
|
2612
|
+
|
|
2613
|
+
// Test 7: Delete
|
|
2614
|
+
const deleteResult = await connection.execute(
|
|
2615
|
+
'DELETE FROM test_table WHERE id = ?',
|
|
2616
|
+
[insertResult.lastInsertRowId]
|
|
2617
|
+
);
|
|
2618
|
+
console.log('✓ Delete successful, affected:', deleteResult.rowsAffected);
|
|
2619
|
+
|
|
2620
|
+
// Test 8: Close
|
|
2621
|
+
await connection.close();
|
|
2622
|
+
console.log('✓ Connection closed');
|
|
2623
|
+
|
|
2624
|
+
console.log(`🎉 All tests passed for ${adapter.constructor.name}`);
|
|
2625
|
+
|
|
2626
|
+
} catch (error) {
|
|
2627
|
+
console.error(`❌ Test failed:`, error);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
```
|
|
2631
|
+
|
|
2632
|
+
## 📄 License
|
|
2633
|
+
|
|
2634
|
+
MIT License - see LICENSE file for details.
|
|
2635
|
+
|
|
2636
|
+
## 🙏 Acknowledgments
|
|
2637
|
+
|
|
2638
|
+
- [sqlite3](https://www.npmjs.com/package/sqlite3) - Node.js SQLite bindings
|
|
2639
|
+
- [sql.js](https://github.com/sql-js/sql.js) - SQLite compiled to WebAssembly
|
|
2640
|
+
- [expo-sqlite](https://docs.expo.dev/versions/latest/sdk/sqlite/) - Expo SQLite support
|
|
2641
|
+
- [react-native-sqlite-storage](https://github.com/andpor/react-native-sqlite-storage) - React Native SQLite
|
|
2642
|
+
- [Deno SQLite](https://deno.land/x/sqlite) - Deno SQLite module
|
|
2643
|
+
|
|
2644
|
+
## 🔗 Links
|
|
2645
|
+
|
|
2646
|
+
- [Documentation](https://github.com/cuongdqpayment/dqcai-sqlite/docs)
|
|
2647
|
+
- [Examples Repository](https://github.com/cuongdqpayment/dqcai-sqlite)
|
|
2648
|
+
- [Issue Tracker](https://github.com/cuongdqpayment/dqcai-sqlite/issues)
|
|
2649
|
+
- [NPM Package](https://www.npmjs.com/package/@dqcai/sqlite)
|
|
2650
|
+
|
|
2651
|
+
---
|
|
2652
|
+
|
|
2653
|
+
**Universal SQLite** - One library, all platforms! 🚀
|