@dqcai/sqlite 1.0.0 → 2.0.2
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 +1491 -2328
- 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 +119 -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 +142 -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 +82 -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,1799 @@
|
|
|
1
|
-
# @dqcai/sqlite - Universal SQLite Library
|
|
1
|
+
# @dqcai/sqlite - A Universal SQLite Library (@dqcai/sqlite v2.0.1)
|
|
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.
|
|
22
|
+
- **DatabaseManager**: Quản lý kết nối, schema và vai trò người dùng
|
|
23
|
+
- **BaseService**: Lớp cơ sở cho CRUD operations
|
|
17
24
|
|
|
18
|
-
##
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Cài đặt qua npm hoặc yarn:
|
|
19
28
|
|
|
20
29
|
```bash
|
|
21
|
-
npm install @dqcai/sqlite
|
|
30
|
+
npm install @dqcai/sqlite@2.0.0
|
|
31
|
+
# hoặc
|
|
32
|
+
yarn add @dqcai/sqlite@2.0.0
|
|
22
33
|
```
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
Đố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).
|
|
36
|
+
|
|
37
|
+
## Cài đặt
|
|
25
38
|
|
|
26
|
-
#### Node.js
|
|
27
39
|
```bash
|
|
28
|
-
npm install
|
|
40
|
+
npm install @dqcai/sqlite
|
|
29
41
|
```
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
## 1. Cấu hình Schema Database
|
|
44
|
+
|
|
45
|
+
Trước tiên, định nghĩa schema cho cơ sở dữ liệu:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { DatabaseSchema } from '@dqcai/sqlite';
|
|
49
|
+
|
|
50
|
+
// Schema cho database users
|
|
51
|
+
const userSchema: DatabaseSchema = {
|
|
52
|
+
version: "1.0.0",
|
|
53
|
+
database_name: "users",
|
|
54
|
+
description: "User management database",
|
|
55
|
+
schemas: {
|
|
56
|
+
users: {
|
|
57
|
+
description: "User table",
|
|
58
|
+
cols: [
|
|
59
|
+
{
|
|
60
|
+
name: "id",
|
|
61
|
+
type: "integer",
|
|
62
|
+
primary_key: true,
|
|
63
|
+
auto_increment: true,
|
|
64
|
+
nullable: false
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "username",
|
|
68
|
+
type: "varchar",
|
|
69
|
+
nullable: false,
|
|
70
|
+
unique: true,
|
|
71
|
+
length: 50
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "email",
|
|
75
|
+
type: "varchar",
|
|
76
|
+
nullable: false,
|
|
77
|
+
unique: true,
|
|
78
|
+
length: 100
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "password",
|
|
82
|
+
type: "varchar",
|
|
83
|
+
nullable: false,
|
|
84
|
+
length: 255
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "created_at",
|
|
88
|
+
type: "datetime",
|
|
89
|
+
nullable: false,
|
|
90
|
+
default: "CURRENT_TIMESTAMP"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "updated_at",
|
|
94
|
+
type: "datetime",
|
|
95
|
+
nullable: true
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
indexes: [
|
|
99
|
+
{
|
|
100
|
+
name: "idx_username",
|
|
101
|
+
columns: ["username"],
|
|
102
|
+
unique: true
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "idx_email",
|
|
106
|
+
columns: ["email"],
|
|
107
|
+
unique: true
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
profiles: {
|
|
112
|
+
description: "User profiles table",
|
|
113
|
+
cols: [
|
|
114
|
+
{
|
|
115
|
+
name: "id",
|
|
116
|
+
type: "integer",
|
|
117
|
+
primary_key: true,
|
|
118
|
+
auto_increment: true
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "user_id",
|
|
122
|
+
type: "integer",
|
|
123
|
+
nullable: false
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "first_name",
|
|
127
|
+
type: "varchar",
|
|
128
|
+
length: 50
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "last_name",
|
|
132
|
+
type: "varchar",
|
|
133
|
+
length: 50
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "phone",
|
|
137
|
+
type: "varchar",
|
|
138
|
+
length: 20
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "address",
|
|
142
|
+
type: "text"
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
foreign_keys: [
|
|
146
|
+
{
|
|
147
|
+
name: "fk_profile_user",
|
|
148
|
+
column: "user_id",
|
|
149
|
+
references: {
|
|
150
|
+
table: "users",
|
|
151
|
+
column: "id"
|
|
152
|
+
},
|
|
153
|
+
on_delete: "CASCADE",
|
|
154
|
+
on_update: "CASCADE"
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Schema core cho hệ thống
|
|
162
|
+
const coreSchema: DatabaseSchema = {
|
|
163
|
+
version: "1.0.0",
|
|
164
|
+
database_name: "core",
|
|
165
|
+
description: "Core system database",
|
|
166
|
+
schemas: {
|
|
167
|
+
settings: {
|
|
168
|
+
description: "System settings",
|
|
169
|
+
cols: [
|
|
170
|
+
{
|
|
171
|
+
name: "key",
|
|
172
|
+
type: "varchar",
|
|
173
|
+
primary_key: true,
|
|
174
|
+
length: 100
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "value",
|
|
178
|
+
type: "text"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "description",
|
|
182
|
+
type: "text"
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
```
|
|
33
189
|
|
|
34
|
-
|
|
35
|
-
Không cần cài đặt - sử dụng Deno SQLite module
|
|
190
|
+
## 2. Setup cho React Native
|
|
36
191
|
|
|
37
|
-
|
|
38
|
-
Sử dụng built-in SQLite của Bun
|
|
192
|
+
### Cài đặt dependencies
|
|
39
193
|
|
|
40
|
-
#### React Native
|
|
41
194
|
```bash
|
|
42
|
-
# Standard React Native
|
|
43
|
-
npm install react-native-sqlite-storage
|
|
44
|
-
# hoặc
|
|
45
|
-
npm install expo-sqlite
|
|
46
|
-
|
|
47
|
-
# React Native Windows
|
|
48
195
|
npm install react-native-sqlite-2
|
|
49
|
-
#
|
|
50
|
-
npm install react-native-
|
|
196
|
+
# Hoặc
|
|
197
|
+
npm install react-native-sqlite-storage
|
|
51
198
|
```
|
|
52
199
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
### Import thư viện
|
|
200
|
+
### Tạo Adapter cho React Native
|
|
56
201
|
|
|
57
202
|
```typescript
|
|
58
|
-
|
|
203
|
+
// adapters/ReactNativeAdapter.ts
|
|
204
|
+
import { BaseAdapter } from '@dqcai/sqlite';
|
|
205
|
+
import SQLite from 'react-native-sqlite-2';
|
|
206
|
+
|
|
207
|
+
export class ReactNativeAdapter extends BaseAdapter {
|
|
208
|
+
isSupported(): boolean {
|
|
209
|
+
return typeof SQLite !== 'undefined';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async connect(path: string): Promise<any> {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const db = SQLite.openDatabase(
|
|
215
|
+
path,
|
|
216
|
+
'1.0',
|
|
217
|
+
'Database',
|
|
218
|
+
200000,
|
|
219
|
+
() => {
|
|
220
|
+
resolve(new ReactNativeConnection(db));
|
|
221
|
+
},
|
|
222
|
+
(error) => {
|
|
223
|
+
reject(error);
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
class ReactNativeConnection {
|
|
231
|
+
constructor(private db: any) {}
|
|
232
|
+
|
|
233
|
+
async execute(sql: string, params: any[] = []): Promise<any> {
|
|
234
|
+
return new Promise((resolve, reject) => {
|
|
235
|
+
this.db.transaction((tx: any) => {
|
|
236
|
+
tx.executeSql(
|
|
237
|
+
sql,
|
|
238
|
+
params,
|
|
239
|
+
(tx: any, results: any) => {
|
|
240
|
+
const rows: any[] = [];
|
|
241
|
+
for (let i = 0; i < results.rows.length; i++) {
|
|
242
|
+
rows.push(results.rows.item(i));
|
|
243
|
+
}
|
|
244
|
+
resolve({
|
|
245
|
+
rows,
|
|
246
|
+
rowsAffected: results.rowsAffected,
|
|
247
|
+
lastInsertRowId: results.insertId
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
(tx: any, error: any) => {
|
|
251
|
+
reject(error);
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async close(): Promise<void> {
|
|
259
|
+
// React Native SQLite không cần close thủ công
|
|
260
|
+
return Promise.resolve();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
59
263
|
```
|
|
60
264
|
|
|
61
|
-
###
|
|
265
|
+
### Khởi tạo DatabaseManager (React Native)
|
|
62
266
|
|
|
63
267
|
```typescript
|
|
64
|
-
|
|
268
|
+
// services/DatabaseService.ts
|
|
269
|
+
import { DatabaseManager, DatabaseFactory } from '@dqcai/sqlite';
|
|
270
|
+
import { ReactNativeAdapter } from '../adapters/ReactNativeAdapter';
|
|
65
271
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// Thêm dữ liệu
|
|
80
|
-
await db.insert('users', {
|
|
81
|
-
name: 'John Doe',
|
|
82
|
-
email: 'john@example.com'
|
|
272
|
+
export class DatabaseService {
|
|
273
|
+
private static isInitialized = false;
|
|
274
|
+
|
|
275
|
+
static async initialize() {
|
|
276
|
+
if (this.isInitialized) return;
|
|
277
|
+
|
|
278
|
+
// Đăng ký adapter
|
|
279
|
+
DatabaseFactory.registerAdapter(new ReactNativeAdapter());
|
|
280
|
+
|
|
281
|
+
// Đăng ký schemas
|
|
282
|
+
DatabaseManager.registerSchemas({
|
|
283
|
+
core: coreSchema,
|
|
284
|
+
users: userSchema
|
|
83
285
|
});
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
await
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
console.
|
|
105
|
-
}
|
|
106
|
-
|
|
286
|
+
|
|
287
|
+
// Đăng ký roles
|
|
288
|
+
DatabaseManager.registerRoles([
|
|
289
|
+
{
|
|
290
|
+
roleName: 'admin',
|
|
291
|
+
requiredDatabases: ['core', 'users'],
|
|
292
|
+
priority: 1
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
roleName: 'user',
|
|
296
|
+
requiredDatabases: ['core'],
|
|
297
|
+
optionalDatabases: ['users'],
|
|
298
|
+
priority: 2
|
|
299
|
+
}
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
// Khởi tạo core database
|
|
303
|
+
await DatabaseManager.initializeCoreConnection();
|
|
304
|
+
|
|
305
|
+
this.isInitialized = true;
|
|
306
|
+
console.log('DatabaseService initialized for React Native');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
static async setUserRole(roles: string[]) {
|
|
310
|
+
await DatabaseManager.setCurrentUserRoles(roles);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
static getConnection(dbKey: string) {
|
|
314
|
+
return DatabaseManager.get(dbKey);
|
|
107
315
|
}
|
|
108
|
-
}
|
|
109
316
|
|
|
110
|
-
|
|
317
|
+
static async closeAll() {
|
|
318
|
+
await DatabaseManager.closeAll();
|
|
319
|
+
this.isInitialized = false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
111
322
|
```
|
|
112
323
|
|
|
113
|
-
##
|
|
324
|
+
## 3. Setup cho Node.js
|
|
114
325
|
|
|
115
|
-
###
|
|
326
|
+
### Cài đặt dependencies
|
|
116
327
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
328
|
+
```bash
|
|
329
|
+
npm install sqlite3
|
|
330
|
+
# Hoặc
|
|
331
|
+
npm install better-sqlite3
|
|
120
332
|
```
|
|
121
333
|
|
|
122
|
-
|
|
334
|
+
### Tạo Adapter cho Node.js
|
|
123
335
|
|
|
124
|
-
##### `connect(path: string): Promise<void>`
|
|
125
336
|
```typescript
|
|
126
|
-
//
|
|
127
|
-
|
|
337
|
+
// adapters/NodeAdapter.ts
|
|
338
|
+
import { BaseAdapter } from '@dqcai/sqlite';
|
|
339
|
+
import sqlite3 from 'sqlite3';
|
|
340
|
+
import path from 'path';
|
|
341
|
+
import fs from 'fs';
|
|
128
342
|
|
|
129
|
-
|
|
130
|
-
|
|
343
|
+
export class NodeAdapter extends BaseAdapter {
|
|
344
|
+
isSupported(): boolean {
|
|
345
|
+
return typeof process !== 'undefined' && process.versions?.node;
|
|
346
|
+
}
|
|
131
347
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
348
|
+
async connect(dbPath: string): Promise<any> {
|
|
349
|
+
const fullPath = path.resolve(dbPath);
|
|
350
|
+
|
|
351
|
+
// Tạo thư mục nếu chưa tồn tại
|
|
352
|
+
const dir = path.dirname(fullPath);
|
|
353
|
+
if (!fs.existsSync(dir)) {
|
|
354
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
355
|
+
}
|
|
135
356
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
const db = new sqlite3.Database(fullPath, (err) => {
|
|
359
|
+
if (err) {
|
|
360
|
+
reject(err);
|
|
361
|
+
} else {
|
|
362
|
+
resolve(new NodeConnection(db));
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
140
368
|
|
|
141
|
-
|
|
142
|
-
|
|
369
|
+
class NodeConnection {
|
|
370
|
+
constructor(private db: sqlite3.Database) {}
|
|
143
371
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
372
|
+
async execute(sql: string, params: any[] = []): Promise<any> {
|
|
373
|
+
return new Promise((resolve, reject) => {
|
|
374
|
+
const isSelect = sql.trim().toUpperCase().startsWith('SELECT');
|
|
375
|
+
|
|
376
|
+
if (isSelect) {
|
|
377
|
+
this.db.all(sql, params, (err, rows) => {
|
|
378
|
+
if (err) {
|
|
379
|
+
reject(err);
|
|
380
|
+
} else {
|
|
381
|
+
resolve({
|
|
382
|
+
rows: rows || [],
|
|
383
|
+
rowsAffected: 0
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
this.db.run(sql, params, function(err) {
|
|
389
|
+
if (err) {
|
|
390
|
+
reject(err);
|
|
391
|
+
} else {
|
|
392
|
+
resolve({
|
|
393
|
+
rows: [],
|
|
394
|
+
rowsAffected: this.changes,
|
|
395
|
+
lastInsertRowId: this.lastID
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
148
402
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
});
|
|
403
|
+
async close(): Promise<void> {
|
|
404
|
+
return new Promise((resolve, reject) => {
|
|
405
|
+
this.db.close((err) => {
|
|
406
|
+
if (err) {
|
|
407
|
+
reject(err);
|
|
408
|
+
} else {
|
|
409
|
+
resolve();
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
158
415
|
```
|
|
159
416
|
|
|
160
|
-
|
|
417
|
+
### Khởi tạo DatabaseManager (Node.js)
|
|
418
|
+
|
|
161
419
|
```typescript
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
});
|
|
420
|
+
// services/DatabaseService.ts
|
|
421
|
+
import { DatabaseManager, DatabaseFactory } from '@dqcai/sqlite';
|
|
422
|
+
import { NodeAdapter } from '../adapters/NodeAdapter';
|
|
423
|
+
import path from 'path';
|
|
167
424
|
|
|
168
|
-
|
|
169
|
-
|
|
425
|
+
export class DatabaseService {
|
|
426
|
+
private static isInitialized = false;
|
|
427
|
+
private static dbDirectory = './databases';
|
|
170
428
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// Lấy tất cả
|
|
174
|
-
const allUsers = await db.select('users');
|
|
429
|
+
static async initialize() {
|
|
430
|
+
if (this.isInitialized) return;
|
|
175
431
|
|
|
176
|
-
//
|
|
177
|
-
|
|
432
|
+
// Đăng ký adapter
|
|
433
|
+
DatabaseFactory.registerAdapter(new NodeAdapter());
|
|
178
434
|
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
435
|
+
// Đăng ký schemas
|
|
436
|
+
DatabaseManager.registerSchemas({
|
|
437
|
+
core: coreSchema,
|
|
438
|
+
users: userSchema
|
|
439
|
+
});
|
|
182
440
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
441
|
+
// Đăng ký roles
|
|
442
|
+
DatabaseManager.registerRoles([
|
|
443
|
+
{
|
|
444
|
+
roleName: 'admin',
|
|
445
|
+
requiredDatabases: ['core', 'users'],
|
|
446
|
+
priority: 1
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
roleName: 'user',
|
|
450
|
+
requiredDatabases: ['core'],
|
|
451
|
+
optionalDatabases: ['users'],
|
|
452
|
+
priority: 2
|
|
453
|
+
}
|
|
454
|
+
]);
|
|
190
455
|
|
|
191
|
-
|
|
192
|
-
|
|
456
|
+
// Khởi tạo core database
|
|
457
|
+
await DatabaseManager.initializeCoreConnection();
|
|
193
458
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
459
|
+
this.isInitialized = true;
|
|
460
|
+
console.log('DatabaseService initialized for Node.js');
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
static async setUserRole(roles: string[]) {
|
|
464
|
+
await DatabaseManager.setCurrentUserRoles(roles);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
static getConnection(dbKey: string) {
|
|
468
|
+
return DatabaseManager.get(dbKey);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
static async closeAll() {
|
|
472
|
+
await DatabaseManager.closeAll();
|
|
473
|
+
this.isInitialized = false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
198
476
|
```
|
|
199
477
|
|
|
200
|
-
|
|
478
|
+
## 4. Tạo Services với BaseService
|
|
479
|
+
|
|
480
|
+
### User Service
|
|
201
481
|
|
|
202
482
|
```typescript
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// SELECT với QueryBuilder
|
|
206
|
-
const selectSQL = QueryBuilder
|
|
207
|
-
.table('users')
|
|
208
|
-
.select(['id', 'name', 'email'])
|
|
209
|
-
.where('age > 18')
|
|
210
|
-
.where('status = "active"')
|
|
211
|
-
.orderBy('name', 'ASC')
|
|
212
|
-
.limit(10)
|
|
213
|
-
.offset(0)
|
|
214
|
-
.toSQL();
|
|
215
|
-
|
|
216
|
-
console.log(selectSQL);
|
|
217
|
-
// Output: SELECT id, name, email FROM users WHERE age > 18 AND status = "active" ORDER BY name ASC LIMIT 10 OFFSET 0
|
|
218
|
-
|
|
219
|
-
const result = await db.query(selectSQL);
|
|
220
|
-
|
|
221
|
-
// INSERT với QueryBuilder
|
|
222
|
-
const insertSQL = QueryBuilder.insert('users', {
|
|
223
|
-
name: 'Bob',
|
|
224
|
-
email: 'bob@example.com',
|
|
225
|
-
age: 30
|
|
226
|
-
});
|
|
483
|
+
// services/UserService.ts
|
|
484
|
+
import { BaseService } from '@dqcai/sqlite';
|
|
227
485
|
|
|
228
|
-
|
|
486
|
+
interface User {
|
|
487
|
+
id?: number;
|
|
488
|
+
username: string;
|
|
489
|
+
email: string;
|
|
490
|
+
password: string;
|
|
491
|
+
created_at?: string;
|
|
492
|
+
updated_at?: string;
|
|
493
|
+
}
|
|
229
494
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
495
|
+
interface Profile {
|
|
496
|
+
id?: number;
|
|
497
|
+
user_id: number;
|
|
498
|
+
first_name?: string;
|
|
499
|
+
last_name?: string;
|
|
500
|
+
phone?: string;
|
|
501
|
+
address?: string;
|
|
502
|
+
}
|
|
235
503
|
|
|
236
|
-
|
|
504
|
+
export class UserService extends BaseService<User> {
|
|
505
|
+
constructor() {
|
|
506
|
+
super('users', 'users'); // schema name, table name
|
|
507
|
+
this.setPrimaryKeyFields(['id']);
|
|
508
|
+
}
|
|
237
509
|
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
510
|
+
// Tạo user mới
|
|
511
|
+
async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User | null> {
|
|
512
|
+
try {
|
|
513
|
+
// Kiểm tra email đã tồn tại chưa
|
|
514
|
+
const existingUser = await this.findFirst({ email: userData.email });
|
|
515
|
+
if (existingUser) {
|
|
516
|
+
throw new Error('Email already exists');
|
|
517
|
+
}
|
|
242
518
|
|
|
243
|
-
|
|
519
|
+
// Kiểm tra username đã tồn tại chưa
|
|
520
|
+
const existingUsername = await this.findFirst({ username: userData.username });
|
|
521
|
+
if (existingUsername) {
|
|
522
|
+
throw new Error('Username already exists');
|
|
523
|
+
}
|
|
244
524
|
|
|
245
|
-
|
|
246
|
-
|
|
525
|
+
const newUser = await this.create({
|
|
526
|
+
...userData,
|
|
527
|
+
created_at: new Date().toISOString()
|
|
528
|
+
});
|
|
247
529
|
|
|
248
|
-
|
|
530
|
+
return newUser;
|
|
531
|
+
} catch (error) {
|
|
532
|
+
console.error('Error creating user:', error);
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
249
536
|
|
|
250
|
-
//
|
|
251
|
-
|
|
537
|
+
// Cập nhật user
|
|
538
|
+
async updateUser(id: number, userData: Partial<User>): Promise<User | null> {
|
|
539
|
+
try {
|
|
540
|
+
const updatedUser = await this.update(id, {
|
|
541
|
+
...userData,
|
|
542
|
+
updated_at: new Date().toISOString()
|
|
543
|
+
});
|
|
544
|
+
return updatedUser;
|
|
545
|
+
} catch (error) {
|
|
546
|
+
console.error('Error updating user:', error);
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
252
550
|
|
|
253
|
-
//
|
|
254
|
-
|
|
551
|
+
// Tìm user theo email
|
|
552
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
553
|
+
return await this.findFirst({ email });
|
|
554
|
+
}
|
|
255
555
|
|
|
256
|
-
//
|
|
257
|
-
|
|
556
|
+
// Tìm user theo username
|
|
557
|
+
async findByUsername(username: string): Promise<User | null> {
|
|
558
|
+
return await this.findFirst({ username });
|
|
559
|
+
}
|
|
258
560
|
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
561
|
+
// Lấy tất cả users với phân trang
|
|
562
|
+
async getAllUsers(page: number = 1, limit: number = 10): Promise<User[]> {
|
|
563
|
+
const offset = (page - 1) * limit;
|
|
564
|
+
return await this.findAll({}, {
|
|
565
|
+
orderBy: [{ name: 'created_at', direction: 'DESC' }],
|
|
566
|
+
limit,
|
|
567
|
+
offset
|
|
568
|
+
});
|
|
569
|
+
}
|
|
262
570
|
|
|
263
|
-
|
|
571
|
+
// Xóa user (soft delete bằng cách cập nhật trường deleted_at)
|
|
572
|
+
async softDeleteUser(id: number): Promise<boolean> {
|
|
573
|
+
const result = await this.update(id, {
|
|
574
|
+
updated_at: new Date().toISOString(),
|
|
575
|
+
// deleted_at: new Date().toISOString() // nếu có field này trong schema
|
|
576
|
+
});
|
|
577
|
+
return result !== null;
|
|
578
|
+
}
|
|
264
579
|
|
|
265
|
-
|
|
580
|
+
// Đếm tổng số users
|
|
581
|
+
async getTotalUsers(): Promise<number> {
|
|
582
|
+
return await this.count();
|
|
583
|
+
}
|
|
266
584
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
585
|
+
// Tìm kiếm users
|
|
586
|
+
async searchUsers(searchTerm: string): Promise<User[]> {
|
|
587
|
+
const dao = await this.init().then(() => this.dao!);
|
|
588
|
+
const sql = `
|
|
589
|
+
SELECT * FROM users
|
|
590
|
+
WHERE username LIKE ? OR email LIKE ?
|
|
591
|
+
ORDER BY created_at DESC
|
|
592
|
+
`;
|
|
593
|
+
const params = [`%${searchTerm}%`, `%${searchTerm}%`];
|
|
594
|
+
const result = await dao.execute(sql, params);
|
|
595
|
+
return result.rows as User[];
|
|
596
|
+
}
|
|
597
|
+
}
|
|
270
598
|
|
|
271
|
-
|
|
599
|
+
export class ProfileService extends BaseService<Profile> {
|
|
600
|
+
constructor() {
|
|
601
|
+
super('users', 'profiles');
|
|
602
|
+
this.setPrimaryKeyFields(['id']);
|
|
603
|
+
}
|
|
272
604
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
}
|
|
605
|
+
// Tạo profile cho user
|
|
606
|
+
async createProfile(profileData: Omit<Profile, 'id'>): Promise<Profile | null> {
|
|
607
|
+
return await this.create(profileData);
|
|
608
|
+
}
|
|
291
609
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
610
|
+
// Lấy profile theo user_id
|
|
611
|
+
async getProfileByUserId(userId: number): Promise<Profile | null> {
|
|
612
|
+
return await this.findFirst({ user_id: userId });
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Cập nhật profile
|
|
616
|
+
async updateProfile(id: number, profileData: Partial<Profile>): Promise<Profile | null> {
|
|
617
|
+
return await this.update(id, profileData);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Lấy thông tin user và profile
|
|
621
|
+
async getUserWithProfile(userId: number): Promise<any> {
|
|
622
|
+
const dao = await this.init().then(() => this.dao!);
|
|
623
|
+
const sql = `
|
|
624
|
+
SELECT
|
|
625
|
+
u.id, u.username, u.email, u.created_at,
|
|
626
|
+
p.first_name, p.last_name, p.phone, p.address
|
|
627
|
+
FROM users u
|
|
628
|
+
LEFT JOIN profiles p ON u.id = p.user_id
|
|
629
|
+
WHERE u.id = ?
|
|
630
|
+
`;
|
|
631
|
+
const result = await dao.execute(sql, [userId]);
|
|
632
|
+
return result.rows[0] || null;
|
|
633
|
+
}
|
|
298
634
|
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Core Service
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
// services/CoreService.ts
|
|
641
|
+
import { BaseService } from '@dqcai/sqlite';
|
|
299
642
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
643
|
+
interface Setting {
|
|
644
|
+
key: string;
|
|
645
|
+
value: string;
|
|
646
|
+
description?: string;
|
|
303
647
|
}
|
|
304
648
|
|
|
305
|
-
|
|
306
|
-
|
|
649
|
+
export class SettingsService extends BaseService<Setting> {
|
|
650
|
+
constructor() {
|
|
651
|
+
super('core', 'settings');
|
|
652
|
+
this.setPrimaryKeyFields(['key']);
|
|
653
|
+
}
|
|
307
654
|
|
|
308
|
-
|
|
655
|
+
// Lấy giá trị setting
|
|
656
|
+
async getSetting(key: string): Promise<string | null> {
|
|
657
|
+
const setting = await this.findById(key);
|
|
658
|
+
return setting?.value || null;
|
|
659
|
+
}
|
|
309
660
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<head>
|
|
314
|
-
<title>Universal SQLite Browser Demo</title>
|
|
315
|
-
</head>
|
|
316
|
-
<body>
|
|
317
|
-
<div id="app"></div>
|
|
661
|
+
// Đặt giá trị setting
|
|
662
|
+
async setSetting(key: string, value: string, description?: string): Promise<void> {
|
|
663
|
+
const existing = await this.findById(key);
|
|
318
664
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
// Kết nối in-memory database
|
|
327
|
-
await db.connect(':memory:');
|
|
328
|
-
|
|
329
|
-
// Tạo bảng todos
|
|
330
|
-
await db.createTable('todos', {
|
|
331
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
332
|
-
title: 'TEXT NOT NULL',
|
|
333
|
-
completed: 'BOOLEAN DEFAULT 0',
|
|
334
|
-
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// Thêm một số todos
|
|
338
|
-
await db.insert('todos', { title: 'Learn SQLite', completed: 0 });
|
|
339
|
-
await db.insert('todos', { title: 'Build awesome app', completed: 0 });
|
|
340
|
-
|
|
341
|
-
// Hiển thị todos
|
|
342
|
-
const todos = await db.select('todos');
|
|
343
|
-
document.getElementById('app').innerHTML = `
|
|
344
|
-
<h1>My Todos</h1>
|
|
345
|
-
<ul>
|
|
346
|
-
${todos.rows.map(todo =>
|
|
347
|
-
`<li>${todo.title} - ${todo.completed ? '✅' : '⏳'}</li>`
|
|
348
|
-
).join('')}
|
|
349
|
-
</ul>
|
|
350
|
-
`;
|
|
351
|
-
|
|
352
|
-
} catch (error) {
|
|
353
|
-
console.error('Error:', error);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
browserDemo();
|
|
358
|
-
</script>
|
|
359
|
-
</body>
|
|
360
|
-
</html>
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### React Native Application
|
|
665
|
+
if (existing) {
|
|
666
|
+
await this.update(key, { value, description });
|
|
667
|
+
} else {
|
|
668
|
+
await this.create({ key, value, description });
|
|
669
|
+
}
|
|
670
|
+
}
|
|
364
671
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
672
|
+
// Lấy tất cả settings
|
|
673
|
+
async getAllSettings(): Promise<Setting[]> {
|
|
674
|
+
return await this.findAll({}, {
|
|
675
|
+
orderBy: [{ name: 'key', direction: 'ASC' }]
|
|
676
|
+
});
|
|
677
|
+
}
|
|
370
678
|
|
|
371
|
-
|
|
679
|
+
// Xóa setting
|
|
680
|
+
async deleteSetting(key: string): Promise<boolean> {
|
|
681
|
+
return await this.delete(key);
|
|
682
|
+
}
|
|
372
683
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
684
|
+
// Lấy nhiều settings cùng lúc
|
|
685
|
+
async getMultipleSettings(keys: string[]): Promise<Record<string, string>> {
|
|
686
|
+
const dao = await this.init().then(() => this.dao!);
|
|
687
|
+
const placeholders = keys.map(() => '?').join(',');
|
|
688
|
+
const sql = `SELECT key, value FROM settings WHERE key IN (${placeholders})`;
|
|
689
|
+
const result = await dao.execute(sql, keys);
|
|
690
|
+
|
|
691
|
+
const settings: Record<string, string> = {};
|
|
692
|
+
result.rows.forEach(row => {
|
|
693
|
+
settings[row.key] = row.value;
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
return settings;
|
|
697
|
+
}
|
|
377
698
|
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
## 5. Sử dụng trong ứng dụng
|
|
378
702
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
703
|
+
### Trong React Native
|
|
704
|
+
|
|
705
|
+
```typescript
|
|
706
|
+
// App.tsx hoặc index.js
|
|
707
|
+
import React, { useEffect, useState } from 'react';
|
|
708
|
+
import { View, Text, Button, Alert } from 'react-native';
|
|
709
|
+
import { DatabaseService } from './services/DatabaseService';
|
|
710
|
+
import { UserService, ProfileService } from './services/UserService';
|
|
711
|
+
import { SettingsService } from './services/CoreService';
|
|
712
|
+
|
|
713
|
+
const App = () => {
|
|
714
|
+
const [isDbReady, setIsDbReady] = useState(false);
|
|
715
|
+
const [userService] = useState(new UserService());
|
|
716
|
+
const [profileService] = useState(new ProfileService());
|
|
717
|
+
const [settingsService] = useState(new SettingsService());
|
|
382
718
|
|
|
383
719
|
useEffect(() => {
|
|
384
|
-
|
|
720
|
+
initializeDatabase();
|
|
721
|
+
|
|
722
|
+
return () => {
|
|
723
|
+
// Cleanup khi component unmount
|
|
724
|
+
DatabaseService.closeAll();
|
|
725
|
+
};
|
|
385
726
|
}, []);
|
|
386
727
|
|
|
387
|
-
const
|
|
728
|
+
const initializeDatabase = async () => {
|
|
388
729
|
try {
|
|
389
|
-
//
|
|
390
|
-
await
|
|
730
|
+
// Khởi tạo database
|
|
731
|
+
await DatabaseService.initialize();
|
|
391
732
|
|
|
392
|
-
//
|
|
393
|
-
|
|
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
|
-
});
|
|
733
|
+
// Đặt role cho user hiện tại
|
|
734
|
+
await DatabaseService.setUserRole(['user']);
|
|
401
735
|
|
|
402
|
-
//
|
|
403
|
-
|
|
736
|
+
// Khởi tạo services
|
|
737
|
+
await userService.init();
|
|
738
|
+
await profileService.init();
|
|
739
|
+
await settingsService.init();
|
|
404
740
|
|
|
741
|
+
setIsDbReady(true);
|
|
742
|
+
console.log('Database ready!');
|
|
743
|
+
} catch (error) {
|
|
744
|
+
console.error('Database initialization failed:', error);
|
|
745
|
+
Alert.alert('Error', 'Failed to initialize database');
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
const handleCreateUser = async () => {
|
|
750
|
+
if (!isDbReady) return;
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
const newUser = await userService.createUser({
|
|
754
|
+
username: 'john_doe',
|
|
755
|
+
email: 'john@example.com',
|
|
756
|
+
password: 'hashed_password'
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
if (newUser) {
|
|
760
|
+
// Tạo profile cho user
|
|
761
|
+
await profileService.createProfile({
|
|
762
|
+
user_id: newUser.id!,
|
|
763
|
+
first_name: 'John',
|
|
764
|
+
last_name: 'Doe',
|
|
765
|
+
phone: '+1234567890'
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
Alert.alert('Success', 'User created successfully');
|
|
769
|
+
}
|
|
405
770
|
} catch (error) {
|
|
406
|
-
console.error('
|
|
771
|
+
console.error('Error creating user:', error);
|
|
772
|
+
Alert.alert('Error', 'Failed to create user');
|
|
407
773
|
}
|
|
408
774
|
};
|
|
409
775
|
|
|
410
|
-
const
|
|
776
|
+
const handleGetAllUsers = async () => {
|
|
777
|
+
if (!isDbReady) return;
|
|
778
|
+
|
|
411
779
|
try {
|
|
412
|
-
const
|
|
413
|
-
|
|
780
|
+
const users = await userService.getAllUsers(1, 10);
|
|
781
|
+
console.log('Users:', users);
|
|
782
|
+
Alert.alert('Users', `Found ${users.length} users`);
|
|
414
783
|
} catch (error) {
|
|
415
|
-
console.error('
|
|
784
|
+
console.error('Error getting users:', error);
|
|
416
785
|
}
|
|
417
786
|
};
|
|
418
787
|
|
|
419
|
-
const
|
|
788
|
+
const handleSetSetting = async () => {
|
|
789
|
+
if (!isDbReady) return;
|
|
790
|
+
|
|
420
791
|
try {
|
|
421
|
-
await
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
792
|
+
await settingsService.setSetting(
|
|
793
|
+
'app_version',
|
|
794
|
+
'1.0.0',
|
|
795
|
+
'Current app version'
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
const version = await settingsService.getSetting('app_version');
|
|
799
|
+
Alert.alert('Setting', `App version: ${version}`);
|
|
426
800
|
} catch (error) {
|
|
427
|
-
console.error('
|
|
801
|
+
console.error('Error setting value:', error);
|
|
428
802
|
}
|
|
429
803
|
};
|
|
430
804
|
|
|
431
805
|
return (
|
|
432
|
-
<View style={{ flex: 1, padding: 20 }}>
|
|
433
|
-
<Text>
|
|
434
|
-
|
|
806
|
+
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
|
|
807
|
+
<Text>Database Status: {isDbReady ? 'Ready' : 'Initializing...'}</Text>
|
|
808
|
+
|
|
809
|
+
<Button
|
|
810
|
+
title="Create User"
|
|
811
|
+
onPress={handleCreateUser}
|
|
812
|
+
disabled={!isDbReady}
|
|
813
|
+
/>
|
|
814
|
+
|
|
815
|
+
<Button
|
|
816
|
+
title="Get All Users"
|
|
817
|
+
onPress={handleGetAllUsers}
|
|
818
|
+
disabled={!isDbReady}
|
|
819
|
+
/>
|
|
435
820
|
|
|
436
|
-
<
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
<View style={{ padding: 10, borderBottomWidth: 1 }}>
|
|
441
|
-
<Text>{item.name}</Text>
|
|
442
|
-
<Text>{item.email}</Text>
|
|
443
|
-
</View>
|
|
444
|
-
)}
|
|
821
|
+
<Button
|
|
822
|
+
title="Set App Setting"
|
|
823
|
+
onPress={handleSetSetting}
|
|
824
|
+
disabled={!isDbReady}
|
|
445
825
|
/>
|
|
446
826
|
</View>
|
|
447
827
|
);
|
|
448
|
-
}
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
export default App;
|
|
449
831
|
```
|
|
450
832
|
|
|
451
|
-
###
|
|
833
|
+
### Trong Node.js
|
|
452
834
|
|
|
453
835
|
```typescript
|
|
454
|
-
//
|
|
455
|
-
import
|
|
836
|
+
// app.ts
|
|
837
|
+
import express from 'express';
|
|
838
|
+
import { DatabaseService } from './services/DatabaseService';
|
|
839
|
+
import { UserService, ProfileService } from './services/UserService';
|
|
840
|
+
import { SettingsService } from './services/CoreService';
|
|
841
|
+
|
|
842
|
+
const app = express();
|
|
843
|
+
app.use(express.json());
|
|
456
844
|
|
|
457
|
-
|
|
845
|
+
// Services
|
|
846
|
+
const userService = new UserService();
|
|
847
|
+
const profileService = new ProfileService();
|
|
848
|
+
const settingsService = new SettingsService();
|
|
458
849
|
|
|
459
|
-
|
|
850
|
+
// Khởi tạo database khi start server
|
|
851
|
+
async function initializeApp() {
|
|
460
852
|
try {
|
|
461
|
-
|
|
853
|
+
console.log('Initializing database...');
|
|
462
854
|
|
|
463
|
-
//
|
|
464
|
-
await
|
|
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' });
|
|
855
|
+
// Khởi tạo DatabaseService
|
|
856
|
+
await DatabaseService.initialize();
|
|
475
857
|
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
console.log('Error logs:', errorLogs.rows);
|
|
858
|
+
// Set role admin cho server
|
|
859
|
+
await DatabaseService.setUserRole(['admin']);
|
|
479
860
|
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
.where('timestamp > datetime("now", "-1 hour")')
|
|
485
|
-
.orderBy('timestamp', 'DESC')
|
|
486
|
-
.limit(10)
|
|
487
|
-
.toSQL();
|
|
861
|
+
// Khởi tạo services
|
|
862
|
+
await userService.init();
|
|
863
|
+
await profileService.init();
|
|
864
|
+
await settingsService.init();
|
|
488
865
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
await db.close();
|
|
866
|
+
console.log('Database initialized successfully');
|
|
867
|
+
} catch (error) {
|
|
868
|
+
console.error('Database initialization failed:', error);
|
|
869
|
+
process.exit(1);
|
|
494
870
|
}
|
|
495
871
|
}
|
|
496
872
|
|
|
497
|
-
//
|
|
498
|
-
denoExample();
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
### Bun Application
|
|
873
|
+
// API Routes
|
|
502
874
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
875
|
+
// POST /users - Tạo user mới
|
|
876
|
+
app.post('/users', async (req, res) => {
|
|
877
|
+
try {
|
|
878
|
+
const { username, email, password } = req.body;
|
|
879
|
+
|
|
880
|
+
if (!username || !email || !password) {
|
|
881
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
882
|
+
}
|
|
506
883
|
|
|
507
|
-
const
|
|
884
|
+
const user = await userService.createUser({
|
|
885
|
+
username,
|
|
886
|
+
email,
|
|
887
|
+
password // Nên hash password trước khi lưu
|
|
888
|
+
});
|
|
508
889
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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);
|
|
890
|
+
res.status(201).json({ success: true, user });
|
|
891
|
+
} catch (error: any) {
|
|
892
|
+
console.error('Error creating user:', error);
|
|
893
|
+
res.status(400).json({ error: error.message });
|
|
532
894
|
}
|
|
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
|
-
}
|
|
895
|
+
});
|
|
546
896
|
|
|
547
|
-
//
|
|
548
|
-
|
|
549
|
-
|
|
897
|
+
// GET /users - Lấy danh sách users
|
|
898
|
+
app.get('/users', async (req, res) => {
|
|
899
|
+
try {
|
|
900
|
+
const page = parseInt(req.query.page as string) || 1;
|
|
901
|
+
const limit = parseInt(req.query.limit as string) || 10;
|
|
902
|
+
|
|
903
|
+
const users = await userService.getAllUsers(page, limit);
|
|
904
|
+
const total = await userService.getTotalUsers();
|
|
905
|
+
|
|
906
|
+
res.json({
|
|
907
|
+
success: true,
|
|
908
|
+
users,
|
|
909
|
+
pagination: {
|
|
910
|
+
page,
|
|
911
|
+
limit,
|
|
912
|
+
total,
|
|
913
|
+
pages: Math.ceil(total / limit)
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
} catch (error: any) {
|
|
917
|
+
console.error('Error getting users:', error);
|
|
918
|
+
res.status(500).json({ error: error.message });
|
|
919
|
+
}
|
|
920
|
+
});
|
|
550
921
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
### 1. Transaction Management
|
|
554
|
-
|
|
555
|
-
```typescript
|
|
556
|
-
import { SQLiteManager } from '@dqcai/sqlite';
|
|
557
|
-
|
|
558
|
-
async function transactionExample() {
|
|
559
|
-
const manager = new SQLiteManager();
|
|
560
|
-
const connection = await manager.connect('transactions.db');
|
|
561
|
-
|
|
922
|
+
// GET /users/:id - Lấy user theo ID
|
|
923
|
+
app.get('/users/:id', async (req, res) => {
|
|
562
924
|
try {
|
|
563
|
-
|
|
564
|
-
await
|
|
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
|
-
);
|
|
925
|
+
const id = parseInt(req.params.id);
|
|
926
|
+
const user = await userService.findById(id);
|
|
593
927
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (aliceBalance.rows[0].balance < 0) {
|
|
597
|
-
throw new Error('Insufficient funds');
|
|
928
|
+
if (!user) {
|
|
929
|
+
return res.status(404).json({ error: 'User not found' });
|
|
598
930
|
}
|
|
599
931
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
console.
|
|
603
|
-
|
|
604
|
-
// Hiển thị kết quả
|
|
605
|
-
const allAccounts = await connection.execute('SELECT * FROM accounts');
|
|
606
|
-
console.log('Final balances:', allAccounts.rows);
|
|
607
|
-
|
|
608
|
-
} catch (error) {
|
|
609
|
-
// Rollback nếu có lỗi
|
|
610
|
-
await connection.execute('ROLLBACK');
|
|
611
|
-
console.error('Transaction failed:', error);
|
|
612
|
-
} finally {
|
|
613
|
-
await connection.close();
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
transactionExample();
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
### 2. Complex Queries với JOIN
|
|
621
|
-
|
|
622
|
-
```typescript
|
|
623
|
-
async function complexQueryExample() {
|
|
624
|
-
const db = new UniversalSQLite();
|
|
625
|
-
await db.connect('ecommerce.db');
|
|
626
|
-
|
|
627
|
-
// Tạo schema phức tạp
|
|
628
|
-
await db.createTable('categories', {
|
|
629
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
630
|
-
name: 'TEXT NOT NULL UNIQUE',
|
|
631
|
-
description: 'TEXT'
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
await db.createTable('products', {
|
|
635
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
636
|
-
name: 'TEXT NOT NULL',
|
|
637
|
-
price: 'REAL NOT NULL',
|
|
638
|
-
category_id: 'INTEGER NOT NULL',
|
|
639
|
-
'FOREIGN KEY (category_id)': 'REFERENCES categories(id)'
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
await db.createTable('orders', {
|
|
643
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
644
|
-
product_id: 'INTEGER NOT NULL',
|
|
645
|
-
quantity: 'INTEGER NOT NULL',
|
|
646
|
-
order_date: 'DATETIME DEFAULT CURRENT_TIMESTAMP',
|
|
647
|
-
'FOREIGN KEY (product_id)': 'REFERENCES products(id)'
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
// Thêm dữ liệu mẫu
|
|
651
|
-
await db.insert('categories', { name: 'Electronics', description: 'Electronic devices' });
|
|
652
|
-
await db.insert('categories', { name: 'Books', description: 'Books and literature' });
|
|
653
|
-
|
|
654
|
-
await db.insert('products', { name: 'iPhone 15', price: 999, category_id: 1 });
|
|
655
|
-
await db.insert('products', { name: 'MacBook Pro', price: 2499, category_id: 1 });
|
|
656
|
-
await db.insert('products', { name: 'JavaScript Guide', price: 29.99, category_id: 2 });
|
|
657
|
-
|
|
658
|
-
await db.insert('orders', { product_id: 1, quantity: 2 });
|
|
659
|
-
await db.insert('orders', { product_id: 2, quantity: 1 });
|
|
660
|
-
await db.insert('orders', { product_id: 1, quantity: 1 });
|
|
661
|
-
|
|
662
|
-
// Query phức tạp với JOIN
|
|
663
|
-
const salesReport = await db.query(`
|
|
664
|
-
SELECT
|
|
665
|
-
c.name as category,
|
|
666
|
-
p.name as product,
|
|
667
|
-
SUM(o.quantity) as total_sold,
|
|
668
|
-
SUM(o.quantity * p.price) as total_revenue,
|
|
669
|
-
AVG(p.price) as avg_price
|
|
670
|
-
FROM orders o
|
|
671
|
-
JOIN products p ON o.product_id = p.id
|
|
672
|
-
JOIN categories c ON p.category_id = c.id
|
|
673
|
-
GROUP BY c.id, p.id
|
|
674
|
-
ORDER BY total_revenue DESC
|
|
675
|
-
`);
|
|
676
|
-
|
|
677
|
-
console.log('Sales Report:', salesReport.rows);
|
|
678
|
-
|
|
679
|
-
// Subquery example
|
|
680
|
-
const topSellingCategory = await db.query(`
|
|
681
|
-
SELECT
|
|
682
|
-
c.name,
|
|
683
|
-
(SELECT SUM(quantity) FROM orders o
|
|
684
|
-
JOIN products p ON o.product_id = p.id
|
|
685
|
-
WHERE p.category_id = c.id) as total_quantity
|
|
686
|
-
FROM categories c
|
|
687
|
-
ORDER BY total_quantity DESC
|
|
688
|
-
LIMIT 1
|
|
689
|
-
`);
|
|
690
|
-
|
|
691
|
-
console.log('Top selling category:', topSellingCategory.rows[0]);
|
|
692
|
-
|
|
693
|
-
await db.close();
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
complexQueryExample();
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
### 3. Real-time Data Synchronization
|
|
700
|
-
|
|
701
|
-
```typescript
|
|
702
|
-
async function realtimeExample() {
|
|
703
|
-
const db = new UniversalSQLite();
|
|
704
|
-
await db.connect('realtime.db');
|
|
705
|
-
|
|
706
|
-
// Tạo bảng cho sync
|
|
707
|
-
await db.createTable('sync_queue', {
|
|
708
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
709
|
-
table_name: 'TEXT NOT NULL',
|
|
710
|
-
operation: 'TEXT NOT NULL', // INSERT, UPDATE, DELETE
|
|
711
|
-
data: 'TEXT', // JSON data
|
|
712
|
-
synced: 'BOOLEAN DEFAULT 0',
|
|
713
|
-
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
await db.createTable('users', {
|
|
717
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
718
|
-
name: 'TEXT NOT NULL',
|
|
719
|
-
email: 'TEXT UNIQUE',
|
|
720
|
-
last_modified: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// Helper function để track changes
|
|
724
|
-
async function trackChange(table: string, operation: string, data: any) {
|
|
725
|
-
await db.insert('sync_queue', {
|
|
726
|
-
table_name: table,
|
|
727
|
-
operation: operation,
|
|
728
|
-
data: JSON.stringify(data)
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// Wrapper functions với tracking
|
|
733
|
-
async function insertUser(userData: any) {
|
|
734
|
-
const result = await db.insert('users', userData);
|
|
735
|
-
await trackChange('users', 'INSERT', { ...userData, id: result.lastInsertRowId });
|
|
736
|
-
return result;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
async function updateUser(id: number, userData: any) {
|
|
740
|
-
const result = await db.update('users',
|
|
741
|
-
{ ...userData, last_modified: new Date().toISOString() },
|
|
742
|
-
'id = ?',
|
|
743
|
-
[id]
|
|
744
|
-
);
|
|
745
|
-
await trackChange('users', 'UPDATE', { id, ...userData });
|
|
746
|
-
return result;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
async function deleteUser(id: number) {
|
|
750
|
-
const result = await db.delete('users', 'id = ?', [id]);
|
|
751
|
-
await trackChange('users', 'DELETE', { id });
|
|
752
|
-
return result;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// Sync function
|
|
756
|
-
async function syncToServer() {
|
|
757
|
-
const unsyncedChanges = await db.select('sync_queue', 'synced = 0');
|
|
758
|
-
|
|
759
|
-
for (const change of unsyncedChanges.rows) {
|
|
760
|
-
try {
|
|
761
|
-
// Giả lập API call
|
|
762
|
-
console.log(`Syncing ${change.operation} on ${change.table_name}:`,
|
|
763
|
-
JSON.parse(change.data));
|
|
764
|
-
|
|
765
|
-
// Đánh dấu đã sync
|
|
766
|
-
await db.update('sync_queue',
|
|
767
|
-
{ synced: 1 },
|
|
768
|
-
'id = ?',
|
|
769
|
-
[change.id]
|
|
770
|
-
);
|
|
771
|
-
|
|
772
|
-
} catch (error) {
|
|
773
|
-
console.error('Sync failed for change:', change.id, error);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
932
|
+
res.json({ success: true, user });
|
|
933
|
+
} catch (error: any) {
|
|
934
|
+
console.error('Error getting user:', error);
|
|
935
|
+
res.status(500).json({ error: error.message });
|
|
776
936
|
}
|
|
777
|
-
|
|
778
|
-
// Demo usage
|
|
779
|
-
await insertUser({ name: 'Alice', email: 'alice@example.com' });
|
|
780
|
-
await insertUser({ name: 'Bob', email: 'bob@example.com' });
|
|
781
|
-
await updateUser(1, { email: 'alice.updated@example.com' });
|
|
782
|
-
|
|
783
|
-
// Sync changes
|
|
784
|
-
await syncToServer();
|
|
785
|
-
|
|
786
|
-
await db.close();
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
realtimeExample();
|
|
790
|
-
```
|
|
791
|
-
|
|
792
|
-
### 4. Database Migration System
|
|
937
|
+
});
|
|
793
938
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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}`);
|
|
939
|
+
// PUT /users/:id - Cập nhật user
|
|
940
|
+
app.put('/users/:id', async (req, res) => {
|
|
941
|
+
try {
|
|
942
|
+
const id = parseInt(req.params.id);
|
|
943
|
+
const updates = req.body;
|
|
819
944
|
|
|
820
|
-
const
|
|
821
|
-
this.migration1_initial,
|
|
822
|
-
this.migration2_addUserProfiles,
|
|
823
|
-
this.migration3_addIndexes,
|
|
824
|
-
this.migration4_addAuditLog
|
|
825
|
-
];
|
|
945
|
+
const user = await userService.updateUser(id, updates);
|
|
826
946
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
await migrations[i].call(this);
|
|
830
|
-
await this.setVersion(i + 1);
|
|
831
|
-
console.log(`Migration ${i + 1} completed`);
|
|
947
|
+
if (!user) {
|
|
948
|
+
return res.status(404).json({ error: 'User not found' });
|
|
832
949
|
}
|
|
833
950
|
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
|
|
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
|
-
`);
|
|
951
|
+
res.json({ success: true, user });
|
|
952
|
+
} catch (error: any) {
|
|
953
|
+
console.error('Error updating user:', error);
|
|
954
|
+
res.status(400).json({ error: error.message });
|
|
886
955
|
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Sử dụng migration
|
|
890
|
-
async function migrationExample() {
|
|
891
|
-
const db = new UniversalSQLite();
|
|
892
|
-
await db.connect('app_with_migrations.db');
|
|
893
|
-
|
|
894
|
-
const migration = new DatabaseMigration(db);
|
|
895
|
-
await migration.migrate();
|
|
896
|
-
|
|
897
|
-
// Test data sau migration
|
|
898
|
-
await db.insert('users', {
|
|
899
|
-
username: 'testuser',
|
|
900
|
-
email: 'test@example.com',
|
|
901
|
-
first_name: 'Test',
|
|
902
|
-
last_name: 'User'
|
|
903
|
-
});
|
|
904
|
-
|
|
905
|
-
// Update để trigger audit
|
|
906
|
-
await db.update('users',
|
|
907
|
-
{ email: 'test.updated@example.com' },
|
|
908
|
-
'username = ?',
|
|
909
|
-
['testuser']
|
|
910
|
-
);
|
|
911
|
-
|
|
912
|
-
// Kiểm tra audit log
|
|
913
|
-
const auditLogs = await db.select('audit_logs');
|
|
914
|
-
console.log('Audit logs:', auditLogs.rows);
|
|
915
|
-
|
|
916
|
-
await db.close();
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
migrationExample();
|
|
920
|
-
```
|
|
921
|
-
|
|
922
|
-
### 5. Full-Text Search
|
|
923
|
-
|
|
924
|
-
```typescript
|
|
925
|
-
async function fullTextSearchExample() {
|
|
926
|
-
const db = new UniversalSQLite();
|
|
927
|
-
await db.connect('search.db');
|
|
928
|
-
|
|
929
|
-
// Tạo FTS table
|
|
930
|
-
await db.query(`
|
|
931
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts
|
|
932
|
-
USING fts5(id, title, content, author)
|
|
933
|
-
`);
|
|
934
|
-
|
|
935
|
-
// Tạo bảng articles thường
|
|
936
|
-
await db.createTable('articles', {
|
|
937
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
938
|
-
title: 'TEXT NOT NULL',
|
|
939
|
-
content: 'TEXT NOT NULL',
|
|
940
|
-
author: 'TEXT NOT NULL',
|
|
941
|
-
published_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
// Thêm dữ liệu mẫu
|
|
945
|
-
const articles = [
|
|
946
|
-
{
|
|
947
|
-
title: 'Introduction to SQLite',
|
|
948
|
-
content: 'SQLite is a lightweight database engine that is perfect for mobile and web applications. It provides ACID compliance and supports most of the SQL standard.',
|
|
949
|
-
author: 'John Doe'
|
|
950
|
-
},
|
|
951
|
-
{
|
|
952
|
-
title: 'Advanced TypeScript Patterns',
|
|
953
|
-
content: 'TypeScript provides powerful type system features including generics, conditional types, and mapped types that enable building robust applications.',
|
|
954
|
-
author: 'Jane Smith'
|
|
955
|
-
},
|
|
956
|
-
{
|
|
957
|
-
title: 'React Native Performance Tips',
|
|
958
|
-
content: 'Optimizing React Native apps requires understanding of the bridge, native modules, and proper state management patterns.',
|
|
959
|
-
author: 'Bob Johnson'
|
|
960
|
-
}
|
|
961
|
-
];
|
|
962
|
-
|
|
963
|
-
for (const article of articles) {
|
|
964
|
-
const result = await db.insert('articles', article);
|
|
965
|
-
// Thêm vào FTS index
|
|
966
|
-
await db.query(`
|
|
967
|
-
INSERT INTO articles_fts (id, title, content, author)
|
|
968
|
-
VALUES (?, ?, ?, ?)
|
|
969
|
-
`, [result.lastInsertRowId, article.title, article.content, article.author]);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// Full-text search
|
|
973
|
-
const searchResults = await db.query(`
|
|
974
|
-
SELECT
|
|
975
|
-
a.id, a.title, a.author, a.published_at,
|
|
976
|
-
snippet(articles_fts, 1, '<b>', '</b>', '...', 50) as title_snippet,
|
|
977
|
-
snippet(articles_fts, 2, '<b>', '</b>', '...', 100) as content_snippet
|
|
978
|
-
FROM articles_fts
|
|
979
|
-
JOIN articles a ON articles_fts.id = a.id
|
|
980
|
-
WHERE articles_fts MATCH ?
|
|
981
|
-
ORDER BY rank
|
|
982
|
-
`, ['SQLite OR TypeScript']);
|
|
983
|
-
|
|
984
|
-
console.log('Search results:', searchResults.rows);
|
|
985
|
-
|
|
986
|
-
// Advanced search với bxm25 ranking
|
|
987
|
-
const advancedSearch = await db.query(`
|
|
988
|
-
SELECT
|
|
989
|
-
a.*,
|
|
990
|
-
bm25(articles_fts) as relevance_score
|
|
991
|
-
FROM articles_fts
|
|
992
|
-
JOIN articles a ON articles_fts.id = a.id
|
|
993
|
-
WHERE articles_fts MATCH ?
|
|
994
|
-
ORDER BY relevance_score
|
|
995
|
-
`, ['application AND (mobile OR web)']);
|
|
996
|
-
|
|
997
|
-
console.log('Advanced search:', advancedSearch.rows);
|
|
998
|
-
|
|
999
|
-
await db.close();
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
fullTextSearchExample();
|
|
1003
|
-
```
|
|
1004
|
-
|
|
1005
|
-
### 6. Data Analytics và Reporting
|
|
1006
|
-
|
|
1007
|
-
```typescript
|
|
1008
|
-
async function analyticsExample() {
|
|
1009
|
-
const db = new UniversalSQLite();
|
|
1010
|
-
await db.connect('analytics.db');
|
|
1011
|
-
|
|
1012
|
-
// Tạo schema cho analytics
|
|
1013
|
-
await db.createTable('events', {
|
|
1014
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1015
|
-
user_id: 'INTEGER NOT NULL',
|
|
1016
|
-
event_type: 'TEXT NOT NULL',
|
|
1017
|
-
event_data: 'TEXT', // JSON
|
|
1018
|
-
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1019
|
-
});
|
|
1020
|
-
|
|
1021
|
-
await db.createTable('users_analytics', {
|
|
1022
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1023
|
-
user_id: 'INTEGER UNIQUE NOT NULL',
|
|
1024
|
-
total_events: 'INTEGER DEFAULT 0',
|
|
1025
|
-
first_seen: 'DATETIME',
|
|
1026
|
-
last_seen: 'DATETIME',
|
|
1027
|
-
session_count: 'INTEGER DEFAULT 0'
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
// Thêm dữ liệu events mẫu
|
|
1031
|
-
const sampleEvents = [
|
|
1032
|
-
{ user_id: 1, event_type: 'page_view', event_data: '{"page": "/home"}' },
|
|
1033
|
-
{ user_id: 1, event_type: 'click', event_data: '{"element": "signup_button"}' },
|
|
1034
|
-
{ user_id: 2, event_type: 'page_view', event_data: '{"page": "/products"}' },
|
|
1035
|
-
{ user_id: 1, event_type: 'purchase', event_data: '{"product_id": 123, "amount": 99.99}' },
|
|
1036
|
-
{ user_id: 3, event_type: 'page_view', event_data: '{"page": "/home"}' },
|
|
1037
|
-
{ user_id: 2, event_type: 'purchase', event_data: '{"product_id": 456, "amount": 149.99}' }
|
|
1038
|
-
];
|
|
1039
|
-
|
|
1040
|
-
for (const event of sampleEvents) {
|
|
1041
|
-
await db.insert('events', event);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// Analytics queries
|
|
1045
|
-
|
|
1046
|
-
// 1. User engagement report
|
|
1047
|
-
const userEngagement = await db.query(`
|
|
1048
|
-
SELECT
|
|
1049
|
-
user_id,
|
|
1050
|
-
COUNT(*) as total_events,
|
|
1051
|
-
COUNT(CASE WHEN event_type = 'purchase' THEN 1 END) as purchases,
|
|
1052
|
-
MIN(timestamp) as first_seen,
|
|
1053
|
-
MAX(timestamp) as last_seen,
|
|
1054
|
-
ROUND(
|
|
1055
|
-
JULIANDAY(MAX(timestamp)) - JULIANDAY(MIN(timestamp))
|
|
1056
|
-
) as days_active
|
|
1057
|
-
FROM events
|
|
1058
|
-
GROUP BY user_id
|
|
1059
|
-
ORDER BY total_events DESC
|
|
1060
|
-
`);
|
|
1061
|
-
|
|
1062
|
-
console.log('User Engagement Report:', userEngagement.rows);
|
|
1063
|
-
|
|
1064
|
-
// 2. Revenue analytics
|
|
1065
|
-
const revenueReport = await db.query(`
|
|
1066
|
-
SELECT
|
|
1067
|
-
DATE(timestamp) as date,
|
|
1068
|
-
COUNT(*) as purchase_count,
|
|
1069
|
-
SUM(CAST(json_extract(event_data, '$.amount') AS REAL)) as total_revenue,
|
|
1070
|
-
AVG(CAST(json_extract(event_data, '$.amount') AS REAL)) as avg_order_value
|
|
1071
|
-
FROM events
|
|
1072
|
-
WHERE event_type = 'purchase'
|
|
1073
|
-
GROUP BY DATE(timestamp)
|
|
1074
|
-
ORDER BY date DESC
|
|
1075
|
-
`);
|
|
1076
|
-
|
|
1077
|
-
console.log('Revenue Report:', revenueReport.rows);
|
|
1078
|
-
|
|
1079
|
-
// 3. Funnel analysis
|
|
1080
|
-
const funnelAnalysis = await db.query(`
|
|
1081
|
-
WITH user_funnel AS (
|
|
1082
|
-
SELECT
|
|
1083
|
-
user_id,
|
|
1084
|
-
MAX(CASE WHEN event_type = 'page_view' THEN 1 ELSE 0 END) as viewed,
|
|
1085
|
-
MAX(CASE WHEN event_type = 'click' THEN 1 ELSE 0 END) as clicked,
|
|
1086
|
-
MAX(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) as purchased
|
|
1087
|
-
FROM events
|
|
1088
|
-
GROUP BY user_id
|
|
1089
|
-
)
|
|
1090
|
-
SELECT
|
|
1091
|
-
SUM(viewed) as total_users,
|
|
1092
|
-
SUM(clicked) as users_clicked,
|
|
1093
|
-
SUM(purchased) as users_purchased,
|
|
1094
|
-
ROUND(SUM(clicked) * 100.0 / SUM(viewed), 2) as click_rate_percent,
|
|
1095
|
-
ROUND(SUM(purchased) * 100.0 / SUM(clicked), 2) as conversion_rate_percent
|
|
1096
|
-
FROM user_funnel
|
|
1097
|
-
`);
|
|
1098
|
-
|
|
1099
|
-
console.log('Funnel Analysis:', funnelAnalysis.rows[0]);
|
|
1100
|
-
|
|
1101
|
-
await db.close();
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
analyticsExample();
|
|
1105
|
-
```
|
|
1106
|
-
|
|
1107
|
-
## 🔍 Debugging và Monitoring
|
|
1108
|
-
|
|
1109
|
-
### 1. Query Performance Monitoring
|
|
956
|
+
});
|
|
1110
957
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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;
|
|
958
|
+
// DELETE /users/:id - Xóa user
|
|
959
|
+
app.delete('/users/:id', async (req, res) => {
|
|
960
|
+
try {
|
|
961
|
+
const id = parseInt(req.params.id);
|
|
962
|
+
const success = await userService.delete(id);
|
|
1124
963
|
|
|
1125
|
-
|
|
964
|
+
if (!success) {
|
|
965
|
+
return res.status(404).json({ error: 'User not found' });
|
|
966
|
+
}
|
|
1126
967
|
|
|
1127
|
-
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
|
|
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
|
-
});
|
|
968
|
+
res.json({ success: true, message: 'User deleted' });
|
|
969
|
+
} catch (error: any) {
|
|
970
|
+
console.error('Error deleting user:', error);
|
|
971
|
+
res.status(500).json({ error: error.message });
|
|
1136
972
|
}
|
|
1137
|
-
}
|
|
973
|
+
});
|
|
1138
974
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
-
});
|
|
975
|
+
// POST /users/:id/profile - Tạo profile cho user
|
|
976
|
+
app.post('/users/:id/profile', async (req, res) => {
|
|
977
|
+
try {
|
|
978
|
+
const user_id = parseInt(req.params.id);
|
|
979
|
+
const profileData = { ...req.body, user_id };
|
|
980
|
+
|
|
981
|
+
const profile = await profileService.createProfile(profileData);
|
|
982
|
+
res.status(201).json({ success: true, profile });
|
|
983
|
+
} catch (error: any) {
|
|
984
|
+
console.error('Error creating profile:', error);
|
|
985
|
+
res.status(400).json({ error: error.message });
|
|
1160
986
|
}
|
|
1161
|
-
|
|
1162
|
-
// Test query performance
|
|
1163
|
-
const { result, duration } = await monitor.queryWithTiming(`
|
|
1164
|
-
SELECT category, COUNT(*) as count, AVG(value) as avg_value
|
|
1165
|
-
FROM large_table
|
|
1166
|
-
GROUP BY category
|
|
1167
|
-
ORDER BY count DESC
|
|
1168
|
-
`);
|
|
1169
|
-
|
|
1170
|
-
console.log('Aggregation result:', result.rows);
|
|
1171
|
-
|
|
1172
|
-
// Explain query
|
|
1173
|
-
await monitor.explainQuery('SELECT * FROM large_table WHERE value > 500');
|
|
1174
|
-
|
|
1175
|
-
await db.close();
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
performanceExample();
|
|
1179
|
-
```
|
|
1180
|
-
|
|
1181
|
-
### 2. Connection Pool (Advanced)
|
|
987
|
+
});
|
|
1182
988
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
async getConnection(dbPath: string): Promise<UniversalSQLite> {
|
|
1189
|
-
if (this.connections.has(dbPath)) {
|
|
1190
|
-
return this.connections.get(dbPath)!;
|
|
1191
|
-
}
|
|
989
|
+
// GET /users/:id/full - Lấy user với profile
|
|
990
|
+
app.get('/users/:id/full', async (req, res) => {
|
|
991
|
+
try {
|
|
992
|
+
const user_id = parseInt(req.params.id);
|
|
993
|
+
const userWithProfile = await profileService.getUserWithProfile(user_id);
|
|
1192
994
|
|
|
1193
|
-
if (
|
|
1194
|
-
|
|
995
|
+
if (!userWithProfile) {
|
|
996
|
+
return res.status(404).json({ error: 'User not found' });
|
|
1195
997
|
}
|
|
1196
998
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
return db;
|
|
999
|
+
res.json({ success: true, user: userWithProfile });
|
|
1000
|
+
} catch (error: any) {
|
|
1001
|
+
console.error('Error getting user with profile:', error);
|
|
1002
|
+
res.status(500).json({ error: error.message });
|
|
1202
1003
|
}
|
|
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
|
-
}
|
|
1004
|
+
});
|
|
1211
1005
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1006
|
+
// Settings API
|
|
1007
|
+
app.get('/settings/:key', async (req, res) => {
|
|
1215
1008
|
try {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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();
|
|
1009
|
+
const value = await settingsService.getSetting(req.params.key);
|
|
1010
|
+
res.json({ success: true, value });
|
|
1011
|
+
} catch (error: any) {
|
|
1012
|
+
console.error('Error getting setting:', error);
|
|
1013
|
+
res.status(500).json({ error: error.message });
|
|
1252
1014
|
}
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
connectionPoolExample();
|
|
1256
|
-
```
|
|
1257
|
-
|
|
1258
|
-
## 🛠️ Configuration và Customization
|
|
1015
|
+
});
|
|
1259
1016
|
|
|
1260
|
-
|
|
1017
|
+
app.post('/settings', async (req, res) => {
|
|
1018
|
+
try {
|
|
1019
|
+
const { key, value, description } = req.body;
|
|
1020
|
+
await settingsService.setSetting(key, value, description);
|
|
1021
|
+
res.json({ success: true, message: 'Setting saved' });
|
|
1022
|
+
} catch (error: any) {
|
|
1023
|
+
console.error('Error setting value:', error);
|
|
1024
|
+
res.status(500).json({ error: error.message });
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1261
1027
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
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
|
-
```
|
|
1028
|
+
// Graceful shutdown
|
|
1029
|
+
process.on('SIGINT', async () => {
|
|
1030
|
+
console.log('Shutting down server...');
|
|
1031
|
+
await DatabaseService.closeAll();
|
|
1032
|
+
process.exit(0);
|
|
1033
|
+
});
|
|
1293
1034
|
|
|
1294
|
-
|
|
1035
|
+
process.on('SIGTERM', async () => {
|
|
1036
|
+
console.log('Shutting down server...');
|
|
1037
|
+
await DatabaseService.closeAll();
|
|
1038
|
+
process.exit(0);
|
|
1039
|
+
});
|
|
1295
1040
|
|
|
1296
|
-
|
|
1297
|
-
|
|
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
|
-
}
|
|
1041
|
+
// Start server
|
|
1042
|
+
const PORT = process.env.PORT || 3000;
|
|
1322
1043
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
await db.createTable('users', {
|
|
1328
|
-
id: 'INTEGER PRIMARY KEY',
|
|
1329
|
-
email: 'TEXT UNIQUE NOT NULL'
|
|
1330
|
-
});
|
|
1331
|
-
|
|
1332
|
-
// Safe insert với error handling
|
|
1333
|
-
const user1 = await DatabaseErrorHandler.safeExecute(async () => {
|
|
1334
|
-
return await db.insert('users', { email: 'test@example.com' });
|
|
1335
|
-
});
|
|
1336
|
-
|
|
1337
|
-
// Duplicate insert sẽ fail gracefully
|
|
1338
|
-
const user2 = await DatabaseErrorHandler.safeExecute(async () => {
|
|
1339
|
-
return await db.insert('users', { email: 'test@example.com' });
|
|
1044
|
+
initializeApp().then(() => {
|
|
1045
|
+
app.listen(PORT, () => {
|
|
1046
|
+
console.log(`Server running on port ${PORT}`);
|
|
1340
1047
|
});
|
|
1341
|
-
|
|
1342
|
-
console.log('User1 result:', user1);
|
|
1343
|
-
console.log('User2 result:', user2); // null due to unique constraint
|
|
1344
|
-
|
|
1345
|
-
await db.close();
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
errorHandlingExample();
|
|
1048
|
+
});
|
|
1349
1049
|
```
|
|
1350
1050
|
|
|
1351
|
-
##
|
|
1051
|
+
## 6. Quản lý Database với DatabaseManager
|
|
1352
1052
|
|
|
1353
|
-
###
|
|
1053
|
+
### Mở/Đóng Connections
|
|
1354
1054
|
|
|
1355
1055
|
```typescript
|
|
1356
|
-
//
|
|
1357
|
-
import {
|
|
1358
|
-
import { StyleSheet, Text, View, Button, FlatList } from 'react-native';
|
|
1359
|
-
import { useState, useEffect } from 'react';
|
|
1360
|
-
import UniversalSQLite from '@dqcai/sqlite';
|
|
1361
|
-
|
|
1362
|
-
const db = new UniversalSQLite();
|
|
1363
|
-
|
|
1364
|
-
export default function App() {
|
|
1365
|
-
const [todos, setTodos] = useState([]);
|
|
1366
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
1056
|
+
// DatabaseHelper.ts
|
|
1057
|
+
import { DatabaseManager } from '@dqcai/sqlite';
|
|
1367
1058
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1059
|
+
export class DatabaseHelper {
|
|
1060
|
+
|
|
1061
|
+
// Kiểm tra trạng thái kết nối
|
|
1062
|
+
static checkConnectionStatus(): void {
|
|
1063
|
+
const connections = DatabaseManager.getConnections();
|
|
1064
|
+
const count = DatabaseManager.getConnectionCount();
|
|
1065
|
+
const activeList = DatabaseManager.listConnections();
|
|
1066
|
+
|
|
1067
|
+
console.log('Active connections:', count);
|
|
1068
|
+
console.log('Connection list:', activeList);
|
|
1069
|
+
console.log('Connection details:', connections);
|
|
1070
|
+
}
|
|
1371
1071
|
|
|
1372
|
-
|
|
1072
|
+
// Đóng kết nối cụ thể
|
|
1073
|
+
static async closeSpecificConnection(dbKey: string): Promise<void> {
|
|
1373
1074
|
try {
|
|
1374
|
-
await
|
|
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();
|
|
1075
|
+
await DatabaseManager.closeConnection(dbKey);
|
|
1076
|
+
console.log(`Connection ${dbKey} closed`);
|
|
1385
1077
|
} catch (error) {
|
|
1386
|
-
console.error(
|
|
1387
|
-
} finally {
|
|
1388
|
-
setIsLoading(false);
|
|
1078
|
+
console.error(`Error closing connection ${dbKey}:`, error);
|
|
1389
1079
|
}
|
|
1390
|
-
}
|
|
1080
|
+
}
|
|
1391
1081
|
|
|
1392
|
-
|
|
1082
|
+
// Mở lại kết nối
|
|
1083
|
+
static async reopenConnection(dbKey: string): Promise<void> {
|
|
1393
1084
|
try {
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1085
|
+
const dao = await DatabaseManager.getLazyLoading(dbKey);
|
|
1086
|
+
console.log(`Connection ${dbKey} reopened`);
|
|
1087
|
+
return dao;
|
|
1396
1088
|
} catch (error) {
|
|
1397
|
-
console.error(
|
|
1089
|
+
console.error(`Error reopening connection ${dbKey}:`, error);
|
|
1090
|
+
throw error;
|
|
1398
1091
|
}
|
|
1399
|
-
}
|
|
1092
|
+
}
|
|
1400
1093
|
|
|
1401
|
-
|
|
1094
|
+
// Đảm bảo kết nối tồn tại
|
|
1095
|
+
static async ensureConnection(dbKey: string): Promise<void> {
|
|
1402
1096
|
try {
|
|
1403
|
-
await
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
});
|
|
1407
|
-
loadTodos();
|
|
1097
|
+
const dao = await DatabaseManager.ensureDatabaseConnection(dbKey);
|
|
1098
|
+
console.log(`Connection ${dbKey} ensured`);
|
|
1099
|
+
return dao;
|
|
1408
1100
|
} catch (error) {
|
|
1409
|
-
console.error(
|
|
1101
|
+
console.error(`Error ensuring connection ${dbKey}:`, error);
|
|
1102
|
+
throw error;
|
|
1410
1103
|
}
|
|
1411
|
-
}
|
|
1104
|
+
}
|
|
1412
1105
|
|
|
1413
|
-
|
|
1106
|
+
// Thực hiện transaction cross-schema
|
|
1107
|
+
static async executeTransactionAcrossSchemas(
|
|
1108
|
+
schemas: string[],
|
|
1109
|
+
operations: (daos: Record<string, any>) => Promise<void>
|
|
1110
|
+
): Promise<void> {
|
|
1414
1111
|
try {
|
|
1415
|
-
await
|
|
1416
|
-
|
|
1417
|
-
'id = ?',
|
|
1418
|
-
[id]
|
|
1419
|
-
);
|
|
1420
|
-
loadTodos();
|
|
1112
|
+
await DatabaseManager.executeCrossSchemaTransaction(schemas, operations);
|
|
1113
|
+
console.log('Cross-schema transaction completed successfully');
|
|
1421
1114
|
} catch (error) {
|
|
1422
|
-
console.error('
|
|
1115
|
+
console.error('Cross-schema transaction failed:', error);
|
|
1116
|
+
throw error;
|
|
1423
1117
|
}
|
|
1424
|
-
};
|
|
1425
|
-
|
|
1426
|
-
if (isLoading) {
|
|
1427
|
-
return (
|
|
1428
|
-
<View style={styles.container}>
|
|
1429
|
-
<Text>Loading database...</Text>
|
|
1430
|
-
</View>
|
|
1431
|
-
);
|
|
1432
1118
|
}
|
|
1433
1119
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
<FlatList
|
|
1442
|
-
data={todos}
|
|
1443
|
-
keyExtractor={item => item.id.toString()}
|
|
1444
|
-
renderItem={({ item }) => (
|
|
1445
|
-
<View style={styles.todoItem}>
|
|
1446
|
-
<Text style={[styles.todoText, item.completed && styles.completed]}>
|
|
1447
|
-
{item.title}
|
|
1448
|
-
</Text>
|
|
1449
|
-
<Button
|
|
1450
|
-
title={item.completed ? "Undo" : "Done"}
|
|
1451
|
-
onPress={() => toggleTodo(item.id, item.completed)}
|
|
1452
|
-
/>
|
|
1453
|
-
</View>
|
|
1454
|
-
)}
|
|
1455
|
-
/>
|
|
1456
|
-
|
|
1457
|
-
<StatusBar style="auto" />
|
|
1458
|
-
</View>
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
const styles = StyleSheet.create({
|
|
1463
|
-
container: {
|
|
1464
|
-
flex: 1,
|
|
1465
|
-
backgroundColor: '#fff',
|
|
1466
|
-
alignItems: 'center',
|
|
1467
|
-
justifyContent: 'center',
|
|
1468
|
-
padding: 20,
|
|
1469
|
-
},
|
|
1470
|
-
title: {
|
|
1471
|
-
fontSize: 24,
|
|
1472
|
-
fontWeight: 'bold',
|
|
1473
|
-
marginBottom: 20,
|
|
1474
|
-
},
|
|
1475
|
-
todoItem: {
|
|
1476
|
-
flexDirection: 'row',
|
|
1477
|
-
alignItems: 'center',
|
|
1478
|
-
padding: 10,
|
|
1479
|
-
borderBottomWidth: 1,
|
|
1480
|
-
borderBottomColor: '#ccc',
|
|
1481
|
-
},
|
|
1482
|
-
todoText: {
|
|
1483
|
-
flex: 1,
|
|
1484
|
-
fontSize: 16,
|
|
1485
|
-
},
|
|
1486
|
-
completed: {
|
|
1487
|
-
textDecorationLine: 'line-through',
|
|
1488
|
-
color: '#888',
|
|
1489
|
-
},
|
|
1490
|
-
});
|
|
1491
|
-
```
|
|
1492
|
-
|
|
1493
|
-
### Next.js API Routes
|
|
1494
|
-
|
|
1495
|
-
```typescript
|
|
1496
|
-
// pages/api/users/[id].ts
|
|
1497
|
-
import { NextApiRequest, NextApiResponse } from 'next';
|
|
1498
|
-
import UniversalSQLite from '@dqcai/sqlite';
|
|
1499
|
-
|
|
1500
|
-
const db = new UniversalSQLite();
|
|
1501
|
-
let isConnected = false;
|
|
1120
|
+
// Event listeners cho reconnection
|
|
1121
|
+
static setupReconnectionHandlers(): void {
|
|
1122
|
+
DatabaseManager.onDatabaseReconnect('users', (dao) => {
|
|
1123
|
+
console.log('Users database reconnected');
|
|
1124
|
+
// Re-initialize services nếu cần
|
|
1125
|
+
});
|
|
1502
1126
|
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
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'
|
|
1127
|
+
DatabaseManager.onDatabaseReconnect('core', (dao) => {
|
|
1128
|
+
console.log('Core database reconnected');
|
|
1129
|
+
// Re-initialize settings
|
|
1514
1130
|
});
|
|
1515
|
-
|
|
1516
|
-
isConnected = true;
|
|
1517
1131
|
}
|
|
1518
|
-
}
|
|
1519
1132
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
try {
|
|
1524
|
-
await ensureConnection();
|
|
1133
|
+
// Health check tất cả connections
|
|
1134
|
+
static async performHealthCheck(): Promise<void> {
|
|
1135
|
+
const connections = DatabaseManager.getConnections();
|
|
1525
1136
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
}
|
|
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)]);
|
|
1137
|
+
for (const [dbKey, dao] of Object.entries(connections)) {
|
|
1138
|
+
try {
|
|
1139
|
+
await dao.execute('SELECT 1');
|
|
1140
|
+
console.log(`${dbKey}: Healthy`);
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
console.error(`${dbKey}: Unhealthy -`, error);
|
|
1563
1143
|
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1144
|
+
// Thử reconnect nếu cần
|
|
1145
|
+
try {
|
|
1146
|
+
await DatabaseManager.ensureDatabaseConnection(dbKey);
|
|
1147
|
+
console.log(`${dbKey}: Reconnected successfully`);
|
|
1148
|
+
} catch (reconnectError) {
|
|
1149
|
+
console.error(`${dbKey}: Failed to reconnect -`, reconnectError);
|
|
1568
1150
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
default:
|
|
1572
|
-
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
|
|
1573
|
-
res.status(405).end(`Method ${req.method} Not Allowed`);
|
|
1151
|
+
}
|
|
1574
1152
|
}
|
|
1575
|
-
|
|
1576
|
-
} catch (error) {
|
|
1577
|
-
console.error('API Error:', error);
|
|
1578
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
1579
1153
|
}
|
|
1580
1154
|
}
|
|
1581
1155
|
```
|
|
1582
1156
|
|
|
1583
|
-
##
|
|
1157
|
+
## 7. Import/Export Dữ liệu
|
|
1584
1158
|
|
|
1585
|
-
###
|
|
1159
|
+
### Import từ CSV
|
|
1586
1160
|
|
|
1587
1161
|
```typescript
|
|
1588
|
-
//
|
|
1589
|
-
import
|
|
1162
|
+
// services/DataImportService.ts
|
|
1163
|
+
import { DatabaseManager, ImportResult, ColumnMapping } from '@dqcai/sqlite';
|
|
1590
1164
|
|
|
1591
|
-
|
|
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
|
-
});
|
|
1165
|
+
export class DataImportService {
|
|
1630
1166
|
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1167
|
+
// Import users từ CSV
|
|
1168
|
+
static async importUsersFromCSV(csvData: string): Promise<ImportResult> {
|
|
1169
|
+
try {
|
|
1170
|
+
const result = await DatabaseManager.importFromCSV(
|
|
1171
|
+
'users', // database key
|
|
1172
|
+
'users', // table name
|
|
1173
|
+
csvData,
|
|
1174
|
+
{
|
|
1175
|
+
hasHeader: true,
|
|
1176
|
+
delimiter: ',',
|
|
1177
|
+
skipErrors: false,
|
|
1178
|
+
validateData: true,
|
|
1179
|
+
batchSize: 500,
|
|
1180
|
+
onProgress: (processed, total) => {
|
|
1181
|
+
console.log(`Import progress: ${processed}/${total}`);
|
|
1182
|
+
},
|
|
1183
|
+
onError: (error, rowIndex, rowData) => {
|
|
1184
|
+
console.error(`Row ${rowIndex} error:`, error, rowData);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
);
|
|
1650
1188
|
|
|
1651
|
-
|
|
1189
|
+
console.log('Import completed:', {
|
|
1190
|
+
total: result.totalRows,
|
|
1191
|
+
success: result.successRows,
|
|
1192
|
+
errors: result.errorRows,
|
|
1193
|
+
time: result.executionTime
|
|
1194
|
+
});
|
|
1652
1195
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
constructor() {
|
|
1658
|
-
this.db = new UniversalSQLite();
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
async connect(path: string) {
|
|
1662
|
-
await this.db.connect(path);
|
|
1663
|
-
|
|
1664
|
-
// Enable foreign key constraints
|
|
1665
|
-
await this.db.query('PRAGMA foreign_keys = ON');
|
|
1666
|
-
|
|
1667
|
-
// Set secure defaults
|
|
1668
|
-
await this.db.query('PRAGMA journal_mode = WAL'); // Write-Ahead Logging
|
|
1669
|
-
await this.db.query('PRAGMA synchronous = NORMAL');
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
// Validate input before queries
|
|
1673
|
-
validateEmail(email: string): boolean {
|
|
1674
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1675
|
-
return emailRegex.test(email);
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
validateUsername(username: string): boolean {
|
|
1679
|
-
return /^[a-zA-Z0-9_]{3,20}$/.test(username);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
async createUser(userData: {name: string, email: string, username: string}) {
|
|
1683
|
-
// Validate input
|
|
1684
|
-
if (!this.validateEmail(userData.email)) {
|
|
1685
|
-
throw new Error('Invalid email format');
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
if (!this.validateUsername(userData.username)) {
|
|
1689
|
-
throw new Error('Invalid username format');
|
|
1196
|
+
return result;
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
console.error('Import failed:', error);
|
|
1199
|
+
throw error;
|
|
1690
1200
|
}
|
|
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
1201
|
}
|
|
1701
|
-
}
|
|
1702
|
-
```
|
|
1703
|
-
|
|
1704
|
-
### 2. Performance Optimization
|
|
1705
1202
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
this.db = new UniversalSQLite();
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
async connect(path: string) {
|
|
1715
|
-
await this.db.connect(path);
|
|
1716
|
-
|
|
1717
|
-
// Performance tuning
|
|
1718
|
-
await this.db.query('PRAGMA cache_size = 10000');
|
|
1719
|
-
await this.db.query('PRAGMA temp_store = memory');
|
|
1720
|
-
await this.db.query('PRAGMA mmap_size = 268435456'); // 256MB
|
|
1721
|
-
await this.db.query('PRAGMA optimize');
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
async bulkInsert(table: string, records: Record<string, any>[]) {
|
|
1725
|
-
await this.db.query('BEGIN TRANSACTION');
|
|
1726
|
-
|
|
1203
|
+
// Import với column mapping
|
|
1204
|
+
static async importUsersWithMapping(
|
|
1205
|
+
data: Record<string, any>[],
|
|
1206
|
+
columnMappings: ColumnMapping[]
|
|
1207
|
+
): Promise<ImportResult> {
|
|
1727
1208
|
try {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1209
|
+
const result = await DatabaseManager.importDataWithMapping(
|
|
1210
|
+
'users',
|
|
1211
|
+
'users',
|
|
1212
|
+
data,
|
|
1213
|
+
columnMappings,
|
|
1214
|
+
{
|
|
1215
|
+
batchSize: 1000,
|
|
1216
|
+
skipErrors: true,
|
|
1217
|
+
updateOnConflict: true,
|
|
1218
|
+
conflictColumns: ['email']
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
return result;
|
|
1732
1223
|
} catch (error) {
|
|
1733
|
-
|
|
1224
|
+
console.error('Import with mapping failed:', error);
|
|
1734
1225
|
throw error;
|
|
1735
1226
|
}
|
|
1736
1227
|
}
|
|
1737
|
-
|
|
1738
|
-
async createOptimizedIndexes(table: string, columns: string[]) {
|
|
1739
|
-
for (const column of columns) {
|
|
1740
|
-
await this.db.query(`
|
|
1741
|
-
CREATE INDEX IF NOT EXISTS idx_${table}_${column}
|
|
1742
|
-
ON ${table}(${column})
|
|
1743
|
-
`);
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
```
|
|
1748
|
-
|
|
1749
|
-
## 🚨 Troubleshooting
|
|
1750
|
-
|
|
1751
|
-
### Common Issues
|
|
1752
1228
|
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
'BrowserAdapter',
|
|
1760
|
-
'DenoAdapter',
|
|
1761
|
-
'BunAdapter',
|
|
1762
|
-
'ReactNativeAdapter'
|
|
1763
|
-
];
|
|
1764
|
-
|
|
1765
|
-
for (const adapterName of adapters) {
|
|
1766
|
-
try {
|
|
1767
|
-
const adapter = new (require(`./adapters/${adapterName.toLowerCase()}`))();
|
|
1768
|
-
console.log(`${adapterName}: ${adapter.isSupported() ? '✅' : '❌'}`);
|
|
1769
|
-
} catch {
|
|
1770
|
-
console.log(`${adapterName}: ❌ (not available)`);
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
```
|
|
1774
|
-
|
|
1775
|
-
#### 2. React Native Windows Setup
|
|
1776
|
-
|
|
1777
|
-
```typescript
|
|
1778
|
-
// Kiểm tra Windows environment
|
|
1779
|
-
async function checkReactNativeWindows() {
|
|
1780
|
-
const db = new UniversalSQLite();
|
|
1781
|
-
|
|
1782
|
-
console.log('Environment:', db.getEnvironment());
|
|
1783
|
-
|
|
1784
|
-
if (db.getEnvironment() === 'React Native Windows') {
|
|
1785
|
-
console.log('✅ React Native Windows detected');
|
|
1786
|
-
|
|
1229
|
+
// Bulk import nhiều bảng cùng lúc
|
|
1230
|
+
static async bulkImportData(importConfigs: Array<{
|
|
1231
|
+
databaseKey: string;
|
|
1232
|
+
tableName: string;
|
|
1233
|
+
data: Record<string, any>[];
|
|
1234
|
+
}>): Promise<void> {
|
|
1787
1235
|
try {
|
|
1788
|
-
await
|
|
1789
|
-
console.log('✅ Database connection successful');
|
|
1236
|
+
const result = await DatabaseManager.bulkImport(importConfigs);
|
|
1790
1237
|
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
await db.connect(':memory:');
|
|
1812
|
-
console.log('✅ Browser in-memory database ready');
|
|
1813
|
-
|
|
1814
|
-
} catch (error) {
|
|
1815
|
-
console.error('❌ Browser setup failed:', error);
|
|
1816
|
-
console.log('💡 Fallback: Using in-memory database');
|
|
1817
|
-
|
|
1818
|
-
// Fallback strategy
|
|
1819
|
-
await db.connect(':memory:');
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
```
|
|
1823
|
-
|
|
1824
|
-
## 📊 Performance Benchmarks
|
|
1825
|
-
|
|
1826
|
-
```typescript
|
|
1827
|
-
async function benchmarkExample() {
|
|
1828
|
-
const db = new UniversalSQLite();
|
|
1829
|
-
await db.connect('benchmark.db');
|
|
1830
|
-
|
|
1831
|
-
await db.createTable('benchmark_table', {
|
|
1832
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1833
|
-
data: 'TEXT',
|
|
1834
|
-
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1835
|
-
});
|
|
1836
|
-
|
|
1837
|
-
// Benchmark bulk insert
|
|
1838
|
-
console.log('Starting bulk insert benchmark...');
|
|
1839
|
-
const insertStart = performance.now();
|
|
1840
|
-
|
|
1841
|
-
await db.query('BEGIN TRANSACTION');
|
|
1842
|
-
for (let i = 0; i < 10000; i++) {
|
|
1843
|
-
await db.insert('benchmark_table', {
|
|
1844
|
-
data: `Sample data ${i}`
|
|
1845
|
-
});
|
|
1846
|
-
}
|
|
1847
|
-
await db.query('COMMIT');
|
|
1848
|
-
|
|
1849
|
-
const insertEnd = performance.now();
|
|
1850
|
-
console.log(`Bulk insert (10k records): ${(insertEnd - insertStart).toFixed(2)}ms`);
|
|
1851
|
-
|
|
1852
|
-
// Benchmark select
|
|
1853
|
-
const selectStart = performance.now();
|
|
1854
|
-
const allRecords = await db.select('benchmark_table');
|
|
1855
|
-
const selectEnd = performance.now();
|
|
1856
|
-
|
|
1857
|
-
console.log(`Select all (${allRecords.rows.length} records): ${(selectEnd - selectStart).toFixed(2)}ms`);
|
|
1858
|
-
|
|
1859
|
-
// Benchmark with index
|
|
1860
|
-
await db.query('CREATE INDEX idx_data ON benchmark_table(data)');
|
|
1861
|
-
|
|
1862
|
-
const indexedSelectStart = performance.now();
|
|
1863
|
-
const searchResult = await db.select('benchmark_table', 'data LIKE ?', ['Sample data 50%']);
|
|
1864
|
-
const indexedSelectEnd = performance.now();
|
|
1865
|
-
|
|
1866
|
-
console.log(`Indexed search (${searchResult.rows.length} results): ${(indexedSelectEnd - indexedSelectStart).toFixed(2)}ms`);
|
|
1867
|
-
|
|
1868
|
-
await db.close();
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
benchmarkExample();
|
|
1872
|
-
```
|
|
1873
|
-
|
|
1874
|
-
## 📚 Advanced Use Cases
|
|
1875
|
-
|
|
1876
|
-
### 1. Multi-Database Application
|
|
1877
|
-
|
|
1878
|
-
```typescript
|
|
1879
|
-
class MultiDatabaseApp {
|
|
1880
|
-
private userDb: UniversalSQLite;
|
|
1881
|
-
private logDb: UniversalSQLite;
|
|
1882
|
-
private cacheDb: UniversalSQLite;
|
|
1883
|
-
|
|
1884
|
-
constructor() {
|
|
1885
|
-
this.userDb = new UniversalSQLite();
|
|
1886
|
-
this.logDb = new UniversalSQLite();
|
|
1887
|
-
this.cacheDb = new UniversalSQLite();
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
async initialize() {
|
|
1891
|
-
// Separate databases for different concerns
|
|
1892
|
-
await this.userDb.connect('users.db');
|
|
1893
|
-
await this.logDb.connect('logs.db');
|
|
1894
|
-
await this.cacheDb.connect(':memory:'); // Cache in memory
|
|
1895
|
-
|
|
1896
|
-
// Setup schemas
|
|
1897
|
-
await this.setupUserDatabase();
|
|
1898
|
-
await this.setupLogDatabase();
|
|
1899
|
-
await this.setupCacheDatabase();
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
private async setupUserDatabase() {
|
|
1903
|
-
await this.userDb.createTable('users', {
|
|
1904
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1905
|
-
username: 'TEXT UNIQUE NOT NULL',
|
|
1906
|
-
email: 'TEXT UNIQUE NOT NULL',
|
|
1907
|
-
profile_data: 'TEXT', // JSON
|
|
1908
|
-
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1909
|
-
});
|
|
1910
|
-
|
|
1911
|
-
await this.userDb.createTable('user_sessions', {
|
|
1912
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1913
|
-
user_id: 'INTEGER NOT NULL',
|
|
1914
|
-
session_token: 'TEXT UNIQUE NOT NULL',
|
|
1915
|
-
expires_at: 'DATETIME NOT NULL',
|
|
1916
|
-
'FOREIGN KEY (user_id)': 'REFERENCES users(id) ON DELETE CASCADE'
|
|
1917
|
-
});
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
private async setupLogDatabase() {
|
|
1921
|
-
await this.logDb.createTable('access_logs', {
|
|
1922
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1923
|
-
user_id: 'INTEGER',
|
|
1924
|
-
action: 'TEXT NOT NULL',
|
|
1925
|
-
ip_address: 'TEXT',
|
|
1926
|
-
user_agent: 'TEXT',
|
|
1927
|
-
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1928
|
-
});
|
|
1929
|
-
|
|
1930
|
-
await this.logDb.createTable('error_logs', {
|
|
1931
|
-
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
1932
|
-
error_type: 'TEXT NOT NULL',
|
|
1933
|
-
error_message: 'TEXT NOT NULL',
|
|
1934
|
-
stack_trace: 'TEXT',
|
|
1935
|
-
context: 'TEXT', // JSON
|
|
1936
|
-
timestamp: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
private async setupCacheDatabase() {
|
|
1941
|
-
await this.cacheDb.createTable('cache_entries', {
|
|
1942
|
-
key: 'TEXT PRIMARY KEY',
|
|
1943
|
-
value: 'TEXT NOT NULL',
|
|
1944
|
-
expires_at: 'DATETIME NOT NULL'
|
|
1945
|
-
});
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
// User operations
|
|
1949
|
-
async createUser(userData: any) {
|
|
1950
|
-
const result = await this.userDb.insert('users', userData);
|
|
1951
|
-
|
|
1952
|
-
// Log the action
|
|
1953
|
-
await this.logAction(result.lastInsertRowId!, 'USER_CREATED', 'User registration');
|
|
1954
|
-
|
|
1955
|
-
return result;
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
async getUserById(id: number) {
|
|
1959
|
-
// Check cache first
|
|
1960
|
-
const cached = await this.getFromCache(`user:${id}`);
|
|
1961
|
-
if (cached) {
|
|
1962
|
-
return JSON.parse(cached);
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
// Query from database
|
|
1966
|
-
const result = await this.userDb.select('users', 'id = ?', [id]);
|
|
1967
|
-
if (result.rows.length > 0) {
|
|
1968
|
-
const user = result.rows[0];
|
|
1969
|
-
// Cache for 1 hour
|
|
1970
|
-
await this.setCache(`user:${id}`, JSON.stringify(user), 3600);
|
|
1971
|
-
return user;
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
return null;
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
// Logging operations
|
|
1978
|
-
async logAction(userId: number, action: string, details: string) {
|
|
1979
|
-
await this.logDb.insert('access_logs', {
|
|
1980
|
-
user_id: userId,
|
|
1981
|
-
action: action,
|
|
1982
|
-
ip_address: '127.0.0.1', // Would get from request
|
|
1983
|
-
user_agent: 'Universal SQLite App'
|
|
1984
|
-
});
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
async logError(error: Error, context?: any) {
|
|
1988
|
-
await this.logDb.insert('error_logs', {
|
|
1989
|
-
error_type: error.name,
|
|
1990
|
-
error_message: error.message,
|
|
1991
|
-
stack_trace: error.stack,
|
|
1992
|
-
context: context ? JSON.stringify(context) : null
|
|
1993
|
-
});
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
// Cache operations
|
|
1997
|
-
async setCache(key: string, value: string, ttlSeconds: number) {
|
|
1998
|
-
const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
1999
|
-
|
|
2000
|
-
await this.cacheDb.query(`
|
|
2001
|
-
INSERT OR REPLACE INTO cache_entries (key, value, expires_at)
|
|
2002
|
-
VALUES (?, ?, ?)
|
|
2003
|
-
`, [key, value, expiresAt]);
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
async getFromCache(key: string): Promise<string | null> {
|
|
2007
|
-
// Clean expired entries
|
|
2008
|
-
await this.cacheDb.delete('cache_entries', 'expires_at < ?', [new Date().toISOString()]);
|
|
2009
|
-
|
|
2010
|
-
const result = await this.cacheDb.select('cache_entries', 'key = ?', [key]);
|
|
2011
|
-
return result.rows.length > 0 ? result.rows[0].value : null;
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
// Analytics across databases
|
|
2015
|
-
async getUserAnalytics(userId: number) {
|
|
2016
|
-
const user = await this.getUserById(userId);
|
|
2017
|
-
if (!user) return null;
|
|
2018
|
-
|
|
2019
|
-
const logs = await this.logDb.query(`
|
|
2020
|
-
SELECT
|
|
2021
|
-
action,
|
|
2022
|
-
COUNT(*) as count,
|
|
2023
|
-
MAX(timestamp) as last_action
|
|
2024
|
-
FROM access_logs
|
|
2025
|
-
WHERE user_id = ?
|
|
2026
|
-
GROUP BY action
|
|
2027
|
-
ORDER BY count DESC
|
|
2028
|
-
`, [userId]);
|
|
2029
|
-
|
|
2030
|
-
return {
|
|
2031
|
-
user: user,
|
|
2032
|
-
activity: logs.rows,
|
|
2033
|
-
total_actions: logs.rows.reduce((sum, row) => sum + row.count, 0)
|
|
2034
|
-
};
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
async cleanup() {
|
|
2038
|
-
await this.userDb.close();
|
|
2039
|
-
await this.logDb.close();
|
|
2040
|
-
await this.cacheDb.close();
|
|
1238
|
+
console.log('Bulk import completed:', {
|
|
1239
|
+
totalDatabases: result.totalDatabases,
|
|
1240
|
+
successDatabases: result.successDatabases,
|
|
1241
|
+
executionTime: result.executionTime
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
// Log chi tiết từng bảng
|
|
1245
|
+
Object.entries(result.results).forEach(([key, importResult]) => {
|
|
1246
|
+
console.log(`${key}: ${importResult.successRows}/${importResult.totalRows} rows imported`);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
// Log lỗi nếu có
|
|
1250
|
+
if (Object.keys(result.errors).length > 0) {
|
|
1251
|
+
console.error('Import errors:', result.errors);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
console.error('Bulk import failed:', error);
|
|
1256
|
+
throw error;
|
|
1257
|
+
}
|
|
2041
1258
|
}
|
|
2042
1259
|
}
|
|
2043
1260
|
|
|
2044
|
-
//
|
|
2045
|
-
async function
|
|
2046
|
-
|
|
2047
|
-
|
|
1261
|
+
// Ví dụ sử dụng import service
|
|
1262
|
+
async function exampleImportUsage() {
|
|
1263
|
+
// CSV data mẫu
|
|
1264
|
+
const csvData = `username,email,password,first_name,last_name
|
|
1265
|
+
john_doe,john@example.com,password123,John,Doe
|
|
1266
|
+
jane_smith,jane@example.com,password456,Jane,Smith
|
|
1267
|
+
bob_wilson,bob@example.com,password789,Bob,Wilson`;
|
|
1268
|
+
|
|
2048
1269
|
try {
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
1270
|
+
// Import từ CSV
|
|
1271
|
+
const importResult = await DataImportService.importUsersFromCSV(csvData);
|
|
1272
|
+
console.log('CSV Import result:', importResult);
|
|
1273
|
+
|
|
1274
|
+
// Import với column mapping
|
|
1275
|
+
const mappedData = [
|
|
1276
|
+
{ user_name: 'alice', user_email: 'alice@test.com', pwd: 'pass123' },
|
|
1277
|
+
{ user_name: 'charlie', user_email: 'charlie@test.com', pwd: 'pass456' }
|
|
1278
|
+
];
|
|
1279
|
+
|
|
1280
|
+
const columnMappings: ColumnMapping[] = [
|
|
1281
|
+
{ sourceColumn: 'user_name', targetColumn: 'username' },
|
|
1282
|
+
{ sourceColumn: 'user_email', targetColumn: 'email' },
|
|
1283
|
+
{
|
|
1284
|
+
sourceColumn: 'pwd',
|
|
1285
|
+
targetColumn: 'password',
|
|
1286
|
+
transform: (value) => `hashed_${value}` // Hash password
|
|
1287
|
+
}
|
|
1288
|
+
];
|
|
1289
|
+
|
|
1290
|
+
const mappingResult = await DataImportService.importUsersWithMapping(
|
|
1291
|
+
mappedData,
|
|
1292
|
+
columnMappings
|
|
1293
|
+
);
|
|
1294
|
+
console.log('Mapping Import result:', mappingResult);
|
|
1295
|
+
|
|
2069
1296
|
} catch (error) {
|
|
2070
|
-
console.error('
|
|
2071
|
-
} finally {
|
|
2072
|
-
await app.cleanup();
|
|
1297
|
+
console.error('Import example failed:', error);
|
|
2073
1298
|
}
|
|
2074
1299
|
}
|
|
2075
|
-
|
|
2076
|
-
multiDbExample();
|
|
2077
1300
|
```
|
|
2078
1301
|
|
|
2079
|
-
###
|
|
1302
|
+
### Export dữ liệu
|
|
2080
1303
|
|
|
2081
1304
|
```typescript
|
|
2082
|
-
//
|
|
2083
|
-
import
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
class RealtimeSync {
|
|
2087
|
-
private db: UniversalSQLite;
|
|
2088
|
-
private ws: WebSocket | null = null;
|
|
2089
|
-
private syncQueue: any[] = [];
|
|
1305
|
+
// services/DataExportService.ts
|
|
1306
|
+
import { DatabaseManager } from '@dqcai/sqlite';
|
|
1307
|
+
|
|
1308
|
+
export class DataExportService {
|
|
2090
1309
|
|
|
2091
|
-
|
|
2092
|
-
|
|
1310
|
+
// Export users ra CSV
|
|
1311
|
+
static async exportUsersToCSV(): Promise<string> {
|
|
1312
|
+
try {
|
|
1313
|
+
const dao = DatabaseManager.get('users');
|
|
1314
|
+
const sql = `
|
|
1315
|
+
SELECT u.username, u.email, u.created_at,
|
|
1316
|
+
p.first_name, p.last_name, p.phone
|
|
1317
|
+
FROM users u
|
|
1318
|
+
LEFT JOIN profiles p ON u.id = p.user_id
|
|
1319
|
+
ORDER BY u.created_at DESC
|
|
1320
|
+
`;
|
|
1321
|
+
|
|
1322
|
+
const result = await dao.execute(sql);
|
|
1323
|
+
|
|
1324
|
+
if (result.rows.length === 0) {
|
|
1325
|
+
return 'No data to export';
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// Tạo CSV header
|
|
1329
|
+
const headers = Object.keys(result.rows[0]);
|
|
1330
|
+
let csvContent = headers.join(',') + '\n';
|
|
1331
|
+
|
|
1332
|
+
// Thêm data rows
|
|
1333
|
+
result.rows.forEach(row => {
|
|
1334
|
+
const values = headers.map(header => {
|
|
1335
|
+
const value = row[header];
|
|
1336
|
+
// Escape quotes và wrap trong quotes nếu chứa comma
|
|
1337
|
+
if (value === null || value === undefined) {
|
|
1338
|
+
return '';
|
|
1339
|
+
}
|
|
1340
|
+
const stringValue = String(value);
|
|
1341
|
+
if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
|
|
1342
|
+
return `"${stringValue.replace(/"/g, '""')}"`;
|
|
1343
|
+
}
|
|
1344
|
+
return stringValue;
|
|
1345
|
+
});
|
|
1346
|
+
csvContent += values.join(',') + '\n';
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
return csvContent;
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
console.error('Export to CSV failed:', error);
|
|
1352
|
+
throw error;
|
|
1353
|
+
}
|
|
2093
1354
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
})
|
|
2117
|
-
|
|
2118
|
-
|
|
1355
|
+
|
|
1356
|
+
// Export với điều kiện tùy chỉnh
|
|
1357
|
+
static async exportUsersWithConditions(
|
|
1358
|
+
whereClause?: string,
|
|
1359
|
+
params?: any[]
|
|
1360
|
+
): Promise<Record<string, any>[]> {
|
|
1361
|
+
try {
|
|
1362
|
+
const dao = DatabaseManager.get('users');
|
|
1363
|
+
let sql = `
|
|
1364
|
+
SELECT u.*, p.first_name, p.last_name, p.phone, p.address
|
|
1365
|
+
FROM users u
|
|
1366
|
+
LEFT JOIN profiles p ON u.id = p.user_id
|
|
1367
|
+
`;
|
|
1368
|
+
|
|
1369
|
+
if (whereClause) {
|
|
1370
|
+
sql += ` WHERE ${whereClause}`;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
sql += ` ORDER BY u.created_at DESC`;
|
|
1374
|
+
|
|
1375
|
+
const result = await dao.execute(sql, params || []);
|
|
1376
|
+
return result.rows;
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
console.error('Export with conditions failed:', error);
|
|
1379
|
+
throw error;
|
|
1380
|
+
}
|
|
2119
1381
|
}
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
1382
|
+
|
|
1383
|
+
// Export dữ liệu backup toàn bộ database
|
|
1384
|
+
static async createDatabaseBackup(dbKey: string): Promise<{
|
|
1385
|
+
tables: Record<string, any[]>;
|
|
1386
|
+
metadata: any;
|
|
1387
|
+
}> {
|
|
1388
|
+
try {
|
|
1389
|
+
const dao = DatabaseManager.get(dbKey);
|
|
1390
|
+
|
|
1391
|
+
// Lấy danh sách tables
|
|
1392
|
+
const tablesResult = await dao.execute(
|
|
1393
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
|
1394
|
+
);
|
|
1395
|
+
|
|
1396
|
+
const backup: Record<string, any[]> = {};
|
|
1397
|
+
|
|
1398
|
+
// Export từng table
|
|
1399
|
+
for (const tableRow of tablesResult.rows) {
|
|
1400
|
+
const tableName = tableRow.name;
|
|
1401
|
+
const dataResult = await dao.execute(`SELECT * FROM ${tableName}`);
|
|
1402
|
+
backup[tableName] = dataResult.rows;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// Thêm metadata
|
|
1406
|
+
const dbInfo = await dao.getDatabaseInfo();
|
|
1407
|
+
|
|
1408
|
+
return {
|
|
1409
|
+
tables: backup,
|
|
1410
|
+
metadata: {
|
|
1411
|
+
...dbInfo,
|
|
1412
|
+
exportDate: new Date().toISOString(),
|
|
1413
|
+
version: await dao.getSchemaVersion()
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
console.error('Database backup failed:', error);
|
|
1418
|
+
throw error;
|
|
1419
|
+
}
|
|
2137
1420
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// Ví dụ sử dụng export
|
|
1424
|
+
async function exampleExportUsage() {
|
|
1425
|
+
try {
|
|
1426
|
+
// Export users ra CSV
|
|
1427
|
+
const csvContent = await DataExportService.exportUsersToCSV();
|
|
1428
|
+
console.log('CSV Export:', csvContent);
|
|
1429
|
+
|
|
1430
|
+
// Export users với điều kiện
|
|
1431
|
+
const recentUsers = await DataExportService.exportUsersWithConditions(
|
|
1432
|
+
"u.created_at > ?",
|
|
1433
|
+
[new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()] // 30 ngày gần đây
|
|
1434
|
+
);
|
|
1435
|
+
console.log('Recent users:', recentUsers);
|
|
1436
|
+
|
|
1437
|
+
// Backup toàn bộ database users
|
|
1438
|
+
const backup = await DataExportService.createDatabaseBackup('users');
|
|
1439
|
+
console.log('Database backup:', backup);
|
|
1440
|
+
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
console.error('Export example failed:', error);
|
|
2148
1443
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
1444
|
+
}
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
## 8. Best Practices & Tips
|
|
1448
|
+
|
|
1449
|
+
### Error Handling
|
|
1450
|
+
|
|
1451
|
+
```typescript
|
|
1452
|
+
// utils/ErrorHandler.ts
|
|
1453
|
+
export class DatabaseErrorHandler {
|
|
1454
|
+
|
|
1455
|
+
static handleServiceError(error: any, context: string): void {
|
|
1456
|
+
console.error(`Database Error in ${context}:`, {
|
|
1457
|
+
message: error.message,
|
|
1458
|
+
stack: error.stack,
|
|
1459
|
+
code: error.code,
|
|
1460
|
+
timestamp: new Date().toISOString()
|
|
2156
1461
|
});
|
|
1462
|
+
|
|
1463
|
+
// Specific error handling
|
|
1464
|
+
if (error.message?.includes('UNIQUE constraint failed')) {
|
|
1465
|
+
throw new Error('Duplicate entry detected');
|
|
1466
|
+
} else if (error.message?.includes('database is locked')) {
|
|
1467
|
+
throw new Error('Database is busy, please try again');
|
|
1468
|
+
} else if (error.message?.includes('no such table')) {
|
|
1469
|
+
throw new Error('Database table not found');
|
|
1470
|
+
} else {
|
|
1471
|
+
throw new Error(`Database operation failed: ${error.message}`);
|
|
1472
|
+
}
|
|
2157
1473
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
private async processSyncQueue() {
|
|
2166
|
-
const pendingSyncs = await this.db.select('sync_log', 'sync_status = ?', ['pending']);
|
|
1474
|
+
|
|
1475
|
+
static async withRetry<T>(
|
|
1476
|
+
operation: () => Promise<T>,
|
|
1477
|
+
maxRetries: number = 3,
|
|
1478
|
+
delay: number = 1000
|
|
1479
|
+
): Promise<T> {
|
|
1480
|
+
let lastError: any;
|
|
2167
1481
|
|
|
2168
|
-
for (
|
|
1482
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
2169
1483
|
try {
|
|
2170
|
-
|
|
2171
|
-
|
|
1484
|
+
return await operation();
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
lastError = error;
|
|
2172
1487
|
|
|
2173
|
-
if (
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
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
|
-
}
|
|
1488
|
+
if (i < maxRetries - 1) {
|
|
1489
|
+
console.log(`Operation failed, retrying in ${delay}ms... (${i + 1}/${maxRetries})`);
|
|
1490
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1491
|
+
delay *= 2; // Exponential backoff
|
|
2188
1492
|
}
|
|
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
1493
|
}
|
|
2197
1494
|
}
|
|
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
|
-
});
|
|
1495
|
+
|
|
1496
|
+
throw lastError;
|
|
2207
1497
|
}
|
|
2208
1498
|
}
|
|
2209
1499
|
|
|
2210
|
-
//
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
// Let sync process run
|
|
2224
|
-
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
1500
|
+
// Sử dụng error handler
|
|
1501
|
+
class SafeUserService extends UserService {
|
|
1502
|
+
async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User | null> {
|
|
1503
|
+
return DatabaseErrorHandler.withRetry(async () => {
|
|
1504
|
+
try {
|
|
1505
|
+
return await super.createUser(userData);
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
DatabaseErrorHandler.handleServiceError(error, 'createUser');
|
|
1508
|
+
throw error; // Re-throw sau khi handle
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
2225
1512
|
}
|
|
2226
|
-
|
|
2227
|
-
realtimeSyncExample();
|
|
2228
1513
|
```
|
|
2229
1514
|
|
|
2230
|
-
###
|
|
1515
|
+
### Performance Optimization
|
|
2231
1516
|
|
|
2232
1517
|
```typescript
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
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);
|
|
1518
|
+
// utils/PerformanceOptimizer.ts
|
|
1519
|
+
export class PerformanceOptimizer {
|
|
1520
|
+
|
|
1521
|
+
// Batch operations để giảm số lần gọi database
|
|
1522
|
+
static async batchCreateUsers(
|
|
1523
|
+
userService: UserService,
|
|
1524
|
+
users: Array<Omit<User, 'id' | 'created_at' | 'updated_at'>>
|
|
1525
|
+
): Promise<void> {
|
|
1526
|
+
const batchSize = 100;
|
|
1527
|
+
|
|
1528
|
+
for (let i = 0; i < users.length; i += batchSize) {
|
|
1529
|
+
const batch = users.slice(i, i + batchSize);
|
|
2263
1530
|
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
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);
|
|
1531
|
+
await userService.executeTransaction(async () => {
|
|
1532
|
+
for (const userData of batch) {
|
|
1533
|
+
await userService.create({
|
|
1534
|
+
...userData,
|
|
1535
|
+
created_at: new Date().toISOString()
|
|
2275
1536
|
});
|
|
2276
|
-
|
|
2277
|
-
backupSQL += `INSERT INTO ${table.name} (${columns.join(', ')}) VALUES (${values.join(', ')});\n`;
|
|
2278
1537
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
console.log(`Processed batch ${Math.ceil((i + batchSize) / batchSize)}`);
|
|
2281
1541
|
}
|
|
2282
|
-
|
|
2283
|
-
return backupSQL;
|
|
2284
1542
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
.filter(stmt => stmt && !stmt.startsWith('--'));
|
|
1543
|
+
|
|
1544
|
+
// Index optimization
|
|
1545
|
+
static async optimizeQueries(dao: any): Promise<void> {
|
|
1546
|
+
// Enable query optimization
|
|
1547
|
+
await dao.execute('PRAGMA optimize');
|
|
2291
1548
|
|
|
2292
|
-
|
|
1549
|
+
// Analyze tables for better query planning
|
|
1550
|
+
await dao.execute('ANALYZE');
|
|
2293
1551
|
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
await this.db.query('COMMIT');
|
|
2301
|
-
console.log('Backup restored successfully');
|
|
2302
|
-
} catch (error) {
|
|
2303
|
-
await this.db.query('ROLLBACK');
|
|
2304
|
-
throw new Error(`Restore failed: ${error}`);
|
|
2305
|
-
}
|
|
1552
|
+
// Set optimal cache size
|
|
1553
|
+
await dao.execute('PRAGMA cache_size = 10000');
|
|
1554
|
+
|
|
1555
|
+
console.log('Database optimization completed');
|
|
2306
1556
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
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);
|
|
1557
|
+
|
|
1558
|
+
// Connection pooling check
|
|
1559
|
+
static monitorConnections(): void {
|
|
1560
|
+
setInterval(() => {
|
|
1561
|
+
const count = DatabaseManager.getConnectionCount();
|
|
1562
|
+
const connections = DatabaseManager.listConnections();
|
|
1563
|
+
|
|
1564
|
+
console.log(`Active connections: ${count}`, connections);
|
|
1565
|
+
|
|
1566
|
+
if (count > 5) {
|
|
1567
|
+
console.warn('High number of database connections detected');
|
|
2327
1568
|
}
|
|
2328
|
-
},
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
|
-
async close() {
|
|
2332
|
-
await this.db.close();
|
|
1569
|
+
}, 30000); // Check every 30 seconds
|
|
2333
1570
|
}
|
|
2334
1571
|
}
|
|
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
1572
|
```
|
|
2379
1573
|
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
### Environment Variables
|
|
1574
|
+
### Testing Utilities
|
|
2383
1575
|
|
|
2384
1576
|
```typescript
|
|
2385
|
-
//
|
|
2386
|
-
|
|
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
|
-
}
|
|
1577
|
+
// utils/TestHelpers.ts
|
|
1578
|
+
import { DatabaseManager } from '@dqcai/sqlite';
|
|
2416
1579
|
|
|
2417
|
-
|
|
2418
|
-
async function productionSetup() {
|
|
2419
|
-
const config = getDatabaseConfig();
|
|
2420
|
-
const db = new UniversalSQLite();
|
|
1580
|
+
export class DatabaseTestHelpers {
|
|
2421
1581
|
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
1582
|
+
static async setupTestDatabase(): Promise<void> {
|
|
1583
|
+
// Use in-memory database for testing
|
|
1584
|
+
const testSchema = {
|
|
1585
|
+
...userSchema,
|
|
1586
|
+
database_name: ':memory:'
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1589
|
+
DatabaseManager.registerSchema('test_users', testSchema);
|
|
1590
|
+
await DatabaseManager.initLazySchema(['test_users']);
|
|
2429
1591
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
// Setup monitoring
|
|
2435
|
-
if (process.env.NODE_ENV === 'production') {
|
|
2436
|
-
setupProductionMonitoring(db);
|
|
1592
|
+
|
|
1593
|
+
static async cleanupTestData(service: any): Promise<void> {
|
|
1594
|
+
await service.truncate();
|
|
2437
1595
|
}
|
|
2438
|
-
|
|
2439
|
-
return db;
|
|
2440
|
-
}
|
|
2441
1596
|
|
|
2442
|
-
async
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
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
|
-
}
|
|
1597
|
+
static async seedTestData(userService: UserService): Promise<User[]> {
|
|
1598
|
+
const testUsers = [
|
|
1599
|
+
{ username: 'test1', email: 'test1@example.com', password: 'pass1' },
|
|
1600
|
+
{ username: 'test2', email: 'test2@example.com', password: 'pass2' },
|
|
1601
|
+
{ username: 'test3', email: 'test3@example.com', password: 'pass3' }
|
|
1602
|
+
];
|
|
2469
1603
|
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
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');
|
|
1604
|
+
const createdUsers: User[] = [];
|
|
1605
|
+
for (const userData of testUsers) {
|
|
1606
|
+
const user = await userService.createUser(userData);
|
|
1607
|
+
if (user) createdUsers.push(user);
|
|
2483
1608
|
}
|
|
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
1609
|
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
```typescript
|
|
2506
|
-
interface SQLiteRow {
|
|
2507
|
-
[key: string]: any;
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
interface SQLiteResult {
|
|
2511
|
-
rows: SQLiteRow[]; // Dữ liệu trả về
|
|
2512
|
-
rowsAffected: number; // Số dòng bị ảnh hưởng
|
|
2513
|
-
lastInsertRowId?: number; // ID của record mới insert
|
|
1610
|
+
return createdUsers;
|
|
1611
|
+
}
|
|
2514
1612
|
}
|
|
2515
1613
|
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
}
|
|
1614
|
+
// Example test
|
|
1615
|
+
describe('UserService Tests', () => {
|
|
1616
|
+
let userService: UserService;
|
|
2520
1617
|
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
}
|
|
2526
|
-
```
|
|
1618
|
+
beforeAll(async () => {
|
|
1619
|
+
await DatabaseTestHelpers.setupTestDatabase();
|
|
1620
|
+
userService = new UserService();
|
|
1621
|
+
await userService.init();
|
|
1622
|
+
});
|
|
2527
1623
|
|
|
2528
|
-
|
|
1624
|
+
beforeEach(async () => {
|
|
1625
|
+
await DatabaseTestHelpers.cleanupTestData(userService);
|
|
1626
|
+
});
|
|
2529
1627
|
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
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 |
|
|
1628
|
+
afterAll(async () => {
|
|
1629
|
+
await DatabaseManager.closeAll();
|
|
1630
|
+
});
|
|
2537
1631
|
|
|
2538
|
-
|
|
1632
|
+
test('should create user successfully', async () => {
|
|
1633
|
+
const userData = {
|
|
1634
|
+
username: 'testuser',
|
|
1635
|
+
email: 'test@example.com',
|
|
1636
|
+
password: 'password123'
|
|
1637
|
+
};
|
|
2539
1638
|
|
|
2540
|
-
|
|
1639
|
+
const user = await userService.createUser(userData);
|
|
1640
|
+
expect(user).toBeTruthy();
|
|
1641
|
+
expect(user?.username).toBe(userData.username);
|
|
1642
|
+
expect(user?.email).toBe(userData.email);
|
|
1643
|
+
});
|
|
1644
|
+
});
|
|
1645
|
+
```
|
|
2541
1646
|
|
|
2542
|
-
|
|
2543
|
-
# Clone repository
|
|
2544
|
-
git clone https://github.com/dqcai/sqlite
|
|
2545
|
-
cd sqlite
|
|
1647
|
+
## 9. Troubleshooting Common Issues
|
|
2546
1648
|
|
|
2547
|
-
|
|
2548
|
-
npm install
|
|
1649
|
+
### Database Locked
|
|
2549
1650
|
|
|
2550
|
-
|
|
2551
|
-
|
|
1651
|
+
```typescript
|
|
1652
|
+
// Giải quyết database locked
|
|
1653
|
+
const handleDatabaseLocked = async () => {
|
|
1654
|
+
try {
|
|
1655
|
+
// Enable WAL mode để tránh lock
|
|
1656
|
+
const dao = DatabaseManager.get('users');
|
|
1657
|
+
await dao.execute('PRAGMA journal_mode = WAL');
|
|
1658
|
+
await dao.execute('PRAGMA busy_timeout = 30000'); // 30 giây timeout
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
console.error('Error setting WAL mode:', error);
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
```
|
|
2552
1664
|
|
|
2553
|
-
|
|
2554
|
-
npm run build
|
|
1665
|
+
### Connection Issues
|
|
2555
1666
|
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
1667
|
+
```typescript
|
|
1668
|
+
// Kiểm tra và khôi phục kết nối
|
|
1669
|
+
const ensureConnectionHealth = async (dbKey: string) => {
|
|
1670
|
+
try {
|
|
1671
|
+
const dao = DatabaseManager.get(dbKey);
|
|
1672
|
+
await dao.execute('SELECT 1');
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
console.log(`Connection ${dbKey} unhealthy, reconnecting...`);
|
|
1675
|
+
await DatabaseManager.closeConnection(dbKey);
|
|
1676
|
+
await DatabaseManager.getLazyLoading(dbKey);
|
|
1677
|
+
console.log(`Connection ${dbKey} restored`);
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
2561
1680
|
```
|
|
2562
1681
|
|
|
2563
|
-
|
|
1682
|
+
## 10. Migration & Schema Updates
|
|
2564
1683
|
|
|
2565
1684
|
```typescript
|
|
2566
|
-
//
|
|
2567
|
-
import {
|
|
1685
|
+
// migrations/001_add_user_status.ts
|
|
1686
|
+
import { UniversalDAO } from '@dqcai/sqlite';
|
|
2568
1687
|
|
|
2569
|
-
export
|
|
2570
|
-
|
|
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
|
-
}
|
|
1688
|
+
export const migration_001 = {
|
|
1689
|
+
version: '1.0.1',
|
|
1690
|
+
description: 'Add status column to users table',
|
|
2579
1691
|
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
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
|
-
)
|
|
1692
|
+
async up(dao: UniversalDAO): Promise<void> {
|
|
1693
|
+
await dao.execute(`
|
|
1694
|
+
ALTER TABLE users
|
|
1695
|
+
ADD COLUMN status VARCHAR(20) DEFAULT 'active'
|
|
2592
1696
|
`);
|
|
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
1697
|
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
1698
|
+
await dao.execute(`
|
|
1699
|
+
CREATE INDEX idx_user_status ON users(status)
|
|
1700
|
+
`);
|
|
1701
|
+
},
|
|
1702
|
+
|
|
1703
|
+
async down(dao: UniversalDAO): Promise<void> {
|
|
1704
|
+
await dao.execute(`
|
|
1705
|
+
DROP INDEX IF EXISTS idx_user_status
|
|
1706
|
+
`);
|
|
2605
1707
|
|
|
2606
|
-
//
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
1708
|
+
// SQLite không hỗ trợ DROP COLUMN, cần recreate table
|
|
1709
|
+
await dao.execute(`
|
|
1710
|
+
CREATE TABLE users_backup AS
|
|
1711
|
+
SELECT id, username, email, password, created_at, updated_at
|
|
1712
|
+
FROM users
|
|
1713
|
+
`);
|
|
2612
1714
|
|
|
2613
|
-
|
|
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);
|
|
1715
|
+
await dao.execute(`DROP TABLE users`);
|
|
2619
1716
|
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
1717
|
+
await dao.execute(`
|
|
1718
|
+
CREATE TABLE users (
|
|
1719
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1720
|
+
username VARCHAR(50) UNIQUE NOT NULL,
|
|
1721
|
+
email VARCHAR(100) UNIQUE NOT NULL,
|
|
1722
|
+
password VARCHAR(255) NOT NULL,
|
|
1723
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1724
|
+
updated_at DATETIME
|
|
1725
|
+
)
|
|
1726
|
+
`);
|
|
2623
1727
|
|
|
2624
|
-
|
|
1728
|
+
await dao.execute(`
|
|
1729
|
+
INSERT INTO users SELECT * FROM users_backup
|
|
1730
|
+
`);
|
|
2625
1731
|
|
|
2626
|
-
|
|
2627
|
-
console.error(`❌ Test failed:`, error);
|
|
1732
|
+
await dao.execute(`DROP TABLE users_backup`);
|
|
2628
1733
|
}
|
|
2629
|
-
}
|
|
1734
|
+
};
|
|
1735
|
+
|
|
1736
|
+
// Chạy migration
|
|
1737
|
+
const runMigration = async () => {
|
|
1738
|
+
const dao = DatabaseManager.get('users');
|
|
1739
|
+
const currentVersion = await dao.getSchemaVersion();
|
|
1740
|
+
|
|
1741
|
+
if (currentVersion < '1.0.1') {
|
|
1742
|
+
await migration_001.up(dao);
|
|
1743
|
+
await dao.setSchemaVersion('1.0.1');
|
|
1744
|
+
console.log('Migration 001 completed');
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
2630
1747
|
```
|
|
2631
1748
|
|
|
1749
|
+
## Kết luận
|
|
1750
|
+
|
|
1751
|
+
Universal SQLite cung cấp một giải pháp mạnh mẽ và linh hoạt để quản lý cơ sở dữ liệu SQLite across platforms. Với DatabaseManager và BaseService, bạn có thể:
|
|
1752
|
+
|
|
1753
|
+
- Dễ dàng quản lý nhiều database connections
|
|
1754
|
+
- Thực hiện CRUD operations một cách type-safe
|
|
1755
|
+
- Import/Export dữ liệu hiệu quả
|
|
1756
|
+
- Quản lý schema và migrations
|
|
1757
|
+
- Handle errors và performance optimization
|
|
1758
|
+
|
|
1759
|
+
Thư viện hỗ trợ tốt cho cả React Native và Node.js, giúp bạn xây dựng ứng dụng database-driven một cách nhất quán và maintainable.
|
|
1760
|
+
|
|
1761
|
+
## API Reference
|
|
1762
|
+
|
|
1763
|
+
- **UniversalSQLite**: Singleton chính, methods: initialize, connect, getDAO, query, execute, importData, etc.
|
|
1764
|
+
- **UniversalDAO**: Core DAO cho CRUD, execute, importData.
|
|
1765
|
+
- **QueryBuilder**: Xây dựng query với fluent API.
|
|
1766
|
+
- **MigrationManager**: Quản lý migration.
|
|
1767
|
+
- **CSVImporter**: Import/export CSV.
|
|
1768
|
+
- **BaseService**: Base cho service layer.
|
|
1769
|
+
- **DatabaseFactory**: Factory để tạo DAO.
|
|
1770
|
+
- **DatabaseManager**: Quản lý connections, roles.
|
|
1771
|
+
|
|
1772
|
+
Xem source code để biết chi tiết types và methods.
|
|
1773
|
+
|
|
1774
|
+
|
|
1775
|
+
## Best Practices
|
|
1776
|
+
|
|
1777
|
+
1. **Always use transactions for multi-step operations**
|
|
1778
|
+
2. **Define schemas for type safety and validation**
|
|
1779
|
+
3. **Use parameterized queries to prevent SQL injection**
|
|
1780
|
+
4. **Implement proper error handling**
|
|
1781
|
+
5. **Close connections when done**
|
|
1782
|
+
6. **Use migrations for schema changes**
|
|
1783
|
+
7. **Batch large operations for better performance**
|
|
1784
|
+
|
|
1785
|
+
## Contributing
|
|
1786
|
+
|
|
1787
|
+
1. Fork the repository
|
|
1788
|
+
2. Create a feature branch
|
|
1789
|
+
3. Make your changes
|
|
1790
|
+
4. Add tests
|
|
1791
|
+
5. Submit a pull request
|
|
1792
|
+
|
|
1793
|
+
|
|
2632
1794
|
## 📄 License
|
|
2633
1795
|
|
|
2634
|
-
MIT
|
|
1796
|
+
MIT © [Cuong Doan](https://github.com/cuongdqpayment)
|
|
2635
1797
|
|
|
2636
1798
|
## 🙏 Acknowledgments
|
|
2637
1799
|
|
|
@@ -2646,8 +1808,9 @@ MIT License - see LICENSE file for details.
|
|
|
2646
1808
|
- [Documentation](https://github.com/cuongdqpayment/dqcai-sqlite/docs)
|
|
2647
1809
|
- [Examples Repository](https://github.com/cuongdqpayment/dqcai-sqlite)
|
|
2648
1810
|
- [Issue Tracker](https://github.com/cuongdqpayment/dqcai-sqlite/issues)
|
|
1811
|
+
- [Issue Facebook](https://www.facebook.com/share/p/19esHGbaGj/)
|
|
2649
1812
|
- [NPM Package](https://www.npmjs.com/package/@dqcai/sqlite)
|
|
2650
1813
|
|
|
2651
1814
|
---
|
|
2652
1815
|
|
|
2653
|
-
|
|
1816
|
+
🔥 **@dqcai/sqlite** — One library, all platforms! 🚀
|