@mlagie/sql-connector 1.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/index.js ADDED
@@ -0,0 +1,701 @@
1
+ const { logs, error, sql } = require("@mlagie/logger");
2
+ const mysql = require("mysql2");
3
+ let client = {};
4
+
5
+ const sqlType = {
6
+ String: "String",
7
+ Number: "Number",
8
+ Boolean: "Boolean",
9
+ Date: "Date",
10
+ Object: "Object",
11
+ Array: "Array",
12
+ Now: "Now",
13
+ Float: "Float",
14
+ Text: "Text",
15
+ DateTime: "DateTime",
16
+ Timestamp: "Timestamp",
17
+ };
18
+
19
+ const sqlTypeMap = {
20
+ String: 'VARCHAR',
21
+ Number: 'INT',
22
+ Boolean: 'BOOLEAN',
23
+ Date: 'DATETIME',
24
+ Object: 'JSON',
25
+ Array: 'VARCHAR',
26
+ Now: 'NOW()',
27
+ Float: 'FLOAT',
28
+ Text: 'TEXT',
29
+ DateTime: "DATETIME",
30
+ Timestamp: "TIMESTAMP",
31
+ };
32
+
33
+ /**
34
+ * Represents a database schema.
35
+ *
36
+ * @example
37
+ * const transferSchema = new Schema({
38
+ * token: {
39
+ * type: String,
40
+ * length: 50
41
+ * },
42
+ * mdp: {
43
+ * type: String,
44
+ * length: 15
45
+ * }
46
+ * });
47
+ */
48
+ class Schema {
49
+ constructor(schemaDict) {
50
+ this.schemaDict = schemaDict;
51
+ }
52
+ }
53
+
54
+ let connexion = null;
55
+
56
+ /**
57
+ * Establishes a connection to the database using a given configuration.
58
+ * @param {Object} config Database connection configuration.
59
+ * @param {string} config.host The database host.
60
+ * @param {number} config.port The database port.
61
+ * @param {string} config.user The username for the connection.
62
+ * @param {string} config.password The password for the connection.
63
+ * @param {string} config.database The name of the database.
64
+ * @returns {Promise<void>} A promise that resolves when the connection is established.
65
+ *
66
+ * @example
67
+ * const config = {
68
+ * host: 'localhost',
69
+ * port: 6666,
70
+ * user: 'root',
71
+ * password: 'password',
72
+ * database: 'mydatabase'
73
+ * };
74
+ * await connect(config);
75
+ */
76
+ async function connect(config) {
77
+ connexion = mysql.createPool(config);
78
+ }
79
+
80
+ /**
81
+ * Closes the database connection.
82
+ * This function terminates the active database connection and records a logging message
83
+ * indicating whether the shutdown succeeded or failed.
84
+ *
85
+ * @returns {Promise<void>} A promise that resolves when the connection is closed.
86
+ *
87
+ * @example
88
+ * await logout();
89
+ */
90
+ async function logout() {
91
+ connexion.end(err => {
92
+ if (err) {
93
+ error(`Error closing database connection: ${err}`);
94
+ return;
95
+ }
96
+
97
+ logs("Database connection closed");
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Generates an SQL_request condition from a filter object.
103
+ *
104
+ * @param {Object} filter An object containing the key-value pairs to use to generate the condition.
105
+ * @param {boolean} [isUpdate=false] A flag to determine whether the condition is used in an update request.
106
+ * @returns {string} A character string representing the generated SQL_request condition.
107
+ *
108
+ * @example
109
+ * const filter = { id: 1, name: "John" };
110
+ * const condition = generateCondition(filter);
111
+ * console.log(condition); // 'id = 1 AND name = "John"'
112
+ *
113
+ * @example
114
+ * const filter = { id: 1, name: "John" };
115
+ * const condition = generateCondition(filter, true);
116
+ * console.log(condition); // 'id = 1, name = "John"'
117
+ */
118
+ function generateCondition(filter, isUpdate = false, schema = null) {
119
+ const keys = Object.keys(filter);
120
+ const values = Object.values(filter);
121
+
122
+ if (!isUpdate && schema && schema.schemaDict) {
123
+ const uniqueKeys = keys.filter(key => {
124
+ const field = schema.schemaDict[key];
125
+ return field && field.unique === true;
126
+ });
127
+
128
+ if (uniqueKeys.length > 0) {
129
+ return uniqueKeys.map(key => {
130
+ const value = filter[key];
131
+ if (Array.isArray(value)) {
132
+ return `${key} IN (${value.map(v => `"${v}"`).join(", ")})`;
133
+ }
134
+ if (typeof value === "object" || (typeof value === "string" && value.trim().startsWith("{") && value.trim().endsWith("}"))) {
135
+ const jsonVal = typeof value === "string" ? value : JSON.stringify(value);
136
+ return `JSON_CONTAINS(${key}, '${jsonVal}')`;
137
+ }
138
+ if (value === null || value === "null") return `${key} IS NULL`;
139
+ return `${key} = ${typeof value === "string" ? `"${value}"` : value}`;
140
+ }).join(" AND ");
141
+ }
142
+ }
143
+
144
+ // Comportement par défaut
145
+ const conditions = keys.map((key, index) => {
146
+ const value = values[index];
147
+
148
+ if (Array.isArray(value)) {
149
+ return `${key} IN (${value.map(v => `"${v}"`).join(", ")})`;
150
+ }
151
+ if (typeof value === "object" || (typeof value === "string" && value.trim().startsWith("{") && value.trim().endsWith("}"))) {
152
+ const jsonVal = typeof value === "string" ? value : JSON.stringify(value);
153
+ if (isUpdate) {
154
+ return `${key} = '${jsonVal}'`;
155
+ }
156
+ return `JSON_CONTAINS(${key}, '${jsonVal}')`;
157
+ }
158
+
159
+ if ((value === null || value === "null") && isUpdate == false) return `${key} IS NULL`;
160
+ return `${key} = ${typeof value === "string" ? `"${value}"` : value}`;
161
+ }).join(` ${isUpdate == false ? "AND" : ","} `);
162
+
163
+ return conditions;
164
+ }
165
+
166
+ function generateValueSQL(value) {
167
+ return value.map(item => {
168
+ if (typeof item === "string") return `"${item.replace(/"/g, '\\"')}"`;
169
+ if (typeof item === "object") return `"${item}"`;
170
+ return item;
171
+ }).join(", ");
172
+ }
173
+
174
+ function formatObject(obj) {
175
+ for (const key in obj) {
176
+ if (obj.hasOwnProperty(key)) {
177
+ const value = obj[key];
178
+ if (typeof value === "string") {
179
+ obj[key] = value.replace(/"/g, '\\"');
180
+ }
181
+ if (typeof value === "object") {
182
+ obj[key] = JSON.stringify(value)
183
+ .replace(/"/g, '\\"')
184
+ .replace(/'/g, "\\'");
185
+ }
186
+ }
187
+ }
188
+ return obj;
189
+ }
190
+
191
+ /**
192
+ * Replaces values ​​from one dictionary with those from another dictionary if the keys match.
193
+ * @param {Object} dict The original dictionary containing the values ​​to replace.
194
+ * @param {Object} replacementDict The dictionary containing the replacement values.
195
+ * @returns {Object} A new dictionary with the replaced values.
196
+ *
197
+ * @example
198
+ * const dict = { a: 1, b: 2, c: 3 };
199
+ * const replacementDict = { b: 20, c: 30 };
200
+ * const result = replaceValues(dict, replacementDict);
201
+ * console.log(result); // { a: 1, b: 20, c: 30 }
202
+ */
203
+ function replaceValues(dict, replacementDict) {
204
+ const resultDict = {};
205
+
206
+ for (const key in dict) {
207
+ if (key in replacementDict) resultDict[key] = replacementDict[key];
208
+ else resultDict[key] = dict[key];
209
+ }
210
+
211
+ return resultDict;
212
+ }
213
+
214
+ const reservedKeywords = ['ADD', 'ALL', 'ALTER', 'AND', 'AS', 'ASC', 'BETWEEN', 'BY', 'CASE', 'CHECK', 'COLUMN', 'CONSTRAINT', 'CREATE', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DEFAULT', 'DELETE', 'DESC', 'DISTINCT', 'DROP', 'ELSE', 'END', 'ESCAPE', 'EXCEPT', 'EXISTS', 'FOR', 'FOREIGN', 'FROM', 'FULL', 'GROUP', 'HAVING', 'IN', 'INNER', 'INSERT', 'INTERSECT', 'INTO', 'IS', 'JOIN', 'LEFT', 'LIKE', 'LIMIT', 'NOT', 'NULL', 'ON', 'OR', 'ORDER', 'OUTER', 'PRIMARY', 'REFERENCES', 'RIGHT', 'SELECT', 'SET', 'SOME', 'TABLE', 'THEN', 'UNION', 'UNIQUE', 'UPDATE', 'VALUES', 'WHEN', 'WHERE'];
215
+
216
+ /**
217
+ * Checks if a table name is a reserved keyword.
218
+ *
219
+ * @param {string} tableName Le nom de la table à vérifier.
220
+ * @returns {boolean} `true` si le nom de la table est un mot-clé réservé, sinon `false`.
221
+ *
222
+ * @example
223
+ * const isReserved = ifReservedKeywords('SELECT');
224
+ * console.log(isReserved); // true
225
+ *
226
+ * @example
227
+ * const isReserved = ifReservedKeywords('myTable');
228
+ * console.log(isReserved); // false
229
+ */
230
+ function ifReservedKeywords(tableName) {
231
+ if (reservedKeywords.includes(tableName.toUpperCase())) {
232
+ return true;
233
+ }
234
+ return false;
235
+ }
236
+
237
+ function getFieldType(field) {
238
+ if (typeof field === "object") {
239
+ if (field.type.name !== undefined) return field.type.name;
240
+ else return field.type;
241
+ } else {
242
+ if (field.name !== undefined) return field.name;
243
+ else return field;
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Represents a database model.
249
+ * @class
250
+ */
251
+ class Model {
252
+ static sqlTypeMap = sqlTypeMap;
253
+ static pendingModels = [];
254
+
255
+ /**
256
+ * Creates an instance of Model.
257
+ * @param {string} name The name of the database table.
258
+ * @param {Object} schema The schema of the database table.
259
+ */
260
+ constructor(name, schema) {
261
+ this.name = name;
262
+ this.schema = schema;
263
+ // Ajoute le modèle à la liste d'attente pour la création différée
264
+ Model.pendingModels.push(this);
265
+ }
266
+
267
+ /**
268
+ * Crée toutes les tables dans l'ordre correct en fonction des foreign keys.
269
+ * @returns {Promise<void>}
270
+ */
271
+ static async createAllTables() {
272
+ // Dépendances : {table: [tables dont elle dépend]}
273
+ const dependencies = {};
274
+ const modelMap = {};
275
+ for (const model of Model.pendingModels) {
276
+ modelMap[model.name] = model;
277
+ dependencies[model.name] = [];
278
+ for (const [fieldName, field] of Object.entries(model.schema.schemaDict)) {
279
+ if (field && field.foreignKey) {
280
+ // field.foreignKey peut être "autreTable(colonne)"
281
+ const refTable = field.foreignKey.split('(')[0].trim();
282
+ dependencies[model.name].push(refTable);
283
+ }
284
+ }
285
+ }
286
+
287
+ // Tri topologique
288
+ const sorted = [];
289
+ const visited = {};
290
+ function visit(table) {
291
+ if (visited[table] === true) return;
292
+ if (visited[table] === 'temp') throw new Error('Cyclic foreign key dependency detected');
293
+ visited[table] = 'temp';
294
+ for (const dep of dependencies[table]) {
295
+ if (modelMap[dep]) visit(dep);
296
+ }
297
+ visited[table] = true;
298
+ sorted.push(table);
299
+ }
300
+ for (const table of Object.keys(dependencies)) {
301
+ if (!visited[table]) visit(table);
302
+ }
303
+
304
+ // Création des tables dans l'ordre
305
+ for (const table of sorted) {
306
+ const model = modelMap[table];
307
+ await new Promise((resolve, reject) => {
308
+ connexion.query(model.generateCreateTableStatement(model.schema.schemaDict), (err) => {
309
+ if (err) {
310
+ error(`Error creating table: ${err} with table name: ${model.name}`);
311
+ return reject(err);
312
+ }
313
+ logs(`La table ${model.name} a été créé`);
314
+ resolve();
315
+ });
316
+ });
317
+ }
318
+ // Vide la liste d'attente
319
+ Model.pendingModels = [];
320
+ }
321
+
322
+ /**
323
+ * Generates an SQL_request statement to create a table based on the provided schema.
324
+ *
325
+ * @param {Object} schema The schema of the database table.
326
+ * @returns {string} A character string representing the SQL_request statement to create the table.
327
+ */
328
+ generateCreateTableStatement(schema) {
329
+ let foreignKey = [];
330
+ const columns = Object.keys(schema).map(fieldName => {
331
+ const field = schema[fieldName];
332
+ let lengthDefault = 255;
333
+
334
+ if (!field.type && typeof field == "object") throw new Error(`Field ${fieldName} has no type defined.`);
335
+
336
+ const fieldType = getFieldType(field);
337
+
338
+ if (field.type && typeof field == "object") {
339
+ if (!sqlTypeMap[fieldType]) throw new Error(`Field ${fieldName} has unsupported type ${fieldType}.`);
340
+
341
+ let columnDefinition = `${fieldName} ${sqlTypeMap[fieldType]}${sqlTypeMap[fieldType] == "VARCHAR" || sqlTypeMap[fieldType] == "INT" ? `(${field.length > 0 ? field.length : lengthDefault})` : ""}`;
342
+
343
+ if (field.required) columnDefinition += ' NOT NULL';
344
+ if (field.default !== undefined && field.default != null) columnDefinition += ` DEFAULT "${field.default}"`;
345
+ if (field.default === null) columnDefinition += ` DEFAULT NULL`;
346
+ if (field.unique) columnDefinition += ' UNIQUE';
347
+ if (field.auto_increment) columnDefinition += ' AUTO_INCREMENT';
348
+ if (field.foreignKey) foreignKey.push(`FOREIGN KEY (${fieldName}) REFERENCES ${field.foreignKey}`);
349
+ if (typeof field.customize === 'string' && field.customize.length != 0) columnDefinition += ` ${field.customize}`;
350
+ return columnDefinition;
351
+ }
352
+
353
+ if (!sqlTypeMap[fieldType]) throw new Error(`Field ${fieldName} has unsupported type ${field}`);
354
+
355
+ return `${fieldName} ${sqlTypeMap[fieldType] == "VARCHAR" ? `${sqlTypeMap[fieldType]}(${lengthDefault})` : sqlTypeMap[fieldType]}`;
356
+ });
357
+ if (ifReservedKeywords(this.name)) {
358
+ error("Error: Invalid table name. Please choose a different name that is not a reserved keyword in SQL_request");
359
+ return;
360
+ }
361
+ return `CREATE TABLE IF NOT EXISTS ${this.name} (${columns.join(', ')}${foreignKey.length > 0 ? ", " + foreignKey.join(', ') : ""}) ENGINE=InnoDB`;
362
+ }
363
+
364
+ /**
365
+ * Saves data to the database table.
366
+ * @param {Object} data The data to insert into the table.
367
+ * @returns {Promise<Object>} A promise that resolves with the result of the insertion.
368
+ * @throws {Error} Throws an error if the insert fails.
369
+ */
370
+ async save(data) {
371
+ const keys = Object.keys(data);
372
+ const sql_request = `INSERT INTO ${this.name} (${keys.join(', ')}) VALUES (${generateValueSQL(Object.values(data))})`;
373
+ sql(this.name, sql_request);
374
+ try {
375
+ const result = await connexion.promise().query(sql_request);
376
+ return result;
377
+ } catch (err) {
378
+ error(`Error inserting data into ${this.name}: ${err}`);
379
+ throw err;
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Récupère plusieurs entrées de la table.
385
+ * @param {Object} [options] - Options de requête (attributs, where, order, limit).
386
+ * @param {string[]} [options.attributes] - Champs à retourner.
387
+ * @param {Object} [options.where] - Filtres (clé/valeur).
388
+ * @param {Array} [options.order] - Ex: [['points', 'DESC']]
389
+ * @param {number} [options.limit] - Limite de résultats.
390
+ * @returns {Promise<Array<Object>>}
391
+ */
392
+ async findAll(options = {}) {
393
+ const {
394
+ attributes = ['*'],
395
+ where = undefined,
396
+ order = undefined,
397
+ limit = undefined
398
+ } = options;
399
+
400
+ let sql_request = `SELECT ${attributes.join(', ')} FROM ${this.name}`;
401
+ if (where) {
402
+ sql_request += ` WHERE ${generateCondition(formatObject(where))}`;
403
+ }
404
+ if (order && Array.isArray(order) && order.length > 0) {
405
+ const orderStr = order.map(([col, dir]) => `${col} ${dir}`).join(', ');
406
+ sql_request += ` ORDER BY ${orderStr}`;
407
+ }
408
+ if (limit) {
409
+ sql_request += ` LIMIT ${limit}`;
410
+ }
411
+
412
+ return new Promise((resolve, reject) => {
413
+ connexion.promise().query(sql_request).then(([rows]) => {
414
+ resolve(rows);
415
+ }).catch((err) => {
416
+ error(`Error executing findAll: ${err}`);
417
+ resolve([]);
418
+ });
419
+ });
420
+ }
421
+
422
+ /**
423
+ * Finds a unique entry in the database table based on the filter provided.
424
+ * @param {Object} filter An object containing the key-value pairs to use to generate the search condition.
425
+ * @param {string[]} [fields=["*"]] An array of field names to return in the result.
426
+ * @returns {Promise<ModelInstance|number>} A promise that resolves to a ModelInstance if an entry is found, otherwise 0.
427
+ */
428
+ async findOne(filter, fields = ["*"]) {
429
+ const sql_request = `SELECT ${fields.join(", ")} FROM ${this.name} WHERE ${generateCondition(formatObject(filter))}`;
430
+
431
+ return new Promise((resolve, reject) => {
432
+ connexion.promise().query(sql_request).then((rows) => {
433
+ if (rows.length == 0) return resolve(0);
434
+
435
+ resolve(new ModelInstance(this.name, Object.values(rows[0])[0], this.schema));
436
+ }).catch((err) => {
437
+ error(`Error executing query: ${err}`);
438
+ return 0;
439
+ });
440
+ });
441
+ }
442
+
443
+ /**
444
+ * Finds a record in the database based on the provided filter.
445
+ *
446
+ * @async
447
+ * @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.
448
+ * @param {Array<string>} [fields=["*"]] - The fields to select in the query. Defaults to selecting all fields.
449
+ * @returns {Promise<ModelInstance|number>} - A promise that resolves to a `ModelInstance` if a record is found, or `0` if no records match the filter.
450
+ *
451
+ * @example
452
+ * // Example usage:
453
+ * const filter = { id: 1 };
454
+ * const fields = ["id", "name"];
455
+ * MyTable.find(filter, fields).then((result) => {
456
+ * if (result === 0) {
457
+ * console.log("No records found.");
458
+ * } else {
459
+ * console.log("Record found:", result);
460
+ * }
461
+ * }).catch((err) => {
462
+ * console.error("Error:", err);
463
+ * });
464
+ */
465
+ async find(filter, fields = ["*"]) {
466
+ const sql_request = `SELECT ${fields.join(", ")} FROM ${this.name} WHERE ${generateCondition(formatObject(filter))}`;
467
+
468
+ return new Promise((resolve, reject) => {
469
+ connexion.promise().query(sql_request).then((rows) => {
470
+ if (rows.length == 0) return resolve(0);
471
+
472
+ resolve(new ModelInstance(this.name, Object.values(rows[0]), this.schema));
473
+ }).catch((err) => {
474
+ error(`Error executing query: ${err}`);
475
+ return;
476
+ });
477
+ });
478
+ }
479
+
480
+ /**
481
+ *
482
+ * @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.
483
+ * @returns {Promise<ModelInstance|number>} - A promise that resolves to a `ModelInstance` if a record is found, or `0` if no records match the filter.
484
+ */
485
+ async count(filter) {
486
+ return this.customRequest(`SELECT COUNT(*) as count FROM ${this.name} ${filter != undefined ? `WHERE ${generateCondition(formatObject(filter))}` : ""}`);
487
+ }
488
+
489
+ /**
490
+ * Runs a custom SQL_request query.
491
+ * @param {string} custom The custom SQL_request query to execute.
492
+ * @returns {Promise<void>} A promise that resolves when the query is executed.
493
+ * @throws {Error} Throws an error if query execution fails.
494
+ */
495
+ async customRequest(custom) {
496
+ return new Promise(async (resolve, reject) => {
497
+ await connexion.promise().query(custom).then((rows) => {
498
+ if (rows.length == 0) return resolve(0);
499
+
500
+ resolve(new ModelInstance(this.name, Object.values(rows[0]), this.schema));
501
+ }).catch((err) => {
502
+ error(`Error executing query: ${err}`);
503
+ return;
504
+ });
505
+ })
506
+ }
507
+
508
+ /**
509
+ * Supprime une entrée de la table SQL correspondant au filtre fourni.
510
+ *
511
+ * @async
512
+ * @function delete
513
+ * @param {Object} filter - Un objet représentant les conditions de filtre pour la suppression.
514
+ * @returns {Promise<number>} Une promesse qui se résout à 0 si aucune ligne n'a été supprimée,
515
+ * ou à une instance de ModelInstance représentant la ligne supprimée.
516
+ * @throws {Error} Lance une erreur si la requête SQL échoue.
517
+ */
518
+ async delete(filter) {
519
+ const sql_request = `DELETE FROM ${this.name} WHERE ${generateCondition(formatObject(filter))}`;
520
+
521
+ return new Promise((resolve, reject) => {
522
+ connexion.promise().query(sql_request).then((rows) => {
523
+ if (rows[1] != undefined) return resolve(0);
524
+
525
+ return resolve(1);
526
+ }).catch((err) => {
527
+ error(`Error executing query: ${err}`);
528
+ return 0;
529
+ });
530
+ });
531
+ }
532
+
533
+ /**
534
+ * Asynchronously drops a table if it exists in the database.
535
+ *
536
+ * This function constructs a SQL_request query to drop a table with the name specified
537
+ * by the `this.name` property. It then executes the query using a promise-based
538
+ * approach. If the query is successful, the result is logged to the console.
539
+ * If an error occurs during the execution of the query, an error message is logged.
540
+ *
541
+ * @returns {Promise<void>} A promise that resolves when the query execution is complete.
542
+ */
543
+ async dropTable() {
544
+ const sql_request = `DROP TABLE IF EXISTS ${this.name};`;
545
+
546
+ return new Promise((resolve, reject) => {
547
+ connexion.promise().query(sql_request).then((rows) => {
548
+ console.log(rows);
549
+ }).catch((err) => {
550
+ error(`Error executing query: ${err}`);
551
+ return;
552
+ });
553
+ })
554
+ }
555
+
556
+ /**
557
+ * Generates a unique UUID for the current model.
558
+ *
559
+ * This function generates a UUID using the SQL_request `UUID()` function and checks if the generated UUID
560
+ * already exists in the database for the current model. If the UUID is unique, it is returned.
561
+ * Otherwise, the function resolves to `null`.
562
+ *
563
+ * @returns {Promise<string|null>} A promise that resolves to a unique UUID string if successful, or `null` if an error occurs or the UUID is not unique.
564
+ *
565
+ * @example
566
+ * const uuid = await model.generate_uuid();
567
+ * if (uuid) {
568
+ * console.log(`Generated UUID: ${uuid}`);
569
+ * } else {
570
+ * console.log('Failed to generate a unique UUID.');
571
+ * }
572
+ *
573
+ * @throws {Error} If there is an error executing the SQL_request query.
574
+ */
575
+ async generate_uuid(var_uuid = "uuid") {
576
+ const uuid = (await connexion.promise().query("SELECT UUID();"))[0][0]["UUID()"];
577
+ const sql_request = `SELECT COUNT(*) FROM ${this.name} WHERE ${var_uuid}="${uuid}";`;
578
+
579
+ return new Promise((resolve, reject) => {
580
+ connexion.promise().query(sql_request).then((rows) => {
581
+ if (rows[0][0]['COUNT(*)'] == 0) return resolve(uuid);
582
+ resolve(null);
583
+ }).catch((err) => {
584
+ error(`Error executing query: ${err}`);
585
+ return null;
586
+ })
587
+ })
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Represents an instance of a database model.
593
+ * @class
594
+ */
595
+ class ModelInstance {
596
+ /**
597
+ * Creates an instance of ModelInstance.
598
+ * @param {string} name The name of the database table.
599
+ * @param {Object} data The instance data.
600
+ * @param {Object|null} [schema=null] The schema for the instance, if available.
601
+ */
602
+ constructor(name, data, schema = null) {
603
+ /**
604
+ * The name of the database table.
605
+ * @type {string}
606
+ */
607
+ this.name = name;
608
+
609
+ /**
610
+ * The instance data.
611
+ * @type {Object}
612
+ */
613
+ this.data = data;
614
+
615
+ /**
616
+ * The schema for the instance.
617
+ * @type {Object|null}
618
+ */
619
+ this.schema = schema;
620
+ }
621
+
622
+ /**
623
+ * Updates a single entry in the database table.
624
+ *
625
+ * @param {Object} model An object containing the key-value pairs to use for updating.
626
+ * @returns {int} A promise that resolves with updated data.
627
+ * @throws {Error} Throws an error if the update fails.
628
+ */
629
+ async updateOne(model) {
630
+ const sql_request = `UPDATE ${this.name} SET ${generateCondition(formatObject(model), true)} WHERE ${generateCondition(formatObject(this.data[0] != undefined ? this.data[0] : this.data), false, this.schema)}`;
631
+
632
+ await connexion.promise().query(sql_request).catch((err) => {
633
+ error(`Error executing query: ${err}`);
634
+ throw err;
635
+ });
636
+ return 1;
637
+ }
638
+
639
+ /**
640
+ * Deletes a single entry in the database table.
641
+ * @param {Object} model An object containing the key-value pairs to use for deletion.
642
+ * @returns {Promise<Object>} A promise that resolves with the data deleted.
643
+ * @throws {Error} Throws an error if the deletion fails.
644
+ */
645
+ async delete(filter) {
646
+ const sql_request = `DELETE FROM ${this.name} WHERE ${generateCondition(formatObject(filter))}`;
647
+
648
+ return new Promise((resolve, reject) => {
649
+ connexion.promise().query(sql_request).then((rows) => {
650
+
651
+ if (rows[1] != undefined) return resolve(0);
652
+
653
+ resolve(1);
654
+ }).catch((err) => {
655
+ error(`Error executing query: ${err}`);
656
+ return 0;
657
+ });
658
+ });
659
+ }
660
+
661
+ /**
662
+ * Deletes a single entry in the database table based on the instance data.
663
+ * @returns {Promise<number>} A promise that resolves to the number of rows deleted.
664
+ * @throws {Error} Throws an error if the deletion fails.
665
+ */
666
+ async deleteOne() {
667
+ const sql_request = `DELETE FROM ${this.name} WHERE ${generateCondition(formatObject(this.data[0] != undefined ? this.data[0] : this.data))}`;
668
+
669
+ return new Promise((resolve, reject) => {
670
+ connexion.promise().query(sql_request).then((rows) => {
671
+ if (rows[1] != undefined) return resolve(0);
672
+
673
+ resolve(1);
674
+ }).catch((err) => {
675
+ error(`Error executing query: ${err}`);
676
+ return 0;
677
+ });
678
+ });
679
+ }
680
+
681
+ /**
682
+ * Runs a custom SQL_request query.
683
+ * @param {string} custom The custom SQL_request query to execute.
684
+ * @returns {Promise<void>} A promise that resolves when the query is executed.
685
+ * @throws {Error} Throws an error if query execution fails.
686
+ */
687
+ async customRequest(custom) {
688
+ return new Promise(async (resolve, reject) => {
689
+ await connexion.promise().query(custom).then((rows) => {
690
+ if (rows.length == 0) return resolve(0);
691
+
692
+ resolve(new ModelInstance(this.name, Object.values(rows[0]), this.schema));
693
+ }).catch((err) => {
694
+ error(`Error executing query: ${err}`);
695
+ return;
696
+ });
697
+ });
698
+ }
699
+ }
700
+
701
+ module.exports = { Schema, connect, logout, Model, client, sqlType };