{"id":49617497,"url":"https://github.com/mazeor9/dotnet-datatable","last_synced_at":"2026-05-04T23:05:03.266Z","repository":{"id":355041257,"uuid":"1225020639","full_name":"mazeor9/dotnet-datatable","owner":"mazeor9","description":"A JavaScript DataTable library with type validation, filtering, sorting and SQL database integration. Inspired by .NET's DataTable.","archived":false,"fork":false,"pushed_at":"2026-05-01T14:25:36.000Z","size":80,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-01T16:25:15.527Z","etag":null,"topics":["asp-net","asp-net-core","datarow","dataset","datatable","datatables","dataview","dotnet","dotnet-core","grid"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/dotnet-datatable","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mazeor9.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-29T21:39:03.000Z","updated_at":"2026-05-01T14:24:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mazeor9/dotnet-datatable","commit_stats":null,"previous_names":["mazeor9/dotnet-datatable"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/mazeor9/dotnet-datatable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mazeor9%2Fdotnet-datatable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mazeor9%2Fdotnet-datatable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mazeor9%2Fdotnet-datatable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mazeor9%2Fdotnet-datatable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mazeor9","download_url":"https://codeload.github.com/mazeor9/dotnet-datatable/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mazeor9%2Fdotnet-datatable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32618672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-04T10:08:07.713Z","status":"ssl_error","status_checked_at":"2026-05-04T10:08:02.005Z","response_time":58,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asp-net","asp-net-core","datarow","dataset","datatable","datatables","dataview","dotnet","dotnet-core","grid"],"created_at":"2026-05-04T23:05:02.324Z","updated_at":"2026-05-04T23:05:03.254Z","avatar_url":"https://github.com/mazeor9.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dotnet-datatable\n\nA .NET-style DataTable library for JavaScript and Node.js. Build typed in-memory tables with DataRow access, DataView filtering and sorting, DataSet relationships, row state tracking, schema validation, merge operations and query result mapping from any database client.\n\n\u003cdiv align=\"center\"\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://www.npmjs.com/package/dotnet-datatable\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/v/dotnet-datatable.svg?logo=npm\u0026style=for-the-badge\u0026color=CB3837\" alt=\"NPM Version\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/dotnet-datatable\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/dt/dotnet-datatable.svg?style=for-the-badge\u0026color=CB3837\" alt=\"NPM Downloads\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/mazeor9/dotnet-datatable/releases/latest\"\u003e\n      \u003cimg src=\"https://img.shields.io/github/v/release/mazeor9/dotnet-datatable?style=for-the-badge\u0026color=green\" alt=\"GitHub Release\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/mazeor9/dotnet-datatable/blob/main/LICENSE\"\u003e\n      \u003cimg src=\"https://img.shields.io/github/license/mazeor9/dotnet-datatable?style=for-the-badge\u0026color=blue\" alt=\"License\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/mazeor9/dotnet-datatable\"\u003e\n      \u003cimg src=\"https://img.shields.io/github/stars/mazeor9/dotnet-datatable?style=for-the-badge\u0026color=yellow\" alt=\"GitHub Stars\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://bundlephobia.com/package/dotnet-datatable\"\u003e\n      \u003cimg src=\"https://img.shields.io/bundlephobia/minzip/dotnet-datatable?style=for-the-badge\u0026color=orange\" alt=\"Bundle Size\" /\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n## Features\n\n- Strongly typed columns\n- Row management\n- Sorting and filtering\n- Data validation\n- Query result loading from plain objects and common SQL client result shapes\n- Database result mapping without database drivers or connections\n- Schema inference and original value tracking\n- Enterprise-style merge for DataTable and DataSet\n- Backend change tracking and broad provider result mapping\n\n## Table of Contents\n\n- [Why dotnet-datatable?](#why-dotnet-datatable)\n- [Installation](#installation)\n- [Basic Usage](#basic-usage)\n- [TypeScript Support](#typescript-support)\n- [Debugging \u0026 Inspection](#debugging--inspection)\n- [Database Integration](#database-integration)\n- [Provider Result Mapping](#provider-result-mapping)\n- [Features](#features)\n- [Methods](#methods)\n  - [Table Operations](#table-operations)\n  - [Row Operations](#row-operations)\n  - [Row State Management](#row-state-management)\n  - [Data Operations](#data-operations)\n  - [Query Result Loading](#query-result-loading)\n  - [Merge Operations](#merge-operations)\n  - [Advanced Filtering](#advanced-filtering-criteria)\n  - [Column Operations](#column-operations)\n  - [Table Manipulation](#table-manipulation)\n  - [DataSet Operations](#dataset-operations)\n  - [DataView Operations](#dataview-operations)\n  - [Schema Management](#advanced-schema-management)\n- [Supported Data Types](#supported-data-types)\n- [Advanced Database Usage](#advanced-database-usage)\n- [Error Handling](#error-handling)\n- [License](#license)\n\n## Installation\n\n```bash\nnpm install dotnet-datatable\n```\n\n## Basic Usage\n\n```javascript\n// Destructuring\nconst { DataTable } = require('dotnet-datatable');\n\n// Direct import\nconst DataTable = require('dotnet-datatable').DataTable;\n\n// Create a new table\nconst dt = new DataTable('Users');\n```\n\n## TypeScript Support\n\nThe package runtime is still plain JavaScript/CommonJS, but TypeScript declarations are included in the npm package. You can use it from TypeScript with named imports and full IntelliSense, similar to `r6-data.js`.\n\n```typescript\nimport {\n  DataSet,\n  DataTable,\n  type DataTableDebugView,\n  type DataTableSchemaDebugView\n} from 'dotnet-datatable';\n\ninterface Customer {\n  id: number;\n  name: string;\n  email: string;\n}\n\nconst customers = DataTable.fromObjects\u003cCustomer\u003e([\n  { id: 1, name: 'Mario', email: 'mario@test.it' },\n  { id: 2, name: 'Laura', email: 'laura@test.it' }\n], {\n  tableName: 'Customers',\n  primaryKey: 'id'\n});\n\nconst first = customers.find(1);\nconst email = first?.get('email');\n\nconst schema: DataTableSchemaDebugView = customers.getSchema();\nconst debugView: DataTableDebugView = customers.toDebugView();\n```\n\n```javascript\nconst { DataTable } = require('dotnet-datatable');\n\nconst users = new DataTable('Users');\n```\n\nThe change tracking subpath is typed too:\n\n```typescript\nimport { DataTableChangeSet } from 'dotnet-datatable/change-tracking';\n\nconst changeSet = DataTableChangeSet.fromTable(customers);\n```\n\n## Debugging \u0026 Inspection\n\nDataTable, DataRow, DataColumn, DataView and DataSet expose debug-friendly serializers and Node.js custom inspect output. This makes `console.log()`, `console.dir()`, `util.inspect()`, the VS Code Debug Console and Watch panels much easier to read without requiring an IDE extension.\n\n```javascript\nconst { DataSet, DataTable } = require('dotnet-datatable');\n\nconst customers = new DataTable('Customers');\ncustomers.addColumn('ID', 'number', { primaryKey: true });\ncustomers.addColumn('Name', 'string');\ncustomers.addColumn('Email', 'string');\ncustomers.addRow({ ID: 1, Name: 'Mario', Email: 'mario@test.it' });\ncustomers.addRow({ ID: 2, Name: 'Laura', Email: 'laura@test.it' });\n\n// Uses Node.js custom inspect under the hood.\nconsole.log(customers);\n\n// Array of plain objects, ready for console.table().\nconsole.table(customers.toConsoleTable());\n\n// Stable object payload for custom debug tools or future IDE visualizers.\nconsole.dir(customers.toDebugView(), { depth: null });\n\n// Schema-only view.\nconsole.dir(customers.getSchema(), { depth: null });\n\n// Small preview for large tables. Default is 10 rows.\nconsole.table(customers.getPreview(5));\n```\n\nExample DataSet and DataView inspection:\n\n```javascript\nconst dataSet = new DataSet('CRM');\ndataSet.addTable(customers);\n\nconsole.dir(dataSet.toDebugView(), { depth: null });\n\nconst activeCustomers = customers\n  .createView()\n  .where('ID', '\u003e=', 1)\n  .orderBy('Name', 'asc');\n\nconsole.table(activeCustomers.getPreview(5));\nconsole.dir(activeCustomers.toDebugView(), { depth: null });\n```\n\nUseful methods:\n\n- `dataTable.toArray()` returns row objects using the existing export behavior.\n- `dataTable.toDebugView()` returns `{ type, name, columns, rows, rowCount, columnCount, preview }`.\n- `dataTable.toDebugString()` returns the same readable text used by custom inspect.\n- `dataTable.toConsoleTable()` returns plain objects for `console.table()`.\n- `dataRow.toObject()` returns a plain object with the row values.\n- `dataView.toDebugView()` includes source table, rows, row count, sort/filter summary and preview.\n- `dataSet.toDebugView()` includes all table debug views and relation metadata.\n\n## Database Integration\n`dotnet-datatable` does not connect to databases, execute SQL, or act as an ORM. Use any external client to run your query, then load the returned rows or query result object into a DataTable.\n\nSupported input shapes include:\n- Plain arrays of objects, such as Prisma, Knex, SQLite and Drizzle results\n- PostgreSQL `pg` results: `{ rows: [...], fields: [...] }`\n- MySQL `mysql2` results: `[rows, fields]`\n- SQL Server `mssql` results: `{ recordset: [...], recordsets: [...] }`\n- Sequelize model instances or plain objects\n- Custom wrappers with `rowsPath`, such as `{ data: { items: [...] } }`\n\n```javascript\n// PostgreSQL\nconst { Pool } = require('pg');\nconst pool = new Pool(config);\n\nconst result = await pool.query('SELECT * FROM products');\nconst products = DataTable.fromQueryResult(result, {\n    tableName: 'Products',\n    primaryKey: 'id',\n    useFieldMetadata: true\n});\n\n// MySQL\nconst mysql = require('mysql2/promise');\nconst connection = await mysql.createConnection(config);\nconst mysqlResult = await connection.execute('SELECT * FROM products');\n\nconst mysqlProducts = DataTable.fromQueryResult(mysqlResult, {\n    tableName: 'Products',\n    primaryKey: 'id'\n});\n\n// Prisma, Knex, SQLite, Drizzle or any plain object rows\nconst rows = await prisma.product.findMany();\nconst prismaProducts = DataTable.fromObjects(rows, {\n    tableName: 'Products',\n    primaryKey: 'id'\n});\n```\n\n## Provider Result Mapping\n\n`dotnet-datatable` stays driver-neutral: it accepts results already returned by your database package and maps them into `DataTable` / `DataSet` instances. It does not connect to databases, execute SQL, or generate SQL commands.\n\n```javascript\nconst users = DataTable.fromQueryResult(resultFromAnyClient, {\n    tableName: 'Users',\n    primaryKey: 'id',\n    useFieldMetadata: true\n});\n```\n\nSupported result families include:\n\n- Plain arrays from Prisma, Knex, Drizzle, Sequelize, Kysely, SQLite and similar libraries\n- PostgreSQL `pg` results: `{ rows, fields }`\n- `postgres.js` / Neon-style row arrays with `.columns` metadata\n- MySQL `mysql2` tuples: `[rows, fields]`\n- SQL Server `mssql` recordsets: `{ recordset, recordsets }`\n- SQLite/libSQL-style `{ rows, columns }`, including array rows mapped through column metadata\n- OracleDB-style `{ rows, metaData }`, including array rows mapped through metadata\n- Supabase/PostgREST or API wrappers such as `{ data: [...] }`\n- Custom wrappers with `rowsPath` and `fieldsPath`\n\nArray-row providers are normalized before loading:\n\n```javascript\nconst oracleUsers = DataTable.fromQueryResult({\n    rows: [[1, 'Mario']],\n    metaData: [\n        { name: 'ID', dbTypeName: 'NUMBER' },\n        { name: 'FULL_NAME', dbTypeName: 'VARCHAR2' }\n    ]\n}, {\n    tableName: 'Users',\n    primaryKey: 'id',\n    columnNameTransform: 'camelCase'\n});\n\nconsole.log(oracleUsers.find(1).get('fullName')); // 'Mario'\n```\n\nFor backend state tracking you can still extract a pure in-memory change snapshot:\n\n```javascript\nconst changeSet = users.getChangeSet();\nconsole.log(changeSet.modified[0].changedColumns);\n```\n\nYou can also import the change-tracking helpers directly:\n\n```javascript\nconst {\n    DataTableChangeSet,\n    DataSetChangeSet\n} = require('dotnet-datatable/change-tracking');\n\nconst tableChanges = DataTableChangeSet.fromTable(users);\nconst dataSetChanges = DataSetChangeSet.fromDataSet(dataSet);\n```\n\n## Features\n\n- Strongly typed columns\n- Row management\n- Sorting and filtering\n- Data validation\n- Query result loading\n- Database integration\n- Enterprise-style merge for DataTable and DataSet\n- DataSet support for related tables \n- DataView for filtered views of tables\n- ChangeSet snapshots for backend state tracking\n- Advanced schema management and serialization\n\n## Methods\n\n### Table Operations\n\n#### Creating a Table\n```javascript\nconst dt = new DataTable('TableName');\n```\n\n#### Adding Columns\n```javascript\n// Add a column with type\ndt.addColumn('age', 'number');\ndt.addColumn('name', 'string');\ndt.addColumn('birthDate', 'date');\n\n// Add column without type\ndt.addColumn('description');\n```\n\nColumn options (constraints and defaults):\n\n```javascript\ndt.addColumn('id', 'number', { primaryKey: true, allowNull: false, readOnly: true, defaultValue: 0 });\ndt.addColumn('email', 'string', { unique: true });\ndt.addColumn('createdAt', 'date', { defaultValue: () =\u003e new Date() });\n```\n\nRead-only columns can be set while the row is `DETACHED`:\n\n```javascript\nconst row = dt.newRow();\nrow.set('id', 1);     // ok (DETACHED)\ndt.rows.add(row);     // now ADDED\nrow.set('id', 2);     // throws (read-only)\n```\n\n#### Adding Rows\n```javascript\n// Add row with object\ndt.addRow({ name: 'John', age: 30, birthDate: new Date('1993-01-01') });\n\n// Add row with array\ndt.addRow(['Jane', 25, new Date('1998-01-01')]);\n\n// Create and add row manually\nconst row = dt.newRow();\nrow.set('name', 'Alice');\nrow.set('age', 28);\ndt.rows.add(row);\n```\n\nPrimary key, unique and find:\n\n```javascript\nconst users = new DataTable('Users');\nusers.addColumn('id', 'number', { primaryKey: true });\nusers.addColumn('email', 'string', { unique: true });\n\nusers.addRow({ id: 1, email: 'a@test.com' });\nusers.addRow({ id: 2, email: 'b@test.com' });\n\nconst u1 = users.find(1);\nconsole.log(u1.get('email')); // 'a@test.com'\n```\n\nComposite primary key:\n\n```javascript\nconst sessions = new DataTable('Sessions');\nsessions.addColumn('tenantId', 'number', { allowNull: false });\nsessions.addColumn('userId', 'number', { allowNull: false });\nsessions.addColumn('token', 'string');\n\nsessions.setPrimaryKey(['tenantId', 'userId']);\n\nsessions.addRow({ tenantId: 1, userId: 10, token: 'x' });\nconst s = sessions.find([1, 10]);\nconsole.log(s.get('token')); // 'x'\n```\n\n### Row Operations\n```javascript\n// Get value using row index and column name (recommended method)\nconst value = dt.rows(0).get(\"value\");  // Gets value from first row, column \"value\"\nconst name = dt.rows(1).get(\"name\");    // Gets value from second row, column \"name\"\n\n// Alternative ways to get values\nconst name = row.get('name');    // Recommended\nconst age = row.item('age');     // Supported for backwards compatibility\n\n// Set value\nrow.set('name', 'NewName');\n\n// Remove row\ndt.removeRow(0);\n\n// Clear all rows\ndt.clear();\n```\n\n### Row State Management\ndotnet-datatable 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.\n\n#### Row States\n- `DETACHED`: Row created via `newRow()` that is not part of the table yet\n- `ADDED`: New row that hasn't been saved\n- `MODIFIED`: Existing row that has been changed\n- `DELETED`: Row marked for deletion\n- `UNCHANGED`: Row with no pending changes\n\nRows loaded with `DataTable.fromObjects()`, `fromRows()`, `fromQueryResult()` or `loadRows()` are initialized as `UNCHANGED` by default and keep a copy of their original values.\n\n#### Individual Row State Operations\n```javascript\n// Check row state\nconst row = dt.rows(0);\nconsole.log(row.getRowState()); // 'DETACHED', 'ADDED', 'MODIFIED', 'DELETED', or 'UNCHANGED'\n\n// Check if row has changes\nif (row.hasChanges()) {\n    console.log('Row has unsaved changes');\n}\n\n// Accept changes (mark as UNCHANGED)\nrow.acceptChanges();\n\n// Reject changes (revert to original values)\nrow.rejectChanges();\n\n// Mark row for deletion\nrow.delete();\nconsole.log(row.getRowState()); // 'DELETED'\n\n// Example workflow\nconst row = dt.newRow();\nrow.set('name', 'John');\nconsole.log(row.getRowState()); // 'DETACHED'\n\ndt.rows.add(row);\nconsole.log(row.getRowState()); // 'ADDED'\n\nrow.acceptChanges();\nconsole.log(row.getRowState()); // 'UNCHANGED'\n\nrow.set('name', 'Jane');\nconsole.log(row.getRowState()); // 'MODIFIED'\nconsole.log(row.hasChanges()); // true\n\nrow.rejectChanges();\nconsole.log(row.get('name')); // 'John' (reverted)\nconsole.log(row.getRowState()); // 'UNCHANGED'\n```\n\n#### Table-Level State Operations\n```javascript\n// Accept all changes in the table\ndt.acceptChanges();\n\n// Reject all changes in the table\ndt.rejectChanges();\n\n// Get all rows with changes\nconst changedRows = dt.getChanges();\nconsole.log(`${changedRows.length} rows have changes`);\n\n// Get changed rows by state\nconst onlyModified = dt.getChanges('MODIFIED');\n\n// Get rows by specific state\nconst addedRows = dt.getRowsByState('ADDED');\nconst modifiedRows = dt.getRowsByState('MODIFIED');\nconst deletedRows = dt.getRowsByState('DELETED');\n\n// Check if table has any changes\nif (dt.hasChanges()) {\n    console.log('Table has unsaved changes');\n    \n    // Show summary of changes\n    console.log(`Added: ${dt.getRowsByState('ADDED').length}`);\n    console.log(`Modified: ${dt.getRowsByState('MODIFIED').length}`);\n    console.log(`Deleted: ${dt.getRowsByState('DELETED').length}`);\n}\n\n// Practical example: Save changes workflow\nasync function saveChanges(dataTable) {\n    if (!dataTable.hasChanges()) {\n        console.log('No changes to save');\n        return;\n    }\n    \n    try {\n        const changes = dataTable.getChanges();\n        \n        for (const row of changes) {\n            const state = row.getRowState();\n            \n            if (state === 'ADDED') {\n                // Insert new row to database\n                await insertRow(row);\n            } else if (state === 'MODIFIED') {\n                // Update existing row in database\n                await updateRow(row);\n            } else if (state === 'DELETED') {\n                // Delete row from database\n                await deleteRow(row);\n            }\n        }\n        \n        // Accept all changes after successful save\n        dataTable.acceptChanges();\n        console.log('All changes saved successfully');\n        \n    } catch (error) {\n        console.error('Error saving changes:', error);\n        // Optionally reject changes on error\n        // dataTable.rejectAllChanges();\n    }\n}\n```\n\n#### DataRowState Utility Methods\n```javascript\nconst { DataRowState } = require('dotnet-datatable');\n\n// Check if a state represents a changed row\nconsole.log(DataRowState.isChanged('MODIFIED')); // true\nconsole.log(DataRowState.isChanged('ADDED')); // true\nconsole.log(DataRowState.isChanged('UNCHANGED')); // false\n\n// Check if a state represents an unchanged row\nconsole.log(DataRowState.isUnchanged('UNCHANGED')); // true\nconsole.log(DataRowState.isUnchanged('MODIFIED')); // false\n```\n\n#### Advanced State Management Methods\n```javascript\n// Get detailed summary of all changes\nconst summary = dt.getChangesSummary();\nconsole.log(summary);\n/* Output:\n{\n    totalRows: 10,\n    addedCount: 2,\n    modifiedCount: 3,\n    deletedCount: 1,\n    unchangedCount: 4,\n    hasChanges: true,\n    addedRows: [DataRow, DataRow],\n    modifiedRows: [DataRow, DataRow, DataRow],\n    deletedRows: [DataRow]\n}\n*/\n\n// Use summary for detailed reporting\nif (summary.hasChanges) {\n    console.log(`Changes detected:`);\n    console.log(`- ${summary.addedCount} new rows`);\n    console.log(`- ${summary.modifiedCount} modified rows`);\n    console.log(`- ${summary.deletedCount} deleted rows`);\n    console.log(`- ${summary.unchangedCount} unchanged rows`);\n}\n\n// Clear all change tracking without losing data\n// This sets all rows to UNCHANGED state\ndt.clearChanges();\nconsole.log(dt.hasChanges()); // false\n\n// Practical example: Reset tracking after manual sync\nasync function syncWithDatabase(dataTable) {\n    const summary = dataTable.getChangesSummary();\n    \n    if (!summary.hasChanges) {\n        console.log('No changes to sync');\n        return;\n    }\n    \n    console.log(`Syncing ${summary.addedCount + summary.modifiedCount + summary.deletedCount} changes...`);\n    \n    // Perform database operations...\n    // After successful sync, clear the change tracking\n    dataTable.clearChanges();\n    \n    console.log('Sync completed, change tracking reset');\n}\n```\n\n### Data Operations\n\n#### Filtering Data\nThe DataTable provides two methods for filtering data:\n- `select()`: Works directly with row values as plain objects. Access values using dot notation (e.g., `row.age`)\n- `findRows()`: Works with DataRow objects. Access values using the get() method (e.g., `row.get('age')`)\n\nExamples:\n```javascript\n// Using select - direct property access\nconst adults = dt.select(row =\u003e row.age \u003e= 18);\nconst activeUsers = dt.select(row =\u003e row.age \u003e 25 \u0026\u0026 row.active === true);\n\n// Using findRows - using get() method\nconst johns = dt.findRows({ name: 'John' });\nconst over25 = dt.findRows(row =\u003e row.get('age') \u003e 25);\n```\n\n### Query Result Loading\n\nUse `fromObjects`, `fromRows`, `fromRecords` or `fromQueryResult` to import data that was already fetched by another library. Imported rows are marked as `UNCHANGED` and their `originalValues` are populated, so later edits are tracked as `MODIFIED`.\n\n```javascript\nconst users = DataTable.fromObjects([\n    { id: 1, name: 'Mario', active: true },\n    { id: 2, name: 'Luca', active: false }\n], {\n    tableName: 'Users',\n    primaryKey: 'id'\n});\n\nusers.rows[0].set('name', 'Mario Rossi');\nconsole.log(users.rows[0].getRowState()); // 'MODIFIED'\nconsole.log(users.rows[0].originalValues.name); // 'Mario'\n```\n\nQuery result shape detection:\n\n```javascript\nconst pgTable = DataTable.fromQueryResult(pgResult, {\n    tableName: 'Users',\n    primaryKey: 'id',\n    useFieldMetadata: true\n});\n\nconst mysqlTable = DataTable.fromQueryResult(mysqlResult, {\n    tableName: 'Users',\n    primaryKey: 'id'\n});\n\nconst customTable = DataTable.fromQueryResult(apiResult, {\n    rowsPath: 'data.items',\n    tableName: 'Users'\n});\n```\n\nLoading into an existing table:\n\n```javascript\nconst table = new DataTable('Users');\ntable.addColumn('id', 'number', { primaryKey: true });\ntable.addColumn('name', 'string');\n\ntable.loadRows(rows, {\n    clearBeforeLoad: true,\n    autoCreateColumns: false,\n    validateSchema: true,\n    convertTypes: true\n});\n```\n\nCommon loading options:\n\n```javascript\n{\n    tableName: 'Users',\n    primaryKey: 'id',\n    columns: {},\n    includeColumns: [],\n    excludeColumns: [],\n    renameColumns: { user_id: 'id' },\n    columnNameTransform: 'camelCase',\n    inferSchema: true,\n    useFieldMetadata: true,\n    autoCreateColumns: true,\n    validateSchema: true,\n    convertTypes: true,\n    rowState: 'UNCHANGED',\n    preserveOriginalValues: true,\n    strict: false\n}\n```\n\n### Merge Operations\n`DataTable.merge()` imports rows from another table with the same table name and a compatible schema. Rows are matched by primary key, updated when found, and inserted when missing. If no primary key is configured, source rows are appended.\n\n```javascript\nconst users = new DataTable('Users');\nusers.addColumn('id', 'number', { primaryKey: true });\nusers.addColumn('name', 'string');\nusers.addColumn('email', 'string');\n\nusers.addRow({ id: 1, name: 'Alice', email: 'alice@old.test' });\nusers.acceptChanges();\n\nconst otherUsers = new DataTable('Users');\notherUsers.addColumn('id', 'number', { primaryKey: true });\notherUsers.addColumn('name', 'string');\notherUsers.addColumn('email', 'string');\notherUsers.addColumn('role', 'string');\n\notherUsers.addRow({ id: 1, name: 'Alice Remote', email: 'alice@new.test', role: 'admin' });\notherUsers.addRow({ id: 2, name: 'Bob', email: 'bob@test.test', role: 'user' });\n\nconst result = users.merge(otherUsers, {\n  preserveChanges: true,\n  missingSchemaAction: 'add'\n});\n\nconsole.log(result);\n// {\n//   tableName: 'Users',\n//   addedColumns: ['role'],\n//   ignoredColumns: [],\n//   updatedRows: 1,\n//   insertedRows: 1,\n//   preservedRows: 0,\n//   skippedRows: 0,\n//   primaryKeyAdded: null\n// }\n```\n\nMerge options:\n\n- `preserveChanges`: when `true`, local `ADDED` rows are left untouched and locally modified column values are preserved.\n- `missingSchemaAction: 'add'`: add source columns missing from the target table.\n- `missingSchemaAction: 'ignore'`: ignore source columns missing from the target table.\n- `missingSchemaAction: 'error'`: throw if the source schema contains missing target columns. This is the default.\n\n`DataSet.merge()` applies the same behavior by table name. With `missingSchemaAction: 'add'`, missing tables are cloned into the target dataset and compatible missing relations are added.\n\n```javascript\nconst company = new DataSet('Company');\nconst incoming = new DataSet('Company');\n\n// configure tables on both datasets...\n\nconst summary = company.merge(incoming, {\n  preserveChanges: true,\n  missingSchemaAction: 'add'\n});\n```\n\nFor database refresh scenarios where you already have plain rows, use `mergeRows()`:\n\n```javascript\nconst result = users.mergeRows(freshRows, {\n  primaryKey: 'id',\n  updateExisting: true,\n  addMissing: true,\n  markModified: false\n});\n\nconsole.log(result.updatedRows);\nconsole.log(result.insertedRows);\n```\n\n### Advanced Filtering Criteria\ndotnet-datatable supports various operators for advanced filtering. Here are all available operators:\n\n```javascript\n// Examples of all available operators\ndt.findRows({\n    // Comparison operators\n    age: { $gt: 25 },          // Greater than (\u003e)\n    score: { $gte: 90 },       // Greater than or equal (\u003e=)\n    price: { $lt: 100 },       // Less than (\u003c)\n    quantity: { $lte: 50 },    // Less than or equal (\u003c=)\n    status: { $ne: 'active' }, // Not equal (!=)\n    \n    // Membership operators\n    category: { $in: ['A', 'B', 'C'] },  // Value exists in array\n    \n    // String operators\n    name: { $contains: 'john' },     // String contains 'john'\n    \n    // Regular expressions\n    email: /gmail\\.com$/,  // Ends with gmail.com\n    \n    // Exact values\n    active: true,          // Exactly matches true\n    type: 'user'          // Exactly matches 'user'\n});\n\n// Practical examples of combined use\nconst results = dt.findRows({\n    age: { $gt: 18, $lt: 30 },           // Age between 18 and 30\n    name: { $contains: 'smith' },         // Name contains 'smith'\n    roles: { $in: ['admin', 'editor'] },  // Role is admin or editor\n    email: /^[a-z]+@company\\.com$/        // Company email\n});\n\n// Custom function search\nconst filtered = dt.findRows(row =\u003e {\n    const age = row.get('age');\n    const status = row.get('status');\n    return age \u003e 25 \u0026\u0026 status === 'active';\n});\n```\n\nAll supported operators:\n- `$gt`: Greater than\n- `$gte`: Greater than or equal to\n- `$lt`: Less than\n- `$lte`: Less than or equal to\n- `$ne`: Not equal to\n- `$in`: Value exists in array\n- `$contains`: String contains value\n- RegExp: Support for regular expressions\n\n#### Sorting\n```javascript\n// Simple sort\ndt.sort('age', 'asc');\n\n// Multiple criteria sort\ndt.sortMultiple(\n    { column: 'age', order: 'desc' },\n    { column: 'name', order: 'asc' }\n);\n\n// Custom sort\ndt.sortBy(row =\u003e row.get('age') + row.get('name'));\n```\n\n#### Loading Data from Database\nThe package receives data that was already fetched by your database client. It does not create connections or execute queries.\n\n```javascript\n// PostgreSQL example\nconst { Pool } = require('pg');\nconst pool = new Pool(config);\n\nconst result = await pool.query('SELECT * FROM products WHERE category = $1', ['electronics']);\nconst products = DataTable.fromQueryResult(result, {\n    tableName: 'Products',\n    primaryKey: 'id',\n    useFieldMetadata: true\n});\n\n// MySQL example\nconst mysql = require('mysql2/promise');\nconst connection = await mysql.createConnection(config);\nconst mysqlResult = await connection.execute('SELECT * FROM products WHERE price \u003e ?', [100]);\nconst mysqlProducts = DataTable.fromQueryResult(mysqlResult, {\n    tableName: 'Products',\n    primaryKey: 'id'\n});\n\n// Loading into an existing table is also supported\nconst dt = new DataTable('Products');\ndt.addColumn('id', 'number', { primaryKey: true });\ndt.addColumn('name', 'string');\ndt.loadRows(result.rows, { clearBeforeLoad: true });\n\n// Load from array of objects\nconst data = [\n    { id: 1, name: 'John' },\n    { id: 2, name: 'Jane' }\n];\nconst users = DataTable.fromRows(data, { tableName: 'Users', primaryKey: 'id' });\n```\n\n### Column Operations\n```javascript\n// Check if column exists\ndt.columnExists('name');\n\n// Remove column\ndt.removeColumn('age');\n```\n\n### Table Manipulation\n```javascript\n// Clone table\nconst newTable = dt.clone();\n\n// Iterate through rows\nfor (const row of newTable) {\n    console.log(row.get('name')); // using recommended get() method\n}\n```\n\n### DataSet Operations\nDataSet allows you to manage multiple related tables and define relationships between them.\n\n```javascript\nconst { DataSet, DataTable } = require('dotnet-datatable');\n\n// Create a new dataset\nconst ds = new DataSet('CompanyData');\n\n// Add tables to the dataset\nconst employees = ds.addTable('Employees');\nemployees.addColumn('id', 'number');\nemployees.addColumn('name', 'string');\nemployees.addColumn('departmentId', 'number');\n\nconst departments = ds.addTable('Departments');\ndepartments.addColumn('id', 'number');\ndepartments.addColumn('name', 'string');\n\n// Add data\ndepartments.addRow({ id: 1, name: 'HR' });\ndepartments.addRow({ id: 2, name: 'IT' });\n\nemployees.addRow({ id: 1, name: 'John', departmentId: 2 });\nemployees.addRow({ id: 2, name: 'Jane', departmentId: 1 });\n\n// Create a relation between tables\nconst relation = ds.addRelation(\n    'EmpDeptRelation',\n    'Departments', \n    'Employees',\n    'id',\n    'departmentId'\n);\n\n// Get related rows\nconst itDept = departments.findOne({ id: 2 });\nconst itEmployees = ds.getChildRows(itDept, 'EmpDeptRelation');\nconsole.log(itEmployees); // [{ id: 1, name: 'John', departmentId: 2 }]\n\n// Get parent row\nconst john = employees.findOne({ name: 'John' });\nconst johnsDept = ds.getParentRow(john, 'EmpDeptRelation');\nconsole.log(johnsDept.get('name')); // 'IT'\n```\n\nYou can also build a DataSet directly from multiple query result arrays or SQL Server-style recordsets:\n\n```javascript\nconst ds = DataSet.fromRecordsets([\n    usersRows,\n    ordersRows\n], {\n    tableNames: ['Users', 'Orders'],\n    relations: [\n        {\n            name: 'UserOrders',\n            parentTable: 'Users',\n            parentColumn: 'id',\n            childTable: 'Orders',\n            childColumn: 'user_id'\n        }\n    ]\n});\n\nconst relation = ds.getRelation('UserOrders');\nconst orders = relation.getChildRows(userRow);\nconst user = relation.getParentRow(orderRow);\n```\n\n### DataView Operations\nDataView provides a filtered and sorted view of a DataTable.\n```javascript\nconst { DataTable, DataView } = require('dotnet-datatable');\n\n// Create a table\nconst users = new DataTable('Users');\nusers.addColumn('id', 'number');\nusers.addColumn('name', 'string');\nusers.addColumn('age', 'number');\nusers.addColumn('active', 'boolean');\n\n// Add some data\nusers.addRow({ id: 1, name: 'John', age: 25, active: true });\nusers.addRow({ id: 2, name: 'Jane', age: 30, active: true });\nusers.addRow({ id: 3, name: 'Bob', age: 22, active: false });\nusers.addRow({ id: 4, name: 'Alice', age: 35, active: true });\n\n// Create a view of active users sorted by age\nconst activeUsersView = new DataView(\n    users,\n    { active: true },  // Filter\n    'age',            // Sort by\n    'desc'            // Sort order\n);\n\n// Use the view\nconsole.log(`Active users: ${activeUsersView.count}`); // 3\n\n// Get the first row (oldest active user due to desc sort)\nconst oldest = activeUsersView.firstRow;\nconsole.log(oldest.get('name')); // 'Alice'\n\n// Iterate through view rows\nfor (const row of activeUsersView) {\n    console.log(`${row.get('name')}: ${row.get('age')}`);\n}\n// Output:\n// Alice: 35\n// Jane: 30\n// John: 25\n\n// Create a new table from the view\nconst activeUsersTable = activeUsersView.toTable();\n\n// Get view data as array of objects\nconst activeUsersArray = activeUsersView.toArray();\n```\n\nThe fluent API is also available:\n\n```javascript\nconst activeAdults = users\n    .createView()\n    .where('active', '=', true)\n    .where('age', '\u003e', 18)\n    .orderBy('name', 'asc')\n    .skip(20)\n    .take(10)\n    .toObjects();\n\nconst quickView = users.createView({\n    filter: \"age \u003e= 18 AND active = true\",\n    sort: 'name ASC'\n});\n```\n\n### Advanced Schema Management\n\nThe DataTable provides advanced schema management capabilities for working with table structures:\n\n```javascript\nconst { DataTable } = require('dotnet-datatable');\n\n// Create a table with schema\nconst users = new DataTable('Users');\nusers.addColumn('id', 'number');\nusers.addColumn('name', 'string');\nusers.addColumn('age', 'number');\n\n// Mark column as primary key\nusers.columns._columns.get('id').isPrimaryKey = true;\nusers.columns._columns.get('id').allowNull = false;\n\n// Export the schema to a portable format\nconst schema = users.exportSchema();\nconsole.log(schema);\n/* Output:\n{\n  tableName: 'Users',\n  caseSensitive: false,\n  columns: [\n    {\n      name: 'id',\n      dataType: 'number',\n      allowNull: false,\n      defaultValue: null,\n      expression: null,\n      readOnly: false,\n      unique: true,\n      ordinal: 0,\n      caption: 'id',\n      isPrimaryKey: true\n    },\n    // ...other columns\n  ],\n  primaryKey: ['id'],\n  uniqueConstraints: []\n}\n*/\n\n// Save schema to JSON\nconst schemaJson = users.serializeSchema();\n// Later, recreate the table from JSON\nconst recreatedTable = DataTable.deserializeSchema(schemaJson);\n\n// Create another table with a different schema\nconst updatedUsers = new DataTable('UpdatedUsers');\nupdatedUsers.addColumn('id', 'number');\nupdatedUsers.addColumn('name', 'string');\nupdatedUsers.addColumn('age', 'number');\nupdatedUsers.addColumn('email', 'string'); // New column\nupdatedUsers.columns._columns.get('name').allowNull = false; // Changed nullability\n\n// Compare schemas\nconst differences = users.compareSchema(updatedUsers);\nconsole.log(differences);\n/* Output:\n{\n  missingColumns: ['email'],\n  extraColumns: [],\n  typeMismatches: [],\n  nullabilityDifferences: [\n    {\n      column: 'name',\n      thisAllowNull: true,\n      otherAllowNull: false\n    }\n  ]\n}\n*/\n\n// Update schema\nconst updateResult = users.updateSchema(updatedUsers);\nconsole.log(updateResult);\n/* Output:\n{\n  addedColumns: ['email'],\n  removedColumns: [],\n  modifiedColumns: [\n    {\n      column: 'name',\n      change: 'allowNull',\n      from: true,\n      to: false\n    }\n  ]\n}\n*/\n\n// Create a table from an existing schema\nconst newTable = DataTable.importSchema({\n  tableName: 'Products',\n  columns: [\n    { name: 'id', dataType: 'number', allowNull: false, defaultValue: null },\n    { name: 'name', dataType: 'string', allowNull: false },\n    { name: 'price', dataType: 'number', defaultValue: 0 },\n    { name: 'createdAt', dataType: 'date', defaultValue: () =\u003e new Date() }\n  ],\n  primaryKey: ['id']\n});\n```\nThe schema management features allow you to:\n\n- Export table structure to a portable format\n- Create tables from existing schemas\n- Compare schemas between tables to identify differences\n- Update a table's schema to match another\n- Serialize/deserialize schemas to JSON\n\nThis is especially useful for:\n\n- -Creating table structures dynamically based on configuration\n- Migrating data between different schema versions\n- Generating table documentation\n- Schema validation and enforcement\n\n## Supported Data Types\n* string\n* number\n* integer\n* date\n* boolean\n* object\n* array\n* json\n* bigint\n* buffer\n* any\n\n## Advanced Database Usage\nThe DataTable automatically creates columns based on query result rows and, when available, field metadata. This is best-effort mapping and does not require any database package dependency:\n\n- PostgreSQL `int2/int4` -\u003e integer, `int8` -\u003e bigint, `numeric/float` -\u003e number, `varchar/text` -\u003e string, `bool` -\u003e boolean, `timestamp/date` -\u003e date, `json/jsonb` -\u003e json, `bytea` -\u003e buffer\n- MySQL `INT` -\u003e integer, `BIGINT` -\u003e bigint, `DECIMAL/FLOAT/DOUBLE` -\u003e number, `VARCHAR/TEXT` -\u003e string, `DATE/DATETIME/TIMESTAMP` -\u003e date, `TINYINT(1)` -\u003e boolean, `JSON` -\u003e json, `BLOB` -\u003e buffer\n- SQL Server `int/smallint/tinyint` -\u003e integer, `bigint` -\u003e bigint, `decimal/numeric/float/real/money` -\u003e number, `varchar/nvarchar/text` -\u003e string, `bit` -\u003e boolean, `date/datetime/datetime2` -\u003e date, `varbinary` -\u003e buffer\n- SQLite `INTEGER` -\u003e integer, `REAL` -\u003e number, `TEXT` -\u003e string, `BLOB` -\u003e buffer\n- OracleDB `NUMBER` -\u003e number, `VARCHAR2/NVARCHAR2/CHAR` -\u003e string, `DATE/TIMESTAMP` -\u003e date, `BLOB/RAW` -\u003e buffer, `CLOB` -\u003e string, `JSON` -\u003e json\n\nThis makes it perfect for scenarios where you need to:\n- Cache database results\n- Manipulate query results before display\n- Create temporary data structures from database queries\n- Transform data before sending to the frontend\n\nLimitations:\n\n- The package does not connect to databases.\n- The package does not execute SQL queries.\n- The package is not an ORM.\n- It does not replace Prisma, Sequelize, TypeORM, Drizzle or Knex.\n- Complex SQL-like DataView filter strings are intentionally limited.\n\n## Error Handling\nThe library throws errors for:\n* Invalid column operations\n* Type mismatches\n* Null violations\n* Duplicate columns\n\n## License\nMIT\n\n## Disclaimer\n\nA DataTable-inspired data structure for JavaScript and TypeScript, designed for developers who like the .NET DataTable, DataView and DataSet programming model.\n\nThis project is not affiliated with Microsoft or the official .NET project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmazeor9%2Fdotnet-datatable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmazeor9%2Fdotnet-datatable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmazeor9%2Fdotnet-datatable/lists"}