tbl-js
A lightweight and flexible package for managing tabular data with features similar to .NET's DataTable. This package provides an efficient way to handle structured data with support for typed columns, data validation, sorting, and filtering. It can be used with any SQL database like PostgreSQL, MySQL, SQLite, or SQL Server.
Features
- Strongly typed columns
- Row management
- Sorting and filtering
- Data validation
- Query result loading
- Database integration
📋 Table of Contents
Installation
npm install tbl-js
Basic Usage
const { DataTable } = require('tbl-js');
const DataTable = require('tbl-js').DataTable;
const dt = new DataTable('Users');
Database Integration
The library seamlessly integrates with any database query results. You can directly load data from:
- PostgreSQL
- MySQL
- SQLite
- SQL Server
- Any other database that returns query results as objects
Example with different databases:
const { Pool } = require('pg');
const pool = new Pool(config);
const dt = new DataTable('Products');
const result = await pool.query('SELECT * FROM products');
dt.loadFromQuery(result.rows);
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection(config);
const [rows] = await connection.execute('SELECT * FROM products');
dt.loadFromQuery(rows);
const sqlite3 = require('sqlite3');
const db = new sqlite3.Database('mydb.sqlite');
db.all('SELECT * FROM products', [], (err, rows) => {
dt.loadFromQuery(rows);
});
Features
- Strongly typed columns
- Row management
- Sorting and filtering
- Data validation
- Query result loading
- Database integration
- DataSet support for related tables
- DataView for filtered views of tables
- Advanced schema management and serialization
Methods
Table Operations
Creating a Table
const dt = new DataTable('TableName');
Adding Columns
dt.addColumn('age', 'number');
dt.addColumn('name', 'string');
dt.addColumn('birthDate', 'date');
dt.addColumn('description');
Adding Rows
dt.addRow({ name: 'John', age: 30, birthDate: new Date('1993-01-01') });
dt.addRow(['Jane', 25, new Date('1998-01-01')]);
const row = dt.newRow();
row.set('name', 'Alice');
row.set('age', 28);
dt.rows.add(row);
Row Operations
const value = dt.rows(0).get("value");
const name = dt.rows(1).get("name");
const name = row.get('name');
const age = row.item('age');
row.set('name', 'NewName');
dt.removeRow(0);
dt.clear();
Row State Management
DataTable-js provides comprehensive row state tracking to monitor changes to your data. Each row has a state that indicates whether it has been added, modified, deleted, or remains unchanged.
Row States
ADDED: New row that hasn't been saved
MODIFIED: Existing row that has been changed
DELETED: Row marked for deletion
UNCHANGED: Row with no pending changes
Individual Row State Operations
const row = dt.rows(0);
console.log(row.getRowState());
if (row.hasChanges()) {
console.log('Row has unsaved changes');
}
row.acceptChanges();
row.rejectChanges();
row.delete();
console.log(row.getRowState());
const row = dt.newRow();
row.set('name', 'John');
console.log(row.getRowState());
dt.rows.add(row);
row.acceptChanges();
console.log(row.getRowState());
row.set('name', 'Jane');
console.log(row.getRowState());
console.log(row.hasChanges());
row.rejectChanges();
console.log(row.get('name'));
console.log(row.getRowState());
Table-Level State Operations
dt.acceptAllChanges();
dt.rejectAllChanges();
const changedRows = dt.getChanges();
console.log(`${changedRows.length} rows have changes`);
const addedRows = dt.getRowsByState('ADDED');
const modifiedRows = dt.getRowsByState('MODIFIED');
const deletedRows = dt.getRowsByState('DELETED');
if (dt.hasChanges()) {
console.log('Table has unsaved changes');
console.log(`Added: ${dt.getRowsByState('ADDED').length}`);
console.log(`Modified: ${dt.getRowsByState('MODIFIED').length}`);
console.log(`Deleted: ${dt.getRowsByState('DELETED').length}`);
}
async function saveChanges(dataTable) {
if (!dataTable.hasChanges()) {
console.log('No changes to save');
return;
}
try {
const changes = dataTable.getChanges();
for (const row of changes) {
const state = row.getRowState();
if (state === 'ADDED') {
await insertRow(row);
} else if (state === 'MODIFIED') {
await updateRow(row);
} else if (state === 'DELETED') {
await deleteRow(row);
}
}
dataTable.acceptAllChanges();
console.log('All changes saved successfully');
} catch (error) {
console.error('Error saving changes:', error);
}
}
DataRowState Utility Methods
const { DataRowState } = require('tbl-js');
console.log(DataRowState.isChanged('MODIFIED'));
console.log(DataRowState.isChanged('ADDED'));
console.log(DataRowState.isChanged('UNCHANGED'));
console.log(DataRowState.isUnchanged('UNCHANGED'));
console.log(DataRowState.isUnchanged('MODIFIED'));
Advanced State Management Methods
const summary = dt.getChangesSummary();
console.log(summary);
if (summary.hasChanges) {
console.log(`Changes detected:`);
console.log(`- ${summary.addedCount} new rows`);
console.log(`- ${summary.modifiedCount} modified rows`);
console.log(`- ${summary.deletedCount} deleted rows`);
console.log(`- ${summary.unchangedCount} unchanged rows`);
}
dt.clearChanges();
console.log(dt.hasChanges());
async function syncWithDatabase(dataTable) {
const summary = dataTable.getChangesSummary();
if (!summary.hasChanges) {
console.log('No changes to sync');
return;
}
console.log(`Syncing ${summary.addedCount + summary.modifiedCount + summary.deletedCount} changes...`);
dataTable.clearChanges();
console.log('Sync completed, change tracking reset');
}
Data Operations
Filtering Data
The DataTable provides two methods for filtering data:
select(): Works directly with row values as plain objects. Access values using dot notation (e.g., row.age)
findRows(): Works with DataRow objects. Access values using the get() method (e.g., row.get('age'))
Examples:
const adults = dt.select(row => row.age >= 18);
const activeUsers = dt.select(row => row.age > 25 && row.active === true);
const johns = dt.findRows({ name: 'John' });
const over25 = dt.findRows(row => row.get('age') > 25);
Advanced Filtering Criteria
DataTable-js supports various operators for advanced filtering. Here are all available operators:
dt.findRows({
age: { $gt: 25 },
score: { $gte: 90 },
price: { $lt: 100 },
quantity: { $lte: 50 },
status: { $ne: 'active' },
category: { $in: ['A', 'B', 'C'] },
name: { $contains: 'john' },
email: /gmail\.com$/,
active: true,
type: 'user'
});
const results = dt.findRows({
age: { $gt: 18, $lt: 30 },
name: { $contains: 'smith' },
roles: { $in: ['admin', 'editor'] },
email: /^[a-z]+@company\.com$/
});
const filtered = dt.findRows(row => {
const age = row.get('age');
const status = row.get('status');
return age > 25 && status === 'active';
});
All supported operators:
$gt: Greater than
$gte: Greater than or equal to
$lt: Less than
$lte: Less than or equal to
$ne: Not equal to
$in: Value exists in array
$contains: String contains value
- RegExp: Support for regular expressions
Sorting
dt.sort('age', 'asc');
dt.sortMultiple(
{ column: 'age', order: 'desc' },
{ column: 'name', order: 'asc' }
);
dt.sortBy(row => row.get('age') + row.get('name'));
Loading Data from Database
const { Pool } = require('pg');
const pool = new Pool(config);
const dt = new DataTable('Products');
const result = await pool.query('SELECT * FROM products WHERE category = $1', ['electronics']);
dt.loadFromQuery(result.rows);
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection(config);
const [rows] = await connection.execute('SELECT * FROM products WHERE price > ?', [100]);
dt.loadFromQuery(rows);
await dt.loadFromQueryAsync(
pool.query('SELECT * FROM products').then(result => result.rows)
);
const data = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
];
dt.loadFromQuery(data);
Column Operations
dt.columnExists('name');
dt.removeColumn('age');
Table Manipulation
const newTable = dt.clone();
for (const row of newTable) {
console.log(row.get('name'));
}
DataSet Operations
DataSet allows you to manage multiple related tables and define relationships between them.
const { DataSet, DataTable } = require('tbl-js');
const ds = new DataSet('CompanyData');
const employees = ds.addTable('Employees');
employees.addColumn('id', 'number');
employees.addColumn('name', 'string');
employees.addColumn('departmentId', 'number');
const departments = ds.addTable('Departments');
departments.addColumn('id', 'number');
departments.addColumn('name', 'string');
departments.addRow({ id: 1, name: 'HR' });
departments.addRow({ id: 2, name: 'IT' });
employees.addRow({ id: 1, name: 'John', departmentId: 2 });
employees.addRow({ id: 2, name: 'Jane', departmentId: 1 });
const relation = ds.addRelation(
'EmpDeptRelation',
'Departments',
'Employees',
'id',
'departmentId'
);
const itDept = departments.findOne({ id: 2 });
const itEmployees = ds.getChildRows(itDept, 'EmpDeptRelation');
console.log(itEmployees);
const john = employees.findOne({ name: 'John' });
const johnsDept = ds.getParentRow(john, 'EmpDeptRelation');
console.log(johnsDept.get('name'));
DataView Operations
DataView provides a filtered and sorted view of a DataTable.
const { DataTable, DataView } = require('tbl-js');
const users = new DataTable('Users');
users.addColumn('id', 'number');
users.addColumn('name', 'string');
users.addColumn('age', 'number');
users.addColumn('active', 'boolean');
users.addRow({ id: 1, name: 'John', age: 25, active: true });
users.addRow({ id: 2, name: 'Jane', age: 30, active: true });
users.addRow({ id: 3, name: 'Bob', age: 22, active: false });
users.addRow({ id: 4, name: 'Alice', age: 35, active: true });
const activeUsersView = new DataView(
users,
{ active: true },
'age',
'desc'
);
console.log(`Active users: ${activeUsersView.count}`);
const oldest = activeUsersView.firstRow;
console.log(oldest.get('name'));
for (const row of activeUsersView) {
console.log(`${row.get('name')}: ${row.get('age')}`);
}
const activeUsersTable = activeUsersView.toTable();
const activeUsersArray = activeUsersView.toArray();
Advanced Schema Management
The DataTable provides advanced schema management capabilities for working with table structures:
const { DataTable } = require('tbl-js');
const users = new DataTable('Users');
users.addColumn('id', 'number');
users.addColumn('name', 'string');
users.addColumn('age', 'number');
users.columns._columns.get('id').isPrimaryKey = true;
users.columns._columns.get('id').allowNull = false;
const schema = users.exportSchema();
console.log(schema);
const schemaJson = users.serializeSchema();
const recreatedTable = DataTable.deserializeSchema(schemaJson);
const updatedUsers = new DataTable('UpdatedUsers');
updatedUsers.addColumn('id', 'number');
updatedUsers.addColumn('name', 'string');
updatedUsers.addColumn('age', 'number');
updatedUsers.addColumn('email', 'string');
updatedUsers.columns._columns.get('name').allowNull = false;
const differences = users.compareSchema(updatedUsers);
console.log(differences);
const updateResult = users.updateSchema(updatedUsers);
console.log(updateResult);
const newTable = DataTable.importSchema({
tableName: 'Products',
columns: [
{ name: 'id', dataType: 'number', allowNull: false, defaultValue: null },
{ name: 'name', dataType: 'string', allowNull: false },
{ name: 'price', dataType: 'number', defaultValue: 0 },
{ name: 'createdAt', dataType: 'date', defaultValue: () => new Date() }
],
primaryKey: ['id']
});
The schema management features allow you to:
- Export table structure to a portable format
- Create tables from existing schemas
- Compare schemas between tables to identify differences
- Update a table's schema to match another
- Serialize/deserialize schemas to JSON
This is especially useful for:
- -Creating table structures dynamically based on configuration
- Migrating data between different schema versions
- Generating table documentation
- Schema validation and enforcement
Supported Data Types
- string
- number
- date
- boolean
Advanced Database Usage
The DataTable automatically creates columns based on the database query results, matching the types from your database:
- INTEGER/BIGINT → number
- VARCHAR/TEXT → string
- TIMESTAMP/DATE → date
- BOOLEAN → boolean
This makes it perfect for scenarios where you need to:
- Cache database results
- Manipulate query results before display
- Create temporary data structures from database queries
- Transform data before sending to the frontend
Error Handling
The library throws errors for:
- Invalid column operations
- Type mismatches
- Null violations
- Duplicate columns
License
MIT