@areumtecnologia/mysql-db-handler 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +266 -118
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,48 @@
|
|
|
1
1
|
# @areumtecnologia/mysql-db-handler
|
|
2
2
|
|
|
3
|
-
A powerful MySQL wrapper
|
|
3
|
+
A powerful, robust, and production-ready MySQL wrapper for Node.js. It features connection pooling, query caching (optional), jQuery DataTables server-side processing integration, safe error handling, and a flexible abstraction layer for CRUD operations.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
* [Features](#features)
|
|
8
|
+
* [Installation](#installation)
|
|
9
|
+
* [Core Classes](#core-classes)
|
|
10
|
+
* [DataBase (Standard Connection)](#1-database-standard-connection)
|
|
11
|
+
* [DataBase (With Query & Schema Caching)](#2-database-with-query--schema-caching)
|
|
12
|
+
* [DataBaseHandler (CRUD Abstraction)](#3-databasehandler-crud-abstraction)
|
|
13
|
+
* [Basic Usage](#basic-usage)
|
|
14
|
+
* [1. Initializing Connection](#1-initializing-connection)
|
|
15
|
+
* [2. Executing Raw SQL](#2-executing-raw-sql)
|
|
16
|
+
* [3. Using the DataBaseHandler](#3-using-the-databasehandler)
|
|
17
|
+
* [Advanced Querying (`select` method)](#advanced-querying-select-method)
|
|
18
|
+
* [Basic Selection](#basic-selection)
|
|
19
|
+
* [Working with the `IN` and `NOT IN` Operators](#working-with-the-in-and-not-in-operators)
|
|
20
|
+
* [Automatic `IN` Detection](#automatic-in-detection)
|
|
21
|
+
* [Explicit `IN` / `NOT IN` Operator](#explicit-in--not-in-operator)
|
|
22
|
+
* [Empty Array Safety (`IN ()` Protection)](#empty-array-safety-in-protection)
|
|
23
|
+
* [Combining Multiple Conditions (AND / OR)](#combining-multiple-conditions-and--or)
|
|
24
|
+
* [CRUD Operations](#crud-operations)
|
|
25
|
+
* [Inserting Records (`insert`)](#inserting-records-insert)
|
|
26
|
+
* [Updating Records (`update`)](#updating-records-update)
|
|
27
|
+
* [Deleting Records (`delete`)](#deleting-records-delete)
|
|
28
|
+
* [jQuery DataTables Server-Side Support](#jquery-datatables-server-side-support)
|
|
29
|
+
* [Database Singleton Pattern](#database-singleton-pattern)
|
|
30
|
+
* [Error Handling Strategy](#error-handling-strategy)
|
|
31
|
+
* [Contributing](#contributing)
|
|
32
|
+
* [License](#license)
|
|
33
|
+
|
|
34
|
+
---
|
|
4
35
|
|
|
5
36
|
## Features
|
|
6
37
|
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
38
|
+
* **Connection Pooling:** Efficiently manages and recycles database connections using `mysql2/promise`.
|
|
39
|
+
* **Flexible Query Builder:** Run complex operations without writing manual SQL strings.
|
|
40
|
+
* **Automatic `IN` Handling:** Smart processing of arrays for SQL `IN` and `NOT IN` operations (including prevention of empty array query crashes).
|
|
41
|
+
* **Optional Caching Wrapper:** Native support for query and schema caching using `node-cache` to boost performance.
|
|
42
|
+
* **Server-Side DataTables Integration:** Effortless integration with jQuery DataTables server-side requests, handling filters, sorting, search (global and column-based), regex, and count totals automatically.
|
|
43
|
+
* **Robust Error Handling:** Intercepts and wraps database errors in predictable objects to prevent application crashes.
|
|
44
|
+
|
|
45
|
+
---
|
|
12
46
|
|
|
13
47
|
## Installation
|
|
14
48
|
|
|
@@ -16,206 +50,318 @@ A powerful MySQL wrapper with connection pooling, singleton support, and advance
|
|
|
16
50
|
npm install @areumtecnologia/mysql-db-handler
|
|
17
51
|
```
|
|
18
52
|
|
|
53
|
+
If you wish to use the caching features, ensure `node-cache` is installed:
|
|
54
|
+
```bash
|
|
55
|
+
npm install node-cache
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Core Classes
|
|
61
|
+
|
|
62
|
+
The package exports the following primary components:
|
|
63
|
+
|
|
64
|
+
### 1. `DataBase` (Standard Connection)
|
|
65
|
+
Located in `lib/database.js`. Manages standard pooled connections, schema discovery, and query execution.
|
|
66
|
+
|
|
67
|
+
### 2. `DataBase` (With Query & Schema Caching)
|
|
68
|
+
Located in `lib/database-cache.js`. Overrides the standard `DataBase` implementation to provide in-memory query and table schema caching. It is ideal for read-heavy environments.
|
|
69
|
+
|
|
70
|
+
### 3. `DataBaseHandler` (CRUD Abstraction)
|
|
71
|
+
Located in `lib/databasehandler.js`. Bind this class to a specific table to perform database actions through utility methods (`select`, `selectBy`, `insert`, `update`, `delete`, `selectToDatatable`).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
19
75
|
## Basic Usage
|
|
20
76
|
|
|
21
|
-
### 1.
|
|
22
|
-
You can create a new instance of the database class directly.
|
|
77
|
+
### 1. Initializing Connection
|
|
23
78
|
|
|
24
79
|
```javascript
|
|
25
80
|
const { DataBase } = require('@areumtecnologia/mysql-db-handler');
|
|
26
81
|
|
|
82
|
+
// Configuration matches mysql2 connection configuration
|
|
27
83
|
const db = new DataBase({
|
|
28
84
|
host: 'localhost',
|
|
29
85
|
user: 'root',
|
|
30
86
|
password: 'password',
|
|
31
87
|
database: 'my_app_db',
|
|
32
88
|
port: 3306,
|
|
33
|
-
connectionLimit: 10
|
|
89
|
+
connectionLimit: 10,
|
|
90
|
+
waitForConnections: true,
|
|
91
|
+
queueLimit: 0
|
|
34
92
|
});
|
|
35
93
|
```
|
|
36
94
|
|
|
37
95
|
### 2. Executing Raw SQL
|
|
38
|
-
|
|
96
|
+
|
|
97
|
+
Raw queries are executed through connection pooling. The library handles obtaining and releasing the connection automatically.
|
|
39
98
|
|
|
40
99
|
```javascript
|
|
41
100
|
try {
|
|
42
|
-
const rows = await db.query('SELECT * FROM users WHERE active = ?', [1]);
|
|
43
|
-
console.log(rows);
|
|
101
|
+
const rows = await db.query('SELECT * FROM users WHERE active = ? AND role = ?', [1, 'editor']);
|
|
102
|
+
console.log('Active editors:', rows);
|
|
44
103
|
} catch (error) {
|
|
45
|
-
console.error(error);
|
|
104
|
+
console.error('Database query failed:', error);
|
|
46
105
|
}
|
|
47
106
|
```
|
|
48
107
|
|
|
49
108
|
### 3. Using the DataBaseHandler
|
|
50
|
-
|
|
109
|
+
|
|
110
|
+
Wrap a table with a handler to abstract SQL construction.
|
|
51
111
|
|
|
52
112
|
```javascript
|
|
53
|
-
const { DataBaseHandler } = require('@areumtecnologia/mysql-db-handler');
|
|
113
|
+
const { DataBase, DataBaseHandler } = require('@areumtecnologia/mysql-db-handler');
|
|
54
114
|
|
|
55
|
-
|
|
56
|
-
const
|
|
115
|
+
const db = new DataBase({ /* config */ });
|
|
116
|
+
const usersHandler = new DataBaseHandler(db, 'users');
|
|
57
117
|
```
|
|
58
118
|
|
|
59
119
|
---
|
|
60
120
|
|
|
61
|
-
## Advanced
|
|
121
|
+
## Advanced Querying (`select` method)
|
|
62
122
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
The `insert` method adds a new row to the table.
|
|
123
|
+
The `select(params, clauses)` method builds complex SQL `WHERE` clauses from arrays of condition objects.
|
|
66
124
|
|
|
67
|
-
|
|
68
|
-
* `params` (Object): An object where keys match the column names and values are the data to insert.
|
|
125
|
+
### Basic Selection
|
|
69
126
|
|
|
70
|
-
|
|
127
|
+
You can pass condition objects where the key is the column name and the value is the target value.
|
|
71
128
|
|
|
72
129
|
```javascript
|
|
73
|
-
|
|
130
|
+
// Simple equals comparison (=)
|
|
131
|
+
const users = await usersHandler.select([
|
|
132
|
+
{ role: 'admin' }
|
|
133
|
+
]);
|
|
134
|
+
// Executes: SELECT * FROM users WHERE `role` = ?
|
|
135
|
+
```
|
|
74
136
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
137
|
+
### Working with the `IN` and `NOT IN` Operators
|
|
138
|
+
|
|
139
|
+
The library provides special support for SQL `IN` operations by mapping JavaScript arrays to queries.
|
|
140
|
+
|
|
141
|
+
#### Automatic `IN` Detection
|
|
142
|
+
If a condition's value is an array, and no operator is explicitly set, the query builder automatically treats it as an `IN` statement:
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
const activeUsers = await usersHandler.select([
|
|
146
|
+
{ status: ['active', 'pending'] }
|
|
147
|
+
]);
|
|
148
|
+
// Executes: SELECT * FROM users WHERE `status` IN (?, ?)
|
|
149
|
+
// Bindings: ['active', 'pending']
|
|
82
150
|
```
|
|
83
151
|
|
|
84
|
-
|
|
152
|
+
#### Explicit `IN` / `NOT IN` Operator
|
|
153
|
+
You can explicitly define the `operator` as `'IN'` or `'NOT IN'`:
|
|
85
154
|
|
|
86
|
-
|
|
155
|
+
```javascript
|
|
156
|
+
// Using NOT IN explicitly
|
|
157
|
+
const nonAdminUsers = await usersHandler.select([
|
|
158
|
+
{ role: ['admin', 'superadmin'], operator: 'NOT IN' }
|
|
159
|
+
]);
|
|
160
|
+
// Executes: SELECT * FROM users WHERE `role` NOT IN (?, ?)
|
|
161
|
+
// Bindings: ['admin', 'superadmin']
|
|
162
|
+
```
|
|
87
163
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* Simple condition: `{ column: value }` (defaults to `=`)
|
|
91
|
-
* Condition with operator: `{ column: value, operator: '>' }`
|
|
92
|
-
* `clauses` (Object): Optional keys like `ORDERBY`, `DESC`, `LIMIT`, `OFFSET`, `GROUP BY`, `HAVING`.
|
|
164
|
+
#### Empty Array Safety (`IN ()` Protection)
|
|
165
|
+
In standard SQL, executing a query with an empty array in the `IN` clause (e.g., `WHERE status IN ()`) yields a syntax error and crashes the execution. The library automatically mitigates this:
|
|
93
166
|
|
|
94
|
-
**
|
|
167
|
+
* **Empty `IN`**: Evaluates to `1 = 0` (always false). No records match this sub-clause, ensuring safe execution.
|
|
168
|
+
* **Empty `NOT IN`**: Evaluates to `1 = 1` (always true). All records match this sub-clause, returning all values since nothing is excluded.
|
|
95
169
|
|
|
96
170
|
```javascript
|
|
97
|
-
|
|
171
|
+
// Safe empty array handling
|
|
172
|
+
const results = await usersHandler.select([
|
|
173
|
+
{ id: [], operator: 'IN' }
|
|
174
|
+
]);
|
|
175
|
+
// Executes: SELECT * FROM users WHERE 1 = 0
|
|
176
|
+
|
|
177
|
+
const allResults = await usersHandler.select([
|
|
178
|
+
{ id: [], operator: 'NOT IN' }
|
|
179
|
+
]);
|
|
180
|
+
// Executes: SELECT * FROM users WHERE 1 = 1
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Combining Multiple Conditions (AND / OR)
|
|
184
|
+
|
|
185
|
+
Interleave condition objects with string elements representing logical operators:
|
|
98
186
|
|
|
99
|
-
|
|
100
|
-
|
|
187
|
+
```javascript
|
|
188
|
+
const filteredProducts = await productsHandler.select(
|
|
101
189
|
[
|
|
102
|
-
{ category: 'electronics' },
|
|
190
|
+
{ category: 'electronics' },
|
|
191
|
+
'AND',
|
|
192
|
+
{ price: 500, operator: '>' },
|
|
103
193
|
'AND',
|
|
104
|
-
{
|
|
194
|
+
{ brand: ['Sony', 'LG'] } // Automatically mapped to IN
|
|
105
195
|
],
|
|
106
|
-
// 2. Clauses: SQL Modifiers
|
|
107
196
|
{
|
|
108
197
|
'ORDERBY': 'price',
|
|
109
198
|
'DESC': true,
|
|
110
|
-
'LIMIT':
|
|
111
|
-
'OFFSET': 0
|
|
199
|
+
'LIMIT': 20
|
|
112
200
|
}
|
|
113
201
|
);
|
|
202
|
+
// Executes:
|
|
203
|
+
// SELECT * FROM products WHERE `category` = ? AND `price` > ? AND `brand` IN (?, ?) ORDER BY price DESC LIMIT 20
|
|
114
204
|
```
|
|
115
205
|
|
|
116
|
-
|
|
206
|
+
---
|
|
117
207
|
|
|
118
|
-
|
|
208
|
+
## CRUD Operations
|
|
119
209
|
|
|
120
|
-
|
|
121
|
-
* `params` (Object): Contains:
|
|
122
|
-
* `set`: Key-value pairs of columns to update.
|
|
123
|
-
* `where`: Key-value pairs for the WHERE clause (uses `AND` implicitly between keys).
|
|
124
|
-
* `options` (Object): Optional settings, e.g., `{ useRegex: true }` to use REGEXP instead of =.
|
|
210
|
+
### Inserting Records (`insert`)
|
|
125
211
|
|
|
126
|
-
|
|
212
|
+
Adds a new row to the associated table using key-value properties.
|
|
127
213
|
|
|
128
214
|
```javascript
|
|
129
|
-
const
|
|
215
|
+
const result = await usersHandler.insert({
|
|
216
|
+
name: 'John Doe',
|
|
217
|
+
email: 'john.doe@example.com',
|
|
218
|
+
role: 'member',
|
|
219
|
+
created_at: new Date()
|
|
220
|
+
});
|
|
130
221
|
|
|
131
|
-
|
|
222
|
+
console.log('Inserted ID:', result.insertId);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Updating Records (`update`)
|
|
226
|
+
|
|
227
|
+
Performs selective updates by specifying both a `set` payload and a `where` filter.
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
const result = await usersHandler.update({
|
|
132
231
|
set: {
|
|
133
|
-
status: '
|
|
134
|
-
|
|
232
|
+
status: 'suspended',
|
|
233
|
+
notes: 'Violated terms of service'
|
|
135
234
|
},
|
|
136
235
|
where: {
|
|
137
|
-
id:
|
|
138
|
-
|
|
236
|
+
id: 42,
|
|
237
|
+
status: 'active'
|
|
139
238
|
}
|
|
140
239
|
});
|
|
141
|
-
|
|
240
|
+
|
|
241
|
+
console.log('Affected rows:', result.affectedRows);
|
|
142
242
|
```
|
|
143
243
|
|
|
144
244
|
### Deleting Records (`delete`)
|
|
145
245
|
|
|
146
|
-
|
|
246
|
+
Deletes rows matching the conditions object (joined by implicit `AND` operators).
|
|
147
247
|
|
|
148
|
-
|
|
149
|
-
|
|
248
|
+
```javascript
|
|
249
|
+
const result = await usersHandler.delete({
|
|
250
|
+
status: 'temporary',
|
|
251
|
+
is_expired: 1
|
|
252
|
+
});
|
|
150
253
|
|
|
151
|
-
|
|
254
|
+
console.log('Deleted rows:', result.affectedRows);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## jQuery DataTables Server-Side Support
|
|
260
|
+
|
|
261
|
+
The `selectToDatatable` method processes the complex nested payloads sent by jQuery DataTables server-side scripts and returns the required format.
|
|
152
262
|
|
|
153
263
|
```javascript
|
|
154
|
-
|
|
264
|
+
// Express.js Route Example
|
|
265
|
+
app.post('/api/users/dt', async (req, res) => {
|
|
266
|
+
// req.body contains the DataTables standard payload:
|
|
267
|
+
// { draw: 1, start: 0, length: 10, search: { value: 'John', regex: false }, columns: [...] }
|
|
268
|
+
|
|
269
|
+
// You can enforce strict conditions that the client cannot bypass (e.g., tenant containment)
|
|
270
|
+
const strictConditions = [
|
|
271
|
+
{ tenant_id: req.user.tenantId },
|
|
272
|
+
'AND',
|
|
273
|
+
{ status: 'deleted', operator: '!=' }
|
|
274
|
+
];
|
|
155
275
|
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
276
|
+
const datatableResult = await usersHandler.selectToDatatable(req.body, strictConditions);
|
|
277
|
+
|
|
278
|
+
// Returns: { draw: 1, recordsTotal: 100, recordsFiltered: 12, data: [...] }
|
|
279
|
+
res.json(datatableResult);
|
|
159
280
|
});
|
|
160
|
-
// Executes: DELETE FROM user_sessions WHERE `user_id` = ? AND `is_expired` = ?
|
|
161
281
|
```
|
|
162
282
|
|
|
163
283
|
---
|
|
164
284
|
|
|
165
|
-
##
|
|
285
|
+
## Database Singleton Pattern
|
|
166
286
|
|
|
167
|
-
|
|
287
|
+
To prevent initializing redundant pool connections across various modules in a Node.js project, it is highly recommended to configure a single shared instance (Singleton).
|
|
168
288
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
289
|
+
Create a `db.js` file:
|
|
290
|
+
```javascript
|
|
291
|
+
// db.js
|
|
292
|
+
const { DataBase } = require('@areumtecnologia/mysql-db-handler');
|
|
172
293
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
294
|
+
const dbInstance = new DataBase({
|
|
295
|
+
host: process.env.DB_HOST || 'localhost',
|
|
296
|
+
user: process.env.DB_USER || 'root',
|
|
297
|
+
password: process.env.DB_PASSWORD,
|
|
298
|
+
database: process.env.DB_NAME,
|
|
299
|
+
connectionLimit: parseInt(process.env.DB_POOL_LIMIT) || 10
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
module.exports = dbInstance;
|
|
303
|
+
```
|
|
178
304
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
305
|
+
Import it in your business controllers/repositories:
|
|
306
|
+
```javascript
|
|
307
|
+
// usersRepository.js
|
|
308
|
+
const db = require('./db');
|
|
309
|
+
const { DataBaseHandler } = require('@areumtecnologia/mysql-db-handler');
|
|
310
|
+
|
|
311
|
+
const usersHandler = new DataBaseHandler(db, 'users');
|
|
312
|
+
|
|
313
|
+
module.exports = {
|
|
314
|
+
getUserById: (id) => usersHandler.selectBy({ id })
|
|
315
|
+
};
|
|
316
|
+
```
|
|
182
317
|
|
|
183
318
|
---
|
|
184
319
|
|
|
185
|
-
|
|
320
|
+
## Error Handling Strategy
|
|
321
|
+
|
|
322
|
+
Database exceptions generated during execution are caught internally by the `DataBase` instance. This means that instead of raising unhandled exceptions that could crash your Node.js application, the library wraps the error.
|
|
186
323
|
|
|
187
|
-
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
324
|
+
Specifically, the following methods from **`DataBase`** and **`DataBaseHandler`** return an object with an `error` property when an execution error occurs:
|
|
325
|
+
* `DataBase.query()`
|
|
326
|
+
* `DataBaseHandler.select()` / `selectBy()`
|
|
327
|
+
* `DataBaseHandler.insert()`
|
|
328
|
+
* `DataBaseHandler.update()`
|
|
329
|
+
* `DataBaseHandler.delete()`
|
|
330
|
+
* `DataBaseHandler.selectToDatatable()`
|
|
191
331
|
|
|
192
|
-
|
|
193
|
-
See "Flexible Selection" above.
|
|
194
|
-
* **Returns**: `Promise<Array>`.
|
|
332
|
+
### Example: Checking for errors in queries
|
|
195
333
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
334
|
+
```javascript
|
|
335
|
+
// Example with Select if column doesn't exist (triggers database error)
|
|
336
|
+
const users = await usersHandler.select([{ non_existent_column: 'admin' }]);
|
|
337
|
+
if (users.error) {
|
|
338
|
+
console.error('Failed to select users:', users.error.message);
|
|
339
|
+
}
|
|
200
340
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
341
|
+
// Example with Insert
|
|
342
|
+
const insertResult = await usersHandler.insert({ name: 'John Doe' });
|
|
343
|
+
if (insertResult.error) {
|
|
344
|
+
console.error('Failed to insert user:', insertResult.error.message);
|
|
345
|
+
}
|
|
205
346
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
347
|
+
// Example with Update
|
|
348
|
+
const updateResult = await usersHandler.update({
|
|
349
|
+
set: { status: 'active' },
|
|
350
|
+
where: { id: 999 }
|
|
351
|
+
});
|
|
352
|
+
if (updateResult.error) {
|
|
353
|
+
console.error('Failed to update user:', updateResult.error.message);
|
|
354
|
+
}
|
|
209
355
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
356
|
+
// Example with Delete
|
|
357
|
+
const deleteResult = await usersHandler.delete({ id: 999 });
|
|
358
|
+
if (deleteResult.error) {
|
|
359
|
+
console.error('Failed to delete user:', deleteResult.error.message);
|
|
360
|
+
}
|
|
361
|
+
```
|
|
213
362
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* **rawDtQuery**: Object. The request object sent by Datatables.
|
|
217
|
-
* **strictCondition**: Array. Additional strict WHERE clauses not controlled by the frontend.
|
|
218
|
-
* **Returns**: Object. Format expected by Datatables `{ draw, recordsTotal, recordsFiltered, data }`.
|
|
363
|
+
> [!NOTE]
|
|
364
|
+
> If you execute a `select` using valid columns but search for values that do not exist in the table (e.g. `await usersHandler.select([{ role: 'non_existent_role' }])`), this is **not** considered a database error. The query will execute successfully and return an empty array `[]` (without any `error` property). An error is only returned for actual database execution failures (such as syntax errors, missing columns, database connection drops, etc.).
|
|
219
365
|
|
|
220
366
|
---
|
|
221
367
|
|
|
@@ -223,12 +369,14 @@ Helper for server-side Datatables.net processing.
|
|
|
223
369
|
|
|
224
370
|
Contributions are welcome! Please open an issue or submit a pull request.
|
|
225
371
|
|
|
226
|
-
1.
|
|
227
|
-
2.
|
|
228
|
-
3.
|
|
229
|
-
4.
|
|
230
|
-
5.
|
|
372
|
+
1. Fork the repository
|
|
373
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
374
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
375
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
376
|
+
5. Open a Pull Request
|
|
377
|
+
|
|
378
|
+
---
|
|
231
379
|
|
|
232
380
|
## License
|
|
233
381
|
|
|
234
|
-
MIT
|
|
382
|
+
MIT
|
package/package.json
CHANGED