@mlagie/sql-connector 1.4.8 → 1.5.0
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/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
- package/README.md +260 -44
- package/docs/fr/README.md +204 -20
- package/index.d.ts +16 -45
- package/package.json +2 -2
- package/releases/1.5.0.md +127 -0
- package/src/models/Model.js +47 -100
- package/src/models/ModelInstance.js +60 -12
- package/src/utils/buildQuery.js +73 -0
- package/src/utils/generateCondition.js +1 -0
package/index.d.ts
CHANGED
|
@@ -95,75 +95,46 @@ export class Model {
|
|
|
95
95
|
schema: Schema;
|
|
96
96
|
constructor(name: string, schema: Schema);
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
98
|
+
* Creates all tables in the correct order based on foreign keys.
|
|
99
|
+
* @param dangerousSync default false
|
|
99
100
|
* @returns {Promise<void>}
|
|
100
101
|
*/
|
|
101
|
-
static syncAllTables(
|
|
102
|
+
static async syncAllTables(dangerousSync: Object): Promise<void>;
|
|
102
103
|
/**
|
|
103
104
|
* Saves data to the database table.
|
|
104
105
|
* @param {Object} data The data to insert into the table.
|
|
105
106
|
* @returns {Promise<Object>} A promise that resolves with the result of the insertion.
|
|
106
107
|
* @throws {Error} Throws an error if the insert fails.
|
|
107
108
|
*/
|
|
108
|
-
save(data: Record<string, any>): Promise<any>;
|
|
109
|
+
async save(data: Record<string, any>): Promise<any>;
|
|
109
110
|
/**
|
|
110
111
|
* Récupère plusieurs entrées de la table.
|
|
111
112
|
* @param {Object} [options] - Options de requête (attributs, where, order, limit).
|
|
112
|
-
* @param {string[]} [options.
|
|
113
|
+
* @param {string[]} [options.select] - Champs à retourner.
|
|
113
114
|
* @param {Object} [options.where] - Filtres (clé/valeur).
|
|
114
115
|
* @param {Array} [options.order] - Ex: [['points', 'DESC']]
|
|
115
116
|
* @param {number} [options.limit] - Limite de résultats.
|
|
116
117
|
* @returns {Promise<Array<Object>>}
|
|
117
118
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
static async find(options?: {
|
|
120
|
+
select?: string[];
|
|
120
121
|
where?: Record<string, any>;
|
|
121
122
|
order?: [string, string][];
|
|
122
123
|
limit?: number;
|
|
123
124
|
}): Promise<any[]>;
|
|
124
|
-
/**
|
|
125
|
-
* Finds a unique entry in the database table based on the filter provided.
|
|
126
|
-
* @param {Object} filter An object containing the key-value pairs to use to generate the search condition.
|
|
127
|
-
* @param {string[]} [fields=["*"]] An array of field names to return in the result.
|
|
128
|
-
* @returns {Promise<ModelInstance|number>} A promise that resolves to a ModelInstance if an entry is found, otherwise 0.
|
|
129
|
-
*/
|
|
130
|
-
findOne(filter: Record<string, any>, fields?: string[]): Promise<ModelInstance | number>;
|
|
131
|
-
/**
|
|
132
|
-
* Finds a record in the database based on the provided filter.
|
|
133
|
-
*
|
|
134
|
-
* @async
|
|
135
|
-
* @param {Object} filter - The filter criteria for the query. Should be an object where keys are column names and values are the values to filter by.
|
|
136
|
-
* @param {Array<string>} [fields=["*"]] - The fields to select in the query. Defaults to selecting all fields.
|
|
137
|
-
* @returns {Promise<ModelInstance|number>} - A promise that resolves to a `ModelInstance` if a record is found, or `0` if no records match the filter.
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* // Example usage:
|
|
141
|
-
* const filter = { id: 1 };
|
|
142
|
-
* const fields = ["id", "name"];
|
|
143
|
-
* MyTable.find(filter, fields).then((result) => {
|
|
144
|
-
* if (result === 0) {
|
|
145
|
-
* console.log("No records found.");
|
|
146
|
-
* } else {
|
|
147
|
-
* console.log("Record found:", result);
|
|
148
|
-
* }
|
|
149
|
-
* }).catch((err) => {
|
|
150
|
-
* console.error("Error:", err);
|
|
151
|
-
* });
|
|
152
|
-
*/
|
|
153
|
-
find(filter: Record<string, any>, fields?: string[]): Promise<ModelInstance | number>;
|
|
154
125
|
/**
|
|
155
126
|
*
|
|
156
127
|
* @param {Object} filter The filter criteria for the query. Should be an object where keys are column names and values are the values to filter by.
|
|
157
128
|
* @returns {Promise<ModelInstance|number>} - A promise that resolves to a `ModelInstance` if a record is found, or `0` if no records match the filter.
|
|
158
129
|
*/
|
|
159
|
-
count(filter?: Record<string, any>): Promise<any>;
|
|
130
|
+
async count(filter?: Record<string, any>): Promise<any>;
|
|
160
131
|
/**
|
|
161
132
|
* Runs a custom SQL_request query.
|
|
162
133
|
* @param {string} custom The custom SQL_request query to execute.
|
|
163
134
|
* @returns {Promise<void>} A promise that resolves when the query is executed.
|
|
164
135
|
* @throws {Error} Throws an error if query execution fails.
|
|
165
136
|
*/
|
|
166
|
-
customRequest(custom: string): Promise<any>;
|
|
137
|
+
async customRequest(custom: string): Promise<any>;
|
|
167
138
|
/**
|
|
168
139
|
* Supprime une entrée de la table SQL correspondant au filtre fourni.
|
|
169
140
|
*
|
|
@@ -174,7 +145,7 @@ export class Model {
|
|
|
174
145
|
* ou à une instance de ModelInstance représentant la ligne supprimée.
|
|
175
146
|
* @throws {Error} Lance une erreur si la requête SQL échoue.
|
|
176
147
|
*/
|
|
177
|
-
delete(filter: Record<string, any>): Promise<number | ModelInstance>;
|
|
148
|
+
async delete(filter: Record<string, any>): Promise<number | ModelInstance>;
|
|
178
149
|
/**
|
|
179
150
|
* Asynchronously drops a table if it exists in the database.
|
|
180
151
|
*
|
|
@@ -185,7 +156,7 @@ export class Model {
|
|
|
185
156
|
*
|
|
186
157
|
* @returns {Promise<void>} A promise that resolves when the query execution is complete.
|
|
187
158
|
*/
|
|
188
|
-
dropTable(): Promise<void>;
|
|
159
|
+
async dropTable(): Promise<void>;
|
|
189
160
|
/**
|
|
190
161
|
* Generates a unique UUID for the current model.
|
|
191
162
|
*
|
|
@@ -205,7 +176,7 @@ export class Model {
|
|
|
205
176
|
*
|
|
206
177
|
* @throws {Error} If there is an error executing the SQL_request query.
|
|
207
178
|
*/
|
|
208
|
-
generate_uuid(var_uuid?: string): Promise<string | null>;
|
|
179
|
+
async generate_uuid(var_uuid?: string): Promise<string | null>;
|
|
209
180
|
}
|
|
210
181
|
|
|
211
182
|
/**
|
|
@@ -224,27 +195,27 @@ export class ModelInstance {
|
|
|
224
195
|
* @returns {int} A promise that resolves with updated data.
|
|
225
196
|
* @throws {Error} Throws an error if the update fails.
|
|
226
197
|
*/
|
|
227
|
-
updateOne(model: Record<string, any>): Promise<number>;
|
|
198
|
+
async updateOne(model: Record<string, any>): Promise<number>;
|
|
228
199
|
/**
|
|
229
200
|
* Deletes a single entry in the database table.
|
|
230
201
|
* @param {Object} model An object containing the key-value pairs to use for deletion.
|
|
231
202
|
* @returns {Promise<Object>} A promise that resolves with the data deleted.
|
|
232
203
|
* @throws {Error} Throws an error if the deletion fails.
|
|
233
204
|
*/
|
|
234
|
-
delete(filter: Record<string, any>): Promise<number | ModelInstance>;
|
|
205
|
+
async delete(filter: Record<string, any>): Promise<number | ModelInstance>;
|
|
235
206
|
/**
|
|
236
207
|
* Deletes a single entry in the database table based on the instance data.
|
|
237
208
|
* @returns {Promise<number>} A promise that resolves to the number of rows deleted.
|
|
238
209
|
* @throws {Error} Throws an error if the deletion fails.
|
|
239
210
|
*/
|
|
240
|
-
deleteOne(): Promise<number>;
|
|
211
|
+
async deleteOne(): Promise<number>;
|
|
241
212
|
/**
|
|
242
213
|
* Runs a custom SQL_request query.
|
|
243
214
|
* @param {string} custom The custom SQL_request query to execute.
|
|
244
215
|
* @returns {Promise<void>} A promise that resolves when the query is executed.
|
|
245
216
|
* @throws {Error} Throws an error if query execution fails.
|
|
246
217
|
*/
|
|
247
|
-
customRequest(custom: string): Promise<any>;
|
|
218
|
+
async customRequest(custom: string): Promise<any>;
|
|
248
219
|
}
|
|
249
220
|
|
|
250
221
|
export const client: Record<string, any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlagie/sql-connector",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Le module sql-connector permet de gérer les connexions à une base de données MySQL, de définir des schémas de tables, et d'interagir avec les données de manière simple et efficace.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@mlagie/logger": "1.0.2",
|
|
39
39
|
"glob": "^13.0.6",
|
|
40
|
-
"mysql2": "3.22.
|
|
40
|
+
"mysql2": "3.22.5"
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# **Release v1.5 — sql-connector**
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This release introduces a major improvement to the query system in sql-connector, focusing on flexibility, performance, and developer experience.
|
|
6
|
+
|
|
7
|
+
The new version simplifies query building, adds support for advanced SQL features (such as aggregation and date formatting), and improves consistency across model operations.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## New Features
|
|
12
|
+
|
|
13
|
+
### Enhanced Query Builder (findAll)
|
|
14
|
+
|
|
15
|
+
- findAll() now supports advanced SQL features through a clean and extensible configuration object.
|
|
16
|
+
- Developers can build complex queries without writing raw SQL.
|
|
17
|
+
|
|
18
|
+
Supported features:
|
|
19
|
+
|
|
20
|
+
- SUM, DATE_FORMAT, and custom field transformations
|
|
21
|
+
- GROUP BY, ORDER BY, HAVING, and LIMIT
|
|
22
|
+
- Multiple aggregations in a single query
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
Model.findAll({
|
|
28
|
+
select: [
|
|
29
|
+
{ dateFormat: ['date_day', '%Y-%m'], as: 'period' },
|
|
30
|
+
{ sum: 'error' },
|
|
31
|
+
{ sum: 'reload' },
|
|
32
|
+
],
|
|
33
|
+
groupBy: ['period'],
|
|
34
|
+
orderBy: [{ field: 'period', direction: 'ASC' }]
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### Dynamic Field Builder
|
|
41
|
+
|
|
42
|
+
- Query fields are now processed individually through a modular builder.
|
|
43
|
+
- Enables combining multiple transformations (e.g., multiple SUM, DATE_FORMAT) without hardcoding specific cases.
|
|
44
|
+
- Improves extensibility for future SQL functions (AVG, COUNT, etc.).
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### Simplified Query Construction
|
|
49
|
+
|
|
50
|
+
- Query generation now follows a fixed SQL order (WHERE → GROUP BY → HAVING → ORDER BY → LIMIT) without unnecessary sorting.
|
|
51
|
+
- Improves performance and avoids overhead from dynamic reordering.
|
|
52
|
+
- Keeps the implementation simple and predictable.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### Improved Aggregation Support
|
|
57
|
+
|
|
58
|
+
- Designed for analytics use cases:
|
|
59
|
+
- Time-based grouping (day, month, year)
|
|
60
|
+
- Multi-metric aggregation
|
|
61
|
+
- Clean integration with frontend dashboards
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### Better Developer Experience
|
|
66
|
+
|
|
67
|
+
- No need to write raw SQL for common queries
|
|
68
|
+
- Clear and readable query configuration
|
|
69
|
+
- Consistent API across simple and advanced queries
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Improvements
|
|
74
|
+
|
|
75
|
+
### customRequest function
|
|
76
|
+
|
|
77
|
+
- customRequest provides all the information instead of taking the first element of the request.
|
|
78
|
+
|
|
79
|
+
### Model Usage
|
|
80
|
+
|
|
81
|
+
- findAll() is now instance-based instead of static for better flexibility and dependency injection.
|
|
82
|
+
- Enables multiple database connections and improved testability.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Performance Optimization
|
|
87
|
+
|
|
88
|
+
- Removed unnecessary iteration and sorting logic in query building.
|
|
89
|
+
- Query generation now runs in constant time structure (no scaling overhead with new features).
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### Code Maintainability
|
|
94
|
+
|
|
95
|
+
- Cleaner separation between:
|
|
96
|
+
- field building
|
|
97
|
+
- query parts
|
|
98
|
+
- execution
|
|
99
|
+
|
|
100
|
+
- Easier to extend without modifying core logic.
|
|
101
|
+
|
|
102
|
+
## Migration from v1.4.x
|
|
103
|
+
|
|
104
|
+
### Main changes
|
|
105
|
+
|
|
106
|
+
1. Switch to instance-based model usage
|
|
107
|
+
2. Update findAll calls to use the new select format
|
|
108
|
+
3. Adapt queries using aggregation to the new field builder system
|
|
109
|
+
|
|
110
|
+
### Recommendation
|
|
111
|
+
|
|
112
|
+
- Replace raw SQL queries with the new findAll configuration when possible
|
|
113
|
+
- Review existing analytics queries to leverage built-in aggregation support
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
See the updated documentation in:
|
|
118
|
+
|
|
119
|
+
- [README.md](../README.md)
|
|
120
|
+
|
|
121
|
+
## Useful Links
|
|
122
|
+
|
|
123
|
+
- GitHub Repository: <https://github.com/lagie-marin/sql-connector>
|
|
124
|
+
|
|
125
|
+
- npm Package: <https://www.npmjs.com/package/@mlagie/sql-connector>
|
|
126
|
+
|
|
127
|
+
- Issues: <https://github.com/lagie-marin/sql-connector/issues>
|
package/src/models/Model.js
CHANGED
|
@@ -7,6 +7,8 @@ const fs = require('fs');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const glob = require('glob');
|
|
9
9
|
const { ModelInstance } = require("./ModelInstance");
|
|
10
|
+
const { buildSelect, buildQueryParts } = require("../utils/buildQuery");
|
|
11
|
+
const util = require("util");
|
|
10
12
|
|
|
11
13
|
function getFieldType(field) {
|
|
12
14
|
if (typeof field === "object") {
|
|
@@ -161,7 +163,7 @@ class Model {
|
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
/**
|
|
164
|
-
*
|
|
166
|
+
* Synchronizes all tables with their JS schemas (creation + adding missing columns).
|
|
165
167
|
* @returns {Promise<void>}
|
|
166
168
|
*/
|
|
167
169
|
static async syncAllTables({ dangerousSync = false } = {}) {
|
|
@@ -496,6 +498,18 @@ class Model {
|
|
|
496
498
|
return `CREATE TABLE IF NOT EXISTS ${this.name} (${columns.join(', ')}${foreignKey.length > 0 ? ", " + foreignKey.join(', ') : ""}) ENGINE=InnoDB`;
|
|
497
499
|
}
|
|
498
500
|
|
|
501
|
+
getRecordData() {
|
|
502
|
+
return Array.isArray(this.data) ? this.data[0] ?? this.data : this.data;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
toJSON() {
|
|
506
|
+
return this.getRecordData()?.data;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
[util.inspect.custom]() {
|
|
510
|
+
return this.getRecordData();
|
|
511
|
+
}
|
|
512
|
+
|
|
499
513
|
/**
|
|
500
514
|
* Saves data to the database table.
|
|
501
515
|
* @param {Object} data The data to insert into the table.
|
|
@@ -508,7 +522,7 @@ class Model {
|
|
|
508
522
|
|
|
509
523
|
try {
|
|
510
524
|
const result = await getConnexion().promise().query(sql_request);
|
|
511
|
-
return result;
|
|
525
|
+
return result[0];
|
|
512
526
|
} catch (err) {
|
|
513
527
|
error(`Error inserting data into ${this.name}: ${err}`);
|
|
514
528
|
throw err;
|
|
@@ -516,109 +530,44 @@ class Model {
|
|
|
516
530
|
}
|
|
517
531
|
|
|
518
532
|
/**
|
|
519
|
-
*
|
|
520
|
-
* @param {Object} [options] -
|
|
521
|
-
* @param {string[]} [options.
|
|
522
|
-
* @param {Object} [options.where] -
|
|
533
|
+
* Retrieves multiple entries from the table.
|
|
534
|
+
* @param {Object} [options] - Query options (attributes, where, order, limit).
|
|
535
|
+
* @param {string[]} [options.select] - Champs à retourner.Champs à retourner.
|
|
536
|
+
* @param {Object} [options.where] - Filters (key/value).
|
|
523
537
|
* @param {Array} [options.order] - Ex: [['points', 'DESC']]
|
|
524
538
|
* @param {number} [options.limit] - Limite de résultats.
|
|
525
539
|
* @returns {Promise<Array<ModelInstance>>}
|
|
526
540
|
*/
|
|
527
|
-
async
|
|
528
|
-
const {
|
|
529
|
-
|
|
530
|
-
where = undefined,
|
|
531
|
-
order = undefined,
|
|
532
|
-
limit = undefined
|
|
533
|
-
} = options;
|
|
534
|
-
|
|
535
|
-
let sql_request = `SELECT ${attributes.join(', ')} FROM ${this.name}`;
|
|
536
|
-
if (where) {
|
|
537
|
-
sql_request += ` WHERE ${generateCondition(formatObject(where))}`;
|
|
538
|
-
}
|
|
539
|
-
if (order && Array.isArray(order) && order.length > 0) {
|
|
540
|
-
const orderStr = order.map(([col, dir]) => `${col} ${dir}`).join(', ');
|
|
541
|
-
sql_request += ` ORDER BY ${orderStr}`;
|
|
542
|
-
}
|
|
543
|
-
if (limit) {
|
|
544
|
-
sql_request += ` LIMIT ${limit}`;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return new Promise((resolve, reject) => {
|
|
548
|
-
getConnexion().promise().query(sql_request).then(([rows]) => {
|
|
549
|
-
const instances = rows.map(row => new ModelInstance(this.name, row, this.schema));
|
|
550
|
-
resolve(instances);
|
|
551
|
-
}).catch((err) => {
|
|
552
|
-
error(`Error executing findAll: ${err}`);
|
|
553
|
-
resolve([]);
|
|
554
|
-
});
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Finds a unique entry in the database table based on the filter provided.
|
|
560
|
-
* @param {Object} filter An object containing the key-value pairs to use to generate the search condition.
|
|
561
|
-
* @param {string[]} [fields=["*"]] An array of field names to return in the result.
|
|
562
|
-
* @returns {Promise<ModelInstance|number>} A promise that resolves to a ModelInstance if an entry is found, otherwise 0.
|
|
563
|
-
*/
|
|
564
|
-
async findOne(filter, fields = ["*"]) {
|
|
565
|
-
const sql_request = `SELECT ${fields.join(", ")} FROM ${this.name} WHERE ${generateCondition(formatObject(filter))}`;
|
|
541
|
+
async find(options = {}) {
|
|
542
|
+
const { select } = options;
|
|
543
|
+
const query = `SELECT ${buildSelect(select)} FROM ${this.name} ${buildQueryParts(options)}`;
|
|
566
544
|
|
|
567
|
-
return new Promise((resolve, reject) => {
|
|
568
|
-
getConnexion().promise().query(
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}).catch((err) => {
|
|
572
|
-
error(`Error executing query: ${err}`);
|
|
573
|
-
return resolve(0);
|
|
574
|
-
});
|
|
575
|
-
});
|
|
576
|
-
}
|
|
545
|
+
return new Promise(async (resolve, reject) => {
|
|
546
|
+
await getConnexion().promise().query(query).then((result) => {
|
|
547
|
+
// Le driver mysql2 renvoie [rows, fields], on isole le tableau de lignes 'rows'
|
|
548
|
+
const rows = result && Array.isArray(result) ? result[0] : result;
|
|
577
549
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
*
|
|
581
|
-
* @async
|
|
582
|
-
* @param {Object} filter - The filter criteria for the query. Should be an object where keys are column names and values are the values to filter by.
|
|
583
|
-
* @param {Array<string>} [fields=["*"]] - The fields to select in the query. Defaults to selecting all fields.
|
|
584
|
-
* @returns {Promise<ModelInstance|number>} - A promise that resolves to a `ModelInstance` if a record is found, or `0` if no records match the filter.
|
|
585
|
-
*
|
|
586
|
-
* @example
|
|
587
|
-
* // Example usage:
|
|
588
|
-
* const filter = { id: 1 };
|
|
589
|
-
* const fields = ["id", "name"];
|
|
590
|
-
* MyTable.find(filter, fields).then((result) => {
|
|
591
|
-
* if (result === 0) {
|
|
592
|
-
* console.log("No records found.");
|
|
593
|
-
* } else {
|
|
594
|
-
* console.log("Record found:", result);
|
|
595
|
-
* }
|
|
596
|
-
* }).catch((err) => {
|
|
597
|
-
* console.error("Error:", err);
|
|
598
|
-
* });
|
|
599
|
-
*/
|
|
600
|
-
async find(filter, fields = ["*"]) {
|
|
601
|
-
const sql_request = `SELECT ${fields.join(", ")} FROM ${this.name} WHERE ${generateCondition(formatObject(filter))}`;
|
|
550
|
+
// S'il n'y a aucun résultat, on renvoie un tableau vide [] (et pas 0, c'est plus propre pour faire des .length)
|
|
551
|
+
if (!rows || rows.length === 0) return resolve([]);
|
|
602
552
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (rows.length == 0) return resolve(0);
|
|
553
|
+
// 🚀 LE DÉCALAGE : On transforme chaque ligne brute en une ModelInstance unique
|
|
554
|
+
const instances = rows.map(row => new ModelInstance(this.name, row, this.schema));
|
|
606
555
|
|
|
607
|
-
resolve(
|
|
556
|
+
resolve(instances);
|
|
608
557
|
}).catch((err) => {
|
|
609
|
-
error(`Error executing query: ${err}`);
|
|
610
|
-
|
|
558
|
+
error(`Error executing query find: ${err}`);
|
|
559
|
+
reject(err);
|
|
611
560
|
});
|
|
612
561
|
});
|
|
613
562
|
}
|
|
614
563
|
|
|
615
564
|
/**
|
|
616
|
-
*
|
|
565
|
+
* Counts the number of records matching the given filter.
|
|
617
566
|
* @param {Object} filter The filter criteria for the query. Should be an object where keys are column names and values are the values to filter by.
|
|
618
567
|
* @returns {Promise<ModelInstance|number>} - A promise that resolves to a `ModelInstance` if a record is found, or `0` if no records match the filter.
|
|
619
568
|
*/
|
|
620
569
|
async count(filter) {
|
|
621
|
-
return this.customRequest(`SELECT COUNT(*) as count FROM ${this.name} ${filter != undefined ? `WHERE ${generateCondition(formatObject(filter))}` : ""}
|
|
570
|
+
return this.customRequest(`SELECT COUNT(*) as count FROM ${this.name} ${filter != undefined ? `WHERE ${generateCondition(formatObject(filter))}` : ""}`, "count");
|
|
622
571
|
}
|
|
623
572
|
|
|
624
573
|
/**
|
|
@@ -627,28 +576,26 @@ class Model {
|
|
|
627
576
|
* @returns {Promise<void>} A promise that resolves when the query is executed.
|
|
628
577
|
* @throws {Error} Throws an error if query execution fails.
|
|
629
578
|
*/
|
|
630
|
-
async customRequest(custom) {
|
|
579
|
+
async customRequest(custom, custom_err_name = "") {
|
|
631
580
|
return new Promise(async (resolve, reject) => {
|
|
632
581
|
await getConnexion().promise().query(custom).then((rows) => {
|
|
633
582
|
if (rows.length == 0) return resolve(0);
|
|
634
583
|
|
|
635
|
-
resolve(new ModelInstance(this.name, rows
|
|
584
|
+
resolve(new ModelInstance(this.name, rows, this.schema));
|
|
636
585
|
}).catch((err) => {
|
|
637
|
-
error(`Error executing query: ${err}`);
|
|
586
|
+
error(`Error executing query ${custom_err_name}: ${err}`);
|
|
638
587
|
return;
|
|
639
588
|
});
|
|
640
589
|
})
|
|
641
590
|
}
|
|
642
591
|
|
|
643
592
|
/**
|
|
644
|
-
*
|
|
593
|
+
* Deletes an entry from the SQL table that matches the provided filter.
|
|
645
594
|
*
|
|
646
|
-
* @
|
|
647
|
-
* @
|
|
648
|
-
*
|
|
649
|
-
* @
|
|
650
|
-
* ou à une instance de ModelInstance représentant la ligne supprimée.
|
|
651
|
-
* @throws {Error} Lance une erreur si la requête SQL échoue.
|
|
595
|
+
* @param {Object} filter An object representing the filter conditions for deletion.
|
|
596
|
+
* @returns {Promise<number>} A promise that resolves to 0 if no rows were deleted,
|
|
597
|
+
* or to a ModelInstance representing the deleted row.
|
|
598
|
+
* @throws {Error} Throws an error if the SQL query fails.
|
|
652
599
|
*/
|
|
653
600
|
async delete(filter) {
|
|
654
601
|
const sql_request = `DELETE FROM ${this.name} WHERE ${generateCondition(formatObject(filter))}`;
|
|
@@ -658,7 +605,7 @@ class Model {
|
|
|
658
605
|
|
|
659
606
|
return resolve(1);
|
|
660
607
|
}).catch((err) => {
|
|
661
|
-
error(`Error executing query: ${err}`);
|
|
608
|
+
error(`Error executing query delete: ${err}`);
|
|
662
609
|
return 0;
|
|
663
610
|
});
|
|
664
611
|
});
|
|
@@ -681,7 +628,7 @@ class Model {
|
|
|
681
628
|
getConnexion().promise().query(sql_request).then((rows) => {
|
|
682
629
|
console.log(rows);
|
|
683
630
|
}).catch((err) => {
|
|
684
|
-
error(`Error executing query: ${err}`);
|
|
631
|
+
error(`Error executing query drop: ${err}`);
|
|
685
632
|
return;
|
|
686
633
|
});
|
|
687
634
|
})
|
|
@@ -715,7 +662,7 @@ class Model {
|
|
|
715
662
|
if (rows[0][0]['COUNT(*)'] == 0) return resolve(uuid);
|
|
716
663
|
resolve(null);
|
|
717
664
|
}).catch((err) => {
|
|
718
|
-
error(`Error executing query: ${err}`);
|
|
665
|
+
error(`Error executing query gen_uuid: ${err}`);
|
|
719
666
|
return null;
|
|
720
667
|
})
|
|
721
668
|
})
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
const { error
|
|
1
|
+
const { error } = require("@mlagie/logger");
|
|
2
2
|
const { getConnexion } = require("../db/connexion");
|
|
3
3
|
const formatObject = require("../utils/formatObject");
|
|
4
4
|
const generateCondition = require("../utils/generateCondition");
|
|
5
5
|
const util = require("util");
|
|
6
|
+
const { serveur } = require("@mlagie/logger");
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Represents an instance of a database model.
|
|
@@ -36,6 +37,37 @@ class ModelInstance {
|
|
|
36
37
|
enumerable: false
|
|
37
38
|
}
|
|
38
39
|
});
|
|
40
|
+
const row = this._getTargetRow();
|
|
41
|
+
if (row && typeof row === 'object') {
|
|
42
|
+
Object.keys(row).forEach(key => {
|
|
43
|
+
// On lie dynamiquement la clé de l'instance directement à la case mémoire de 'row'
|
|
44
|
+
Object.defineProperty(this, key, {
|
|
45
|
+
get: () => {
|
|
46
|
+
const val = row[key];
|
|
47
|
+
// Auto-parse propre du JSON si la colonne MySQL stocke une String JSON
|
|
48
|
+
if (typeof val === 'string' && val.trim().startsWith('{') && val.trim().endsWith('}')) {
|
|
49
|
+
try { return JSON.parse(val); } catch (e) { return val; }
|
|
50
|
+
}
|
|
51
|
+
return val;
|
|
52
|
+
},
|
|
53
|
+
set: (newVal) => {
|
|
54
|
+
// L'écriture modifie directement la référence d'origine dans 'row'
|
|
55
|
+
row[key] = newVal;
|
|
56
|
+
},
|
|
57
|
+
enumerable: true, // Permet à JSON.stringify et console.log de voir la propriété
|
|
58
|
+
configurable: true
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extrait la ligne de données réelle en gérant la structure du driver de BDD [rows, fields]
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
_getTargetRow() {
|
|
69
|
+
const rows = Array.isArray(this.data) && Array.isArray(this.data[0]) ? this.data[0] : this.data;
|
|
70
|
+
return Array.isArray(rows) ? rows[0] : rows;
|
|
39
71
|
}
|
|
40
72
|
|
|
41
73
|
getRecordData() {
|
|
@@ -60,10 +92,24 @@ class ModelInstance {
|
|
|
60
92
|
async updateOne(model) {
|
|
61
93
|
const setClause = generateCondition(formatObject(model), true);
|
|
62
94
|
|
|
63
|
-
// prefer primary key(s) in WHERE to avoid mismatches on nullable/text fields
|
|
64
95
|
let whereClause;
|
|
65
96
|
try {
|
|
66
|
-
|
|
97
|
+
// 1. On récupère le tableau de données
|
|
98
|
+
const recordsArray = this.getRecordData();
|
|
99
|
+
|
|
100
|
+
// 2. On extrait le premier élément (la ligne actuelle)
|
|
101
|
+
let rawRec = Array.isArray(recordsArray) ? recordsArray[0] : recordsArray;
|
|
102
|
+
|
|
103
|
+
// 3. Si cet élément est une chaîne JSON, on le transforme en vrai objet JS
|
|
104
|
+
if (typeof rawRec === 'string') {
|
|
105
|
+
try {
|
|
106
|
+
rawRec = JSON.parse(rawRec);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// Pas du JSON valide, on garde la string d'origine
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const rec = rawRec;
|
|
112
|
+
|
|
67
113
|
const schemaDict = this.schema && this.schema.schemaDict ? this.schema.schemaDict : null;
|
|
68
114
|
if (schemaDict) {
|
|
69
115
|
const pkKeys = Object.entries(schemaDict).filter(([k, v]) => v && v.primary_key === true).map(([k]) => k);
|
|
@@ -77,15 +123,17 @@ class ModelInstance {
|
|
|
77
123
|
}
|
|
78
124
|
if (!whereClause) whereClause = generateCondition(formatObject(rec), false, this.schema);
|
|
79
125
|
} catch (e) {
|
|
80
|
-
|
|
126
|
+
// Fallback de sécurité au cas où
|
|
127
|
+
let fallbackRec = this.getRecordData();
|
|
128
|
+
if (Array.isArray(fallbackRec)) fallbackRec = fallbackRec[0];
|
|
129
|
+
if (typeof fallbackRec === 'string') { try { fallbackRec = JSON.parse(fallbackRec); } catch (e) { } }
|
|
130
|
+
whereClause = generateCondition(formatObject(fallbackRec), false, this.schema);
|
|
81
131
|
}
|
|
82
|
-
const sql_request = `UPDATE ${this.name} SET ${setClause} WHERE ${whereClause}`;
|
|
83
132
|
|
|
84
|
-
|
|
85
|
-
try { logs(`ModelInstance.updateOne SQL -> ${sql_request}`); } catch (e) {}
|
|
133
|
+
const sql_request = `UPDATE ${this.name} SET ${setClause} WHERE ${whereClause}`;
|
|
86
134
|
|
|
87
135
|
const [result] = await getConnexion().promise().query(sql_request).catch((err) => {
|
|
88
|
-
error(`Error executing query: ${err}`);
|
|
136
|
+
error(`Error executing query updateOne: ${err}`);
|
|
89
137
|
throw err;
|
|
90
138
|
});
|
|
91
139
|
|
|
@@ -120,7 +168,7 @@ class ModelInstance {
|
|
|
120
168
|
|
|
121
169
|
resolve(1);
|
|
122
170
|
}).catch((err) => {
|
|
123
|
-
error(`Error executing query: ${err}`);
|
|
171
|
+
error(`Error executing query delete: ${err}`);
|
|
124
172
|
return 0;
|
|
125
173
|
});
|
|
126
174
|
});
|
|
@@ -140,7 +188,7 @@ class ModelInstance {
|
|
|
140
188
|
|
|
141
189
|
resolve(1);
|
|
142
190
|
}).catch((err) => {
|
|
143
|
-
error(`Error executing query: ${err}`);
|
|
191
|
+
error(`Error executing query deleteOne: ${err}`);
|
|
144
192
|
return 0;
|
|
145
193
|
});
|
|
146
194
|
});
|
|
@@ -157,7 +205,7 @@ class ModelInstance {
|
|
|
157
205
|
await getConnexion().promise().query(custom).then((rows) => {
|
|
158
206
|
if (rows.length == 0) return resolve(0);
|
|
159
207
|
|
|
160
|
-
resolve(new ModelInstance(this.name, rows
|
|
208
|
+
resolve(new ModelInstance(this.name, rows, this.schema)).data;
|
|
161
209
|
}).catch((err) => {
|
|
162
210
|
error(`Error executing query: ${err}`);
|
|
163
211
|
return;
|
|
@@ -166,4 +214,4 @@ class ModelInstance {
|
|
|
166
214
|
}
|
|
167
215
|
}
|
|
168
216
|
|
|
169
|
-
module.exports = {ModelInstance}
|
|
217
|
+
module.exports = { ModelInstance }
|