Giao diện
@digiforce-nc/database
| npm | @digiforce-nc/database |
| Import | import { Database, Collection, Repository } from '@digiforce-nc/database' |
| Runtime | Node.js |
| DB Support | PostgreSQL, MySQL, MariaDB, SQLite |
Tổng quan
@digiforce-nc/database là ORM layer của Digiforce, xây trên Sequelize. Package quản lý:
- Schema definition - Collection (table metadata) + Field (column metadata)
- Data access - Repository pattern (CRUD + relation query)
- Migration - tích hợp Umzug
- Relation - HasOne, HasMany, BelongsTo, BelongsToMany
- Filter & Sort - dot-path relation support, custom operators
Ba class cốt lõi tạo thành kiến trúc 3 tầng:
| Tầng | Class | Trách nhiệm |
|---|---|---|
| Meta | Database | Quản lý collections, connection, sync, migration, event bus |
| Schema | Collection | Định nghĩa table: fields, indexes, relations, model options |
| Data | Repository | CRUD operations, filter parsing, relation loading |
Class diagram
Database API
Constructor
typescript
const db = new Database({
dialect: 'postgres',
host: 'localhost',
port: 5432,
database: 'my_db',
username: 'admin',
password: 'secret',
logging: false,
});Constructor thực hiện:
- Validate dialect (postgres, mysql, sqlite, mariadb)
- Setup logger cho DB queries
- Chuẩn hóa timezone theo dialect
- Khởi tạo Sequelize instance
- Đăng ký built-in field types, operators, interfaces
- Bind listener
afterDefineCollection→ resolve pending relations
Collection management
| Method | Mô tả |
|---|---|
collection(options) | Tạo collection mới. Emit beforeDefineCollection → tạo → emit afterDefineCollection. |
extendCollection(options) | Mở rộng collection đã có (thêm field, index). Nếu chưa tồn tại, lưu vào queue chờ. |
getCollection(name) | Lấy collection instance theo tên. |
removeCollection(name) | Xóa collection khỏi registry. Emit afterRemoveCollection. |
import({ directory, from }) | Load tất cả collection definition từ thư mục. Module extend → extendCollection, module thường → collection(). |
Khi nào dùng extendCollection?
Plugin thường dùng extendCollection để thêm field vào collection của plugin khác mà không sửa source gốc.
typescript
db.extendCollection({
name: 'users',
fields: [
{ type: 'date', name: 'lastAuditAt' },
],
});Database operations
| Method | Mô tả |
|---|---|
sync(options?) | Đồng bộ schema → DB (tạo/sửa bảng). Options: force, alter. |
clean(options?) | Xóa tất cả data trong DB (truncate). |
close() | Đóng connection pool. |
auth() | Kiểm tra kết nối DB. |
reconnect() | Đóng và mở lại connection. |
runSQL(sql, options?) | Chạy raw SQL query. |
Migration
| Method | Mô tả |
|---|---|
addMigrations(options) | Đăng ký migration files. Tích hợp Umzug engine. |
typescript
db.addMigrations({
namespace: 'my-plugin',
directory: path.resolve(__dirname, './migrations'),
context: { db },
});Events
| Method | Mô tả |
|---|---|
on(event, listener) | Lắng nghe DB-level event. |
| Event | Khi nào |
|---|---|
beforeDefineCollection | Trước khi tạo collection |
afterDefineCollection | Sau khi tạo collection |
afterRemoveCollection | Sau khi xóa collection |
beforeSync | Trước khi sync schema |
afterSync | Sau khi sync schema |
Collection API
Collection đại diện một bảng trong database, chứa metadata về fields, indexes, relations.
Định nghĩa collection
typescript
const tasks = db.collection({
name: 'tasks',
fields: [
{ type: 'string', name: 'title', required: true },
{ type: 'text', name: 'description' },
{ type: 'boolean', name: 'completed', defaultValue: false },
{ type: 'date', name: 'dueDate' },
{ type: 'integer', name: 'priority', defaultValue: 0 },
{ type: 'belongsTo', name: 'assignee', target: 'users' },
{ type: 'hasMany', name: 'comments', target: 'comments' },
],
});Field management
| Method | Mô tả |
|---|---|
addField(name, options) | Thêm field vào collection. Trả về Field instance. |
removeField(name) | Xóa field. |
setFields(fields) | Set toàn bộ fields (replace). |
hasField(name) | Kiểm tra field tồn tại. |
getField(name) | Lấy field instance. |
Properties
| Property | Kiểu | Mô tả |
|---|---|---|
fields | Map<string, Field> | Tất cả field definitions |
repository | Repository | Repository instance cho collection này |
model | Model | Sequelize Model tương ứng |
name | string | Tên collection (= tên bảng) |
db | Database | Tham chiếu ngược Database |
Repository API
Repository cung cấp data access layer chuẩn hóa cho mỗi collection.
Query methods
| Method | Mô tả |
|---|---|
find(options?) | Tìm nhiều record. Trả về array. |
findOne(options?) | Tìm một record. |
findAndCount(options?) | Tìm + đếm tổng (cho pagination). Trả về [rows, count]. |
count(options?) | Đếm số record thỏa filter. |
Mutation methods
| Method | Mô tả |
|---|---|
create(options) | Tạo record mới. |
update(options) | Cập nhật record. |
destroy(options) | Xóa record. |
firstOrCreate(options) | Tìm record đầu tiên, tạo mới nếu không có. |
updateOrCreate(options) | Tìm và cập nhật, tạo mới nếu không có. |
Options phổ biến
typescript
interface FindOptions {
filter?: FilterType; // Điều kiện lọc
fields?: string[]; // Chọn fields (SELECT)
except?: string[]; // Loại trừ fields
appends?: string[]; // Load relation
sort?: string[]; // Sắp xếp: ['createdAt'] hoặc ['-createdAt']
limit?: number; // Giới hạn
offset?: number; // Bỏ qua N record đầu
filterByTk?: any; // Filter theo primary key
context?: any; // Koa context (cho hook)
transaction?: Transaction; // Sequelize transaction
}Ví dụ sử dụng Repository
typescript
const repo = db.getRepository('tasks');
// Tìm tất cả task chưa hoàn thành, kèm thông tin assignee
const tasks = await repo.find({
filter: {
completed: false,
'assignee.department': 'engineering',
},
appends: ['assignee', 'comments'],
sort: ['-priority', 'dueDate'],
limit: 20,
});
// Tạo task mới
const task = await repo.create({
values: {
title: 'Review PR #42',
assignee: 1,
priority: 5,
},
});
// Cập nhật theo primary key
await repo.update({
filterByTk: task.id,
values: { completed: true },
});
// Tạo hoặc cập nhật (upsert)
await repo.updateOrCreate({
filterKeys: ['title'],
values: { title: 'Daily standup', priority: 3 },
});
// Xóa theo filter
await repo.destroy({
filter: { completed: true, updatedAt: { $lt: '2024-01-01' } },
});Relation Repositories
Mỗi kiểu relation có repository chuyên biệt cho thao tác qua association:
| Repository | Relation | Thao tác đặc biệt |
|---|---|---|
HasOneRepository | 1:1 owner side | set(), remove() |
HasManyRepository | 1:N | add(), remove(), set(), toggle() |
BelongsToRepository | N:1 | set(), remove() |
BelongsToManyRepository | M:N | add(), remove(), set(), toggle() |
typescript
// HasMany: thêm comment vào task
const commentsRepo = db.getRepository('tasks.comments', taskId);
await commentsRepo.create({
values: { content: 'LGTM!', author: userId },
});
// BelongsToMany: gắn tags
const tagsRepo = db.getRepository('tasks.tags', taskId);
await tagsRepo.add([tagId1, tagId2]);
await tagsRepo.toggle(tagId3); // thêm nếu chưa có, bỏ nếu đã cóField types
| Type | Category | Mô tả |
|---|---|---|
string | Scalar | VARCHAR |
text | Scalar | TEXT (không giới hạn) |
integer | Scalar | INTEGER |
bigInt | Scalar | BIGINT |
float | Scalar | FLOAT |
double | Scalar | DOUBLE |
decimal | Scalar | DECIMAL |
boolean | Scalar | BOOLEAN |
date | Scalar | DATEONLY |
dateTime | Scalar | DATE (có time) |
time | Scalar | TIME |
json | Scalar | JSON / JSONB |
jsonb | Scalar | JSONB (PostgreSQL) |
array | Scalar | ARRAY (PostgreSQL) |
uuid | Scalar | UUID |
uid | Scalar | UID (nanoid) |
enum | Scalar | ENUM |
set | Scalar | SET (MySQL) |
password | Scalar | Hashed password field |
sort | Scalar | Sort order field |
virtual | Virtual | Không lưu DB |
formula | Virtual | Tính toán từ field khác |
belongsTo | Relation | N:1 |
hasOne | Relation | 1:1 |
hasMany | Relation | 1:N |
belongsToMany | Relation | M:N (qua junction table) |
FilterParser
FilterParser chuyển đổi filter object thành Sequelize where clause, hỗ trợ:
Dot-path relation support
Filter tự động resolve qua relation:
typescript
// Filter theo field của relation lồng nhau
const filter = {
'assignee.department.name': 'Engineering',
'tags.name.$includes': 'urgent',
};Operators
| Operator | SQL tương đương | Ví dụ |
|---|---|---|
$eq | = | { age: { $eq: 18 } } |
$ne | != | { status: { $ne: 'deleted' } } |
$gt | > | { price: { $gt: 100 } } |
$gte | >= | { price: { $gte: 100 } } |
$lt | < | { age: { $lt: 30 } } |
$lte | <= | { age: { $lte: 30 } } |
$in | IN | { status: { $in: ['active', 'pending'] } } |
$notIn | NOT IN | { role: { $notIn: ['admin'] } } |
$like | LIKE | { name: { $like: '%john%' } } |
$notLike | NOT LIKE | { name: { $notLike: '%test%' } } |
$includes | LIKE %v% | { title: { $includes: 'report' } } |
$startsWith | LIKE v% | { code: { $startsWith: 'PRJ' } } |
$endsWith | LIKE %v | { email: { $endsWith: '@company.com' } } |
$exists | IS NOT NULL / IS NULL | { avatar: { $exists: true } } |
$empty | = '' OR IS NULL | { bio: { $empty: true } } |
$between | BETWEEN | { age: { $between: [18, 60] } } |
Logical operators
typescript
const filter = {
$and: [
{ status: 'active' },
{
$or: [
{ priority: { $gte: 5 } },
{ 'assignee.role': 'manager' },
],
},
],
};Migration support
Tích hợp Umzug cho database migration:
typescript
db.addMigrations({
namespace: 'my-plugin',
directory: path.resolve(__dirname, './migrations'),
context: { db },
});
// Migration file: migrations/20240101-add-status-field.ts
export async function up(db) {
const collection = db.getCollection('tasks');
collection.addField('status', {
type: 'enum',
values: ['draft', 'active', 'archived'],
defaultValue: 'draft',
});
await collection.sync({ alter: true });
}
export async function down(db) {
const collection = db.getCollection('tasks');
collection.removeField('status');
await collection.sync({ alter: true });
}Re-exports từ Sequelize
Package re-export các utility phổ dùng từ Sequelize:
typescript
import { Op, DataTypes, Transaction, Sequelize } from '@digiforce-nc/database';
// Sử dụng Op trong raw query
const results = await model.findAll({
where: {
createdAt: { [Op.gte]: new Date('2024-01-01') },
},
});
// Transaction
await db.sequelize.transaction(async (transaction) => {
await repo.create({ values: {...}, transaction });
await repo.update({ filterByTk: id, values: {...}, transaction });
});| Export | Mô tả |
|---|---|
Op | Sequelize operators (Op.and, Op.or, Op.gte, …) |
DataTypes | Column type definitions |
Transaction | Transaction class |
Sequelize | Sequelize class |
Ví dụ tổng hợp
typescript
import { Database } from '@digiforce-nc/database';
const db = new Database({
dialect: 'postgres',
host: 'localhost',
database: 'myapp',
username: 'admin',
password: 'secret',
});
// Định nghĩa collection
db.collection({
name: 'projects',
fields: [
{ type: 'string', name: 'name' },
{ type: 'text', name: 'description' },
{ type: 'belongsTo', name: 'owner', target: 'users' },
{ type: 'hasMany', name: 'tasks', target: 'tasks' },
{ type: 'belongsToMany', name: 'members', target: 'users', through: 'project_members' },
],
});
db.collection({
name: 'tasks',
fields: [
{ type: 'string', name: 'title' },
{ type: 'boolean', name: 'completed', defaultValue: false },
{ type: 'integer', name: 'priority' },
{ type: 'belongsTo', name: 'project', target: 'projects' },
],
});
// Sync schema
await db.sync();
// Query
const projects = await db.getRepository('projects').find({
filter: { 'owner.role': 'admin' },
appends: ['tasks', 'members'],
sort: ['-createdAt'],
});Xem thêm
- Database - Collection & Repository - phân tích code-level chi tiết
- Data & Request Flow - luồng dữ liệu từ request đến DB
- FAQ - câu hỏi thường gặp
- Mã nguồn