@mlagie/sql-connector 1.4.9 → 2.0.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/.github/workflows/publish.yml +47 -0
- package/README.md +260 -48
- package/docs/fr/README.md +204 -24
- package/eslint.config.mjs +12 -0
- package/index.d.ts +15 -45
- package/package.json +7 -3
- package/releases/1.5.0.md +127 -0
- package/releases/2.0.0.md +32 -0
- package/src/models/Model.js +110 -368
- package/src/models/ModelInstance.js +52 -10
- package/src/utils/buildQuery.js +72 -0
- package/src/utils/formatObject.js +6 -4
- package/src/utils/generateCondition.js +9 -6
- package/src/utils/security/safe.js +35 -0
package/docs/fr/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Documentation du module sql-connector
|
|
2
2
|
|
|
3
|
+
    
|
|
4
|
+

|
|
5
|
+
|
|
3
6
|
[English](../../README.md) | Français
|
|
4
7
|
|
|
5
8
|
Le module sql-connector permet de gérer des connexions MySQL, de définir des schémas, de synchroniser automatiquement des tables et d'exposer des modèles pour manipuler les données simplement.
|
|
@@ -36,18 +39,18 @@ await logout();
|
|
|
36
39
|
|
|
37
40
|
`Schema` décrit la structure d'une table. Chaque champ peut utiliser les propriétés suivantes.
|
|
38
41
|
|
|
39
|
-
| Propriété
|
|
40
|
-
|
|
41
|
-
| type
|
|
42
|
-
| length
|
|
43
|
-
| required
|
|
44
|
-
| default
|
|
45
|
-
| unique
|
|
46
|
-
| auto_increment | `boolean`
|
|
47
|
-
| foreignKey
|
|
48
|
-
| enum
|
|
49
|
-
| primary_key
|
|
50
|
-
| customize
|
|
42
|
+
| Propriété | Type | Description |
|
|
43
|
+
|----------------|----------------------------------|-----------------------------|
|
|
44
|
+
| type | `SqlType` ou `{ name: SqlType }` | Type SQL du champ |
|
|
45
|
+
| length | `number` | Longueur maximale |
|
|
46
|
+
| required | `boolean` | Champ obligatoire |
|
|
47
|
+
| default | `any` | Valeur par défaut |
|
|
48
|
+
| unique | `boolean` | Valeur unique |
|
|
49
|
+
| auto_increment | `boolean` | Auto-incrément |
|
|
50
|
+
| foreignKey | `string` | Référence de clé étrangère |
|
|
51
|
+
| enum | `string[]` | Liste de valeurs autorisées |
|
|
52
|
+
| primary_key | `boolean` | Clé primaire |
|
|
53
|
+
| customize | `string` | Options SQL additionnelles |
|
|
51
54
|
|
|
52
55
|
```javascript
|
|
53
56
|
const userSchema = new Schema({
|
|
@@ -66,7 +69,22 @@ const userSchema = new Schema({
|
|
|
66
69
|
type: String,
|
|
67
70
|
enum: ['active', 'inactive', 'pending'],
|
|
68
71
|
default: 'pending'
|
|
69
|
-
}
|
|
72
|
+
},
|
|
73
|
+
uuid: {
|
|
74
|
+
type: String,
|
|
75
|
+
required: true,
|
|
76
|
+
primary_key: true,
|
|
77
|
+
length: 36
|
|
78
|
+
},
|
|
79
|
+
my_uuid: {
|
|
80
|
+
type: String,
|
|
81
|
+
required: true,
|
|
82
|
+
length: 36
|
|
83
|
+
},
|
|
84
|
+
created_at: {
|
|
85
|
+
type: Date,
|
|
86
|
+
default: sqlTypeMap.CurrentTimestamp
|
|
87
|
+
}
|
|
70
88
|
});
|
|
71
89
|
```
|
|
72
90
|
|
|
@@ -75,13 +93,9 @@ const userSchema = new Schema({
|
|
|
75
93
|
`Model.syncAllTables()` compare les schémas JS avec la base et applique uniquement les différences utiles.
|
|
76
94
|
|
|
77
95
|
- Ajout de colonne: automatique.
|
|
78
|
-
- Suppression de colonne: uniquement avec `dangerousSync: true`.
|
|
79
|
-
- Renommage de colonne: possible avec `oldName`.
|
|
80
|
-
- Tables orphelines: sauvegarde avant suppression dans un fichier `backup_*.sql`.
|
|
81
96
|
|
|
82
97
|
```javascript
|
|
83
98
|
await Model.syncAllTables();
|
|
84
|
-
await Model.syncAllTables({ dangerousSync: true });
|
|
85
99
|
```
|
|
86
100
|
|
|
87
101
|
Point important: ne combinez pas `primary_key: true` et `unique: true` sur le même champ. Une clé primaire est déjà unique et non nulle.
|
|
@@ -93,24 +107,190 @@ Point important: ne combinez pas `primary_key: true` et `unique: true` sur le m
|
|
|
93
107
|
Méthodes principales:
|
|
94
108
|
|
|
95
109
|
- `save(data)` pour insérer une ligne
|
|
96
|
-
- `findOne(filter, fields)` pour récupérer une seule entrée
|
|
97
110
|
- `find(filter, fields)` pour récupérer plusieurs entrées
|
|
98
|
-
- `findAll(options)` pour les recherches avancées
|
|
99
111
|
- `count(filter)` pour compter les lignes
|
|
100
112
|
- `customRequest(custom)` pour exécuter une requête SQL personnalisée
|
|
101
113
|
- `delete(filter)` pour supprimer une entrée
|
|
102
114
|
- `dropTable()` pour supprimer la table
|
|
103
115
|
- `generate_uuid()` pour générer un UUID unique
|
|
104
|
-
- `Model.createAllTables()` pour créer toutes les tables dans le bon ordre
|
|
105
116
|
|
|
106
117
|
```javascript
|
|
107
118
|
const userModel = new Model('users', userSchema);
|
|
108
119
|
|
|
109
|
-
await Model.
|
|
120
|
+
await Model.syncAllTables();
|
|
110
121
|
await userModel.save({ email: 'user@example.com', status: 'active' });
|
|
111
122
|
|
|
112
|
-
const user = await userModel.
|
|
113
|
-
await
|
|
123
|
+
const user = await userModel.find({ where: { email: 'user@example.com' }});
|
|
124
|
+
await user[0].deleteOne();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Fonction save
|
|
128
|
+
|
|
129
|
+
Enregistre les données dans la table de la base de données.
|
|
130
|
+
|
|
131
|
+
- **Parameters** `data` *(Object)* - Les données à insérer dans la table.
|
|
132
|
+
- **Returns** `Promise<Object>` - Une promesse avec le résultat de l'insertion.
|
|
133
|
+
- **Throws** `Error` - Lève une erreur si l'insertion échoue.
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const User = require("user");
|
|
137
|
+
|
|
138
|
+
async function createUser(email, stat) {
|
|
139
|
+
if (!email || !stat) {
|
|
140
|
+
console.error("Email & stat is required");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await User.save({ email: email, status: stat });
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Fonction find
|
|
149
|
+
|
|
150
|
+
Récupère des entrées de la table.
|
|
151
|
+
|
|
152
|
+
- **Parameters** `options` *(Object)* - Options de requête
|
|
153
|
+
- **Parameters** `options.select` *(string[])* - Champs à renvoyer.
|
|
154
|
+
- **Parameters** `options.where` *(Object)* - Filtre (key/value).
|
|
155
|
+
- **Parameters** `options.order` *(Array)* - Ex: [['points', 'DESC']]
|
|
156
|
+
- **Parameters** `options.limit` *(number)* - Limite de résultats.
|
|
157
|
+
- **Returns** `Promise<Array<ModelInstance>>`
|
|
158
|
+
|
|
159
|
+
### Options de find
|
|
160
|
+
|
|
161
|
+
| Option | Type | Description | Example |
|
|
162
|
+
|------------|-----------------|----------------------------------------------------------|----------------------------------------------|
|
|
163
|
+
| `select` | Array | Champs ou transformations à récupérer | `['date_day']` |
|
|
164
|
+
| `where` | Object / String | Conditions de filtrage | `{ project_id: 1 }` |
|
|
165
|
+
| `groupBy` | Array | Champs utilisés pour regrouper les résultats | `['period']` |
|
|
166
|
+
| `orderBy` | Array | Règles de tri | `[{ field: 'date_day', direction: 'DESC' }]` |
|
|
167
|
+
| `having` | String | Clause HAVING pour les requêtes agrégées | `'SUM(total_runs) > 100'` |
|
|
168
|
+
| `limit` | Number | Limite le nombre de résultats | `100` |
|
|
169
|
+
|
|
170
|
+
## Exemple find
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const User = require("user");
|
|
174
|
+
|
|
175
|
+
await User.find({
|
|
176
|
+
select: [
|
|
177
|
+
{ dateFormat: ['date_day', '%Y-%m'], as: 'period' },
|
|
178
|
+
{ sum: 'error' },
|
|
179
|
+
{ sum: 'reload' },
|
|
180
|
+
],
|
|
181
|
+
groupBy: ['period'],
|
|
182
|
+
orderBy: [{ field: 'period', direction: 'ASC' }],
|
|
183
|
+
limit: 10
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
const User = require("user");
|
|
189
|
+
|
|
190
|
+
await User.find({
|
|
191
|
+
select: [
|
|
192
|
+
"email"
|
|
193
|
+
],
|
|
194
|
+
where: {
|
|
195
|
+
id: 1
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Fonction count
|
|
201
|
+
|
|
202
|
+
Compte le nombre d'enregistrements correspondant au filtre donné.
|
|
203
|
+
|
|
204
|
+
- **Parameters** `filter` *(Object)* Les critères de filtrage de la requête. Il doit s'agir d'un objet dont les clés sont les noms des colonnes et les valeurs sont les valeurs de filtrage.
|
|
205
|
+
- **Returns** `Promise<ModelInstance|number>` - Une promesse qui se résout en une instance `ModelInstance` si un enregistrement est trouvé, ou en `0` si aucun enregistrement ne correspond au filtre.
|
|
206
|
+
|
|
207
|
+
## Exemple count
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
const User = require("user");
|
|
211
|
+
|
|
212
|
+
await User.count({
|
|
213
|
+
id: id
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Fonction customRequest
|
|
218
|
+
|
|
219
|
+
La fonction customRequest vous permet d'exécuter des requêtes SQL non prises en charge par sql-connector ; cela peut concerner des requêtes utilisant des mots-clés qui ne sont pas encore implémentés.
|
|
220
|
+
|
|
221
|
+
- **Parameters** `custom` *(string)* La requête SQL personnalisée à exécuter.
|
|
222
|
+
- **Returns** `Promise<void>` Valeur retournée
|
|
223
|
+
- **Throws** `Error` Lève une erreur si l'exécution de la requête échoue.
|
|
224
|
+
|
|
225
|
+
## Exemple customRequest
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
const User = require("user");
|
|
229
|
+
|
|
230
|
+
await User.customRequest("SELECT id, email, status
|
|
231
|
+
FROM users
|
|
232
|
+
WHERE status IN ('active', 'pending')
|
|
233
|
+
AND email LIKE '%gmail.com';")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Fonction delete
|
|
237
|
+
|
|
238
|
+
Supprime de la table SQL une entrée correspondant au filtre fourni.
|
|
239
|
+
|
|
240
|
+
- **Parameters** `filter` *(Object)* Un objet représentant les conditions de filtrage pour la suppression.
|
|
241
|
+
- **Returns** `Promise<number>` Une promesse qui se résout à 0 si aucune ligne n'a été supprimée, ou à une instance de modèle représentant la ligne supprimée.
|
|
242
|
+
- **Throws** `Error` Une promesse qui se résout à 0 si aucune ligne n'a été supprimée, ou à une instance de modèle représentant la ligne supprimée.
|
|
243
|
+
|
|
244
|
+
## Exemple delete
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
const User = require("user");
|
|
248
|
+
|
|
249
|
+
await User.delete({
|
|
250
|
+
email: my@gmail.com
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Fonction dropTable
|
|
255
|
+
|
|
256
|
+
Supprime de manière asynchrone une table si elle existe dans la base de données.
|
|
257
|
+
|
|
258
|
+
Cette fonction construit une requête SQL pour supprimer la table dont le nom est spécifié par la propriété `this.name`. Elle exécute ensuite la requête en utilisant une approche basée sur les promesses.
|
|
259
|
+
Si la requête aboutit, le résultat est consigné dans la console.
|
|
260
|
+
|
|
261
|
+
En cas d'erreur lors de l'exécution de la requête, un message d'erreur est consigné.
|
|
262
|
+
|
|
263
|
+
- **Returns** `Promise<void>` Une promesse qui se résout une fois l'exécution de la requête terminée.
|
|
264
|
+
|
|
265
|
+
## Example dropTable
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
const User = require("user");
|
|
269
|
+
|
|
270
|
+
await User.dropTable();
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## generate_uuid function
|
|
274
|
+
|
|
275
|
+
Génère un UUID unique pour le modèle actuel.
|
|
276
|
+
|
|
277
|
+
Cette fonction génère un UUID à l'aide de la fonction `UUID()` de SQL_request et vérifie si cet UUID existe déjà dans la base de données pour le modèle actuel. Si l'UUID est unique, il est renvoyé.
|
|
278
|
+
|
|
279
|
+
Sinon, la fonction renvoie `null`.
|
|
280
|
+
|
|
281
|
+
- **Parameters** `string` var_uuid Par defaut il vaut uuid
|
|
282
|
+
- **Returns** `Promise<string|null>` Une promesse qui se résout en une chaîne UUID unique en cas de succès, ou en `null` si une erreur survient ou si l'UUID n'est pas unique.
|
|
283
|
+
- **Throws** `Error` S'il y a une erreur lors de l'exécution de la requête SQL_request.
|
|
284
|
+
|
|
285
|
+
## Example generate_uuid
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
const User = require("user");
|
|
289
|
+
|
|
290
|
+
const uuid = await User.generate_uuid();
|
|
291
|
+
const my_uuid = await User.generate_uuid("my_uuid");
|
|
292
|
+
|
|
293
|
+
await User.save{ email: "user@example.com", status: "active", uuid: uuid, my_uuid: my_uuid }
|
|
114
294
|
```
|
|
115
295
|
|
|
116
296
|
## Instances de modèle
|
|
@@ -155,4 +335,4 @@ module.exports = client => {
|
|
|
155
335
|
|
|
156
336
|
## Résumé
|
|
157
337
|
|
|
158
|
-
sql-connector fournit une couche simple pour connecter une base MySQL, décrire des schémas, synchroniser les tables et manipuler les données avec des modèles typés.
|
|
338
|
+
sql-connector fournit une couche simple pour connecter une base MySQL, décrire des schémas, synchroniser les tables et manipuler les données avec des modèles typés.
|
package/index.d.ts
CHANGED
|
@@ -95,10 +95,10 @@ 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
99
|
* @returns {Promise<void>}
|
|
100
100
|
*/
|
|
101
|
-
static syncAllTables(
|
|
101
|
+
static syncAllTables(): Promise<void>;
|
|
102
102
|
/**
|
|
103
103
|
* Saves data to the database table.
|
|
104
104
|
* @param {Object} data The data to insert into the table.
|
|
@@ -107,50 +107,20 @@ export class Model {
|
|
|
107
107
|
*/
|
|
108
108
|
save(data: Record<string, any>): Promise<any>;
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
111
|
-
* @param {Object} [options] -
|
|
112
|
-
* @param {string[]} [options.
|
|
113
|
-
* @param {Object} [options.where] -
|
|
114
|
-
* @param {Array} [options.order] -
|
|
115
|
-
* @param {number} [options.limit] -
|
|
110
|
+
* Retrieves multiple rows from the table.
|
|
111
|
+
* @param {Object} [options] - Query options (attributes, where, order, limit).
|
|
112
|
+
* @param {string[]} [options.select] - Fields to return.
|
|
113
|
+
* @param {Object} [options.where] - Filters (key/value).
|
|
114
|
+
* @param {Array} [options.order] - Example: [['points', 'DESC']]
|
|
115
|
+
* @param {number} [options.limit] - Result limit.
|
|
116
116
|
* @returns {Promise<Array<Object>>}
|
|
117
117
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
static find(options?: {
|
|
119
|
+
select?: string[];
|
|
120
120
|
where?: Record<string, any>;
|
|
121
121
|
order?: [string, string][];
|
|
122
122
|
limit?: number;
|
|
123
123
|
}): 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
124
|
/**
|
|
155
125
|
*
|
|
156
126
|
* @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.
|
|
@@ -165,14 +135,14 @@ export class Model {
|
|
|
165
135
|
*/
|
|
166
136
|
customRequest(custom: string): Promise<any>;
|
|
167
137
|
/**
|
|
168
|
-
*
|
|
138
|
+
* Deletes a record from the SQL table corresponding to the provided filter.
|
|
169
139
|
*
|
|
170
140
|
* @async
|
|
171
141
|
* @function delete
|
|
172
|
-
* @param {Object} filter -
|
|
173
|
-
* @returns {Promise<number>}
|
|
174
|
-
*
|
|
175
|
-
* @throws {Error}
|
|
142
|
+
* @param {Object} filter - An object representing the filter conditions for the deletion.
|
|
143
|
+
* @returns {Promise<number>} A promise that resolves to 0 if no rows were deleted,
|
|
144
|
+
* or an instance of ModelInstance representing the deleted row.
|
|
145
|
+
* @throws {Error} Throws an error if the SQL query fails.
|
|
176
146
|
*/
|
|
177
147
|
delete(filter: Record<string, any>): Promise<number | ModelInstance>;
|
|
178
148
|
/**
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlagie/sql-connector",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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": {
|
|
7
|
-
"
|
|
7
|
+
"security": "npx eslint . --max-warnings 0"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -37,6 +37,10 @@
|
|
|
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
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"eslint": "^10.6.0",
|
|
44
|
+
"eslint-plugin-security": "^4.0.1"
|
|
41
45
|
}
|
|
42
46
|
}
|
|
@@ -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>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# **Release v2.0.0 — Security Hardening & Prototype Pollution Prevention**
|
|
2
|
+
|
|
3
|
+
This release focuses heavily on `security improvements` across the ORM's core model management and data access layers. We have successfully mitigated potential security vulnerabilities regarding dynamic property access and file path operations, satisfying `eslint-plugin-security` analysis.
|
|
4
|
+
|
|
5
|
+
## **Key Changes & Security Fixes**
|
|
6
|
+
|
|
7
|
+
### **1. Prototype Pollution & Object Injection Prevention**
|
|
8
|
+
|
|
9
|
+
- **Problem**: Using dynamic bracket notations like obj[key] where key originates from user inputs or database schemas exposed the application to **Object Injection** and **Prototype Pollution** (allowing attackers to alter critical global properties via keys like `__proto__` or `constructor`).
|
|
10
|
+
|
|
11
|
+
- **Fix**: Introduced a centralized `safe.js` security utility leveraging `Reflect.get()` and `Reflect.set()` coupled with a strict property blacklist (`__proto__`, `constructor`, `prototype`).
|
|
12
|
+
- Migrated all dynamic model lookups and topological sorting dictionaries in `Model.js` to safe wrappers.
|
|
13
|
+
- Secured dynamic getters and setters mapped to data columns in `ModelInstance.js`.
|
|
14
|
+
|
|
15
|
+
### **2. Path Traversal & Safe Database Backups**
|
|
16
|
+
|
|
17
|
+
- **Problem**: Dynamically naming automated backup files (**.sql**) based on database table names without validation triggered `detect-non-literal-fs-filename` warnings, opening up potential `Path Traversal` vector concerns.
|
|
18
|
+
|
|
19
|
+
- **Fix**: Implemented string sanitization via regex (`/[^a-zA-Z0-9_]/g`) on table-driven variables prior to passing arguments to standard `fs` sync methods (`writeFileSync`, `readFileSync`, `unlinkSync`, `renameSync`).
|
|
20
|
+
- Fully resolved all local `fs` filename security alerts.
|
|
21
|
+
|
|
22
|
+
### **3. General Code Refactoring**
|
|
23
|
+
|
|
24
|
+
- Replaced native object literals `{}` with prototype-less structures `(Object.create(null))` for configuration mappings and dependency graph resolution arrays to prevent unexpected inheritance bugs.
|
|
25
|
+
|
|
26
|
+
## **Component Impact**
|
|
27
|
+
|
|
28
|
+
| File | Changes Made | Warning Cleared |
|
|
29
|
+
|-------------------------------|-------------------------------------------------------------------|-------------------------------------------------------------|
|
|
30
|
+
| `src/utils/security/safe.js` | Created utility with `Reflect` API + validation blacklist | None (Fully clean) |
|
|
31
|
+
| `src/models/Model.js` | Patched `syncAllTables`, topological sort, and `fs` operations | `detect-object-injection`, `detect-non-literal-fs-filename` |
|
|
32
|
+
| `src/models/ModelInstance.js` | Secured active schema columns maps inside constructor proxies | `detect-object-injection` |
|