Giao diện
@digiforce-nc/evaluators
| Package | @digiforce-nc/evaluators |
| Depends on | @digiforce-nc/utils |
1) Tổng quan
@digiforce-nc/evaluators cung cấp registry các engine đánh giá biểu thức dùng cho computed fields, formula processing, và template interpolation trong Digiforce. Package hỗ trợ nhiều loại evaluator: toán học (math.js), công thức Excel-like (formula.js), và nội suy chuỗi (string).
Ứng dụng
Evaluators được dùng chủ yếu bởi computed fields trong database collection - cho phép user định nghĩa công thức tính toán trực tiếp trong UI mà không cần viết code.
2) Evaluator interface
Mọi evaluator phải tuân theo interface:
typescript
interface Evaluator {
/**
* Đánh giá biểu thức với scope cho trước
* @param expression - Biểu thức cần đánh giá
* @param scope - Object chứa giá trị các biến
* @returns Kết quả đánh giá
*/
evaluate(expression: string, scope?: Record<string, any>): any;
}Registry pattern
3) Server evaluators
math.js - Biểu thức toán học
Dựa trên thư viện math.js, hỗ trợ toàn bộ cú pháp toán học:
typescript
import { evaluate } from '@digiforce-nc/evaluators';
// Phép tính cơ bản
evaluate('math.js', '2 + 3 * 4'); // 14
evaluate('math.js', 'sqrt(16) + pow(2, 3)'); // 12
// Với scope (biến)
evaluate('math.js', 'price * quantity * (1 - discount)', {
price: 100000,
quantity: 5,
discount: 0.1,
}); // 450000
// Hàm thống kê
evaluate('math.js', 'mean(scores)', {
scores: [85, 92, 78, 95, 88],
}); // 87.6Hàm phổ biến:
| Hàm | Mô tả | Ví dụ |
|---|---|---|
sqrt(x) | Căn bậc hai | sqrt(144) → 12 |
pow(x, n) | Lũy thừa | pow(2, 10) → 1024 |
abs(x) | Giá trị tuyệt đối | abs(-5) → 5 |
round(x, n) | Làm tròn | round(3.456, 2) → 3.46 |
min(a, b, ...) | Giá trị nhỏ nhất | min(3, 1, 4) → 1 |
max(a, b, ...) | Giá trị lớn nhất | max(3, 1, 4) → 4 |
mean(arr) | Trung bình cộng | mean([1,2,3]) → 2 |
sum(arr) | Tổng | sum([1,2,3]) → 6 |
formula.js - Công thức Excel-like
Dựa trên formula.js, cung cấp các hàm tương thích Excel/Google Sheets:
typescript
// Hàm tài chính
evaluate('formula.js', 'PMT(0.08/12, 360, 500000000)');
// → Tính trả góp hàng tháng cho khoản vay 500tr, lãi 8%/năm, 30 năm
// Hàm logic
evaluate('formula.js', 'IF(score >= 50, "Đạt", "Không đạt")', {
score: 75,
}); // "Đạt"
// Hàm text
evaluate('formula.js', 'CONCATENATE(lastName, " ", firstName)', {
lastName: 'Nguyễn',
firstName: 'Văn A',
}); // "Nguyễn Văn A"
// Hàm ngày
evaluate('formula.js', 'DATEDIF(startDate, endDate, "D")', {
startDate: new Date('2024-01-01'),
endDate: new Date('2024-12-31'),
}); // 365Nhóm hàm hỗ trợ:
| Nhóm | Ví dụ |
|---|---|
| Math | SUM, AVERAGE, MIN, MAX, ROUND, CEILING, FLOOR |
| Text | CONCATENATE, LEFT, RIGHT, MID, TRIM, UPPER, LOWER |
| Logic | IF, AND, OR, NOT, IFERROR, SWITCH |
| Date | TODAY, NOW, YEAR, MONTH, DAY, DATEDIF |
| Financial | PMT, FV, PV, NPV, IRR |
| Lookup | VLOOKUP, INDEX, MATCH |
string - Nội suy chuỗi (Template interpolation)
Thay thế placeholder {{variable}} bằng giá trị thực:
typescript
evaluate('string', 'Xin chào {{name}}, đơn hàng #{{orderId}} đã được xác nhận.', {
name: 'Nguyễn Văn A',
orderId: '12345',
}); // "Xin chào Nguyễn Văn A, đơn hàng #12345 đã được xác nhận."
// Hỗ trợ nested object
evaluate('string', 'Giao đến: {{address.city}}, {{address.district}}', {
address: {
city: 'Hà Nội',
district: 'Cầu Giấy',
},
}); // "Giao đến: Hà Nội, Cầu Giấy"4) Registry API
evaluators.register(name, evaluator)
Đăng ký evaluator mới vào registry:
typescript
import { evaluators } from '@digiforce-nc/evaluators';
evaluators.register('custom', {
evaluate(expression: string, scope?: Record<string, any>) {
// logic đánh giá tùy chỉnh
return customEval(expression, scope);
},
});evaluate(name, expression, scope)
Hàm tiện ích gọi evaluator theo tên:
typescript
import { evaluate } from '@digiforce-nc/evaluators';
// Sử dụng math.js
const result1 = evaluate('math.js', 'a + b', { a: 1, b: 2 });
// Sử dụng formula.js
const result2 = evaluate('formula.js', 'SUM(values)', { values: [1, 2, 3] });
// Sử dụng custom evaluator
const result3 = evaluate('custom', 'myExpression', { x: 10 });5) appendArrayColumn
Utility function hỗ trợ đánh giá công thức trên mảng dữ liệu (array column):
typescript
import { appendArrayColumn } from '@digiforce-nc/evaluators';
const data = [
{ price: 100, quantity: 2 },
{ price: 200, quantity: 3 },
{ price: 150, quantity: 1 },
];
// Thêm cột tính toán vào mỗi row
const result = appendArrayColumn(data, 'total', 'math.js', 'price * quantity');
// [
// { price: 100, quantity: 2, total: 200 },
// { price: 200, quantity: 3, total: 600 },
// { price: 150, quantity: 1, total: 150 },
// ]6) Client vs Server
Package có hai entry point riêng biệt:
Server (src/server) | Client (src/client) | |
|---|---|---|
| math.js | Có | Không (bundle quá lớn) |
| formula.js | Có | Không (bundle quá lớn) |
| string | Có | Có |
| Bundle size | Không giới hạn | Tối thiểu |
Lưu ý bundle size
math.js và formula.js có bundle size lớn (~500KB+). Chúng chỉ được include ở server-side. Client-side chỉ có string evaluator để giữ bundle nhỏ.
7) Code example: Đăng ký custom evaluator
typescript
import { evaluators } from '@digiforce-nc/evaluators';
// Evaluator dùng regular expression matching
evaluators.register('regex', {
evaluate(expression: string, scope?: Record<string, any>) {
const { input, flags } = scope || {};
const regex = new RegExp(expression, flags);
return regex.test(input);
},
});
// Sử dụng
const isEmail = evaluators.get('regex').evaluate(
'^[\\w.-]+@[\\w.-]+\\.\\w+$',
{ input: 'test@example.com' },
); // true
// Evaluator cho Vietnamese currency formatting
evaluators.register('currency-vnd', {
evaluate(expression: string, scope?: Record<string, any>) {
const value = Number(evaluators.get('math.js').evaluate(expression, scope));
return new Intl.NumberFormat('vi-VN', {
style: 'currency',
currency: 'VND',
}).format(value);
},
});
const formatted = evaluators.get('currency-vnd').evaluate('price * quantity', {
price: 150000,
quantity: 3,
}); // "450.000 ₫"8) Sử dụng trong computed fields
Performance
Kết quả evaluator được cache theo expression + scope hash. Cùng một biểu thức với cùng input sẽ không bị tính lại.