@mlagie/sql-connector 3.0.3 → 3.1.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.
@@ -1,483 +1,727 @@
1
- # Documentation du module sql-connector
1
+ # @mlagie/sql-connector — Documentation française
2
2
 
3
- ![GitHub package.json version](https://img.shields.io/github/package-json/v/lagie-marin/sql-connector?color=#008000) ![NPM Downloads](https://img.shields.io/npm/d18m/%40mlagie%2Fsql-connector?color=#008000) ![NPM Downloads](https://img.shields.io/npm/dw/%40mlagie%2Fsql-connector?color=#008000) ![GitHub followers](https://img.shields.io/github/followers/lagie-marin?style=plastic&color=color%3D%23008000) ![GitHub repo size](https://img.shields.io/github/repo-size/lagie-marin/sql-connector?color=%green)
4
- ![GitHub last commit](https://img.shields.io/github/last-commit/lagie-marin/sql-connector)
3
+ [![npm version](https://img.shields.io/npm/v/%40mlagie%2Fsql-connector)](https://www.npmjs.com/package/@mlagie/sql-connector)
5
4
 
6
- [English](../../README.md) | Français
5
+ Couche SQL légère et orientée dialecte pour **MySQL** et **PostgreSQL**.
7
6
 
8
- Le module sql-connector permet de gérer des connexions MySQL et PostgreSQL, de définir des schémas, de synchroniser automatiquement des tables et d'exposer des modèles pour manipuler les données simplement.
7
+ `@mlagie/sql-connector` fournit :
9
8
 
10
- ## Importation
9
+ - gestion des connexions ;
10
+ - schémas déclaratifs ;
11
+ - types SQL et alias de types ;
12
+ - modèles CRUD ;
13
+ - conditions structurées `WHERE` / `HAVING` ;
14
+ - `IN`, `BETWEEN`, `LIKE`, `ILIKE`, `NULL`, `AND`, `OR`, `NOT` ;
15
+ - agrégations, groupements, tris et jointures ;
16
+ - gestion des dépendances de clés étrangères ;
17
+ - synchronisation des schémas ;
18
+ - planification et exécution des migrations ;
19
+ - checksum, fichiers de migration chiffrés et sauvegardes chiffrées optionnelles ;
20
+ - déclarations TypeScript.
11
21
 
12
- ```javascript
13
- const { Schema, connect, logout, Model, ModelInstance, client, sqlTypeMap } = require('sql-connector');
14
- ```
15
-
16
- ## Connexion à la base
17
-
18
- `connect(config, dialect)` ouvre une connexion à une base de données. MySQL est utilisé par défaut ; utilisez `"postgres"` pour PostgreSQL.
19
-
20
- ```javascript
21
- const config = {
22
- host: 'localhost',
23
- port: 3306,
24
- user: 'root',
25
- password: 'password',
26
- database: 'mydatabase'
27
- };
28
-
29
- await connect(config);
30
- ```
31
-
32
- ### PostgreSQL
22
+ > **SGBD supportés :** MySQL et PostgreSQL.
33
23
 
34
- PostgreSQL utilise le driver `pg` :
24
+ **Documentation :** [English](../../README.md) · Français
35
25
 
36
- ```javascript
37
- const postgresConfig = {
38
- host: 'localhost',
39
- port: 5432,
40
- user: 'postgres',
41
- password: 'password',
42
- database: 'mydatabase'
43
- };
44
-
45
- await connect(postgresConfig, 'postgres');
46
- ```
26
+ ---
47
27
 
48
- Les dialectes supportés sont `mysql` et `postgres`.
28
+ ## Sommaire
29
+
30
+ 1. [Installation](#installation)
31
+ 2. [Démarrage rapide](#démarrage-rapide)
32
+ 3. [Connexion](#connexion)
33
+ 4. [Schémas](#schémas)
34
+ - [Propriétés d'un champ](#propriétés-dun-champ)
35
+ - [Types](#types)
36
+ - [Valeurs par défaut](#valeurs-par-défaut)
37
+ - [Contraintes](#contraintes)
38
+ - [Clés étrangères](#clés-étrangères)
39
+ 5. [Modèles](#modèles)
40
+ 6. [Conditions](#conditions)
41
+ 7. [Select, agrégations, groupement et tri](#select-agrégations-groupement-et-tri)
42
+ 8. [Jointures](#jointures)
43
+ 9. [Synchronisation](#synchronisation)
44
+ 10. [Instances de modèle](#instances-de-modèle)
45
+ 11. [Migrations](#migrations)
46
+ - [Migration workflow](#migration-workflow)
47
+ - [Migration options](#migration-options)
48
+ - [Migration opérations](#migration-opérations)
49
+ - [Migration renommage](#migration-renommage)
50
+ - [Changement destructifs](#changements-destructifs)
51
+ - [Checksum et historique](#checksum-et-historique)
52
+ - [Chriffrement](#chiffrement)
53
+ - [Sauvegardes](#sauvegardes)
54
+ - [Statut et nettoyage](#statut-et-nettoyage)
55
+ 12. [TypeScript](#typescript)
56
+ 13. [Sécurité SQL](#sécurité-sql)
57
+ 14. [API exportée](#api-exportée)
58
+ 15. [Tests](#tests)
59
+ 16. [Documentation détaillée](#documentation-détaillée)
60
+ 17. [Licence](#licence)
49
61
 
50
- | Dialecte | Guillemets des identifiants | Placeholders | Valeurs booléennes par défaut |
51
- |----------|-----------------------------|--------------|-------------------------------|
52
- | MySQL | Accolades inverses | `?` | `1` / `0` |
53
- | PostgreSQL | Guillemets doubles | `$1`, `$2`, ... | `TRUE` / `FALSE` |
62
+ ---
54
63
 
55
- `logout()` ferme proprement la connexion.
64
+ ## Installation
56
65
 
57
- ```javascript
58
- await logout();
66
+ ```bash
67
+ npm install @mlagie/sql-connector
59
68
  ```
60
69
 
61
- ## Schéma
62
-
63
- `Schema` décrit la structure d'une table. Chaque champ peut utiliser les propriétés suivantes.
70
+ ---
64
71
 
65
- | Propriété | Type | Description |
66
- |----------------|----------------------------------|-----------------------------|
67
- | type | `SqlType` ou `{ name: SqlType }` | Type SQL du champ |
68
- | length | `number` | Longueur maximale |
69
- | required | `boolean` | Champ obligatoire |
70
- | default | `any` | Valeur par défaut |
71
- | unique | `boolean` | Valeur unique |
72
- | auto_increment | `boolean` | Auto-incrément |
73
- | foreignKey | `string` | Référence de clé étrangère |
74
- | enum | `string[]` | Liste de valeurs autorisées |
75
- | primary_key | `boolean` | Clé primaire |
76
- | customize | `string` | Options SQL additionnelles |
72
+ ## Démarrage rapide
77
73
 
78
- ```javascript
79
- const { Schema, Model, sqlTypeMap } = require("@mlagie/sql-connector");
74
+ ```js
75
+ const {
76
+ Schema,
77
+ Model,
78
+ connect,
79
+ logout,
80
+ } = require('@mlagie/sql-connector');
81
+
82
+ await connect({
83
+ host: 'localhost',
84
+ port: 3306,
85
+ user: 'root',
86
+ password: 'password',
87
+ database: 'mydatabase',
88
+ });
80
89
 
81
90
  const userSchema = new Schema({
82
91
  id: {
83
92
  type: Number,
84
93
  auto_increment: true,
85
- primary_key: true
94
+ primary_key: true,
86
95
  },
87
96
  email: {
88
97
  type: String,
89
98
  length: 255,
99
+ required: true,
90
100
  unique: true,
91
- required: true
92
101
  },
93
102
  status: {
94
103
  type: String,
95
104
  enum: ['active', 'inactive', 'pending'],
96
- default: 'pending'
105
+ default: 'pending',
97
106
  },
98
- uuid: {
99
- type: String,
100
- required: true,
101
- primary_key: true,
102
- length: 36
103
- },
104
- my_uuid: {
105
- type: String,
106
- required: true,
107
- length: 36
108
- },
109
- created_at: {
110
- type: Date,
111
- default: sqlTypeMap.CurrentTimestamp
112
- }
113
107
  });
114
108
 
115
- module.exports = new Model("User", userSchema);
116
- ```
109
+ const User = new Model('users', userSchema);
117
110
 
118
- Exemple avec plusieurs clés étrangères :
111
+ await Model.syncAllTables();
112
+ await User.save({ email: 'user@example.com', status: 'active' });
119
113
 
120
- ```javascript
121
- const orderSchema = new Schema({
122
- user_id: { type: Number, foreignKey: 'users(id)' },
123
- product_id: { type: Number, foreignKey: 'products.id' }
114
+ const users = await User.find({
115
+ where: {
116
+ status: { IN: ['active', 'pending'] },
117
+ },
124
118
  });
119
+
120
+ await logout();
125
121
  ```
126
122
 
127
- ## Synchronisation des tables
123
+ ---
128
124
 
129
- `Model.syncAllTables()` compare les schémas JS avec la base et applique uniquement les différences utiles. Les dépendances de clés étrangères sont créées dans le bon ordre et les cycles sont rejetés.
125
+ ## Connexion
130
126
 
131
- - Ajout de colonne: automatique.
132
- - Les clés étrangères utilisent le format `table(colonne)` et peuvent être déclarées sur plusieurs champs.
127
+ ### MySQL
133
128
 
134
- ```javascript
135
- await Model.syncAllTables();
129
+ MySQL est le dialecte par défaut.
130
+
131
+ ```js
132
+ await connect({
133
+ host: 'localhost',
134
+ port: 3306,
135
+ user: 'root',
136
+ password: 'password',
137
+ database: 'mydatabase',
138
+ });
136
139
  ```
137
140
 
138
- 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.
141
+ ### PostgreSQL
139
142
 
140
- ## Modèles
143
+ ```js
144
+ await connect({
145
+ host: 'localhost',
146
+ port: 5432,
147
+ user: 'postgres',
148
+ password: 'password',
149
+ database: 'mydatabase',
150
+ }, 'postgres');
151
+ ```
152
+
153
+ ### Fermer la connexion
154
+
155
+ ```js
156
+ await logout();
157
+ ```
141
158
 
142
- `Model` représente une table SQL.
159
+ ---
143
160
 
144
- Méthodes principales:
161
+ ## Schémas
145
162
 
146
- - `save(data)` pour insérer une ligne
147
- - `find(filter, fields)` pour récupérer plusieurs entrées
148
- - `count(filter)` pour compter les lignes
149
- - `customRequest(custom)` pour exécuter une requête SQL personnalisée
150
- - `delete(filter)` pour supprimer une entrée
151
- - `dropTable()` pour supprimer la table
152
- - `generate_uuid()` pour générer un UUID unique
163
+ ```js
164
+ const schema = new Schema({
165
+ name: { type: String, length: 120, required: true },
166
+ age: { type: Number },
167
+ active: { type: Boolean, default: true },
168
+ });
169
+ ```
153
170
 
154
- ```javascript
155
- const userModel = new Model('users', userSchema);
171
+ ### Propriétés d'un champ
172
+
173
+ | Propriété | Type | Description |
174
+ |---|---|---|
175
+ | `type` | constructeur / chaîne / `{ name }` | Type du champ. |
176
+ | `length` | `number` | Longueur du type. |
177
+ | `precision` | `number` | Précision décimale. |
178
+ | `scale` | `number` | Échelle décimale. |
179
+ | `required` | `boolean` | Ajoute `NOT NULL`. |
180
+ | `default` | quelconque | Valeur SQL par défaut. |
181
+ | `unique` | `boolean` | Ajoute `UNIQUE`. |
182
+ | `primary_key` | `boolean` | Ajoute `PRIMARY KEY`. |
183
+ | `auto_increment` | `boolean` | Auto-incrément / identity selon le dialecte. |
184
+ | `enum` | `string[]` | Valeurs autorisées. |
185
+ | `foreignKey` | `string` | Référence comme `users(id)` ou `users.id`. |
186
+ | `oldname` | `string` | Ancien nom utilisé explicitement par les migrations. |
187
+ | `customize` | `string` | SQL additionnel de définition. |
188
+
189
+ ### Types
190
+
191
+ | Alias | SQL |
192
+ |---|---|
193
+ | `String` | `VARCHAR` |
194
+ | `Char` | `CHAR` |
195
+ | `Number` | `INT` |
196
+ | `SmallInt` | `SMALLINT` |
197
+ | `BigInt` | `BIGINT` |
198
+ | `Decimal` | `DECIMAL` |
199
+ | `Boolean` | `BOOLEAN` |
200
+ | `Date` | `DATETIME` |
201
+ | `DateTime` | `DATETIME` |
202
+ | `Timestamp` | `TIMESTAMP` |
203
+ | `Float` | `FLOAT` |
204
+ | `Double` | `DOUBLE` |
205
+ | `Text` | `TEXT` |
206
+ | `Blob` | `BLOB` |
207
+ | `Binary` | `VARBINARY` |
208
+ | `Uuid` | `CHAR` |
209
+ | `Object` | `JSON` |
210
+ | `Array` | `VARCHAR` |
211
+ | `Now` | `NOW()` |
212
+ | `CurrentTimestamp` | `CURRENT_TIMESTAMP` |
213
+
214
+ Les constructeurs JavaScript `String`, `Number`, `Boolean`, `Date`, `Object` et `Array` sont également acceptés. `Now` et `CurrentTimestamp` représentent des expressions/constantes SQL temporelles et sont surtout destinés aux valeurs par défaut, pas à des types de stockage portables.
215
+
216
+ ### Valeurs par défaut
156
217
 
157
- await Model.syncAllTables();
158
- await userModel.save({ email: 'user@example.com', status: 'active' });
218
+ ```js
219
+ const schema = new Schema({
220
+ active: { type: Boolean, default: true },
221
+ retries: { type: Number, default: 0 },
222
+ note: { type: String, default: 'pending' },
223
+ created_at: { type: Date, default: 'CURRENT_TIMESTAMP' },
224
+ metadata: { type: Object, default: { source: 'api' } },
225
+ });
226
+ ```
159
227
 
160
- const user = await userModel.find({ where: { email: 'user@example.com' }});
161
- await user[0].delete();
228
+ Les valeurs par défaut peuvent être `null`, chaînes, nombres, booléens, `Date`, objets, expressions temporelles SQL ou fonctions retournant une valeur supportée.
229
+
230
+ ```js
231
+ created_at: {
232
+ type: Date,
233
+ default: sqlTypeMap.CurrentTimestamp,
234
+ }
162
235
  ```
163
236
 
164
- ## Fonction save
237
+ ### Contraintes
165
238
 
166
- Enregistre les données dans la table de la base de données.
239
+ Le module prend en charge `NOT NULL`, `UNIQUE`, `PRIMARY KEY`, `FOREIGN KEY`, `DEFAULT`, `ENUM`, l'auto-incrément et `customize`.
167
240
 
168
- - **Parameters** `data` *(Object)* - Les données à insérer dans la table.
169
- - **Returns** `Promise<Object>` - Une promesse avec le résultat de l'insertion.
170
- - **Throws** `Error` - Lève une erreur si l'insertion échoue.
241
+ Ne combinez pas `primary_key: true` et `unique: true`.
242
+
243
+ ### Clés étrangères
171
244
 
172
245
  ```js
173
- const User = require("user");
246
+ const orderSchema = new Schema({
247
+ user_id: { type: Number, foreignKey: 'users(id)' },
248
+ product_id: { type: Number, foreignKey: 'products.id' },
249
+ });
250
+ ```
174
251
 
175
- async function createUser(email, stat) {
176
- if (!email || !stat) {
177
- console.error("Email & stat is required");
178
- return;
179
- }
180
- await User.save({ email: email, status: stat });
252
+ `Model.syncAllTables()` respecte l'ordre des dépendances et rejette les cycles.
181
253
 
182
- }
254
+ ---
255
+
256
+ ## Modèles
257
+
258
+ ```js
259
+ const User = new Model('users', userSchema);
183
260
  ```
184
261
 
185
- ## Fonction find
262
+ Méthodes principales :
186
263
 
187
- Récupère des enregistrements de la table.
264
+ - `save(data)` — insertion ;
265
+ - `find(options)` — recherche structurée ;
266
+ - `count(filter)` — comptage ;
267
+ - `delete(filter)` — suppression ;
268
+ - `dropTable()` — suppression de table ;
269
+ - `generate_uuid(column?)` — génération d'UUID ;
270
+ - `customRequest(sql)` — SQL brut de confiance.
188
271
 
189
- - **Paramètres** `options` *(Object)* – Options de la requête.
190
- - **Paramètres** `options.select` *(Array<string|SelectAggregation>)* – Champs, agrégations ou transformations à retourner.
191
- - **Paramètres** `options.where` *(Object / string)* – Conditions de filtrage (objet clé/valeur ou clause brute sous forme de chaîne).
192
- - **Paramètres** `options.groupBy` *(Array<string|GroupAggregation>)* – Champs, colonnes ou expressions utilisés pour regrouper les résultats.
193
- - **Paramètres** `options.orderBy` *(Array<string|OrderByOption>)* – Règles de tri.
194
- - **Paramètres** `options.join` *(JoinOption / JoinOption[])* – Structures de configuration pour les jointures de tables.
195
- - **Paramètres** `options.limit` *(number)* – Nombre maximal de résultats à retourner.
196
- - **Retourne** `Promise<Array<ModelInstance>>`
272
+ Exemple :
197
273
 
198
- ### Options avancées de `select` (`SelectAggregation`)
274
+ ```js
275
+ const users = await User.find({
276
+ select: ['id', 'email'],
277
+ where: {
278
+ status: { IN: ['active', 'pending'] },
279
+ },
280
+ orderBy: [
281
+ { field: 'email', direction: 'ASC' },
282
+ ],
283
+ limit: 50,
284
+ offset: 0,
285
+ });
286
+ ```
199
287
 
200
- Chaque élément du tableau `select` peut être soit une chaîne de caractères standard (nom brut de la colonne), soit un objet offrant des fonctionnalités SQL et des agrégations avancées :
288
+ ---
201
289
 
202
- | Propriété dans l'objet `select` | Type | Description | Exemple / SQL Généré |
203
- |---------------------------------|-------------------|-----------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
204
- | `col` | `string` | Sélectionne une colonne de table simple, sans agrégation. | `{ col: 'email' }` $\rightarrow$ \`email\` |
205
- | `sum` | `string` | Calcule la somme de toutes les valeurs numériques d'une colonne. | `{ sum: 'error' }` $\rightarrow$ `SUM(`\`error\``)` |
206
- | `distinct` | `string` | Applique une contrainte DISTINCT sur la colonne spécifiée. | `{ distinct: 'status' }` $\rightarrow$ `DISTINCT `\`status\` |
207
- | `dateFormat` | `[string, string]`| Formate une colonne de type Date en utilisant le formatage standard MySQL (`[colonne, chaine_de_format]`).| `{ dateFormat: ['created_at', '%Y-%m'] }` $\rightarrow$ `DATE_FORMAT(`\`created_at\``, '%Y-%m')` |
208
- | `count` | `string` | Comptage de lignes standard (ignore les valeurs `NULL`). | `{ count: 'id' }` $\rightarrow$ `COUNT(`\`id\``)` |
209
- | `count` (Array) | `string[]` | Calcule le nombre de combinaisons uniques sur plusieurs colonnes (COUNT DISTINCT). | `{ count: ['team', 'source'] }` $\rightarrow$ `COUNT( DISTINCT `\`team\``, `\`source\`` )` |
210
- | `count` (Object) | `Object` | Agrégation conditionnelle automatisée (`CASE WHEN`). Idéal pour les indicateurs clés (KPIs) et statuts. | `{ count: { deletedAt: null } }` $\rightarrow$ `COUNT(CASE WHEN `\`deletedAt\`` = NULL THEN 1 END)` |
211
- | `as` | `string` | Définit un identifiant de sortie personnalisé ou un alias d'agrégation (SQL `AS`). | `{ count: 'id', as: 'total' }` $\rightarrow$ `COUNT(`\`id\``) AS `\`total\` |
290
+ ## Conditions
212
291
 
213
- ### Options Avancées de `groupBy` (`GroupAggregation`)
292
+ Les `where` et `having` utilisent des objets structurés. Les chaînes SQL brutes sont refusées par le builder.
214
293
 
215
- L'option `groupBy` accepte un tableau pouvant mélanger des chaînes de caractères simples (noms de colonnes bruts) et des objets de configuration avancés pour un regroupement sécurisé, paramétré et la gestion des alias :
294
+ ### Valeur simple
216
295
 
217
- | Propriété dans l'objet `groupBy` | Type | Description | Exemple / SQL Généré |
218
- |----------------------------------|-------------------|----------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
219
- | `col` | `string` | Cible de manière sécurisée une colonne de table non agrégée grâce à l'isolation structurelle de l'objet. | `{ col: 'role' }` → \`role\` |
220
- | `dateFormat` | `[string, string]`| Regroupe de manière sécurisée par une colonne Date formatée à l'aide d'entrées paramétrées (Format : `[colonne, format]`). | `{ dateFormat: ['created_at', '%Y-%m'] }` → `DATE_FORMAT(` \`created_at\``, '%Y-%m')` |
221
- | `as` | `string` | Cible directement l'alias défini dans le select pour respecter les contraintes strictes de syntaxe MySQL. | `{ col: 'role', as: 'user_role' }` → \`user_role\` |
296
+ ```js
297
+ { status: 'active' }
298
+ ```
222
299
 
223
- ---
300
+ ### Opérateurs
301
+
302
+ Les opérateurs supportés sont :
303
+
304
+ ```text
305
+ =
306
+ !=
307
+ >
308
+ <
309
+ >=
310
+ <=
311
+ LIKE
312
+ NOT LIKE
313
+ ILIKE
314
+ NOT ILIKE
315
+ IN
316
+ NOT IN
317
+ BETWEEN
318
+ NOT BETWEEN
319
+ ```
224
320
 
225
- ### Options complexes structurées (`orderBy` & `join`)
321
+ Exemples :
226
322
 
227
- #### OrderByOption
323
+ ```js
324
+ { age: { '>=': 18 } }
325
+ { email: { LIKE: '%@example.com' } }
326
+ { status: { IN: ['active', 'pending'] } }
327
+ { age: { BETWEEN: [18, 65] } }
328
+ ```
329
+
330
+ ### NULL
331
+
332
+ ```js
333
+ { deleted_at: null }
334
+ { deleted_at: { '=': null } }
335
+ { deleted_at: { '!=': null } }
336
+ ```
228
337
 
229
- Permet d'appliquer un tri explicite sur une ou plusieurs colonnes :
338
+ Ces formes deviennent `IS NULL` / `IS NOT NULL`.
230
339
 
231
- - **field** `(string)` : Le nom de la colonne cible sur laquelle appliquer le tri.
232
- - **direction** `('ASC'\|'DESC')` : Le sens du tri (Par défaut : `'ASC'`).
340
+ ### AND / OR / NOT
233
341
 
234
- #### JoinOption
342
+ ```js
343
+ {
344
+ AND: [
345
+ { active: true },
346
+ {
347
+ OR: [
348
+ { role: 'admin' },
349
+ { role: 'moderator' },
350
+ ],
351
+ },
352
+ ],
353
+ }
354
+ ```
235
355
 
236
- Spécifie une ou plusieurs jointures de tables relationnelles :
356
+ ```js
357
+ {
358
+ NOT: {
359
+ status: { IN: ['deleted', 'blocked'] },
360
+ },
361
+ }
362
+ ```
237
363
 
238
- - **table** `(string)` : Nom de la table cible à joindre.
239
- - **on** `(string)` : Chaîne de caractères représentant la condition de jointure (ex: `"MyTable.project_id = Projects.id"`).
240
- - **alias** `(string)` *(Optionnel)* : Un alias SQL alternatif pour la table jointe.
241
- - **type** `('INNER'\|'LEFT'\|'RIGHT')` *(Optionnel)* : Type de jointure SQL (Par défaut : `'INNER'`).
364
+ Les tableaux `AND` / `OR` vides et les valeurs invalides de `NOT` sont refusés.
242
365
 
243
366
  ---
244
367
 
245
- ## Exemple find
368
+ ## Select, agrégations, groupement et tri
246
369
 
247
- ```js
248
- const User = require("user");
370
+ Le `select` accepte des chaînes ou des objets structurés :
249
371
 
250
- await User.find({
372
+ ```js
373
+ const stats = await User.find({
251
374
  select: [
252
- { dateFormat: ['date_day', '%Y-%m'], as: 'period' },
253
- { sum: 'error' },
254
- { sum: 'reload' },
375
+ 'status',
376
+ { count: 'id', as: 'total' },
377
+ { sum: 'amount', as: 'revenue' },
255
378
  ],
256
- groupBy: ['period', { dateFormat: ['date_day', '%Y-%m'] }],
257
- orderBy: [{ field: 'period', direction: 'ASC' }],
258
- limit: 10
379
+ where: {
380
+ active: true,
381
+ },
259
382
  });
260
383
  ```
261
384
 
262
- ```js
263
- const User = require("user");
385
+ ### Groupement avec `GROUP BY`
386
+
387
+ `groupBy` est une option de `find()` et permet de regrouper les lignes avant l'application de `having`.
264
388
 
265
- await User.find({
389
+ ```js
390
+ const stats = await User.find({
266
391
  select: [
267
- "email"
392
+ 'status',
393
+ { count: 'id', as: 'total' },
268
394
  ],
269
- where: {
270
- id: 1
271
- }
272
- })
395
+ groupBy: ['status'],
396
+ });
273
397
  ```
274
398
 
275
- ### Exemples avancés avec `find`
399
+ Les éléments de `groupBy` peuvent être des colonnes ou des expressions structurées comme `dateFormat`.
276
400
 
277
- #### 1. Comptage standard, distinct et multi-colonnes
401
+ ### Filtrer les groupes avec `HAVING`
278
402
 
279
- Comptez les lignes globales parallèlement à des combinaisons uniques multi-colonnes, comme l'identification des couples uniques d'équipes et de sources de pipelines :
403
+ `having` filtre le résultat **après** le `GROUP BY` et utilise le même format de conditions structurées que `where` :
280
404
 
281
405
  ```js
282
- const { MyTable } = require("./models");
283
-
284
- const stats = await MyTable.find({
406
+ const stats = await User.find({
285
407
  select: [
286
- { count: 'id', as: 'total_pipelines' },
287
- { count: ['cteam', 'csource'], as: 'unique_groups' } // COUNT DISTINCT multi-colonnes
408
+ 'status',
409
+ { count: 'id', as: 'total' },
288
410
  ],
289
- where: { csource: 'web' }
411
+ groupBy: ['status'],
412
+ having: {
413
+ total: { '>': 10 },
414
+ },
290
415
  });
291
416
  ```
292
417
 
293
- #### 2. Agrégations conditionnelles (Indicateurs KPIs Actifs / Inactifs)
418
+ Les chaînes SQL brutes dans `having` sont refusées.
294
419
 
295
- En passant un objet à l'attribut count, l'ORM structure automatiquement une clause conditionnelle CASE WHEN. Cela vous permet de ventiler différents compteurs de statuts en une seule et unique requête vers la base de données :
420
+ ### Tri avec `ORDER BY`
296
421
 
297
- ```js
298
- const { ProjectPipeline } = require("./models");
422
+ `orderBy` permet de trier le résultat final. Il accepte des colonnes ou des alias de sélection, avec une direction `ASC` ou `DESC` :
299
423
 
300
- const ppiStats = await ProjectPipeline.find({
424
+ ```js
425
+ const stats = await User.find({
301
426
  select: [
302
- { count: { deletedAt: null }, as: 'active' }, // Compte les lignes où deletedAt = NULL
303
- { count: { status: 'SUCCESS' }, as: 'total_success' } // Compte les lignes où status = 'SUCCESS'
427
+ 'status',
428
+ { count: 'id', as: 'total' },
429
+ ],
430
+ groupBy: ['status'],
431
+ having: {
432
+ total: { '>': 10 },
433
+ },
434
+ orderBy: [
435
+ { field: 'total', direction: 'DESC' },
436
+ { field: 'status', direction: 'ASC' },
304
437
  ],
305
- where: {
306
- source: 'jenkins',
307
- team: 'GROUP-1'
308
- }
309
438
  });
310
- ```
439
+ ```
311
440
 
312
- #### 3. Jointures de tables, groupement temporel et tri multi-colonnes
441
+ ### Exemple complet
313
442
 
314
- Une orchestration de requête avancée combinant une jointure gauche (LEFT JOIN), des conversions de formats de date et des tris :
443
+ Toutes les clauses peuvent être combinées dans une seule requête :
315
444
 
316
445
  ```js
317
- const { ProjectPipeline } = require("./models");
318
-
319
- const history = await ProjectPipeline.find({
446
+ const stats = await User.find({
320
447
  select: [
321
- { col: 'Projects.name', as: 'project_name' },
322
- { dateFormat: ['MyTable.created_at', '%Y-%m'], as: 'period' },
323
- { count: 'MyTable.id', as: 'pipelines_count' }
448
+ 'status',
449
+ { count: 'id', as: 'total' },
450
+ { sum: 'amount', as: 'revenue' },
324
451
  ],
325
452
  where: {
326
- status: 'ACTIVE'
453
+ active: true,
327
454
  },
328
- join: {
329
- table: 'Projects',
330
- on: 'MyTable.project_id = Projects.id',
331
- type: 'LEFT'
455
+ groupBy: ['status'],
456
+ having: {
457
+ total: { '>': 10 },
332
458
  },
333
- groupBy: [
334
- 'project_name',
335
- { dateFormat: ['ProjectPipelines.created_at', '%Y-%m'] }
336
- ],
337
459
  orderBy: [
338
- { field: 'period', direction: 'DESC' },
339
- { field: 'project_name', direction: 'ASC' }
460
+ { field: 'total', direction: 'DESC' },
340
461
  ],
341
- limit: 50
342
462
  });
343
463
  ```
344
464
 
345
- ## Fonction count
465
+ Ce flux correspond à `SELECT` → `WHERE` → `GROUP BY` → `HAVING` → `ORDER BY`. Les valeurs de `where` et `having` sont paramétrées par le Connector.
346
466
 
347
- Compte le nombre d'enregistrements correspondant au filtre donné.
467
+ ---
348
468
 
349
- - **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.
350
- - **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.
469
+ ## Jointures
351
470
 
352
- ## Exemple count
471
+ La couche `Model.find()` génère actuellement une `INNER JOIN` à partir de `table` et `on` :
353
472
 
354
473
  ```js
355
- const User = require("user");
474
+ join: {
475
+ table: 'profiles',
476
+ on: 'users.id = profiles.user_id',
477
+ }
478
+ ```
479
+
480
+ L'expression `on` doit être considérée comme du SQL de confiance.
481
+
482
+ ---
483
+
484
+ ## Synchronisation
356
485
 
357
- await User.count({
358
- id: id
359
- })
486
+ ```js
487
+ await Model.syncAllTables();
360
488
  ```
361
489
 
362
- ## Fonction customRequest
490
+ La synchronisation crée les tables dans l'ordre des dépendances de clés étrangères et détecte les cycles.
363
491
 
364
- 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.
492
+ Pour les évolutions contrôlées du schéma, utilisez les migrations.
365
493
 
366
- - **Parameters** `custom` *(string)* La requête SQL personnalisée à exécuter.
367
- - **Returns** `Promise<void>` Valeur retournée
368
- - **Throws** `Error` Lève une erreur si l'exécution de la requête échoue.
494
+ ---
369
495
 
370
- ## Exemple customRequest
496
+ ## Instances de modèle
371
497
 
372
498
  ```js
373
- const User = require("user");
499
+ const [user] = await User.find({
500
+ where: { email: 'user@example.com' },
501
+ });
374
502
 
375
- await User.customRequest("SELECT id, email, status
376
- FROM users
377
- WHERE status IN ('active', 'pending')
378
- AND email LIKE '%gmail.com';")
503
+ await user.updateOne({ status: 'inactive' });
504
+ await user.delete();
379
505
  ```
380
506
 
381
- ## Fonction delete
507
+ Méthodes : `updateOne`, `delete`, `customRequest`, `getRecordData`, `toJSON`.
382
508
 
383
- Supprime de la table SQL une entrée correspondant au filtre fourni.
509
+ ---
384
510
 
385
- - **Parameters** `filter` *(Object)* Un objet représentant les conditions de filtrage pour la suppression.
386
- - **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.
387
- - **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.
511
+ ## Migrations
388
512
 
389
- ## Exemple delete
513
+ Le système de migration compare les modèles souhaités au schéma réellement présent dans la base, produit un plan puis génère le SQL adapté au dialecte.
390
514
 
391
515
  ```js
392
- const User = require("user");
393
-
394
- await User.delete({
395
- email: my@gmail.com
396
- })
516
+ const {
517
+ Migration,
518
+ MigrationPlan,
519
+ MigrationStore,
520
+ MigrationRunner,
521
+ SchemaDiffer,
522
+ SchemaInspector,
523
+ } = require('@mlagie/sql-connector');
397
524
  ```
398
525
 
399
- ## Fonction dropTable
526
+ ### Migration Workflow
400
527
 
401
- Supprime de manière asynchrone une table si elle existe dans la base de données.
528
+ ```js
529
+ const migration = new Migration({
530
+ encryptionKey: process.env.SQL_CONNECTOR_MIGRATION_KEY,
531
+ });
402
532
 
403
- 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.
404
- Si la requête aboutit, le résultat est consigné dans la console.
533
+ const plan = await migration.plan({
534
+ name: 'users-schema',
535
+ });
405
536
 
406
- En cas d'erreur lors de l'exécution de la requête, un message d'erreur est consigné.
537
+ console.log(plan.toJSON());
407
538
 
408
- - **Returns** `Promise<void>` Une promesse qui se résout une fois l'exécution de la requête terminée.
539
+ if (plan.hasChanges()) {
540
+ plan.approve();
541
+ await plan.execute();
542
+ }
543
+ ```
409
544
 
410
- ## Example dropTable
545
+ Ou directement :
411
546
 
412
547
  ```js
413
- const User = require("user");
414
-
415
- await User.dropTable();
548
+ await Migration.migrate({
549
+ name: 'users-schema',
550
+ encryptionKey: process.env.SQL_CONNECTOR_MIGRATION_KEY,
551
+ });
416
552
  ```
417
553
 
418
- ## generate_uuid function
554
+ ### Migration Options
555
+
556
+ | Option | Description |
557
+ |---|---|
558
+ | `connection` | Connexion existante. |
559
+ | `dialect` | `mysql`, `postgres` ou instance de dialecte. |
560
+ | `models` | Modèles à comparer. |
561
+ | `directory` | Dossier des migrations. |
562
+ | `backupDirectory` | Dossier des sauvegardes. |
563
+ | `encryptionKey` | Clé de chiffrement. |
564
+ | `retention` | Durée de conservation (`ms`, `s`, `m`, `h`, `d`, `w`). |
565
+ | `allowDestructiveChanges` | Autorise les suppressions de colonnes. |
566
+ | `allowDropTables` | Autorise les suppressions de tables. |
419
567
 
420
- Génère un UUID unique pour le modèle actuel.
568
+ ### Migration Opérations
421
569
 
422
- 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é.
570
+ Le diff peut générer :
423
571
 
424
- Sinon, la fonction renvoie `null`.
572
+ - `createTable` ;
573
+ - `addColumn` ;
574
+ - `renameColumn` ;
575
+ - `alterColumn` ;
576
+ - `dropColumn` — destructif ;
577
+ - `dropTable` — destructif.
425
578
 
426
- - **Parameters** `string` var_uuid Par defaut il vaut uuid
427
- - **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.
428
- - **Throws** `Error` S'il y a une erreur lors de l'exécution de la requête SQL_request.
579
+ ### Migration Renommage
429
580
 
430
- ## Example generate_uuid
581
+ Le renommage doit être explicite avec `oldname` :
431
582
 
432
583
  ```js
433
- const User = require("user");
584
+ email_address: {
585
+ type: String,
586
+ length: 320,
587
+ oldname: 'email',
588
+ }
589
+ ```
590
+
591
+ ### Changements destructifs
592
+
593
+ Par défaut, la suppression d'une colonne ou d'une table est bloquée.
434
594
 
435
- const uuid = await User.generate_uuid();
436
- const my_uuid = await User.generate_uuid("my_uuid");
595
+ ```js
596
+ const plan = await migration.plan({
597
+ allowDestructiveChanges: true,
598
+ allowDropTables: true,
599
+ });
437
600
 
438
- await User.save({ email: "user@example.com", status: "active", uuid: uuid, my_uuid: my_uuid })
601
+ plan.approve();
602
+ await plan.execute({
603
+ allowDestructiveChanges: true,
604
+ });
439
605
  ```
440
606
 
441
- ## Instances de modèle
607
+ L'approbation et l'autorisation destructive sont deux protections distinctes.
608
+
609
+ ### Checksum et historique
610
+
611
+ Le runner crée `sql_connector_migrations` et enregistre l'identifiant, le checksum SHA-256 et les dates d'application/création.
442
612
 
443
- `ModelInstance` représente une ligne déjà chargée depuis la base.
613
+ Un identifiant déjà appliqué avec un checksum différent provoque une erreur.
444
614
 
445
- - `updateOne(model)` met à jour la ligne
446
- - `delete(model)` supprime la ligne avec un filtre
447
- - `delete()` supprime la ligne de l'instance
448
- - `customRequest(custom)` exécute une requête personnalisée
615
+ ### Chiffrement
449
616
 
450
- ```javascript
451
- const userInstance = new ModelInstance('users', { email: 'user@example.com' });
617
+ Les migrations sont écrites en `.sql.enc`.
452
618
 
453
- await userInstance.updateOne({ status: 'inactive' });
454
- await userInstance.delete();
619
+ ```bash
620
+ export SQL_CONNECTOR_MIGRATION_KEY='un-secret-long-et-aleatoire'
455
621
  ```
456
622
 
457
- ## Types SQL
623
+ Le format utilise AES-256-GCM et une clé dérivée.
458
624
 
459
- `sqlTypeMap` expose les types SQL les plus courants.
625
+ ### Sauvegardes
460
626
 
461
- ```javascript
462
- console.log(sqlTypeMap.String); // "VARCHAR"
627
+ Avec `retention`, les tables affectées sont sauvegardées avant la migration dans des fichiers JSON chiffrés.
628
+
629
+ ```js
630
+ new Migration({
631
+ encryptionKey: process.env.SQL_CONNECTOR_MIGRATION_KEY,
632
+ retention: '30d',
633
+ });
463
634
  ```
464
635
 
465
- ## Client
636
+ La sauvegarde est un snapshot de données et ne constitue pas un rollback automatique.
466
637
 
467
- `client` est un objet partagé pensé pour centraliser des fonctions applicatives réutilisables.
638
+ ### Statut et nettoyage
468
639
 
469
- ```javascript
470
- module.exports = client => {
471
- client.checkServer = () => {
472
- if (server.isLaunch()) {
473
- return 1;
474
- }
640
+ ```js
641
+ const migration = new Migration();
642
+ const status = await migration.status();
643
+ console.table(status);
475
644
 
476
- return 0;
477
- };
478
- };
645
+ await migration.cleanup();
479
646
  ```
480
647
 
481
- ## Résumé
648
+ ---
649
+
650
+ ## TypeScript
651
+
652
+ Le package fournit `index.d.ts` avec les types de schéma, conditions, requêtes, modèles et migrations.
653
+
654
+ ```ts
655
+ import {
656
+ Schema,
657
+ Model,
658
+ Migration,
659
+ InferSchema,
660
+ } from '@mlagie/sql-connector';
661
+ ```
662
+
663
+ ---
664
+
665
+ ## Sécurité SQL
666
+
667
+ Le builder structuré sépare les identifiants SQL des valeurs :
668
+
669
+ - valeurs `WHERE` paramétrées ;
670
+ - valeurs `HAVING` paramétrées ;
671
+ - éléments de `IN` / `NOT IN` paramétrés ;
672
+ - bornes de `BETWEEN` paramétrées ;
673
+ - placeholders PostgreSQL numérotés ;
674
+ - échappement des identifiants ;
675
+ - refus des `WHERE` / `HAVING` sous forme de chaînes brutes.
676
+
677
+ `customRequest()` reste volontairement une porte d'accès au SQL brut et doit recevoir uniquement du SQL de confiance.
678
+
679
+ ---
680
+
681
+ ## API exportée
682
+
683
+ ```text
684
+ client
685
+ connect
686
+ logout
687
+ Schema
688
+ Model
689
+ ModelInstance
690
+ sqlTypeMap
691
+ Migration
692
+ MigrationPlan
693
+ MigrationStore
694
+ MigrationRunner
695
+ SchemaDiffer
696
+ SchemaInspector
697
+ MySQLDialect
698
+ PostgreSQLDialect
699
+ ```
700
+
701
+ ---
702
+
703
+ ## Tests
704
+
705
+ ```bash
706
+ npm install
707
+ npm test
708
+ npm run test:coverage
709
+ npm run security
710
+ ```
711
+
712
+ ---
713
+
714
+ ## Documentation détaillée
715
+
716
+ - [Index de documentation](../README.md)
717
+ - [Schéma](../SCHEMA.md)
718
+ - [Conditions et requêtes](../QUERY.md)
719
+ - [Migrations](../MIGRATIONS.md)
720
+ - [API](../API.md)
721
+ - [Dialectes](../DIALECTS.md)
722
+ - [Contribution](../../CONTRIBUTING.md)
723
+ - [Sécurité](../../SECURITY.md)
724
+
725
+ ## Licence
482
726
 
483
- 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.
727
+ MIT — voir [LICENSE](../../LICENSE).