@mlagie/sql-connector 1.4.7 → 1.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -222
- package/docs/fr/README.md +158 -0
- package/package.json +1 -1
- package/releases/1.4.8.md +43 -0
- package/src/models/Model.js +2 -2
- package/src/models/ModelInstance.js +76 -23
- package/src/utils/formatObject.js +19 -4
- package/src/utils/generateCondition.js +51 -9
package/README.md
CHANGED
|
@@ -1,31 +1,18 @@
|
|
|
1
|
-
# sql-connector
|
|
2
|
-
    
|
|
3
|
-

|
|
1
|
+
# sql-connector documentation
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
[Français](./docs/fr/README.md) | English
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
sql-connector helps manage MySQL connections, define table schemas, sync tables automatically, and work with database models through a small API.
|
|
8
6
|
|
|
9
|
-
##
|
|
7
|
+
## Import
|
|
10
8
|
|
|
11
9
|
```javascript
|
|
12
|
-
const { Schema, connect, logout, Model, client, sqlTypeMap } = require('sql-connector');
|
|
10
|
+
const { Schema, connect, logout, Model, ModelInstance, client, sqlTypeMap } = require('sql-connector');
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
## Database connection
|
|
16
14
|
|
|
17
|
-
`connect(config)`
|
|
18
|
-
|
|
19
|
-
* Paramètres:
|
|
20
|
-
* `config` (Object) : La configuration de la connexion à la base de données.
|
|
21
|
-
* `host` (string) : L'hôte de la base de données.
|
|
22
|
-
* `port` (number) : Le port de la base de données.
|
|
23
|
-
* `user` (string) : Le nom d'utilisateur pour la connexion.
|
|
24
|
-
* `password` (string) : Le mot de passe pour la connexion.
|
|
25
|
-
* `database` (string) : Le nom de la base de données.
|
|
26
|
-
* `ect` pour en savoir plus vous pouvez vous rendre sur https://github.com/mysqljs/mysql dans la section `Connection options`
|
|
27
|
-
|
|
28
|
-
* ### Exemple:
|
|
15
|
+
`connect(config)` opens a MySQL connection using a configuration object compatible with mysql2.
|
|
29
16
|
|
|
30
17
|
```javascript
|
|
31
18
|
const config = {
|
|
@@ -35,253 +22,137 @@ const config = {
|
|
|
35
22
|
password: 'password',
|
|
36
23
|
database: 'mydatabase'
|
|
37
24
|
};
|
|
25
|
+
|
|
38
26
|
await connect(config);
|
|
39
27
|
```
|
|
40
28
|
|
|
41
|
-
`logout()`
|
|
42
|
-
|
|
43
|
-
* ### Exemple:
|
|
29
|
+
`logout()` closes the active connection.
|
|
44
30
|
|
|
45
31
|
```javascript
|
|
46
32
|
await logout();
|
|
47
33
|
```
|
|
48
34
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Décrit les propriétés d'un champ dans le schéma d'une table SQL. Chaque clé du dictionnaire passé au constructeur de `Schema` doit respecter cette interface.
|
|
35
|
+
## Schema
|
|
52
36
|
|
|
53
|
-
|
|
54
|
-
|------------------|--------------------------------------|-----------------------------------------------------------------------------|
|
|
55
|
-
| type | `SqlType` ou `{ name: SqlType }` | Type du champ (String, Number, Boolean, etc.) |
|
|
56
|
-
| length | `number` | Longueur maximale (pour VARCHAR ou INT) |
|
|
57
|
-
| required | `boolean` | Si le champ est obligatoire (NOT NULL) |
|
|
58
|
-
| default | `any` | Valeur par défaut |
|
|
59
|
-
| unique | `boolean` | Si le champ doit être unique |
|
|
60
|
-
| auto_increment | `boolean` | Si le champ est auto-incrémenté |
|
|
61
|
-
| foreignKey | `string` | Clé étrangère (ex: "otherTable(column)") |
|
|
62
|
-
| enum | `string[]` | Liste de valeurs pour un champ ENUM |
|
|
63
|
-
| primary_key | `boolean` | Si le champ est une clé primaire |
|
|
64
|
-
| customize | `string` | Ajout d'options SQL personnalisées |
|
|
37
|
+
`Schema` describes the structure of a table. Each field can use the following properties.
|
|
65
38
|
|
|
66
|
-
|
|
39
|
+
| Property | Type | Description |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| type | `SqlType` or `{ name: SqlType }` | SQL type for the field |
|
|
42
|
+
| length | `number` | Maximum length |
|
|
43
|
+
| required | `boolean` | Not null constraint |
|
|
44
|
+
| default | `any` | Default value |
|
|
45
|
+
| unique | `boolean` | Unique constraint |
|
|
46
|
+
| auto_increment | `boolean` | Auto increment |
|
|
47
|
+
| foreignKey | `string` | Foreign key reference |
|
|
48
|
+
| enum | `string[]` | Allowed values |
|
|
49
|
+
| primary_key | `boolean` | Primary key flag |
|
|
50
|
+
| customize | `string` | Extra SQL options |
|
|
67
51
|
|
|
68
52
|
```javascript
|
|
69
53
|
const userSchema = new Schema({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
54
|
+
id: {
|
|
55
|
+
type: Number,
|
|
56
|
+
auto_increment: true,
|
|
57
|
+
primary_key: true
|
|
58
|
+
},
|
|
59
|
+
email: {
|
|
60
|
+
type: String,
|
|
61
|
+
length: 255,
|
|
62
|
+
unique: true,
|
|
63
|
+
required: true
|
|
64
|
+
},
|
|
65
|
+
status: {
|
|
66
|
+
type: String,
|
|
67
|
+
enum: ['active', 'inactive', 'pending'],
|
|
68
|
+
default: 'pending'
|
|
69
|
+
}
|
|
87
70
|
});
|
|
88
71
|
```
|
|
89
72
|
|
|
90
|
-
|
|
73
|
+
## Table synchronization
|
|
91
74
|
|
|
92
|
-
|
|
93
|
-
* Constructeur:
|
|
94
|
-
* Schema(schemaDict) : Crée une instance de Schema.
|
|
95
|
-
* schemaDict (Object) : Un dictionnaire définissant le schéma.
|
|
75
|
+
`Model.syncAllTables()` compares JS schemas with the database and applies only meaningful differences.
|
|
96
76
|
|
|
97
|
-
|
|
77
|
+
- New columns are added automatically.
|
|
78
|
+
- Removed columns are only dropped with `dangerousSync: true`.
|
|
79
|
+
- Column renames are supported through `oldName`.
|
|
80
|
+
- Orphan tables are backed up to a `backup_*.sql` file before deletion.
|
|
98
81
|
|
|
99
82
|
```javascript
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
type: String,
|
|
103
|
-
length: 50
|
|
104
|
-
},
|
|
105
|
-
mdp: {
|
|
106
|
-
type: String,
|
|
107
|
-
length: 15
|
|
108
|
-
}
|
|
109
|
-
});
|
|
83
|
+
await Model.syncAllTables();
|
|
84
|
+
await Model.syncAllTables({ dangerousSync: true });
|
|
110
85
|
```
|
|
111
86
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
> **⚠️ Important : Synchronisation du schéma et sécurité**
|
|
115
|
-
>
|
|
116
|
-
> Toutes les opérations de synchronisation avancée du schéma (modification de type, contraintes, renommage, suppression de colonnes, etc.) **ne sont effectuées que si vous activez explicitement l’option `{ dangerousSync: true }`** lors de l’appel à `Model.syncAllTables`.
|
|
117
|
-
>
|
|
118
|
-
> Par défaut (`dangerousSync: false`), aucune modification de structure n’est appliquée sur les tables existantes pour garantir la sécurité de vos données en production.
|
|
119
|
-
> Utilisez `dangerousSync` uniquement dans un environnement de développement ou lors de migrations contrôlées.
|
|
120
|
-
|
|
121
|
-
### Ajout, suppression et renommage de colonnes
|
|
122
|
-
|
|
123
|
-
- **Ajout automatique de colonnes**
|
|
124
|
-
Lorsque vous ajoutez un champ dans le schéma JS, la colonne correspondante est automatiquement ajoutée dans la base de données lors de la synchronisation avec :
|
|
125
|
-
```js
|
|
126
|
-
await Model.syncAllTables();
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
- **Suppression automatique de colonnes**
|
|
130
|
-
Si vous retirez un champ du schéma JS, la colonne reste dans la base par défaut.
|
|
131
|
-
Pour supprimer automatiquement les colonnes disparues, utilisez :
|
|
132
|
-
```js
|
|
133
|
-
await Model.syncAllTables({ dangerousSync: true });
|
|
134
|
-
```
|
|
135
|
-
⚠️ Attention, cela supprime les données de ces colonnes.
|
|
136
|
-
|
|
137
|
-
- **Renommage de colonne sans perte de données**
|
|
138
|
-
Pour renommer une colonne, ajoutez la propriété `oldName` dans le schéma :
|
|
139
|
-
```js
|
|
140
|
-
const userSchema = new Schema({
|
|
141
|
-
role: { type: String, oldName: "rang" }
|
|
142
|
-
});
|
|
143
|
-
```
|
|
144
|
-
Lors de la synchronisation, la colonne SQL sera renommée sans perte de données.
|
|
145
|
-
Un avertissement s’affichera pour vous rappeler de retirer `oldName` du schéma après migration.
|
|
146
|
-
|
|
147
|
-
### Suppression et sauvegarde des tables orphelines
|
|
148
|
-
|
|
149
|
-
- Si une table SQL n’a plus de schéma JS associé, elle est supprimée automatiquement lors de la synchronisation.
|
|
150
|
-
- **Avant suppression**, un fichier de backup SQL (INSERTs) est généré dans le dossier courant (ex : `backup_MaTable_1690000000000.sql`).
|
|
87
|
+
Important: do not set both `primary_key: true` and `unique: true` on the same field. A primary key is already unique and not null.
|
|
151
88
|
|
|
152
|
-
|
|
89
|
+
## Models
|
|
153
90
|
|
|
154
|
-
|
|
155
|
-
1. **De restaurer les données** (exécution du fichier SQL).
|
|
156
|
-
2. **De supprimer le backup** après restauration ou non.
|
|
157
|
-
3. Si vous refusez la suppression, le backup est renommé en `.ignored` et ne sera plus proposé.
|
|
91
|
+
`Model` represents a SQL table.
|
|
158
92
|
|
|
159
|
-
|
|
93
|
+
Main methods:
|
|
160
94
|
|
|
161
|
-
|
|
95
|
+
- `save(data)` inserts a row
|
|
96
|
+
- `findOne(filter, fields)` fetches a single row
|
|
97
|
+
- `find(filter, fields)` fetches multiple rows
|
|
98
|
+
- `findAll(options)` supports advanced queries
|
|
99
|
+
- `count(filter)` counts rows
|
|
100
|
+
- `customRequest(custom)` runs a custom SQL query
|
|
101
|
+
- `delete(filter)` deletes a row
|
|
102
|
+
- `dropTable()` drops the table
|
|
103
|
+
- `generate_uuid()` generates a unique UUID
|
|
104
|
+
- `Model.createAllTables()` creates tables in dependency order
|
|
162
105
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
> ⚠️ Une colonne `primary_key` est toujours considérée comme `NOT NULL` même si `required` n'est pas précisé.
|
|
166
|
-
- **Valeur par défaut** : Si la valeur par défaut diffère, la colonne est modifiée.
|
|
167
|
-
- **Unique** : Si la contrainte `UNIQUE` diffère, la colonne est modifiée.
|
|
168
|
-
- **Primary key** : Si la contrainte `PRIMARY KEY` diffère, la colonne est modifiée.
|
|
169
|
-
|
|
170
|
-
Seules les différences réelles entraînent une modification SQL, ce qui évite les migrations inutiles.
|
|
171
|
-
|
|
172
|
-
> **Note :**
|
|
173
|
-
> Il est interdit de déclarer une colonne à la fois `primary_key: true` et `unique: true` dans le schéma JS.
|
|
174
|
-
> **Explication :** En SQL, une clé primaire (`PRIMARY KEY`) est déjà unique par définition et impose la contrainte `UNIQUE` et `NOT NULL` sur la colonne. Ajouter explicitement `unique: true` en plus de `primary_key: true` est redondant et provoque une erreur SQL ("Multiple primary key defined").
|
|
175
|
-
>
|
|
176
|
-
> **En résumé :**
|
|
177
|
-
> - Utilisez seulement `primary_key: true` pour une colonne qui doit être la clé primaire.
|
|
178
|
-
> - Utilisez `unique: true` pour une colonne qui doit être unique mais n'est pas la clé primaire.
|
|
106
|
+
```javascript
|
|
107
|
+
const userModel = new Model('users', userSchema);
|
|
179
108
|
|
|
180
|
-
|
|
109
|
+
await Model.createAllTables();
|
|
110
|
+
await userModel.save({ email: 'user@example.com', status: 'active' });
|
|
181
111
|
|
|
112
|
+
const user = await userModel.findOne({ email: 'user@example.com' });
|
|
113
|
+
await userModel.delete({ email: 'user@example.com' });
|
|
114
|
+
```
|
|
182
115
|
|
|
183
|
-
|
|
116
|
+
## Model instances
|
|
184
117
|
|
|
185
|
-
|
|
186
|
-
// Synchronisation simple (ajout/renommage de colonnes, suppression de tables orphelines avec backup)
|
|
187
|
-
await Model.syncAllTables();
|
|
118
|
+
`ModelInstance` represents a row already loaded from the database.
|
|
188
119
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
120
|
+
- `updateOne(model)` updates the row
|
|
121
|
+
- `delete(model)` deletes the row using a filter
|
|
122
|
+
- `deleteOne()` deletes the instance row
|
|
123
|
+
- `customRequest(custom)` runs a custom query
|
|
192
124
|
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## Class Model
|
|
196
|
-
Représente un modèle de base de données.
|
|
197
|
-
* ### Constructeur :
|
|
198
|
-
* `Model(name, schema)` : Crée une instance de `Model`.
|
|
199
|
-
* `name (string)` : Le nom de la table de base de données.
|
|
200
|
-
* `schema (Schema)` : Le schéma de la table de base de données.
|
|
201
|
-
* ### Méthodes :
|
|
202
|
-
* `generateCreateTableStatement(schema)` : Génère une requête SQL pour créer une table.
|
|
203
|
-
* `save(data)` : Sauvegarde des données dans la table.
|
|
204
|
-
* `findOne(filter, fields)` : Trouve une entrée unique dans la table.
|
|
205
|
-
* `find(filter, fields)` : Trouve des entrées dans la table.
|
|
206
|
-
* `customRequest(custom)` : Exécute une requête SQL personnalisée.
|
|
207
|
-
* `delete(filter)` : Supprime une entrée de la table.
|
|
208
|
-
* `dropTable()` : Supprime la table si elle existe.
|
|
209
|
-
* `generate_uuid()` : Génère un UUID unique pour le modèle.
|
|
210
|
-
* `createAllTables()` : **Crée toutes les tables dans le bon ordre selon les dépendances de clés étrangères.** (statique)
|
|
211
|
-
* ### Exemple
|
|
212
125
|
```javascript
|
|
213
|
-
const
|
|
214
|
-
// Après avoir instancié tous les modèles :
|
|
215
|
-
await Model.createAllTables(); // Crée toutes les tables dans le bon ordre
|
|
216
|
-
|
|
217
|
-
// Sauvegarder des données
|
|
218
|
-
await userModel.save({ token: 'abc123', mdp: 'password' });
|
|
126
|
+
const userInstance = new ModelInstance('users', { email: 'user@example.com' });
|
|
219
127
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
// Supprimer une entrée
|
|
224
|
-
await userModel.delete({ token: 'abc123' });
|
|
128
|
+
await userInstance.updateOne({ status: 'inactive' });
|
|
129
|
+
await userInstance.deleteOne();
|
|
225
130
|
```
|
|
226
|
-
## Class ModelInstance
|
|
227
|
-
Représente une instance d'un modèle de base de données.
|
|
228
|
-
* ### Constructeur:
|
|
229
|
-
* `ModelInstance(name, data)` : Crée une instance de `ModelInstance`.
|
|
230
|
-
* `name (string)` : Le nom de la table de base de données.
|
|
231
|
-
* `data (Object)` : Les données de l'instance.
|
|
232
|
-
* ### Méthodes :
|
|
233
|
-
* `updateOne(model)` : Met à jour une entrée unique dans la table.
|
|
234
|
-
* `delete(model)` : Supprime une entrée unique dans la table.
|
|
235
|
-
* `customRequest(custom)` : Exécute une requête SQL personnalisée.
|
|
236
|
-
* ### Exemple
|
|
237
|
-
```javascript
|
|
238
|
-
const userInstance = new ModelInstance('users', { token: 'abc123', mdp: 'password' });
|
|
239
131
|
|
|
240
|
-
|
|
241
|
-
|
|
132
|
+
## SQL types
|
|
133
|
+
|
|
134
|
+
`sqlTypeMap` exposes the common SQL types.
|
|
242
135
|
|
|
243
|
-
// Supprimer des données
|
|
244
|
-
await userInstance.delete({ token: 'abc123' });
|
|
245
|
-
```
|
|
246
|
-
# Types SQL
|
|
247
|
-
Le module fournit une map des types SQL courants via `sqlTypeMap`.
|
|
248
|
-
## Type
|
|
249
|
-
* String
|
|
250
|
-
* Number
|
|
251
|
-
* Boolean
|
|
252
|
-
* Object
|
|
253
|
-
* Array
|
|
254
|
-
* Now
|
|
255
|
-
* Float
|
|
256
|
-
* Text
|
|
257
|
-
* DateTime
|
|
258
|
-
* Timestamp
|
|
259
|
-
* ### Exemple
|
|
260
136
|
```javascript
|
|
261
137
|
console.log(sqlTypeMap.String); // "VARCHAR"
|
|
262
138
|
```
|
|
263
|
-
## Conclusion
|
|
264
|
-
Le module `sql-connector` fournit une interface simple et efficace pour interagir avec une base de données MySQL, permettant de définir des schémas, de gérer des connexions, et de manipuler des données de manière intuitive.
|
|
265
139
|
|
|
266
|
-
|
|
267
|
-
|
|
140
|
+
## Client
|
|
141
|
+
|
|
142
|
+
`client` is a shared object meant to host reusable application functions.
|
|
268
143
|
|
|
269
|
-
### Utilisation
|
|
270
|
-
Pour ajouter une fonction à l'objet client, vous pouvez simplement définir une nouvelle propriété sur l'objet et lui assigner une fonction.
|
|
271
|
-
* ### Exemple
|
|
272
144
|
```javascript
|
|
273
145
|
module.exports = client => {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
146
|
+
client.checkServer = () => {
|
|
147
|
+
if (server.isLaunch()) {
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return 0;
|
|
152
|
+
};
|
|
279
153
|
};
|
|
280
154
|
```
|
|
281
|
-
### Avantages
|
|
282
|
-
* `Centralisation` : Toutes les fonctions liées à des opérations spécifiques peuvent être centralisées dans un seul objet, ce qui facilite la gestion et l'organisation du code.
|
|
283
|
-
* `Réutilisabilité` : Les fonctions stockées dans l'objet `client` peuvent être facilement réutilisées dans différentes parties de l'application.
|
|
284
|
-
* `Modularité` : En utilisant un objet pour stocker des fonctions, il est plus facile de maintenir et de mettre à jour le code, car les fonctions peuvent être ajoutées, modifiées ou supprimées sans affecter d'autres parties de l'application.
|
|
285
155
|
|
|
286
|
-
##
|
|
287
|
-
|
|
156
|
+
## Summary
|
|
157
|
+
|
|
158
|
+
sql-connector provides a small layer to connect to MySQL, describe schemas, synchronize tables, and manipulate data with typed models.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Documentation du module sql-connector
|
|
2
|
+
|
|
3
|
+
[English](../../README.md) | Français
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
## Importation
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const { Schema, connect, logout, Model, ModelInstance, client, sqlTypeMap } = require('sql-connector');
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Connexion à la base
|
|
14
|
+
|
|
15
|
+
`connect(config)` ouvre une connexion MySQL à partir d'un objet de configuration compatible avec mysql2.
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const config = {
|
|
19
|
+
host: 'localhost',
|
|
20
|
+
port: 3306,
|
|
21
|
+
user: 'root',
|
|
22
|
+
password: 'password',
|
|
23
|
+
database: 'mydatabase'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
await connect(config);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`logout()` ferme proprement la connexion.
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
await logout();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Schéma
|
|
36
|
+
|
|
37
|
+
`Schema` décrit la structure d'une table. Chaque champ peut utiliser les propriétés suivantes.
|
|
38
|
+
|
|
39
|
+
| Propriété | Type | Description |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| type | `SqlType` ou `{ name: SqlType }` | Type SQL du champ |
|
|
42
|
+
| length | `number` | Longueur maximale |
|
|
43
|
+
| required | `boolean` | Champ obligatoire |
|
|
44
|
+
| default | `any` | Valeur par défaut |
|
|
45
|
+
| unique | `boolean` | Valeur unique |
|
|
46
|
+
| auto_increment | `boolean` | Auto-incrément |
|
|
47
|
+
| foreignKey | `string` | Référence de clé étrangère |
|
|
48
|
+
| enum | `string[]` | Liste de valeurs autorisées |
|
|
49
|
+
| primary_key | `boolean` | Clé primaire |
|
|
50
|
+
| customize | `string` | Options SQL additionnelles |
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
const userSchema = new Schema({
|
|
54
|
+
id: {
|
|
55
|
+
type: Number,
|
|
56
|
+
auto_increment: true,
|
|
57
|
+
primary_key: true
|
|
58
|
+
},
|
|
59
|
+
email: {
|
|
60
|
+
type: String,
|
|
61
|
+
length: 255,
|
|
62
|
+
unique: true,
|
|
63
|
+
required: true
|
|
64
|
+
},
|
|
65
|
+
status: {
|
|
66
|
+
type: String,
|
|
67
|
+
enum: ['active', 'inactive', 'pending'],
|
|
68
|
+
default: 'pending'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Synchronisation des tables
|
|
74
|
+
|
|
75
|
+
`Model.syncAllTables()` compare les schémas JS avec la base et applique uniquement les différences utiles.
|
|
76
|
+
|
|
77
|
+
- 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
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
await Model.syncAllTables();
|
|
84
|
+
await Model.syncAllTables({ dangerousSync: true });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
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.
|
|
88
|
+
|
|
89
|
+
## Modèles
|
|
90
|
+
|
|
91
|
+
`Model` représente une table SQL.
|
|
92
|
+
|
|
93
|
+
Méthodes principales:
|
|
94
|
+
|
|
95
|
+
- `save(data)` pour insérer une ligne
|
|
96
|
+
- `findOne(filter, fields)` pour récupérer une seule entrée
|
|
97
|
+
- `find(filter, fields)` pour récupérer plusieurs entrées
|
|
98
|
+
- `findAll(options)` pour les recherches avancées
|
|
99
|
+
- `count(filter)` pour compter les lignes
|
|
100
|
+
- `customRequest(custom)` pour exécuter une requête SQL personnalisée
|
|
101
|
+
- `delete(filter)` pour supprimer une entrée
|
|
102
|
+
- `dropTable()` pour supprimer la table
|
|
103
|
+
- `generate_uuid()` pour générer un UUID unique
|
|
104
|
+
- `Model.createAllTables()` pour créer toutes les tables dans le bon ordre
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const userModel = new Model('users', userSchema);
|
|
108
|
+
|
|
109
|
+
await Model.createAllTables();
|
|
110
|
+
await userModel.save({ email: 'user@example.com', status: 'active' });
|
|
111
|
+
|
|
112
|
+
const user = await userModel.findOne({ email: 'user@example.com' });
|
|
113
|
+
await userModel.delete({ email: 'user@example.com' });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Instances de modèle
|
|
117
|
+
|
|
118
|
+
`ModelInstance` représente une ligne déjà chargée depuis la base.
|
|
119
|
+
|
|
120
|
+
- `updateOne(model)` met à jour la ligne
|
|
121
|
+
- `delete(model)` supprime la ligne avec un filtre
|
|
122
|
+
- `deleteOne()` supprime la ligne de l'instance
|
|
123
|
+
- `customRequest(custom)` exécute une requête personnalisée
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
const userInstance = new ModelInstance('users', { email: 'user@example.com' });
|
|
127
|
+
|
|
128
|
+
await userInstance.updateOne({ status: 'inactive' });
|
|
129
|
+
await userInstance.deleteOne();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Types SQL
|
|
133
|
+
|
|
134
|
+
`sqlTypeMap` expose les types SQL les plus courants.
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
console.log(sqlTypeMap.String); // "VARCHAR"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Client
|
|
141
|
+
|
|
142
|
+
`client` est un objet partagé pensé pour centraliser des fonctions applicatives réutilisables.
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
module.exports = client => {
|
|
146
|
+
client.checkServer = () => {
|
|
147
|
+
if (server.isLaunch()) {
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return 0;
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Résumé
|
|
157
|
+
|
|
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlagie/sql-connector",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.8",
|
|
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": {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# **Release v1.4.8 — sql-connector**
|
|
2
|
+
|
|
3
|
+
## **Présentation**
|
|
4
|
+
Cette version poursuit la refonte de `sql-connector` avec une documentation plus lisible, une séparation claire entre les langues, et plusieurs améliorations de fiabilité sur les modèles et la génération SQL.
|
|
5
|
+
|
|
6
|
+
## **Nouveautés**
|
|
7
|
+
|
|
8
|
+
### Documentation bilingue
|
|
9
|
+
- La documentation a été restructurée pour séparer plus clairement le contenu en français et en anglais.
|
|
10
|
+
- Le README racine sert désormais de point d'entrée rapide vers la documentation.
|
|
11
|
+
- Le dossier `docs/` est utilisé pour regrouper les pages de documentation par langue.
|
|
12
|
+
|
|
13
|
+
### Synchronisation et schémas
|
|
14
|
+
- `Model.syncAllTables()` continue de comparer le schéma JS avec la base et applique les différences utiles de manière plus sûre.
|
|
15
|
+
- La longueur par défaut est désormais gérée correctement pour les types `VARCHAR` et `INT` quand `length` n'est pas défini.
|
|
16
|
+
- Une erreur explicite est levée si un champ est déclaré à la fois `primary_key` et `unique`.
|
|
17
|
+
|
|
18
|
+
### Modèles et requêtes
|
|
19
|
+
- `updateOne()` ignore automatiquement les champs `undefined`.
|
|
20
|
+
- Les mises à jour utilisent une condition `WHERE` plus robuste, en privilégiant les clés primaires quand elles sont disponibles.
|
|
21
|
+
- La génération des conditions SQL a été améliorée pour mieux normaliser les chaînes, les dates et les valeurs JSON.
|
|
22
|
+
|
|
23
|
+
### Dépendances et maintenance
|
|
24
|
+
- `glob` a été ajouté pour améliorer la détection des fichiers de backup SQL.
|
|
25
|
+
- Les logs de synchronisation et d'exécution ont été renforcés pour faciliter le débogage.
|
|
26
|
+
|
|
27
|
+
## **Migration depuis v1.4.5**
|
|
28
|
+
|
|
29
|
+
### Points principaux
|
|
30
|
+
1. La documentation a été réorganisée autour d'un accès plus simple par langue.
|
|
31
|
+
2. Les opérations de mise à jour de modèles sont maintenant plus fiables.
|
|
32
|
+
3. Les règles de génération SQL sont plus strictes et plus prévisibles.
|
|
33
|
+
|
|
34
|
+
### Recommandation
|
|
35
|
+
- Vérifiez vos schémas si vous utilisiez des champs sans `length`, des mises à jour avec des valeurs `undefined`, ou des colonnes déclarées à la fois `primary_key` et `unique`.
|
|
36
|
+
|
|
37
|
+
## **Documentation**
|
|
38
|
+
Pour plus de détails, consultez la documentation du projet dans `README.md` et le dossier `docs/`.
|
|
39
|
+
|
|
40
|
+
## **Liens utiles**
|
|
41
|
+
- [GitHub Repository](https://github.com/lagie-marin/sql-connector)
|
|
42
|
+
- [npm Package](https://www.npmjs.com/package/@mlagie/sql-connector)
|
|
43
|
+
- [Issues](https://github.com/lagie-marin/sql-connector/issues)
|
package/src/models/Model.js
CHANGED
|
@@ -604,7 +604,7 @@ class Model {
|
|
|
604
604
|
getConnexion().promise().query(sql_request).then((rows) => {
|
|
605
605
|
if (rows.length == 0) return resolve(0);
|
|
606
606
|
|
|
607
|
-
resolve(new ModelInstance(this.name,
|
|
607
|
+
resolve(new ModelInstance(this.name, rows[0], this.schema));
|
|
608
608
|
}).catch((err) => {
|
|
609
609
|
error(`Error executing query: ${err}`);
|
|
610
610
|
return;
|
|
@@ -632,7 +632,7 @@ class Model {
|
|
|
632
632
|
await getConnexion().promise().query(custom).then((rows) => {
|
|
633
633
|
if (rows.length == 0) return resolve(0);
|
|
634
634
|
|
|
635
|
-
resolve(new ModelInstance(this.name,
|
|
635
|
+
resolve(new ModelInstance(this.name, rows[0], this.schema));
|
|
636
636
|
}).catch((err) => {
|
|
637
637
|
error(`Error executing query: ${err}`);
|
|
638
638
|
return;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
const { error } = require("@mlagie/logger");
|
|
1
|
+
const { error, logs } = 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
|
+
const util = require("util");
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Represents an instance of a database model.
|
|
@@ -15,23 +16,38 @@ class ModelInstance {
|
|
|
15
16
|
* @param {Object|null} [schema=null] The schema for the instance, if available.
|
|
16
17
|
*/
|
|
17
18
|
constructor(name, data, schema = null) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
Object.defineProperties(this, {
|
|
20
|
+
name: {
|
|
21
|
+
value: name,
|
|
22
|
+
writable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
enumerable: false
|
|
25
|
+
},
|
|
26
|
+
data: {
|
|
27
|
+
value: data,
|
|
28
|
+
writable: true,
|
|
29
|
+
configurable: true,
|
|
30
|
+
enumerable: true
|
|
31
|
+
},
|
|
32
|
+
schema: {
|
|
33
|
+
value: schema,
|
|
34
|
+
writable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
enumerable: false
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getRecordData() {
|
|
42
|
+
return Array.isArray(this.data) ? this.data[0] ?? this.data : this.data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
toJSON() {
|
|
46
|
+
return this.getRecordData();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[util.inspect.custom]() {
|
|
50
|
+
return this.getRecordData();
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
/**
|
|
@@ -42,13 +58,50 @@ class ModelInstance {
|
|
|
42
58
|
* @throws {Error} Throws an error if the update fails.
|
|
43
59
|
*/
|
|
44
60
|
async updateOne(model) {
|
|
45
|
-
const
|
|
61
|
+
const setClause = generateCondition(formatObject(model), true);
|
|
62
|
+
|
|
63
|
+
// prefer primary key(s) in WHERE to avoid mismatches on nullable/text fields
|
|
64
|
+
let whereClause;
|
|
65
|
+
try {
|
|
66
|
+
const rec = this.getRecordData();
|
|
67
|
+
const schemaDict = this.schema && this.schema.schemaDict ? this.schema.schemaDict : null;
|
|
68
|
+
if (schemaDict) {
|
|
69
|
+
const pkKeys = Object.entries(schemaDict).filter(([k, v]) => v && v.primary_key === true).map(([k]) => k);
|
|
70
|
+
if (pkKeys.length > 0) {
|
|
71
|
+
const pkObj = {};
|
|
72
|
+
for (const k of pkKeys) {
|
|
73
|
+
if (rec && Object.prototype.hasOwnProperty.call(rec, k)) pkObj[k] = rec[k];
|
|
74
|
+
}
|
|
75
|
+
if (Object.keys(pkObj).length > 0) whereClause = generateCondition(formatObject(pkObj), false, this.schema);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!whereClause) whereClause = generateCondition(formatObject(rec), false, this.schema);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
whereClause = generateCondition(formatObject(this.getRecordData()), false, this.schema);
|
|
81
|
+
}
|
|
82
|
+
const sql_request = `UPDATE ${this.name} SET ${setClause} WHERE ${whereClause}`;
|
|
83
|
+
|
|
84
|
+
// log SQL for debugging why update may not match
|
|
85
|
+
try { logs(`ModelInstance.updateOne SQL -> ${sql_request}`); } catch (e) {}
|
|
46
86
|
|
|
47
|
-
await getConnexion().promise().query(sql_request).catch((err) => {
|
|
87
|
+
const [result] = await getConnexion().promise().query(sql_request).catch((err) => {
|
|
48
88
|
error(`Error executing query: ${err}`);
|
|
49
89
|
throw err;
|
|
50
90
|
});
|
|
51
|
-
|
|
91
|
+
|
|
92
|
+
const affected = result && (result.affectedRows !== undefined ? result.affectedRows : 0);
|
|
93
|
+
|
|
94
|
+
// Update in-memory data if DB was modified
|
|
95
|
+
if (affected > 0) {
|
|
96
|
+
const record = this.getRecordData();
|
|
97
|
+
if (Array.isArray(this.data)) {
|
|
98
|
+
if (this.data[0] && typeof this.data[0] === 'object') Object.assign(this.data[0], model);
|
|
99
|
+
} else if (record && typeof record === 'object') {
|
|
100
|
+
Object.assign(this.data, model);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return affected;
|
|
52
105
|
}
|
|
53
106
|
|
|
54
107
|
/**
|
|
@@ -79,7 +132,7 @@ class ModelInstance {
|
|
|
79
132
|
* @throws {Error} Throws an error if the deletion fails.
|
|
80
133
|
*/
|
|
81
134
|
async deleteOne() {
|
|
82
|
-
const sql_request = `DELETE FROM ${this.name} WHERE ${generateCondition(formatObject(this.
|
|
135
|
+
const sql_request = `DELETE FROM ${this.name} WHERE ${generateCondition(formatObject(this.getRecordData()))}`;
|
|
83
136
|
|
|
84
137
|
return new Promise((resolve, reject) => {
|
|
85
138
|
getConnexion().promise().query(sql_request).then((rows) => {
|
|
@@ -104,7 +157,7 @@ class ModelInstance {
|
|
|
104
157
|
await getConnexion().promise().query(custom).then((rows) => {
|
|
105
158
|
if (rows.length == 0) return resolve(0);
|
|
106
159
|
|
|
107
|
-
resolve(new ModelInstance(this.name,
|
|
160
|
+
resolve(new ModelInstance(this.name, rows[0], this.schema));
|
|
108
161
|
}).catch((err) => {
|
|
109
162
|
error(`Error executing query: ${err}`);
|
|
110
163
|
return;
|
|
@@ -2,13 +2,28 @@ module.exports = function (obj) {
|
|
|
2
2
|
for (const key in obj) {
|
|
3
3
|
if (obj.hasOwnProperty(key)) {
|
|
4
4
|
const value = obj[key];
|
|
5
|
+
if (value instanceof Date) {
|
|
6
|
+
// convert Date to MySQL DATETIME (no timezone)
|
|
7
|
+
obj[key] = value.toISOString().slice(0, 19).replace('T', ' ');
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
if (typeof value === "string") {
|
|
6
|
-
|
|
12
|
+
// remove surrounding quotes if present and unescape
|
|
13
|
+
let v = value.trim();
|
|
14
|
+
// remove wrapping double or single quotes
|
|
15
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
16
|
+
v = v.slice(1, -1);
|
|
17
|
+
}
|
|
18
|
+
// unescape common escaped quotes
|
|
19
|
+
v = v.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
20
|
+
obj[key] = v;
|
|
21
|
+
continue;
|
|
7
22
|
}
|
|
23
|
+
|
|
8
24
|
if (typeof value === "object") {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.replace(/'/g, "\\'");
|
|
25
|
+
// stringify objects and escape single quotes for SQL safety
|
|
26
|
+
obj[key] = JSON.stringify(value).replace(/'/g, "\\'");
|
|
12
27
|
}
|
|
13
28
|
}
|
|
14
29
|
}
|
|
@@ -29,37 +29,79 @@ module.exports = function (filter, isUpdate = false, schema = null) {
|
|
|
29
29
|
});
|
|
30
30
|
if (uniqueKeys.length > 0) {
|
|
31
31
|
return uniqueKeys.map(key => {
|
|
32
|
-
|
|
32
|
+
let value = filter[key];
|
|
33
|
+
// normalize strings that may contain surrounding quotes or escaped quotes
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
value = value.trim();
|
|
36
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
37
|
+
value = value.slice(1, -1);
|
|
38
|
+
}
|
|
39
|
+
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
40
|
+
}
|
|
33
41
|
if (Array.isArray(value)) {
|
|
34
|
-
return `${key} IN (${value.map(v => `
|
|
42
|
+
return `${key} IN (${value.map(v => `'${String(v).replace(/'/g, "\\'")}'`).join(", ")})`;
|
|
35
43
|
}
|
|
36
44
|
if (typeof value === "object" || (typeof value === "string" && value.trim().startsWith("{") && value.trim().endsWith("}"))) {
|
|
37
45
|
const jsonVal = typeof value === "string" ? value : JSON.stringify(value);
|
|
38
|
-
return `JSON_CONTAINS(${key}, '${jsonVal}')`;
|
|
46
|
+
return `JSON_CONTAINS(${key}, '${String(jsonVal).replace(/'/g, "\\'")}')`;
|
|
39
47
|
}
|
|
40
48
|
if (value === null || value === "null") return `${key} IS NULL`;
|
|
41
|
-
|
|
49
|
+
// if string looks like an ISO datetime, convert to MySQL DATETIME format
|
|
50
|
+
if (typeof value === 'string' && /T/.test(value)) {
|
|
51
|
+
let val = value.replace(/\.\d+Z$/,'').replace(/Z$/,'').replace('T',' ');
|
|
52
|
+
return `${key} = '${String(val).replace(/'/g, "\\'")}'`;
|
|
53
|
+
}
|
|
54
|
+
return `${key} = ${typeof value === "string" ? `'${String(value).replace(/'/g, "\\'")}'` : value}`;
|
|
42
55
|
}).join(" AND ");
|
|
43
56
|
}
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
// Comportement par défaut
|
|
47
60
|
const conditions = filteredKeys.map((key, index) => {
|
|
48
|
-
|
|
61
|
+
let value = filteredValues[index];
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
value = value.trim();
|
|
64
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
65
|
+
value = value.slice(1, -1);
|
|
66
|
+
}
|
|
67
|
+
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
68
|
+
}
|
|
49
69
|
|
|
50
70
|
if (Array.isArray(value)) {
|
|
51
|
-
return `${key} IN (${value.map(v => `
|
|
71
|
+
return `${key} IN (${value.map(v => `'${String(v).replace(/'/g, "\\'")}'`).join(", ")})`;
|
|
52
72
|
}
|
|
53
73
|
if (typeof value === "object" || (typeof value === "string" && value.trim().startsWith("{") && value.trim().endsWith("}"))) {
|
|
54
74
|
const jsonVal = typeof value === "string" ? value : JSON.stringify(value);
|
|
55
75
|
if (isUpdate) {
|
|
56
|
-
return `${key} = '${jsonVal}'`;
|
|
76
|
+
return `${key} = '${String(jsonVal).replace(/'/g, "\\'")}'`;
|
|
57
77
|
}
|
|
58
|
-
return `JSON_CONTAINS(${key}, '${jsonVal}')`;
|
|
78
|
+
return `JSON_CONTAINS(${key}, '${String(jsonVal).replace(/'/g, "\\'")}')`;
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
if ((value === null || value === "null") && isUpdate == false) return `${key} IS NULL`;
|
|
62
|
-
|
|
82
|
+
|
|
83
|
+
// handle date-like strings when schema tells us the field is temporal
|
|
84
|
+
const fieldDef = schema && schema.schemaDict ? schema.schemaDict[key] : null;
|
|
85
|
+
let fieldType = null;
|
|
86
|
+
if (fieldDef) {
|
|
87
|
+
if (fieldDef.type && fieldDef.type.name !== undefined) fieldType = fieldDef.type.name;
|
|
88
|
+
else if (fieldDef.type !== undefined) fieldType = fieldDef.type;
|
|
89
|
+
else if (fieldDef && fieldDef.name !== undefined) fieldType = fieldDef.name;
|
|
90
|
+
}
|
|
91
|
+
const normalizedFieldType = String(fieldType ?? "").toLowerCase();
|
|
92
|
+
const isDateLike = ["date", "datetime", "timestamp", "now"].includes(normalizedFieldType);
|
|
93
|
+
|
|
94
|
+
if (typeof value === "string") {
|
|
95
|
+
let val = value;
|
|
96
|
+
// strip surrounding quotes if any (double safety)
|
|
97
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) val = val.slice(1,-1);
|
|
98
|
+
// if ISO timestamp with Z, convert to MySQL DATETIME format
|
|
99
|
+
if (isDateLike && /T/.test(val)) {
|
|
100
|
+
val = val.replace(/\.\d+Z$/,'').replace(/Z$/,'').replace('T',' ');
|
|
101
|
+
}
|
|
102
|
+
return `${key} = '${String(val).replace(/'/g, "\\'")}'`;
|
|
103
|
+
}
|
|
104
|
+
return `${key} = ${value}`;
|
|
63
105
|
}).join(` ${isUpdate == false ? "AND" : ","} `);
|
|
64
106
|
return conditions;
|
|
65
107
|
}
|