Bỏ qua, đến nội dung

@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.6

Hàm phổ biến:

HàmMô tảVí dụ
sqrt(x)Căn bậc haisqrt(144) → 12
pow(x, n)Lũy thừapow(2, 10) → 1024
abs(x)Giá trị tuyệt đốiabs(-5) → 5
round(x, n)Làm trònround(3.456, 2) → 3.46
min(a, b, ...)Giá trị nhỏ nhấtmin(3, 1, 4) → 1
max(a, b, ...)Giá trị lớn nhấtmax(3, 1, 4) → 4
mean(arr)Trung bình cộngmean([1,2,3]) → 2
sum(arr)Tổngsum([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'),
}); // 365

Nhóm hàm hỗ trợ:

NhómVí dụ
MathSUM, AVERAGE, MIN, MAX, ROUND, CEILING, FLOOR
TextCONCATENATE, LEFT, RIGHT, MID, TRIM, UPPER, LOWER
LogicIF, AND, OR, NOT, IFERROR, SWITCH
DateTODAY, NOW, YEAR, MONTH, DAY, DATEDIF
FinancialPMT, FV, PV, NPV, IRR
LookupVLOOKUP, 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.jsKhông (bundle quá lớn)
formula.jsKhông (bundle quá lớn)
string
Bundle sizeKhông giới hạnTối thiểu

Lưu ý bundle size

math.jsformula.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.