@dqcai/sqlite 2.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 CHANGED
@@ -1,4 +1,4 @@
1
- # @dqcai/sqlite - A Universal SQLite Library (@dqcai/sqlite v2.0.0)
1
+ # @dqcai/sqlite - A Universal SQLite Library (@dqcai/sqlite v2.0.1)
2
2
 
3
3
  ![Universal SQLite](https://img.shields.io/badge/SQLite-Universal-blue)
4
4
  ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
@@ -19,6 +19,8 @@ UniversalSQLite là một thư viện SQLite toàn diện, hỗ trợ đa nền
19
19
  - **Adapters**: Tự động detect môi trường, hỗ trợ register adapter tùy chỉnh.
20
20
  - **Type-Safe**: Đầy đủ types TypeScript cho schema, query, và operations.
21
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
22
24
 
23
25
  ## Installation
24
26
 
@@ -32,480 +34,1729 @@ yarn add @dqcai/sqlite@2.0.0
32
34
 
33
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).
34
36
 
35
- ## Quick Start
37
+ ## Cài đặt
36
38
 
37
- ### Bước 1: Định nghĩa Schema
38
- Schema một object JSON mô tả cấu trúc database.
39
+ ```bash
40
+ npm install @dqcai/sqlite
41
+ ```
42
+
43
+ ## 1. Cấu hình Schema Database
44
+
45
+ Trước tiên, định nghĩa schema cho cơ sở dữ liệu:
39
46
 
40
47
  ```typescript
41
48
  import { DatabaseSchema } from '@dqcai/sqlite';
42
49
 
43
- const exampleSchema: DatabaseSchema = {
44
- version: '1.0.0',
45
- database_name: 'example_db',
46
- description: 'Example database',
50
+ // Schema cho database users
51
+ const userSchema: DatabaseSchema = {
52
+ version: "1.0.0",
53
+ database_name: "users",
54
+ description: "User management database",
47
55
  schemas: {
48
56
  users: {
49
- description: 'Users table',
57
+ description: "User table",
50
58
  cols: [
51
- { name: 'id', type: 'integer', primary_key: true, auto_increment: true },
52
- { name: 'name', type: 'string', nullable: false },
53
- { name: 'email', type: 'string', unique: true },
54
- ],
55
- indexes: [{ name: 'idx_email', columns: ['email'], unique: true }],
56
- foreign_keys: [],
57
- },
58
- },
59
- };
60
- ```
61
-
62
- ```json
63
- // schema.json ví dụ kiểu json
64
- {
65
- "version": "1.0.0",
66
- "database_name": "example.db",
67
- "description": "Schema for example application.",
68
- "schemas": {
69
- "users": {
70
- "description": "User accounts table.",
71
- "cols": [
72
59
  {
73
- "name": "id",
74
- "type": "integer",
75
- "constraints": "PRIMARY KEY AUTOINCREMENT"
60
+ name: "id",
61
+ type: "integer",
62
+ primary_key: true,
63
+ auto_increment: true,
64
+ nullable: false
76
65
  },
77
66
  {
78
- "name": "name",
79
- "type": "text",
80
- "constraints": "NOT NULL"
67
+ name: "username",
68
+ type: "varchar",
69
+ nullable: false,
70
+ unique: true,
71
+ length: 50
81
72
  },
82
73
  {
83
- "name": "email",
84
- "type": "text",
85
- "constraints": "UNIQUE NOT NULL"
74
+ name: "email",
75
+ type: "varchar",
76
+ nullable: false,
77
+ unique: true,
78
+ length: 100
86
79
  },
87
80
  {
88
- "name": "created_at",
89
- "type": "timestamp",
90
- "constraints": "DEFAULT CURRENT_TIMESTAMP"
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
91
108
  }
92
109
  ]
93
110
  },
94
- "posts": {
95
- "description": "Posts created by users.",
96
- "cols": [
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
+ },
97
120
  {
98
- "name": "id",
99
- "type": "integer",
100
- "constraints": "PRIMARY KEY AUTOINCREMENT"
121
+ name: "user_id",
122
+ type: "integer",
123
+ nullable: false
101
124
  },
102
125
  {
103
- "name": "title",
104
- "type": "text",
105
- "constraints": "NOT NULL"
126
+ name: "first_name",
127
+ type: "varchar",
128
+ length: 50
106
129
  },
107
130
  {
108
- "name": "content",
109
- "type": "text"
131
+ name: "last_name",
132
+ type: "varchar",
133
+ length: 50
110
134
  },
111
135
  {
112
- "name": "user_id",
113
- "type": "integer",
114
- "constraints": "NOT NULL"
136
+ name: "phone",
137
+ type: "varchar",
138
+ length: 20
139
+ },
140
+ {
141
+ name: "address",
142
+ type: "text"
115
143
  }
116
144
  ],
117
- "foreign_keys": [
145
+ foreign_keys: [
118
146
  {
119
- "name": "fk_posts_user_id",
120
- "column": "user_id",
121
- "references": {
122
- "table": "users",
123
- "column": "id"
147
+ name: "fk_profile_user",
148
+ column: "user_id",
149
+ references: {
150
+ table: "users",
151
+ column: "id"
124
152
  },
125
- "on_delete": "CASCADE"
153
+ on_delete: "CASCADE",
154
+ on_update: "CASCADE"
126
155
  }
127
156
  ]
128
157
  }
129
158
  }
130
- }
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
+ ```
189
+
190
+ ## 2. Setup cho React Native
191
+
192
+ ### Cài đặt dependencies
193
+
194
+ ```bash
195
+ npm install react-native-sqlite-2
196
+ # Hoặc
197
+ npm install react-native-sqlite-storage
131
198
  ```
132
199
 
133
- ### Bước 2: Khởi tạo và Sử dụng
134
- Sử dụng singleton UniversalSQLite.
200
+ ### Tạo Adapter cho React Native
135
201
 
136
202
  ```typescript
137
- import UniversalSQLite from '@dqcai/sqlite';
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
+ }
138
211
 
139
- const sqlite = UniversalSQLite.getInstance();
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
+ }
140
229
 
141
- async function init() {
142
- await sqlite.initializeFromSchema(exampleSchema);
143
- const dao = await sqlite.connect('example_db');
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
+ }
144
257
 
145
- // CRUD example
146
- await dao.insert({ name: 'users', cols: [{ name: 'name', value: 'John' }, { name: 'email', value: 'john@example.com' }] });
147
- const users = await dao.selectAll({ name: 'users' });
148
- console.log(users);
258
+ async close(): Promise<void> {
259
+ // React Native SQLite không cần close thủ công
260
+ return Promise.resolve();
261
+ }
149
262
  }
263
+ ```
264
+
265
+ ### Khởi tạo DatabaseManager (React Native)
150
266
 
151
- init();
267
+ ```typescript
268
+ // services/DatabaseService.ts
269
+ import { DatabaseManager, DatabaseFactory } from '@dqcai/sqlite';
270
+ import { ReactNativeAdapter } from '../adapters/ReactNativeAdapter';
271
+
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
285
+ });
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);
315
+ }
316
+
317
+ static async closeAll() {
318
+ await DatabaseManager.closeAll();
319
+ this.isInitialized = false;
320
+ }
321
+ }
152
322
  ```
153
323
 
154
- ## Adapters and Environment Setup
324
+ ## 3. Setup cho Node.js
155
325
 
156
- UniversalSQLite tự động detect môi trường và chọn adapter phù hợp. Tuy nhiên, bạn có thể register adapter tùy chỉnh nếu cần.
326
+ ### Cài đặt dependencies
157
327
 
158
- ### Common Adapters
159
- - **Browser**: Sử dụng sql.js (cần import sql.js library).
160
- - **Node.js**: Sử dụng better-sqlite3 hoặc sqlite3 (cần cài đặt package tương ứng).
161
- - **React Native**: Sử dụng react-native-sqlite-storage (cần cài đặt package).
162
- - **Deno/Bun**: Sử dụng native SQLite support.
328
+ ```bash
329
+ npm install sqlite3
330
+ # Hoặc
331
+ npm install better-sqlite3
332
+ ```
163
333
 
164
- ### Register Adapter
165
- Khai báo adapter tùy chỉnh bằng cách extend BaseAdapter và register với DatabaseFactory.
334
+ ### Tạo Adapter cho Node.js
166
335
 
167
336
  ```typescript
168
- import { BaseAdapter, SQLiteAdapter } from '@dqcai/sqlite';
169
- import DatabaseFactory from '@dqcai/sqlite';
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';
342
+
343
+ export class NodeAdapter extends BaseAdapter {
344
+ isSupported(): boolean {
345
+ return typeof process !== 'undefined' && process.versions?.node;
346
+ }
170
347
 
171
- class CustomNodeAdapter extends BaseAdapter implements SQLiteAdapter {
172
- // Implement connect and isSupported
173
- async connect(path: string) { /* ... */ }
174
- isSupported() { return true; }
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
+ }
356
+
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
+ }
175
367
  }
176
368
 
177
- DatabaseFactory.registerAdapter(new CustomNodeAdapter());
369
+ class NodeConnection {
370
+ constructor(private db: sqlite3.Database) {}
371
+
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
+ }
402
+
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
+ }
178
415
  ```
179
416
 
180
- ## Usage Guide
417
+ ### Khởi tạo DatabaseManager (Node.js)
418
+
419
+ ```typescript
420
+ // services/DatabaseService.ts
421
+ import { DatabaseManager, DatabaseFactory } from '@dqcai/sqlite';
422
+ import { NodeAdapter } from '../adapters/NodeAdapter';
423
+ import path from 'path';
424
+
425
+ export class DatabaseService {
426
+ private static isInitialized = false;
427
+ private static dbDirectory = './databases';
428
+
429
+ static async initialize() {
430
+ if (this.isInitialized) return;
431
+
432
+ // Đăng ký adapter
433
+ DatabaseFactory.registerAdapter(new NodeAdapter());
434
+
435
+ // Đăng ký schemas
436
+ DatabaseManager.registerSchemas({
437
+ core: coreSchema,
438
+ users: userSchema
439
+ });
440
+
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
+ ]);
455
+
456
+ // Khởi tạo core database
457
+ await DatabaseManager.initializeCoreConnection();
458
+
459
+ this.isInitialized = true;
460
+ console.log('DatabaseService initialized for Node.js');
461
+ }
181
462
 
182
- ### 1. Khởi tạo Database
183
- Sử dụng DatabaseFactory để tạo hoặc mở database.
463
+ static async setUserRole(roles: string[]) {
464
+ await DatabaseManager.setCurrentUserRoles(roles);
465
+ }
184
466
 
185
- - **Tạo từ Schema**:
186
- ```typescript
187
- const dao = await DatabaseFactory.createFromConfig(exampleSchema);
188
- ```
467
+ static getConnection(dbKey: string) {
468
+ return DatabaseManager.get(dbKey);
469
+ }
189
470
 
190
- - **Mở Existing Database**:
191
- ```typescript
192
- const dao = await DatabaseFactory.openExisting('example_db.db');
193
- ```
471
+ static async closeAll() {
472
+ await DatabaseManager.closeAll();
473
+ this.isInitialized = false;
474
+ }
475
+ }
476
+ ```
194
477
 
195
- - **Create DAO Directly**:
196
- ```typescript
197
- const dao = DatabaseFactory.createDAO('example_db.db', { createIfNotExists: true });
198
- await dao.connect();
199
- ```
478
+ ## 4. Tạo Services với BaseService
200
479
 
201
- ### 2. CRUD Operations với UniversalDAO
202
- UniversalDAO cung cấp các method CRUD cơ bản.
480
+ ### User Service
203
481
 
204
482
  ```typescript
205
- // Insert
206
- await dao.insert({ name: 'users', cols: [{ name: 'name', value: 'Alice' }] });
483
+ // services/UserService.ts
484
+ import { BaseService } from '@dqcai/sqlite';
485
+
486
+ interface User {
487
+ id?: number;
488
+ username: string;
489
+ email: string;
490
+ password: string;
491
+ created_at?: string;
492
+ updated_at?: string;
493
+ }
494
+
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
+ }
503
+
504
+ export class UserService extends BaseService<User> {
505
+ constructor() {
506
+ super('users', 'users'); // schema name, table name
507
+ this.setPrimaryKeyFields(['id']);
508
+ }
509
+
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
+ }
518
+
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
+ }
524
+
525
+ const newUser = await this.create({
526
+ ...userData,
527
+ created_at: new Date().toISOString()
528
+ });
529
+
530
+ return newUser;
531
+ } catch (error) {
532
+ console.error('Error creating user:', error);
533
+ throw error;
534
+ }
535
+ }
207
536
 
208
- // Update
209
- await dao.update({ name: 'users', cols: [{ name: 'name', value: 'Bob' }], wheres: [{ name: 'id', value: 1 }] });
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
+ }
210
550
 
211
- // Delete
212
- await dao.delete({ name: 'users', wheres: [{ name: 'id', value: 1 }] });
551
+ // Tìm user theo email
552
+ async findByEmail(email: string): Promise<User | null> {
553
+ return await this.findFirst({ email });
554
+ }
555
+
556
+ // Tìm user theo username
557
+ async findByUsername(username: string): Promise<User | null> {
558
+ return await this.findFirst({ username });
559
+ }
213
560
 
214
- // Select
215
- const row = await dao.select({ name: 'users', wheres: [{ name: 'id', value: 1 }] });
216
- const allRows = await dao.selectAll({ name: 'users' });
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
+ }
570
+
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
+ }
579
+
580
+ // Đếm tổng số users
581
+ async getTotalUsers(): Promise<number> {
582
+ return await this.count();
583
+ }
584
+
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
+ }
598
+
599
+ export class ProfileService extends BaseService<Profile> {
600
+ constructor() {
601
+ super('users', 'profiles');
602
+ this.setPrimaryKeyFields(['id']);
603
+ }
604
+
605
+ // Tạo profile cho user
606
+ async createProfile(profileData: Omit<Profile, 'id'>): Promise<Profile | null> {
607
+ return await this.create(profileData);
608
+ }
609
+
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
+ }
634
+ }
217
635
  ```
218
636
 
219
- ### 3. Query Builder
220
- Xây dựng query phức tạp.
637
+ ### Core Service
221
638
 
222
639
  ```typescript
223
- import { QueryBuilder } from '@dqcai/sqlite';
640
+ // services/CoreService.ts
641
+ import { BaseService } from '@dqcai/sqlite';
642
+
643
+ interface Setting {
644
+ key: string;
645
+ value: string;
646
+ description?: string;
647
+ }
648
+
649
+ export class SettingsService extends BaseService<Setting> {
650
+ constructor() {
651
+ super('core', 'settings');
652
+ this.setPrimaryKeyFields(['key']);
653
+ }
654
+
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
+ }
224
660
 
225
- const qb = new QueryBuilder(dao).table('users')
226
- .select('id', 'name')
227
- .where('age', '>', 18)
228
- .orderBy('name', 'ASC')
229
- .limit(10);
661
+ // Đặt giá trị setting
662
+ async setSetting(key: string, value: string, description?: string): Promise<void> {
663
+ const existing = await this.findById(key);
664
+
665
+ if (existing) {
666
+ await this.update(key, { value, description });
667
+ } else {
668
+ await this.create({ key, value, description });
669
+ }
670
+ }
671
+
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
+ }
678
+
679
+ // Xóa setting
680
+ async deleteSetting(key: string): Promise<boolean> {
681
+ return await this.delete(key);
682
+ }
230
683
 
231
- const results = await qb.get();
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
+ }
698
+ }
232
699
  ```
233
700
 
234
- ### 4. Migration
235
- Sử dụng MigrationManager.
701
+ ## 5. Sử dụng trong ứng dụng
702
+
703
+ ### Trong React Native
236
704
 
237
705
  ```typescript
238
- const migrationManager = new MigrationManager(dao);
239
- migrationManager.addMigration({
240
- version: '1.1.0',
241
- description: 'Add column',
242
- up: async (dao) => { await dao.execute('ALTER TABLE users ADD COLUMN age INTEGER'); },
243
- down: async (dao) => { /* rollback */ },
244
- });
245
- await migrationManager.migrate();
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());
718
+
719
+ useEffect(() => {
720
+ initializeDatabase();
721
+
722
+ return () => {
723
+ // Cleanup khi component unmount
724
+ DatabaseService.closeAll();
725
+ };
726
+ }, []);
727
+
728
+ const initializeDatabase = async () => {
729
+ try {
730
+ // Khởi tạo database
731
+ await DatabaseService.initialize();
732
+
733
+ // Đặt role cho user hiện tại
734
+ await DatabaseService.setUserRole(['user']);
735
+
736
+ // Khởi tạo services
737
+ await userService.init();
738
+ await profileService.init();
739
+ await settingsService.init();
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
+ }
770
+ } catch (error) {
771
+ console.error('Error creating user:', error);
772
+ Alert.alert('Error', 'Failed to create user');
773
+ }
774
+ };
775
+
776
+ const handleGetAllUsers = async () => {
777
+ if (!isDbReady) return;
778
+
779
+ try {
780
+ const users = await userService.getAllUsers(1, 10);
781
+ console.log('Users:', users);
782
+ Alert.alert('Users', `Found ${users.length} users`);
783
+ } catch (error) {
784
+ console.error('Error getting users:', error);
785
+ }
786
+ };
787
+
788
+ const handleSetSetting = async () => {
789
+ if (!isDbReady) return;
790
+
791
+ try {
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}`);
800
+ } catch (error) {
801
+ console.error('Error setting value:', error);
802
+ }
803
+ };
804
+
805
+ return (
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
+ />
820
+
821
+ <Button
822
+ title="Set App Setting"
823
+ onPress={handleSetSetting}
824
+ disabled={!isDbReady}
825
+ />
826
+ </View>
827
+ );
828
+ };
829
+
830
+ export default App;
246
831
  ```
247
832
 
248
- ### 5. Import/Export Data
249
- Sử dụng CSVImporter hoặc importData.
833
+ ### Trong Node.js
250
834
 
251
835
  ```typescript
252
- const importer = new CSVImporter(dao);
253
- await importer.importFromCSV('users', csvString, { hasHeader: true });
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';
254
841
 
255
- // Hoặc import JSON
256
- await dao.importData({ tableName: 'users', data: [{ name: 'John' }] });
842
+ const app = express();
843
+ app.use(express.json());
844
+
845
+ // Services
846
+ const userService = new UserService();
847
+ const profileService = new ProfileService();
848
+ const settingsService = new SettingsService();
849
+
850
+ // Khởi tạo database khi start server
851
+ async function initializeApp() {
852
+ try {
853
+ console.log('Initializing database...');
854
+
855
+ // Khởi tạo DatabaseService
856
+ await DatabaseService.initialize();
857
+
858
+ // Set role admin cho server
859
+ await DatabaseService.setUserRole(['admin']);
860
+
861
+ // Khởi tạo services
862
+ await userService.init();
863
+ await profileService.init();
864
+ await settingsService.init();
865
+
866
+ console.log('Database initialized successfully');
867
+ } catch (error) {
868
+ console.error('Database initialization failed:', error);
869
+ process.exit(1);
870
+ }
871
+ }
872
+
873
+ // API Routes
874
+
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
+ }
883
+
884
+ const user = await userService.createUser({
885
+ username,
886
+ email,
887
+ password // Nên hash password trước khi lưu
888
+ });
889
+
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 });
894
+ }
895
+ });
896
+
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
+ });
921
+
922
+ // GET /users/:id - Lấy user theo ID
923
+ app.get('/users/:id', async (req, res) => {
924
+ try {
925
+ const id = parseInt(req.params.id);
926
+ const user = await userService.findById(id);
927
+
928
+ if (!user) {
929
+ return res.status(404).json({ error: 'User not found' });
930
+ }
931
+
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 });
936
+ }
937
+ });
938
+
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;
944
+
945
+ const user = await userService.updateUser(id, updates);
946
+
947
+ if (!user) {
948
+ return res.status(404).json({ error: 'User not found' });
949
+ }
950
+
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 });
955
+ }
956
+ });
957
+
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);
963
+
964
+ if (!success) {
965
+ return res.status(404).json({ error: 'User not found' });
966
+ }
967
+
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 });
972
+ }
973
+ });
974
+
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 });
986
+ }
987
+ });
988
+
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);
994
+
995
+ if (!userWithProfile) {
996
+ return res.status(404).json({ error: 'User not found' });
997
+ }
998
+
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 });
1003
+ }
1004
+ });
1005
+
1006
+ // Settings API
1007
+ app.get('/settings/:key', async (req, res) => {
1008
+ try {
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 });
1014
+ }
1015
+ });
1016
+
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
+ });
1027
+
1028
+ // Graceful shutdown
1029
+ process.on('SIGINT', async () => {
1030
+ console.log('Shutting down server...');
1031
+ await DatabaseService.closeAll();
1032
+ process.exit(0);
1033
+ });
1034
+
1035
+ process.on('SIGTERM', async () => {
1036
+ console.log('Shutting down server...');
1037
+ await DatabaseService.closeAll();
1038
+ process.exit(0);
1039
+ });
1040
+
1041
+ // Start server
1042
+ const PORT = process.env.PORT || 3000;
1043
+
1044
+ initializeApp().then(() => {
1045
+ app.listen(PORT, () => {
1046
+ console.log(`Server running on port ${PORT}`);
1047
+ });
1048
+ });
257
1049
  ```
258
1050
 
259
- ### 6. Transaction
1051
+ ## 6. Quản lý Database với DatabaseManager
1052
+
1053
+ ### Mở/Đóng Connections
1054
+
260
1055
  ```typescript
261
- await dao.beginTransaction();
262
- try {
263
- // operations
264
- await dao.commitTransaction();
265
- } catch {
266
- await dao.rollbackTransaction();
1056
+ // DatabaseHelper.ts
1057
+ import { DatabaseManager } from '@dqcai/sqlite';
1058
+
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
+ }
1071
+
1072
+ // Đóng kết nối cụ thể
1073
+ static async closeSpecificConnection(dbKey: string): Promise<void> {
1074
+ try {
1075
+ await DatabaseManager.closeConnection(dbKey);
1076
+ console.log(`Connection ${dbKey} closed`);
1077
+ } catch (error) {
1078
+ console.error(`Error closing connection ${dbKey}:`, error);
1079
+ }
1080
+ }
1081
+
1082
+ // Mở lại kết nối
1083
+ static async reopenConnection(dbKey: string): Promise<void> {
1084
+ try {
1085
+ const dao = await DatabaseManager.getLazyLoading(dbKey);
1086
+ console.log(`Connection ${dbKey} reopened`);
1087
+ return dao;
1088
+ } catch (error) {
1089
+ console.error(`Error reopening connection ${dbKey}:`, error);
1090
+ throw error;
1091
+ }
1092
+ }
1093
+
1094
+ // Đảm bảo kết nối tồn tại
1095
+ static async ensureConnection(dbKey: string): Promise<void> {
1096
+ try {
1097
+ const dao = await DatabaseManager.ensureDatabaseConnection(dbKey);
1098
+ console.log(`Connection ${dbKey} ensured`);
1099
+ return dao;
1100
+ } catch (error) {
1101
+ console.error(`Error ensuring connection ${dbKey}:`, error);
1102
+ throw error;
1103
+ }
1104
+ }
1105
+
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> {
1111
+ try {
1112
+ await DatabaseManager.executeCrossSchemaTransaction(schemas, operations);
1113
+ console.log('Cross-schema transaction completed successfully');
1114
+ } catch (error) {
1115
+ console.error('Cross-schema transaction failed:', error);
1116
+ throw error;
1117
+ }
1118
+ }
1119
+
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
+ });
1126
+
1127
+ DatabaseManager.onDatabaseReconnect('core', (dao) => {
1128
+ console.log('Core database reconnected');
1129
+ // Re-initialize settings
1130
+ });
1131
+ }
1132
+
1133
+ // Health check tất cả connections
1134
+ static async performHealthCheck(): Promise<void> {
1135
+ const connections = DatabaseManager.getConnections();
1136
+
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);
1143
+
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);
1150
+ }
1151
+ }
1152
+ }
1153
+ }
267
1154
  }
268
1155
  ```
269
1156
 
270
- ### 7. BaseService
271
- Tạo service layer.
1157
+ ## 7. Import/Export Dữ liệu
1158
+
1159
+ ### Import từ CSV
272
1160
 
273
1161
  ```typescript
274
- class UserService extends BaseService<{ id: number; name: string }> {
275
- constructor() {
276
- super('example_db', 'users');
1162
+ // services/DataImportService.ts
1163
+ import { DatabaseManager, ImportResult, ColumnMapping } from '@dqcai/sqlite';
1164
+
1165
+ export class DataImportService {
1166
+
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
+ );
1188
+
1189
+ console.log('Import completed:', {
1190
+ total: result.totalRows,
1191
+ success: result.successRows,
1192
+ errors: result.errorRows,
1193
+ time: result.executionTime
1194
+ });
1195
+
1196
+ return result;
1197
+ } catch (error) {
1198
+ console.error('Import failed:', error);
1199
+ throw error;
1200
+ }
1201
+ }
1202
+
1203
+ // Import với column mapping
1204
+ static async importUsersWithMapping(
1205
+ data: Record<string, any>[],
1206
+ columnMappings: ColumnMapping[]
1207
+ ): Promise<ImportResult> {
1208
+ try {
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;
1223
+ } catch (error) {
1224
+ console.error('Import with mapping failed:', error);
1225
+ throw error;
1226
+ }
1227
+ }
1228
+
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> {
1235
+ try {
1236
+ const result = await DatabaseManager.bulkImport(importConfigs);
1237
+
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
+ }
277
1258
  }
278
1259
  }
279
1260
 
280
- const userService = new UserService();
281
- await userService.create({ name: 'John' });
1261
+ // 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
+
1269
+ try {
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
+
1296
+ } catch (error) {
1297
+ console.error('Import example failed:', error);
1298
+ }
1299
+ }
282
1300
  ```
283
1301
 
284
- ### 8. Role-Based Management
285
- Sử dụng DatabaseManager để quản lý role.
1302
+ ### Export dữ liệu
286
1303
 
287
1304
  ```typescript
288
- DatabaseManager.registerRole({ roleName: 'admin', requiredDatabases: ['example_db'] });
289
- await DatabaseManager.setCurrentUserRoles(['admin']);
290
- const dao = DatabaseManager.get('example_db');
1305
+ // services/DataExportService.ts
1306
+ import { DatabaseManager } from '@dqcai/sqlite';
1307
+
1308
+ export class DataExportService {
1309
+
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
+ }
1354
+ }
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
+ }
1381
+ }
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
+ }
1420
+ }
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);
1443
+ }
1444
+ }
291
1445
  ```
292
1446
 
293
- ## Examples
1447
+ ## 8. Best Practices & Tips
294
1448
 
295
- ### Example for React Native
1449
+ ### Error Handling
296
1450
 
297
- 1. Cài đặt dependencies:
298
- ```bash
299
- npm install react-native-sqlite-storage
300
- ```
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()
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
+ }
1473
+ }
301
1474
 
302
- 2. Khai báo Adapter (nếu cần tùy chỉnh, nhưng thư viện tự detect):
303
- ```typescript
304
- import { enablePromise, openDatabase} from 'react-native-sqlite-storage';
305
- import { BaseAdapter } from '@dqcai/sqlite';
306
- enablePromise(true);
307
- class ReactNativeAdapter extends BaseAdapter {
308
- isSupported() {
309
- return typeof Platform !== 'undefined' && Platform.OS !== 'web';
1475
+ static async withRetry<T>(
1476
+ operation: () => Promise<T>,
1477
+ maxRetries: number = 3,
1478
+ delay: number = 1000
1479
+ ): Promise<T> {
1480
+ let lastError: any;
1481
+
1482
+ for (let i = 0; i < maxRetries; i++) {
1483
+ try {
1484
+ return await operation();
1485
+ } catch (error) {
1486
+ lastError = error;
1487
+
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
310
1492
  }
1493
+ }
1494
+ }
1495
+
1496
+ throw lastError;
1497
+ }
1498
+ }
311
1499
 
312
- connect(path) {
313
- return new Promise((resolve, reject) => {
314
- const db = openDatabase(
315
- { name: path, location: 'default' },
316
- () => {
317
- resolve({
318
- execute: (sql, params) => {
319
- return new Promise((execResolve, execReject) => {
320
- db.transaction(tx => {
321
- tx.executeSql(
322
- sql,
323
- params,
324
- (tx, results) => {
325
- const rowsAffected = results.rowsAffected;
326
- let rows = [];
327
- for (let i = 0; i < results.rows.length; i++) {
328
- rows.push(results.rows.item(i));
329
- }
330
- execResolve({ rows, rowsAffected, lastInsertRowId: results.insertId });
331
- },
332
- (tx, error) => {
333
- execReject(error);
334
- }
335
- );
336
- });
337
- });
338
- },
339
- close: () => {
340
- return new Promise((closeResolve, closeReject) => {
341
- db.close((err) => {
342
- if (err) return closeReject(err);
343
- closeResolve();
344
- });
345
- });
346
- }
347
- });
348
- },
349
- (error) => reject(error)
350
- );
351
- });
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
+ }
1512
+ }
1513
+ ```
1514
+
1515
+ ### Performance Optimization
1516
+
1517
+ ```typescript
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);
1530
+
1531
+ await userService.executeTransaction(async () => {
1532
+ for (const userData of batch) {
1533
+ await userService.create({
1534
+ ...userData,
1535
+ created_at: new Date().toISOString()
1536
+ });
352
1537
  }
1538
+ });
1539
+
1540
+ console.log(`Processed batch ${Math.ceil((i + batchSize) / batchSize)}`);
353
1541
  }
354
- export default ReactNativeAdapter;
1542
+ }
1543
+
1544
+ // Index optimization
1545
+ static async optimizeQueries(dao: any): Promise<void> {
1546
+ // Enable query optimization
1547
+ await dao.execute('PRAGMA optimize');
1548
+
1549
+ // Analyze tables for better query planning
1550
+ await dao.execute('ANALYZE');
1551
+
1552
+ // Set optimal cache size
1553
+ await dao.execute('PRAGMA cache_size = 10000');
355
1554
 
356
- ```
1555
+ console.log('Database optimization completed');
1556
+ }
357
1557
 
358
- 3. Tạo Mở Database, CRUD:
359
- ```typescript
360
- import { DatabaseFactory } from '@dqcai/sqlite';
361
- import ReactNativeAdapter from './adapters/react-native-adapter'; // Hoặc NodejsAdapter
362
- import schema from './schema.json';
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');
1568
+ }
1569
+ }, 30000); // Check every 30 seconds
1570
+ }
1571
+ }
1572
+ ```
363
1573
 
364
- async function setupDatabase() {
365
- // Đăng ký adapter để thư viện biết cách kết nối
366
- DatabaseFactory.registerAdapter(new ReactNativeAdapter()); // Đăng ký adapter cho môi trường
1574
+ ### Testing Utilities
367
1575
 
368
- // Tạo hoặc mở database. Nếu file chưa tồn tại, nó sẽ được tạo mới và init schema.
369
- // Nếu đã tồn tại, nó sẽ được mở.
370
- const dao = await DatabaseFactory.createOrOpen({ config: schema });
1576
+ ```typescript
1577
+ // utils/TestHelpers.ts
1578
+ import { DatabaseManager } from '@dqcai/sqlite';
1579
+
1580
+ export class DatabaseTestHelpers {
1581
+
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']);
1591
+ }
1592
+
1593
+ static async cleanupTestData(service: any): Promise<void> {
1594
+ await service.truncate();
1595
+ }
371
1596
 
372
- console.log(`Kết nối thành công tới database: ${dao.getDatabaseInfo().name}`);
373
- return dao;
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
+ ];
1603
+
1604
+ const createdUsers: User[] = [];
1605
+ for (const userData of testUsers) {
1606
+ const user = await userService.createUser(userData);
1607
+ if (user) createdUsers.push(user);
374
1608
  }
375
1609
 
376
- async function crudOperations(dao) {
377
- // 1. CREATE (Insert)
378
- console.log('Inserting a new user...');
379
- const userToInsert = {
380
- name: 'John Doe',
381
- email: 'john.doe@example.com',
382
- };
383
- const insertResult = await dao.insert({
384
- name: 'users',
385
- cols: Object.entries(userToInsert).map(([name, value]) => ({ name, value })),
386
- });
387
- const newUserId = insertResult.lastInsertRowId;
388
- console.log(`User inserted with ID: ${newUserId}`);
389
-
390
- // 2. READ (Select)
391
- console.log('Selecting user by email...');
392
- const selectedUser = await dao.select({
393
- name: 'users',
394
- cols: [], // Chọn tất cả các cột
395
- wheres: [{ name: 'email', value: 'john.doe@example.com' }],
396
- });
397
- console.log('Selected user:', selectedUser);
398
-
399
- // 3. UPDATE
400
- console.log('Updating user name...');
401
- await dao.update({
402
- name: 'users',
403
- cols: [{ name: 'name', value: 'John Smith' }],
404
- wheres: [{ name: 'id', value: newUserId }],
405
- });
406
- const updatedUser = await dao.select({
407
- name: 'users',
408
- cols: ['id', 'name'],
409
- wheres: [{ name: 'id', value: newUserId }],
410
- });
411
- console.log('Updated user:', updatedUser);
1610
+ return createdUsers;
1611
+ }
1612
+ }
412
1613
 
413
- // 4. DELETE
414
- console.log('Deleting the user...');
415
- const deleteResult = await dao.delete({
416
- name: 'users',
417
- wheres: [{ name: 'id', value: newUserId }],
418
- });
419
- console.log(`Rows deleted: ${deleteResult.rowsAffected}`);
1614
+ // Example test
1615
+ describe('UserService Tests', () => {
1616
+ let userService: UserService;
1617
+
1618
+ beforeAll(async () => {
1619
+ await DatabaseTestHelpers.setupTestDatabase();
1620
+ userService = new UserService();
1621
+ await userService.init();
1622
+ });
1623
+
1624
+ beforeEach(async () => {
1625
+ await DatabaseTestHelpers.cleanupTestData(userService);
1626
+ });
1627
+
1628
+ afterAll(async () => {
1629
+ await DatabaseManager.closeAll();
1630
+ });
1631
+
1632
+ test('should create user successfully', async () => {
1633
+ const userData = {
1634
+ username: 'testuser',
1635
+ email: 'test@example.com',
1636
+ password: 'password123'
1637
+ };
1638
+
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
+ ```
420
1646
 
421
- const checkUser = await dao.select({
422
- name: 'users',
423
- wheres: [{ name: 'id', value: newUserId }],
424
- });
425
- console.log('User exists after delete?', Object.keys(checkUser).length > 0);
426
- }
1647
+ ## 9. Troubleshooting Common Issues
427
1648
 
428
- // Chạy ví dụ
429
- async function main() {
430
- try {
431
- const dao = await setupDatabase();
432
- await crudOperations(dao);
433
- await dao.close();
434
- console.log('Đã đóng kết nối database.');
435
- } catch (error) {
436
- console.error('Đã xảy ra lỗi:', error);
437
- }
438
- }
1649
+ ### Database Locked
1650
+
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
+ ```
1664
+
1665
+ ### Connection Issues
1666
+
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
+ };
1680
+ ```
1681
+
1682
+ ## 10. Migration & Schema Updates
1683
+
1684
+ ```typescript
1685
+ // migrations/001_add_user_status.ts
1686
+ import { UniversalDAO } from '@dqcai/sqlite';
1687
+
1688
+ export const migration_001 = {
1689
+ version: '1.0.1',
1690
+ description: 'Add status column to users table',
1691
+
1692
+ async up(dao: UniversalDAO): Promise<void> {
1693
+ await dao.execute(`
1694
+ ALTER TABLE users
1695
+ ADD COLUMN status VARCHAR(20) DEFAULT 'active'
1696
+ `);
1697
+
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
+ `);
1707
+
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
+ `);
1714
+
1715
+ await dao.execute(`DROP TABLE users`);
1716
+
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
+ `);
1727
+
1728
+ await dao.execute(`
1729
+ INSERT INTO users SELECT * FROM users_backup
1730
+ `);
1731
+
1732
+ await dao.execute(`DROP TABLE users_backup`);
1733
+ }
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
+ };
1747
+ ```
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
439
1758
 
440
- main();
441
- ```
442
-
443
- ### Example for Node.js
444
-
445
- 1. Cài đặt dependencies:
446
- ```bash
447
- npm install better-sqlite3
448
- ```
449
-
450
- 2. Khai báo Adapter (tự detect, nhưng có thể tùy chỉnh):
451
- ```typescript
452
- class NodeAdapter extends BaseAdapter {
453
- async connect(path: string) {
454
- const SQLite3 = require('better-sqlite3');
455
- const db = new SQLite3(path);
456
- return {
457
- execute: (sql: string, params = []) => {
458
- const stmt = db.prepare(sql);
459
- const result = stmt.run(params);
460
- return { rows: stmt.all(params), rowsAffected: result.changes };
461
- },
462
- close: () => db.close(),
463
- };
464
- }
465
- isSupported() { return true; }
466
- }
467
-
468
- DatabaseFactory.registerAdapter(new NodeAdapter());
469
- ```
470
-
471
- 3. Tạo và Mở Database, CRUD:
472
- ```typescript
473
- import UniversalSQLite from '@dqcai/sqlite';
474
-
475
- const sqlite = UniversalSQLite.getInstance();
476
-
477
- async function nodeExample() {
478
- await sqlite.initializeFromSchema(exampleSchema);
479
- const dao = await sqlite.connect('example_db');
480
-
481
- // CRUD
482
- await dao.insert({ name: 'users', cols: [{ name: 'name', value: 'NodeUser' }, { name: 'email', value: 'node@example.com' }] });
483
- const users = await dao.selectAll({ name: 'users' });
484
- console.log('Users:', users);
485
-
486
- // Query Builder
487
- const qb = sqlite.query('users');
488
- const results = await qb.select('*').where('name', 'LIKE', '%User%').get();
489
-
490
- // Migration
491
- const migrationManager = sqlite.createMigrationManager();
492
- migrationManager.addMigration({
493
- version: '1.0.1',
494
- description: 'Add age',
495
- up: async (dao) => await dao.execute('ALTER TABLE users ADD COLUMN age INTEGER'),
496
- down: async (dao) => await dao.execute('ALTER TABLE users DROP COLUMN age'),
497
- });
498
- await migrationManager.migrate();
499
-
500
- // Import from File
501
- const importer = sqlite.createCSVImporter();
502
- await importer.importFromFile('users', './data.csv', { hasHeader: true });
503
-
504
- await sqlite.closeAll();
505
- }
506
-
507
- nodeExample();
508
- ```
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.
509
1760
 
510
1761
  ## API Reference
511
1762