@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/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
- * Crée toutes les tables dans l'ordre correct en fonction des foreign keys.
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({ dangerousSync = false } = {}): Promise<void>;
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.attributes] - Champs à retourner.
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
- findAll(options?: {
119
- attributes?: string[];
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.4.8",
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.4"
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>
@@ -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
- * Synchronise toutes les tables avec leur schéma JS (création + ajout des colonnes manquantes).
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
- * Récupère plusieurs entrées de la table.
520
- * @param {Object} [options] - Options de requête (attributs, where, order, limit).
521
- * @param {string[]} [options.attributes] - Champs à retourner.
522
- * @param {Object} [options.where] - Filtres (clé/valeur).
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 findAll(options = {}) {
528
- const {
529
- attributes = ['*'],
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(sql_request).then(([rows]) => {
569
- if (!rows || rows.length === 0) return resolve(0);
570
- resolve(new ModelInstance(this.name, rows[0], this.schema));
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
- * Finds a record in the database based on the provided filter.
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
- return new Promise((resolve, reject) => {
604
- getConnexion().promise().query(sql_request).then((rows) => {
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(new ModelInstance(this.name, rows[0], this.schema));
556
+ resolve(instances);
608
557
  }).catch((err) => {
609
- error(`Error executing query: ${err}`);
610
- return;
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[0], this.schema));
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
- * Supprime une entrée de la table SQL correspondant au filtre fourni.
593
+ * Deletes an entry from the SQL table that matches the provided filter.
645
594
  *
646
- * @async
647
- * @function delete
648
- * @param {Object} filter - Un objet représentant les conditions de filtre pour la suppression.
649
- * @returns {Promise<number>} Une promesse qui se résout à 0 si aucune ligne n'a été supprimée,
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, logs } = require("@mlagie/logger");
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
- const rec = this.getRecordData();
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
- whereClause = generateCondition(formatObject(this.getRecordData()), false, this.schema);
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
- // log SQL for debugging why update may not match
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[0], this.schema));
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 }