Bỏ qua, đến nội dung

@digiforce-nc/database

npm@digiforce-nc/database
Importimport { Database, Collection, Repository } from '@digiforce-nc/database'
RuntimeNode.js
DB SupportPostgreSQL, MySQL, MariaDB, SQLite

Tổng quan

@digiforce-nc/databaseORM 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ầngClassTrách nhiệm
MetaDatabaseQuản lý collections, connection, sync, migration, event bus
SchemaCollectionĐịnh nghĩa table: fields, indexes, relations, model options
DataRepositoryCRUD 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:

  1. Validate dialect (postgres, mysql, sqlite, mariadb)
  2. Setup logger cho DB queries
  3. Chuẩn hóa timezone theo dialect
  4. Khởi tạo Sequelize instance
  5. Đăng ký built-in field types, operators, interfaces
  6. Bind listener afterDefineCollection → resolve pending relations

Collection management

MethodMô 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 extendextendCollection, 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

MethodMô 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

MethodMô 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

MethodMô tả
on(event, listener)Lắng nghe DB-level event.
EventKhi nào
beforeDefineCollectionTrước khi tạo collection
afterDefineCollectionSau khi tạo collection
afterRemoveCollectionSau khi xóa collection
beforeSyncTrước khi sync schema
afterSyncSau 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

MethodMô 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

PropertyKiểuMô tả
fieldsMap<string, Field>Tất cả field definitions
repositoryRepositoryRepository instance cho collection này
modelModelSequelize Model tương ứng
namestringTên collection (= tên bảng)
dbDatabaseTham chiếu ngược Database

Repository API

Repository cung cấp data access layer chuẩn hóa cho mỗi collection.

Query methods

MethodMô 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

MethodMô 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:

RepositoryRelationThao tác đặc biệt
HasOneRepository1:1 owner sideset(), remove()
HasManyRepository1:Nadd(), remove(), set(), toggle()
BelongsToRepositoryN:1set(), remove()
BelongsToManyRepositoryM:Nadd(), 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

TypeCategoryMô tả
stringScalarVARCHAR
textScalarTEXT (không giới hạn)
integerScalarINTEGER
bigIntScalarBIGINT
floatScalarFLOAT
doubleScalarDOUBLE
decimalScalarDECIMAL
booleanScalarBOOLEAN
dateScalarDATEONLY
dateTimeScalarDATE (có time)
timeScalarTIME
jsonScalarJSON / JSONB
jsonbScalarJSONB (PostgreSQL)
arrayScalarARRAY (PostgreSQL)
uuidScalarUUID
uidScalarUID (nanoid)
enumScalarENUM
setScalarSET (MySQL)
passwordScalarHashed password field
sortScalarSort order field
virtualVirtualKhông lưu DB
formulaVirtualTính toán từ field khác
belongsToRelationN:1
hasOneRelation1:1
hasManyRelation1:N
belongsToManyRelationM: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

OperatorSQL tương đươngVí 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 } }
$inIN{ status: { $in: ['active', 'pending'] } }
$notInNOT IN{ role: { $notIn: ['admin'] } }
$likeLIKE{ name: { $like: '%john%' } }
$notLikeNOT LIKE{ name: { $notLike: '%test%' } }
$includesLIKE %v%{ title: { $includes: 'report' } }
$startsWithLIKE v%{ code: { $startsWith: 'PRJ' } }
$endsWithLIKE %v{ email: { $endsWith: '@company.com' } }
$existsIS NOT NULL / IS NULL{ avatar: { $exists: true } }
$empty= '' OR IS NULL{ bio: { $empty: true } }
$betweenBETWEEN{ 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 });
});
ExportMô tả
OpSequelize operators (Op.and, Op.or, Op.gte, …)
DataTypesColumn type definitions
TransactionTransaction class
SequelizeSequelize 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