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

|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
UniversalSQLite là một thư viện SQLite toàn diện, hỗ trợ đa nền tảng, được thiết kế để hoạt động mượt mà trên các môi trường như Browser, Node.js, Deno, Bun và React Native. Thư viện cung cấp giao diện thống nhất để quản lý cơ sở dữ liệu SQLite, bao gồm tạo schema, CRUD, query nâng cao, migration, import/export dữ liệu, và quản lý transaction. Nó sử dụng mô hình DAO (Data Access Object) để tách biệt logic truy cập dữ liệu, hỗ trợ role-based access control, và tích hợp dễ dàng với các framework.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Features
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
11
|
+
- **Cross-Platform Support**: Hoạt động trên Browser, Node.js, Deno, Bun, React Native (iOS/Android/Windows).
|
|
12
|
+
- **Schema-Based Management**: Tạo và quản lý database từ JSON schema.
|
|
13
|
+
- **DAO Pattern**: UniversalDAO để thực hiện CRUD, query, transaction.
|
|
14
|
+
- **Query Builder**: Xây dựng query phức tạp với join, where, group by, having, union, CTE.
|
|
15
|
+
- **Migration System**: Quản lý migration với up/down scripts.
|
|
16
|
+
- **Data Import/Export**: Hỗ trợ import từ CSV/JSON với mapping, validation, và export to CSV.
|
|
17
|
+
- **Role-Based Access**: Quản lý kết nối dựa trên role người dùng.
|
|
18
|
+
- **Transaction Management**: Hỗ trợ transaction đơn và cross-schema.
|
|
19
|
+
- **Adapters**: Tự động detect môi trường, hỗ trợ register adapter tùy chỉnh.
|
|
20
|
+
- **Type-Safe**: Đầy đủ types TypeScript cho schema, query, và operations.
|
|
21
|
+
- **Utilities**: CSVImporter, MigrationManager, BaseService cho service layer.
|
|
17
22
|
|
|
18
|
-
##
|
|
23
|
+
## Installation
|
|
19
24
|
|
|
20
|
-
|
|
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
|
|
25
|
+
Cài đặt qua npm hoặc yarn:
|
|
36
26
|
|
|
37
|
-
#### Bun
|
|
38
|
-
Sử dụng built-in SQLite của Bun
|
|
39
|
-
|
|
40
|
-
#### React Native
|
|
41
27
|
```bash
|
|
42
|
-
|
|
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
|
|
28
|
+
npm install @dqcai/sqlite@2.0.0
|
|
49
29
|
# hoặc
|
|
50
|
-
|
|
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);
|
|
30
|
+
yarn add @dqcai/sqlite@2.0.0
|
|
169
31
|
```
|
|
170
32
|
|
|
171
|
-
|
|
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
|
-
```
|
|
33
|
+
Đối với React Native, đảm bảo cài đặt các dependencies cần thiết cho adapter (nếu sử dụng adapter cụ thể như react-native-sqlite-storage).
|
|
182
34
|
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
const result = await db.update('users',
|
|
186
|
-
{ email: 'newemail@example.com', age: 26 },
|
|
187
|
-
'id = ?',
|
|
188
|
-
[1]
|
|
189
|
-
);
|
|
35
|
+
## Quick Start
|
|
190
36
|
|
|
191
|
-
|
|
192
|
-
|
|
37
|
+
### Bước 1: Định nghĩa Schema
|
|
38
|
+
Schema là một object JSON mô tả cấu trúc database.
|
|
193
39
|
|
|
194
|
-
##### `delete(table: string, where: string, params?: any[]): Promise<SQLiteResult>`
|
|
195
40
|
```typescript
|
|
196
|
-
|
|
197
|
-
console.log('Deleted rows:', result.rowsAffected);
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### QueryBuilder - Xây dựng truy vấn nâng cao
|
|
41
|
+
import { DatabaseSchema } from '@dqcai/sqlite';
|
|
201
42
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// 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
|
-
}
|
|
43
|
+
const exampleSchema: DatabaseSchema = {
|
|
44
|
+
version: '1.0.0',
|
|
45
|
+
database_name: 'example_db',
|
|
46
|
+
description: 'Example database',
|
|
47
|
+
schemas: {
|
|
48
|
+
users: {
|
|
49
|
+
description: 'Users table',
|
|
50
|
+
cols: [
|
|
51
|
+
{ name: 'id', type: 'integer', primary_key: true, auto_increment: true },
|
|
52
|
+
{ name: 'name', type: 'string', nullable: false },
|
|
53
|
+
{ name: 'email', type: 'string', unique: true },
|
|
54
|
+
],
|
|
55
|
+
indexes: [{ name: 'idx_email', columns: ['email'], unique: true }],
|
|
56
|
+
foreign_keys: [],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
// schema.json ví dụ kiểu json
|
|
64
|
+
{
|
|
65
|
+
"version": "1.0.0",
|
|
66
|
+
"database_name": "example.db",
|
|
67
|
+
"description": "Schema for example application.",
|
|
68
|
+
"schemas": {
|
|
69
|
+
"users": {
|
|
70
|
+
"description": "User accounts table.",
|
|
71
|
+
"cols": [
|
|
72
|
+
{
|
|
73
|
+
"name": "id",
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"constraints": "PRIMARY KEY AUTOINCREMENT"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "name",
|
|
79
|
+
"type": "text",
|
|
80
|
+
"constraints": "NOT NULL"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "email",
|
|
84
|
+
"type": "text",
|
|
85
|
+
"constraints": "UNIQUE NOT NULL"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "created_at",
|
|
89
|
+
"type": "timestamp",
|
|
90
|
+
"constraints": "DEFAULT CURRENT_TIMESTAMP"
|
|
355
91
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
// 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);
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
"posts": {
|
|
95
|
+
"description": "Posts created by users.",
|
|
96
|
+
"cols": [
|
|
97
|
+
{
|
|
98
|
+
"name": "id",
|
|
99
|
+
"type": "integer",
|
|
100
|
+
"constraints": "PRIMARY KEY AUTOINCREMENT"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "title",
|
|
104
|
+
"type": "text",
|
|
105
|
+
"constraints": "NOT NULL"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "content",
|
|
109
|
+
"type": "text"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "user_id",
|
|
113
|
+
"type": "integer",
|
|
114
|
+
"constraints": "NOT NULL"
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"foreign_keys": [
|
|
118
|
+
{
|
|
119
|
+
"name": "fk_posts_user_id",
|
|
120
|
+
"column": "user_id",
|
|
121
|
+
"references": {
|
|
122
|
+
"table": "users",
|
|
123
|
+
"column": "id"
|
|
124
|
+
},
|
|
125
|
+
"on_delete": "CASCADE"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
428
128
|
}
|
|
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
129
|
}
|
|
495
130
|
}
|
|
496
|
-
|
|
497
|
-
// Chạy với: deno run --allow-read --allow-write main.ts
|
|
498
|
-
denoExample();
|
|
499
131
|
```
|
|
500
132
|
|
|
501
|
-
###
|
|
133
|
+
### Bước 2: Khởi tạo và Sử dụng
|
|
134
|
+
Sử dụng singleton UniversalSQLite.
|
|
502
135
|
|
|
503
136
|
```typescript
|
|
504
|
-
// app.ts
|
|
505
137
|
import UniversalSQLite from '@dqcai/sqlite';
|
|
506
138
|
|
|
507
|
-
const
|
|
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
|
-
```
|
|
139
|
+
const sqlite = UniversalSQLite.getInstance();
|
|
550
140
|
|
|
551
|
-
|
|
141
|
+
async function init() {
|
|
142
|
+
await sqlite.initializeFromSchema(exampleSchema);
|
|
143
|
+
const dao = await sqlite.connect('example_db');
|
|
552
144
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
}
|
|
145
|
+
// CRUD example
|
|
146
|
+
await dao.insert({ name: 'users', cols: [{ name: 'name', value: 'John' }, { name: 'email', value: 'john@example.com' }] });
|
|
147
|
+
const users = await dao.selectAll({ name: 'users' });
|
|
148
|
+
console.log(users);
|
|
615
149
|
}
|
|
616
150
|
|
|
617
|
-
|
|
151
|
+
init();
|
|
618
152
|
```
|
|
619
153
|
|
|
620
|
-
|
|
154
|
+
## Adapters and Environment Setup
|
|
621
155
|
|
|
622
|
-
|
|
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
|
-
}
|
|
156
|
+
UniversalSQLite tự động detect môi trường và chọn adapter phù hợp. Tuy nhiên, bạn có thể register adapter tùy chỉnh nếu cần.
|
|
695
157
|
|
|
696
|
-
|
|
697
|
-
|
|
158
|
+
### Common Adapters
|
|
159
|
+
- **Browser**: Sử dụng sql.js (cần import sql.js library).
|
|
160
|
+
- **Node.js**: Sử dụng better-sqlite3 hoặc sqlite3 (cần cài đặt package tương ứng).
|
|
161
|
+
- **React Native**: Sử dụng react-native-sqlite-storage (cần cài đặt package).
|
|
162
|
+
- **Deno/Bun**: Sử dụng native SQLite support.
|
|
698
163
|
|
|
699
|
-
###
|
|
164
|
+
### Register Adapter
|
|
165
|
+
Khai báo adapter tùy chỉnh bằng cách extend BaseAdapter và register với DatabaseFactory.
|
|
700
166
|
|
|
701
167
|
```typescript
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
}
|
|
168
|
+
import { BaseAdapter, SQLiteAdapter } from '@dqcai/sqlite';
|
|
169
|
+
import DatabaseFactory from '@dqcai/sqlite';
|
|
788
170
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
}
|
|
171
|
+
class CustomNodeAdapter extends BaseAdapter implements SQLiteAdapter {
|
|
172
|
+
// Implement connect and isSupported
|
|
173
|
+
async connect(path: string) { /* ... */ }
|
|
174
|
+
isSupported() { return true; }
|
|
887
175
|
}
|
|
888
176
|
|
|
889
|
-
|
|
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();
|
|
177
|
+
DatabaseFactory.registerAdapter(new CustomNodeAdapter());
|
|
920
178
|
```
|
|
921
179
|
|
|
922
|
-
|
|
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
|
-
```
|
|
180
|
+
## Usage Guide
|
|
1004
181
|
|
|
1005
|
-
###
|
|
182
|
+
### 1. Khởi tạo Database
|
|
183
|
+
Sử dụng DatabaseFactory để tạo hoặc mở database.
|
|
1006
184
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const
|
|
1010
|
-
|
|
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
|
-
}
|
|
185
|
+
- **Tạo từ Schema**:
|
|
186
|
+
```typescript
|
|
187
|
+
const dao = await DatabaseFactory.createFromConfig(exampleSchema);
|
|
188
|
+
```
|
|
1103
189
|
|
|
1104
|
-
|
|
1105
|
-
```
|
|
190
|
+
- **Mở Existing Database**:
|
|
191
|
+
```typescript
|
|
192
|
+
const dao = await DatabaseFactory.openExisting('example_db.db');
|
|
193
|
+
```
|
|
1106
194
|
|
|
1107
|
-
|
|
195
|
+
- **Create DAO Directly**:
|
|
196
|
+
```typescript
|
|
197
|
+
const dao = DatabaseFactory.createDAO('example_db.db', { createIfNotExists: true });
|
|
198
|
+
await dao.connect();
|
|
199
|
+
```
|
|
1108
200
|
|
|
1109
|
-
###
|
|
201
|
+
### 2. CRUD Operations với UniversalDAO
|
|
202
|
+
UniversalDAO cung cấp các method CRUD cơ bản.
|
|
1110
203
|
|
|
1111
204
|
```typescript
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
-
}
|
|
205
|
+
// Insert
|
|
206
|
+
await dao.insert({ name: 'users', cols: [{ name: 'name', value: 'Alice' }] });
|
|
1138
207
|
|
|
1139
|
-
|
|
1140
|
-
|
|
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
|
-
```
|
|
208
|
+
// Update
|
|
209
|
+
await dao.update({ name: 'users', cols: [{ name: 'name', value: 'Bob' }], wheres: [{ name: 'id', value: 1 }] });
|
|
1180
210
|
|
|
1181
|
-
|
|
211
|
+
// Delete
|
|
212
|
+
await dao.delete({ name: 'users', wheres: [{ name: 'id', value: 1 }] });
|
|
1182
213
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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();
|
|
214
|
+
// Select
|
|
215
|
+
const row = await dao.select({ name: 'users', wheres: [{ name: 'id', value: 1 }] });
|
|
216
|
+
const allRows = await dao.selectAll({ name: 'users' });
|
|
1256
217
|
```
|
|
1257
218
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
### Environment Detection
|
|
219
|
+
### 3. Query Builder
|
|
220
|
+
Xây dựng query phức tạp.
|
|
1261
221
|
|
|
1262
222
|
```typescript
|
|
1263
|
-
import {
|
|
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
|
-
}
|
|
223
|
+
import { QueryBuilder } from '@dqcai/sqlite';
|
|
1322
224
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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
|
-
}
|
|
225
|
+
const qb = new QueryBuilder(dao).table('users')
|
|
226
|
+
.select('id', 'name')
|
|
227
|
+
.where('age', '>', 18)
|
|
228
|
+
.orderBy('name', 'ASC')
|
|
229
|
+
.limit(10);
|
|
1347
230
|
|
|
1348
|
-
|
|
231
|
+
const results = await qb.get();
|
|
1349
232
|
```
|
|
1350
233
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
### React Native với Expo
|
|
234
|
+
### 4. Migration
|
|
235
|
+
Sử dụng MigrationManager.
|
|
1354
236
|
|
|
1355
237
|
```typescript
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
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
|
-
},
|
|
238
|
+
const migrationManager = new MigrationManager(dao);
|
|
239
|
+
migrationManager.addMigration({
|
|
240
|
+
version: '1.1.0',
|
|
241
|
+
description: 'Add column',
|
|
242
|
+
up: async (dao) => { await dao.execute('ALTER TABLE users ADD COLUMN age INTEGER'); },
|
|
243
|
+
down: async (dao) => { /* rollback */ },
|
|
1490
244
|
});
|
|
245
|
+
await migrationManager.migrate();
|
|
1491
246
|
```
|
|
1492
247
|
|
|
1493
|
-
###
|
|
248
|
+
### 5. Import/Export Data
|
|
249
|
+
Sử dụng CSVImporter hoặc importData.
|
|
1494
250
|
|
|
1495
251
|
```typescript
|
|
1496
|
-
|
|
1497
|
-
|
|
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
|
-
}
|
|
252
|
+
const importer = new CSVImporter(dao);
|
|
253
|
+
await importer.importFromCSV('users', csvString, { hasHeader: true });
|
|
1519
254
|
|
|
1520
|
-
|
|
1521
|
-
|
|
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
|
-
});
|
|
255
|
+
// Hoặc import JSON
|
|
256
|
+
await dao.importData({ tableName: 'users', data: [{ name: 'John' }] });
|
|
1647
257
|
```
|
|
1648
258
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
### 1. Security
|
|
1652
|
-
|
|
259
|
+
### 6. Transaction
|
|
1653
260
|
```typescript
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
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
|
-
}
|
|
261
|
+
await dao.beginTransaction();
|
|
262
|
+
try {
|
|
263
|
+
// operations
|
|
264
|
+
await dao.commitTransaction();
|
|
265
|
+
} catch {
|
|
266
|
+
await dao.rollbackTransaction();
|
|
1701
267
|
}
|
|
1702
268
|
```
|
|
1703
269
|
|
|
1704
|
-
###
|
|
270
|
+
### 7. BaseService
|
|
271
|
+
Tạo service layer.
|
|
1705
272
|
|
|
1706
273
|
```typescript
|
|
1707
|
-
class
|
|
1708
|
-
private db: UniversalSQLite;
|
|
1709
|
-
|
|
274
|
+
class UserService extends BaseService<{ id: number; name: string }> {
|
|
1710
275
|
constructor() {
|
|
1711
|
-
|
|
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
|
-
}
|
|
276
|
+
super('example_db', 'users');
|
|
1745
277
|
}
|
|
1746
278
|
}
|
|
1747
|
-
```
|
|
1748
279
|
|
|
1749
|
-
|
|
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
|
-
}
|
|
280
|
+
const userService = new UserService();
|
|
281
|
+
await userService.create({ name: 'John' });
|
|
1773
282
|
```
|
|
1774
283
|
|
|
1775
|
-
|
|
284
|
+
### 8. Role-Based Management
|
|
285
|
+
Sử dụng DatabaseManager để quản lý role.
|
|
1776
286
|
|
|
1777
287
|
```typescript
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
-
}
|
|
288
|
+
DatabaseManager.registerRole({ roleName: 'admin', requiredDatabases: ['example_db'] });
|
|
289
|
+
await DatabaseManager.setCurrentUserRoles(['admin']);
|
|
290
|
+
const dao = DatabaseManager.get('example_db');
|
|
1800
291
|
```
|
|
1801
292
|
|
|
1802
|
-
|
|
293
|
+
## Examples
|
|
1803
294
|
|
|
1804
|
-
|
|
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
|
-
```
|
|
295
|
+
### Example for React Native
|
|
1873
296
|
|
|
1874
|
-
|
|
297
|
+
1. Cài đặt dependencies:
|
|
298
|
+
```bash
|
|
299
|
+
npm install react-native-sqlite-storage
|
|
300
|
+
```
|
|
1875
301
|
|
|
1876
|
-
|
|
302
|
+
2. Khai báo Adapter (nếu cần tùy chỉnh, nhưng thư viện tự detect):
|
|
303
|
+
```typescript
|
|
304
|
+
import { enablePromise, openDatabase} from 'react-native-sqlite-storage';
|
|
305
|
+
import { BaseAdapter } from '@dqcai/sqlite';
|
|
306
|
+
enablePromise(true);
|
|
307
|
+
class ReactNativeAdapter extends BaseAdapter {
|
|
308
|
+
isSupported() {
|
|
309
|
+
return typeof Platform !== 'undefined' && Platform.OS !== 'web';
|
|
310
|
+
}
|
|
1877
311
|
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
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;
|
|
312
|
+
connect(path) {
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
const db = openDatabase(
|
|
315
|
+
{ name: path, location: 'default' },
|
|
316
|
+
() => {
|
|
317
|
+
resolve({
|
|
318
|
+
execute: (sql, params) => {
|
|
319
|
+
return new Promise((execResolve, execReject) => {
|
|
320
|
+
db.transaction(tx => {
|
|
321
|
+
tx.executeSql(
|
|
322
|
+
sql,
|
|
323
|
+
params,
|
|
324
|
+
(tx, results) => {
|
|
325
|
+
const rowsAffected = results.rowsAffected;
|
|
326
|
+
let rows = [];
|
|
327
|
+
for (let i = 0; i < results.rows.length; i++) {
|
|
328
|
+
rows.push(results.rows.item(i));
|
|
329
|
+
}
|
|
330
|
+
execResolve({ rows, rowsAffected, lastInsertRowId: results.insertId });
|
|
331
|
+
},
|
|
332
|
+
(tx, error) => {
|
|
333
|
+
execReject(error);
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
close: () => {
|
|
340
|
+
return new Promise((closeResolve, closeReject) => {
|
|
341
|
+
db.close((err) => {
|
|
342
|
+
if (err) return closeReject(err);
|
|
343
|
+
closeResolve();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
},
|
|
349
|
+
(error) => reject(error)
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
1972
353
|
}
|
|
354
|
+
export default ReactNativeAdapter;
|
|
1973
355
|
|
|
1974
|
-
|
|
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
|
-
}
|
|
356
|
+
```
|
|
2043
357
|
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
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
|
-
}
|
|
358
|
+
3. Tạo và Mở Database, CRUD:
|
|
359
|
+
```typescript
|
|
360
|
+
import { DatabaseFactory } from '@dqcai/sqlite';
|
|
361
|
+
import ReactNativeAdapter from './adapters/react-native-adapter'; // Hoặc NodejsAdapter
|
|
362
|
+
import schema from './schema.json';
|
|
2075
363
|
|
|
2076
|
-
|
|
2077
|
-
|
|
364
|
+
async function setupDatabase() {
|
|
365
|
+
// Đăng ký adapter để thư viện biết cách kết nối
|
|
366
|
+
DatabaseFactory.registerAdapter(new ReactNativeAdapter()); // Đăng ký adapter cho môi trường
|
|
2078
367
|
|
|
2079
|
-
|
|
368
|
+
// Tạo hoặc mở database. Nếu file chưa tồn tại, nó sẽ được tạo mới và init schema.
|
|
369
|
+
// Nếu đã tồn tại, nó sẽ được mở.
|
|
370
|
+
const dao = await DatabaseFactory.createOrOpen({ config: schema });
|
|
2080
371
|
|
|
2081
|
-
|
|
2082
|
-
|
|
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
|
-
}
|
|
372
|
+
console.log(`Kết nối thành công tới database: ${dao.getDatabaseInfo().name}`);
|
|
373
|
+
return dao;
|
|
2197
374
|
}
|
|
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
375
|
|
|
2227
|
-
|
|
2228
|
-
|
|
376
|
+
async function crudOperations(dao) {
|
|
377
|
+
// 1. CREATE (Insert)
|
|
378
|
+
console.log('Inserting a new user...');
|
|
379
|
+
const userToInsert = {
|
|
380
|
+
name: 'John Doe',
|
|
381
|
+
email: 'john.doe@example.com',
|
|
382
|
+
};
|
|
383
|
+
const insertResult = await dao.insert({
|
|
384
|
+
name: 'users',
|
|
385
|
+
cols: Object.entries(userToInsert).map(([name, value]) => ({ name, value })),
|
|
386
|
+
});
|
|
387
|
+
const newUserId = insertResult.lastInsertRowId;
|
|
388
|
+
console.log(`User inserted with ID: ${newUserId}`);
|
|
389
|
+
|
|
390
|
+
// 2. READ (Select)
|
|
391
|
+
console.log('Selecting user by email...');
|
|
392
|
+
const selectedUser = await dao.select({
|
|
393
|
+
name: 'users',
|
|
394
|
+
cols: [], // Chọn tất cả các cột
|
|
395
|
+
wheres: [{ name: 'email', value: 'john.doe@example.com' }],
|
|
396
|
+
});
|
|
397
|
+
console.log('Selected user:', selectedUser);
|
|
398
|
+
|
|
399
|
+
// 3. UPDATE
|
|
400
|
+
console.log('Updating user name...');
|
|
401
|
+
await dao.update({
|
|
402
|
+
name: 'users',
|
|
403
|
+
cols: [{ name: 'name', value: 'John Smith' }],
|
|
404
|
+
wheres: [{ name: 'id', value: newUserId }],
|
|
405
|
+
});
|
|
406
|
+
const updatedUser = await dao.select({
|
|
407
|
+
name: 'users',
|
|
408
|
+
cols: ['id', 'name'],
|
|
409
|
+
wheres: [{ name: 'id', value: newUserId }],
|
|
410
|
+
});
|
|
411
|
+
console.log('Updated user:', updatedUser);
|
|
2229
412
|
|
|
2230
|
-
|
|
413
|
+
// 4. DELETE
|
|
414
|
+
console.log('Deleting the user...');
|
|
415
|
+
const deleteResult = await dao.delete({
|
|
416
|
+
name: 'users',
|
|
417
|
+
wheres: [{ name: 'id', value: newUserId }],
|
|
418
|
+
});
|
|
419
|
+
console.log(`Rows deleted: ${deleteResult.rowsAffected}`);
|
|
2231
420
|
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
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`;
|
|
421
|
+
const checkUser = await dao.select({
|
|
422
|
+
name: 'users',
|
|
423
|
+
wheres: [{ name: 'id', value: newUserId }],
|
|
424
|
+
});
|
|
425
|
+
console.log('User exists after delete?', Object.keys(checkUser).length > 0);
|
|
2278
426
|
}
|
|
2279
|
-
|
|
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
|
-
|
|
427
|
+
|
|
428
|
+
// Chạy ví dụ
|
|
429
|
+
async function main() {
|
|
2294
430
|
try {
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
}
|
|
2300
|
-
await this.db.query('COMMIT');
|
|
2301
|
-
console.log('Backup restored successfully');
|
|
431
|
+
const dao = await setupDatabase();
|
|
432
|
+
await crudOperations(dao);
|
|
433
|
+
await dao.close();
|
|
434
|
+
console.log('Đã đóng kết nối database.');
|
|
2302
435
|
} catch (error) {
|
|
2303
|
-
|
|
2304
|
-
throw new Error(`Restore failed: ${error}`);
|
|
436
|
+
console.error('Đã xảy ra lỗi:', error);
|
|
2305
437
|
}
|
|
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
438
|
}
|
|
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
439
|
|
|
2503
|
-
|
|
440
|
+
main();
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Example for Node.js
|
|
444
|
+
|
|
445
|
+
1. Cài đặt dependencies:
|
|
446
|
+
```bash
|
|
447
|
+
npm install better-sqlite3
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
2. Khai báo Adapter (tự detect, nhưng có thể tùy chỉnh):
|
|
451
|
+
```typescript
|
|
452
|
+
class NodeAdapter extends BaseAdapter {
|
|
453
|
+
async connect(path: string) {
|
|
454
|
+
const SQLite3 = require('better-sqlite3');
|
|
455
|
+
const db = new SQLite3(path);
|
|
456
|
+
return {
|
|
457
|
+
execute: (sql: string, params = []) => {
|
|
458
|
+
const stmt = db.prepare(sql);
|
|
459
|
+
const result = stmt.run(params);
|
|
460
|
+
return { rows: stmt.all(params), rowsAffected: result.changes };
|
|
461
|
+
},
|
|
462
|
+
close: () => db.close(),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
isSupported() { return true; }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
DatabaseFactory.registerAdapter(new NodeAdapter());
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
3. Tạo và Mở Database, CRUD:
|
|
472
|
+
```typescript
|
|
473
|
+
import UniversalSQLite from '@dqcai/sqlite';
|
|
474
|
+
|
|
475
|
+
const sqlite = UniversalSQLite.getInstance();
|
|
476
|
+
|
|
477
|
+
async function nodeExample() {
|
|
478
|
+
await sqlite.initializeFromSchema(exampleSchema);
|
|
479
|
+
const dao = await sqlite.connect('example_db');
|
|
480
|
+
|
|
481
|
+
// CRUD
|
|
482
|
+
await dao.insert({ name: 'users', cols: [{ name: 'name', value: 'NodeUser' }, { name: 'email', value: 'node@example.com' }] });
|
|
483
|
+
const users = await dao.selectAll({ name: 'users' });
|
|
484
|
+
console.log('Users:', users);
|
|
485
|
+
|
|
486
|
+
// Query Builder
|
|
487
|
+
const qb = sqlite.query('users');
|
|
488
|
+
const results = await qb.select('*').where('name', 'LIKE', '%User%').get();
|
|
489
|
+
|
|
490
|
+
// Migration
|
|
491
|
+
const migrationManager = sqlite.createMigrationManager();
|
|
492
|
+
migrationManager.addMigration({
|
|
493
|
+
version: '1.0.1',
|
|
494
|
+
description: 'Add age',
|
|
495
|
+
up: async (dao) => await dao.execute('ALTER TABLE users ADD COLUMN age INTEGER'),
|
|
496
|
+
down: async (dao) => await dao.execute('ALTER TABLE users DROP COLUMN age'),
|
|
497
|
+
});
|
|
498
|
+
await migrationManager.migrate();
|
|
499
|
+
|
|
500
|
+
// Import from File
|
|
501
|
+
const importer = sqlite.createCSVImporter();
|
|
502
|
+
await importer.importFromFile('users', './data.csv', { hasHeader: true });
|
|
503
|
+
|
|
504
|
+
await sqlite.closeAll();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
nodeExample();
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## API Reference
|
|
511
|
+
|
|
512
|
+
- **UniversalSQLite**: Singleton chính, methods: initialize, connect, getDAO, query, execute, importData, etc.
|
|
513
|
+
- **UniversalDAO**: Core DAO cho CRUD, execute, importData.
|
|
514
|
+
- **QueryBuilder**: Xây dựng query với fluent API.
|
|
515
|
+
- **MigrationManager**: Quản lý migration.
|
|
516
|
+
- **CSVImporter**: Import/export CSV.
|
|
517
|
+
- **BaseService**: Base cho service layer.
|
|
518
|
+
- **DatabaseFactory**: Factory để tạo DAO.
|
|
519
|
+
- **DatabaseManager**: Quản lý connections, roles.
|
|
520
|
+
|
|
521
|
+
Xem source code để biết chi tiết types và methods.
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
## Best Practices
|
|
525
|
+
|
|
526
|
+
1. **Always use transactions for multi-step operations**
|
|
527
|
+
2. **Define schemas for type safety and validation**
|
|
528
|
+
3. **Use parameterized queries to prevent SQL injection**
|
|
529
|
+
4. **Implement proper error handling**
|
|
530
|
+
5. **Close connections when done**
|
|
531
|
+
6. **Use migrations for schema changes**
|
|
532
|
+
7. **Batch large operations for better performance**
|
|
533
|
+
|
|
534
|
+
## Contributing
|
|
535
|
+
|
|
536
|
+
1. Fork the repository
|
|
537
|
+
2. Create a feature branch
|
|
538
|
+
3. Make your changes
|
|
539
|
+
4. Add tests
|
|
540
|
+
5. Submit a pull request
|
|
2504
541
|
|
|
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
542
|
|
|
2632
543
|
## 📄 License
|
|
2633
544
|
|
|
2634
|
-
MIT
|
|
545
|
+
MIT © [Cuong Doan](https://github.com/cuongdqpayment)
|
|
2635
546
|
|
|
2636
547
|
## 🙏 Acknowledgments
|
|
2637
548
|
|
|
@@ -2646,8 +557,9 @@ MIT License - see LICENSE file for details.
|
|
|
2646
557
|
- [Documentation](https://github.com/cuongdqpayment/dqcai-sqlite/docs)
|
|
2647
558
|
- [Examples Repository](https://github.com/cuongdqpayment/dqcai-sqlite)
|
|
2648
559
|
- [Issue Tracker](https://github.com/cuongdqpayment/dqcai-sqlite/issues)
|
|
560
|
+
- [Issue Facebook](https://www.facebook.com/share/p/19esHGbaGj/)
|
|
2649
561
|
- [NPM Package](https://www.npmjs.com/package/@dqcai/sqlite)
|
|
2650
562
|
|
|
2651
563
|
---
|
|
2652
564
|
|
|
2653
|
-
|
|
565
|
+
🔥 **@dqcai/sqlite** — One library, all platforms! 🚀
|