@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.
Files changed (2) hide show
  1. package/README.md +266 -118
  2. 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 with connection pooling, singleton support, and advanced query handling for Node.js.
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
- * **Connection Pooling:** Efficiently manages database connections.
8
- * **Promise-based:** Fully supports `async/await` for clean code.
9
- * **Table Handler:** An abstraction layer to perform CRUD operations easily.
10
- * **Datatables Integration:** Built-in helper to parse and query data specifically for jQuery Datatables server-side processing.
11
- * **Singleton Support:** Includes a singleton pattern setup for easy global access.
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. Initialize Connection
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
- Use the `query` method to execute standard SQL.
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
- The `DataBaseHandler` simplifies interactions with specific tables.
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
- // Initialize handler for 'customers' table
56
- const customers = new DataBaseHandler(db, 'customers');
115
+ const db = new DataBase({ /* config */ });
116
+ const usersHandler = new DataBaseHandler(db, 'users');
57
117
  ```
58
118
 
59
119
  ---
60
120
 
61
- ## Advanced Usage & Examples
121
+ ## Advanced Querying (`select` method)
62
122
 
63
- ### Inserting Records (`insert`)
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
- **Parameters:**
68
- * `params` (Object): An object where keys match the column names and values are the data to insert.
125
+ ### Basic Selection
69
126
 
70
- **Example:**
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
- const logs = new DataBaseHandler(db, 'app_logs');
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
- const result = await logs.insert({
76
- level: 'info',
77
- message: 'User logged in',
78
- timestamp: new Date()
79
- });
80
- // Executes: INSERT INTO app_logs SET `level` = ?, `message` = ?, `timestamp` = ?
81
- console.log(`Inserted Row ID: ${result.insertId}`);
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
- ### Flexible Selection (`select`)
152
+ #### Explicit `IN` / `NOT IN` Operator
153
+ You can explicitly define the `operator` as `'IN'` or `'NOT IN'`:
85
154
 
86
- The `select` method allows you to build complex queries using an array of parameters and a clauses object.
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
- **Parameters:**
89
- * `params` (Array): An array where each element is either a condition object or a logical string ('AND', 'OR').
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
- **Example:**
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
- const products = new DataBaseHandler(db, 'products');
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
- const results = await products.select(
100
- // 1. Params: Conditions
187
+ ```javascript
188
+ const filteredProducts = await productsHandler.select(
101
189
  [
102
- { category: 'electronics' }, // WHERE `category` = 'electronics'
190
+ { category: 'electronics' },
191
+ 'AND',
192
+ { price: 500, operator: '>' },
103
193
  'AND',
104
- { price: 1000, operator: '>' } // AND `price` > 1000
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': 10,
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
- ### Updating Records (`update`)
206
+ ---
117
207
 
118
- The `update` method separates the values to change from the conditions.
208
+ ## CRUD Operations
119
209
 
120
- **Parameters:**
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
- **Example:**
212
+ Adds a new row to the associated table using key-value properties.
127
213
 
128
214
  ```javascript
129
- const users = new DataBaseHandler(db, 'users');
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
- const result = await users.update({
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: 'inactive',
134
- last_seen: new Date()
232
+ status: 'suspended',
233
+ notes: 'Violated terms of service'
135
234
  },
136
235
  where: {
137
- id: 123,
138
- role: 'guest'
236
+ id: 42,
237
+ status: 'active'
139
238
  }
140
239
  });
141
- // Executes: UPDATE users SET `status` = ?, `last_seen` = ? WHERE `id` = ? AND `role` = ?
240
+
241
+ console.log('Affected rows:', result.affectedRows);
142
242
  ```
143
243
 
144
244
  ### Deleting Records (`delete`)
145
245
 
146
- The `delete` method removes rows matching the provided conditions.
246
+ Deletes rows matching the conditions object (joined by implicit `AND` operators).
147
247
 
148
- **Parameters:**
149
- * `conditions` (Object): Key-value pairs to match rows to delete (uses `AND` implicitly).
248
+ ```javascript
249
+ const result = await usersHandler.delete({
250
+ status: 'temporary',
251
+ is_expired: 1
252
+ });
150
253
 
151
- **Example:**
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
- const sessions = new DataBaseHandler(db, 'user_sessions');
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 result = await sessions.delete({
157
- user_id: 55,
158
- is_expired: 1
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
- ## API Documentation
285
+ ## Database Singleton Pattern
166
286
 
167
- ### Class: `DataBase`
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
- #### `constructor(config)`
170
- Creates a database instance with a connection pool.
171
- * **config**: Object. MySQL2 connection configuration (host, user, password, database, port, connectionLimit, etc).
289
+ Create a `db.js` file:
290
+ ```javascript
291
+ // db.js
292
+ const { DataBase } = require('@areumtecnologia/mysql-db-handler');
172
293
 
173
- #### `async query(sql, params)`
174
- Executes a SQL query using a pooled connection.
175
- * **sql**: String. The SQL query to execute.
176
- * **params**: Array. Parameters for the prepared statement.
177
- * **Returns**: `Promise<Array|Object>`. Returns rows on success, or an error object if failed.
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
- #### `async close()`
180
- Closes the connection pool completely.
181
- * **Returns**: `Promise<void>`.
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
- ### Class: `DataBaseHandler`
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
- #### `constructor(database, table, expression)`
188
- * **database**: Instance of `DataBase`.
189
- * **table**: String. The name of the table.
190
- * **expression**: String (Optional). Additional SQL expression (e.g., extra columns or joins).
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
- #### `async select(params, clauses)`
193
- See "Flexible Selection" above.
194
- * **Returns**: `Promise<Array>`.
332
+ ### Example: Checking for errors in queries
195
333
 
196
- #### `async selectBy(params)`
197
- Quickly select rows matching a specific object condition.
198
- * **params**: Object. Key-value pairs for WHERE clause (e.g., `{ id: 5 }`).
199
- * **Returns**: `Promise<Array>`. (contains rows array from query)
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
- #### `async insert(params)`
202
- Inserts a new row.
203
- * **params**: Object. Key-value pairs representing column names and values.
204
- * **Returns**: `Promise<OkPacket>` (contains insertId, or error).
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
- #### `async update(params, options)`
207
- See "Updating Records" above.
208
- * **Returns**: `Promise<OkPacket>`. (contains affectedRows or error).
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
- #### `async delete(conditions)`
211
- See "Deleting Records" above.
212
- * **Returns**: `Promise<OkPacket>`. (contains affectedRows or error)
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
- #### `async selectToDatatable(rawDtQuery, strictCondition)`
215
- Helper for server-side Datatables.net processing.
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. Fork the repository
227
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
228
- 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
229
- 4. Push to the branch (`git push origin feature/amazing-feature`)
230
- 5. Open a Pull Request
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@areumtecnologia/mysql-db-handler",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "A powerful MySQL wrapper with connection pooling, singleton support, and advanced query handling for Node.js.",
5
5
  "main": "index.js",
6
6
  "scripts": {