@mlagie/sql-connector 2.0.5 → 2.0.7

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,6 +1,7 @@
1
1
  name: Publish Package
2
2
 
3
3
  on:
4
+ workflow_dispatch:
4
5
  release:
5
6
  types: [published]
6
7
  push:
@@ -9,7 +10,6 @@ on:
9
10
  pull_request:
10
11
  branches:
11
12
  - main
12
- workflow_dispatch:
13
13
  jobs:
14
14
  check_security:
15
15
  runs-on: ubuntu-latest
@@ -33,38 +33,48 @@ jobs:
33
33
  integrity:
34
34
  runs-on: ubuntu-latest
35
35
  steps:
36
- - name: Checkout GH repository
37
- uses: actions/checkout@v6
38
- - name: setup node
39
- uses: actions/setup-node@v6
40
- with:
41
- node-version: 22
42
- - name: Install deps
43
- run: npm ci
44
- - name: Run integrity check
45
- continue-on-error: false
46
- run: |
47
- npm run test
36
+ - name: Checkout GH repository
37
+ uses: actions/checkout@v6
38
+ - name: setup node
39
+ uses: actions/setup-node@v6
40
+ with:
41
+ node-version: 22
42
+ - name: Install deps
43
+ run: npm ci
44
+ - name: Run integrity check
45
+ continue-on-error: false
46
+ run: |
47
+ npm run test
48
48
  publish:
49
- needs:
49
+ needs:
50
50
  - check_security
51
51
  - integrity
52
- if: github.event_name == 'release' || github.ref_name == 'main'
52
+ if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
53
53
  runs-on: ubuntu-latest
54
54
  environment: sqlc
55
55
  permissions:
56
56
  contents: read
57
- id-token: write
57
+ id-token: write # Requis pour la provenance (--provenance)
58
58
 
59
59
  steps:
60
- - uses: actions/checkout@v6
61
- - uses: actions/setup-node@v6
60
+ # 1. Récupérer le code source
61
+ - name: Checkout du code
62
+ uses: actions/checkout@v6
63
+
64
+ # 2. Configurer Node.js pour registry.npmjs.org
65
+ - name: Configurer Node.js
66
+ uses: actions/setup-node@v6
62
67
  with:
63
- node-version: 22
64
- registry-url: https://registry.npmjs.org
68
+ node-version: "22"
69
+ registry-url: "https://registry.npmjs.org/" # <-- Cible npmjs
65
70
  scope: "@mlagie"
66
- - name: Update npm and Publish
67
- run: |
68
- npm install -g npm@latest
69
- npm ci
70
- npm publish --provenance --access public
71
+
72
+ # 3. Installer les dépendances
73
+ - name: Installer les dépendances
74
+ run: npm ci
75
+
76
+ # 4. Publier sur le vrai registre npm
77
+ - name: Publier sur npmjs.com
78
+ run: npm publish --provenance --access public
79
+ env:
80
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md CHANGED
@@ -184,23 +184,51 @@ async function createUser(email, stat) {
184
184
 
185
185
  Retrieves entries from the table.
186
186
 
187
- - **Parameters** `options` *(Object)* - Query options
188
- - **Parameters** `options.select` *(string[])* - Fields to be returned.
189
- - **Parameters** `options.where` *(Object)* - Filters (key/value).
190
- - **Parameters** `options.order` *(Array)* - Ex: [['points', 'DESC']]
191
- - **Parameters** `options.limit` *(number)* - Limit of results.
187
+ - **Parameters** `options` *(Object)* - Query options.
188
+ - **Parameters** `options.select` *(Array<string|SelectAggregation>)* - Fields, aggregations, or transformations to be returned.
189
+ - **Parameters** `options.where` *(Object / string)* - Filtering conditions (key/value object or raw string condition).
190
+ - **Parameters** `options.groupBy` *(string[])* - Fields used to group results.
191
+ - **Parameters** `options.orderBy` *(Array<string|OrderByOption>)* - Sorting rules.
192
+ - **Parameters** `options.join` *(JoinOption / JoinOption[])* - Table join configuration structures.
193
+ - **Parameters** `options.limit` *(number)* - Maximum number of results to return.
192
194
  - **Returns** `Promise<Array<ModelInstance>>`
193
195
 
194
- ### find Options
196
+ ### Advanced `select` Options (`SelectAggregation`)
195
197
 
196
- | Option | Type | Description | Example |
197
- |------------|-----------------|----------------------------------------------------------|----------------------------------------------|
198
- | `select` | Array | Fields or transformations to retrieve | `['date_day']` |
199
- | `where` | Object / String | Filtering conditions | `{ project_id: 1 }` |
200
- | `groupBy` | Array | Fields used to group results | `['period']` |
201
- | `orderBy` | Array | Sorting rules | `[{ field: 'date_day', direction: 'DESC' }]` |
202
- | `having` | String | HAVING clause for aggregated queries | `'SUM(total_runs) > 100'` |
203
- | `limit` | Number | Limits the number of results | `100` |
198
+ Each element in the `select` array can be either a standard string (raw column name) or an object providing advanced SQL capabilities and aggregations:
199
+
200
+ | Property inside `select` object | Type | Description | Example / Generated SQL |
201
+ |---------------------------------|-------------------|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
202
+ | `col` | `string` | Selects a plain, unaggregated table column. | `{ col: 'email' }` $\rightarrow$ \`email\` |
203
+ | `sum` | `string` | Computes the sum of all numerical values in a column. | `{ sum: 'error' }` $\rightarrow$ `SUM(` \`error\``)` |
204
+ | `distinct` | `string` | Applies a DISTINCT constraint on the specified column. | `{ distinct: 'status' }` $\rightarrow$ `DISTINCT` \`status\` |
205
+ | `dateFormat` | `[string, string]`| Formats a Date column using standard MySQL formatting (Format: `[column, mysql_format_string]`). | `{ dateFormat: ['created_at', '%Y-%m'] }` $\rightarrow$ `DATE_FORMAT(` \`created_at\``, '%Y-%m')` |
206
+ | `count` | `string` | Standard row counting (ignores `NULL` values). | `{ count: 'id' }` $\rightarrow$ `COUNT(` \`id\``)` |
207
+ | `count` (Array) | `string[]` | Counts unique combinations across multiple columns (COUNT DISTINCT). | `{ count: ['team', 'source'] }` $\rightarrow$ `COUNT( DISTINCT` \`team\``,` \`source\``)` |
208
+ | `count` (Object) | `Object` | Automated conditional aggregation (`CASE WHEN`). Perfect for KPIs and status metrics. | `{ count: { deletedAt: null } }` $\rightarrow$ `COUNT(CASE WHEN` \`deletedAt\``= NULL THEN 1 END)` |
209
+ | `as` | `string` | Sets a custom output identifier or aggregation alias (SQL `AS`). | `{ count: 'id', as: 'total' }` $\rightarrow$ `COUNT(` \`id\``) AS` \`total\` |
210
+
211
+ ---
212
+
213
+ ### Complex Structured Options (`orderBy` & `join`)
214
+
215
+ #### OrderByOption
216
+
217
+ Enables explicit sorting across one or multiple columns:
218
+
219
+ - **field** `(string)`: The target column name to apply sorting on.
220
+ - **direction** `('ASC'\|'DESC')`: The sorting direction (Defaults to `'ASC'`).
221
+
222
+ #### JoinOption
223
+
224
+ Specifies one or multiple relational database table joins:
225
+
226
+ - **table** `(string)`: Target table name to join with.
227
+ - **on** `(string)`: Relational equation statement string (e.g., `"ProjectPipelines.project_id = Projects.id"`).
228
+ - **alias** `(string)` *(Optional)*: An alternative SQL alias name for the joined table.
229
+ - **type** `('INNER'\|'LEFT'\|'RIGHT')` *(Optional)*: SQL join modality (Defaults to `'INNER'`).
230
+
231
+ ---
204
232
 
205
233
  ## Example find
206
234
 
@@ -232,6 +260,71 @@ User.find({
232
260
  })
233
261
  ```
234
262
 
263
+ ### Advanced Examples using `find`
264
+
265
+ #### 1. Standard, Distinct, and Multi-Column Counting
266
+
267
+ Count global rows alongside multi-column unique combinations, such as identifying unique team and source pipelines:
268
+
269
+ ```js
270
+ const { MyTable } = require("./models");
271
+
272
+ const stats = await MyTable.find({
273
+ select: [
274
+ { count: 'id', as: 'total_pipelines' },
275
+ { count: ['cteam', 'csource'], as: 'unique_groups' } // Multi-column COUNT DISTINCT
276
+ ],
277
+ where: { csource: 'web' }
278
+ });
279
+ ```
280
+
281
+ #### 2. Conditional Aggregations (Active / Decommissioned KPI Metrics)
282
+
283
+ By passing an object to the `count` attribute, the ORM automatically structures a conditional `CASE WHEN` clause. This allows you to split different status counters into a single database trip:
284
+
285
+ ```js
286
+ const { MyTable } = require("./models");
287
+
288
+ const ppiStats = await MyTable.find({
289
+ select: [
290
+ { count: { deletedAt: null }, as: 'active' }, // Counts where deletedAt = NULL
291
+ { count: { status: 'SUCCESS' }, as: 'total_success' } // Counts where status = 'SUCCESS'
292
+ ],
293
+ where: {
294
+ source: 'jenkins',
295
+ team: 'GROUP-1'
296
+ }
297
+ });
298
+ ```
299
+
300
+ #### 3. Table Joins, Time Series Grouping, and Multi-Column Sorting
301
+
302
+ An advanced query orchestration combining left table joining, date formatting conversions, and sorting:
303
+
304
+ ```js
305
+ const { MyTable } = require("./models");
306
+
307
+ const history = await MyTable.find({
308
+ select: [
309
+ { col: 'Projects.name', as: 'project_name' },
310
+ { dateFormat: ['ProjectPipelines.created_at', '%Y-%m'], as: 'period' },
311
+ { count: 'ProjectPipelines.id', as: 'pipelines_count' }
312
+ ],
313
+ where: "ProjectPipelines.deletedAt IS NULL", // Raw condition strings are permitted
314
+ join: {
315
+ table: 'Projects',
316
+ on: 'ProjectPipelines.project_id = Projects.id',
317
+ type: 'LEFT'
318
+ },
319
+ groupBy: ['project_name', 'period'],
320
+ orderBy: [
321
+ { field: 'period', direction: 'DESC' },
322
+ { field: 'project_name', direction: 'ASC' }
323
+ ],
324
+ limit: 50
325
+ });
326
+ ```
327
+
235
328
  ## count function
236
329
 
237
330
  Counts the number of records matching the given filter.
package/docs/fr/README.md CHANGED
@@ -147,25 +147,53 @@ async function createUser(email, stat) {
147
147
 
148
148
  ## Fonction find
149
149
 
150
- Récupère des entrées de la table.
151
-
152
- - **Parameters** `options` *(Object)* - Options de requête
153
- - **Parameters** `options.select` *(string[])* - Champs à renvoyer.
154
- - **Parameters** `options.where` *(Object)* - Filtre (key/value).
155
- - **Parameters** `options.order` *(Array)* - Ex: [['points', 'DESC']]
156
- - **Parameters** `options.limit` *(number)* - Limite de résultats.
157
- - **Returns** `Promise<Array<ModelInstance>>`
158
-
159
- ### Options de find
160
-
161
- | Option | Type | Description | Example |
162
- |------------|-----------------|----------------------------------------------------------|----------------------------------------------|
163
- | `select` | Array | Champs ou transformations à récupérer | `['date_day']` |
164
- | `where` | Object / String | Conditions de filtrage | `{ project_id: 1 }` |
165
- | `groupBy` | Array | Champs utilisés pour regrouper les résultats | `['period']` |
166
- | `orderBy` | Array | Règles de tri | `[{ field: 'date_day', direction: 'DESC' }]` |
167
- | `having` | String | Clause HAVING pour les requêtes agrégées | `'SUM(total_runs) > 100'` |
168
- | `limit` | Number | Limite le nombre de résultats | `100` |
150
+ Récupère des enregistrements de la table.
151
+
152
+ - **Paramètres** `options` *(Object)* Options de la requête.
153
+ - **Paramètres** `options.select` *(Array<string|SelectAggregation>)* Champs, agrégations ou transformations à retourner.
154
+ - **Paramètres** `options.where` *(Object / string)* Conditions de filtrage (objet clé/valeur ou clause brute sous forme de chaîne).
155
+ - **Paramètres** `options.groupBy` *(string[])* Champs utilisés pour grouper les résultats.
156
+ - **Paramètres** `options.orderBy` *(Array<string|OrderByOption>)* Règles de tri.
157
+ - **Paramètres** `options.join` *(JoinOption / JoinOption[])* – Structures de configuration pour les jointures de tables.
158
+ - **Paramètres** `options.limit` *(number)* – Nombre maximal de résultats à retourner.
159
+ - **Retourne** `Promise<Array<ModelInstance>>`
160
+
161
+ ### Options avancées de `select` (`SelectAggregation`)
162
+
163
+ 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 :
164
+
165
+ | Propriété dans l'objet `select` | Type | Description | Exemple / SQL Généré |
166
+ |---------------------------------|-------------------|-----------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
167
+ | `col` | `string` | Sélectionne une colonne de table simple, sans agrégation. | `{ col: 'email' }` $\rightarrow$ \`email\` |
168
+ | `sum` | `string` | Calcule la somme de toutes les valeurs numériques d'une colonne. | `{ sum: 'error' }` $\rightarrow$ `SUM(`\`error\``)` |
169
+ | `distinct` | `string` | Applique une contrainte DISTINCT sur la colonne spécifiée. | `{ distinct: 'status' }` $\rightarrow$ `DISTINCT `\`status\` |
170
+ | `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')` |
171
+ | `count` | `string` | Comptage de lignes standard (ignore les valeurs `NULL`). | `{ count: 'id' }` $\rightarrow$ `COUNT(`\`id\``)` |
172
+ | `count` (Array) | `string[]` | Calcule le nombre de combinaisons uniques sur plusieurs colonnes (COUNT DISTINCT). | `{ count: ['team', 'source'] }` $\rightarrow$ `COUNT( DISTINCT `\`team\``, `\`source\`` )` |
173
+ | `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)` |
174
+ | `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\` |
175
+
176
+ ---
177
+
178
+ ### Options complexes structurées (`orderBy` & `join`)
179
+
180
+ #### OrderByOption
181
+
182
+ Permet d'appliquer un tri explicite sur une ou plusieurs colonnes :
183
+
184
+ - **field** `(string)` : Le nom de la colonne cible sur laquelle appliquer le tri.
185
+ - **direction** `('ASC'\|'DESC')` : Le sens du tri (Par défaut : `'ASC'`).
186
+
187
+ #### JoinOption
188
+
189
+ Spécifie une ou plusieurs jointures de tables relationnelles :
190
+
191
+ - **table** `(string)` : Nom de la table cible à joindre.
192
+ - **on** `(string)` : Chaîne de caractères représentant la condition de jointure (ex: `"MyTable.project_id = Projects.id"`).
193
+ - **alias** `(string)` *(Optionnel)* : Un alias SQL alternatif pour la table jointe.
194
+ - **type** `('INNER'\|'LEFT'\|'RIGHT')` *(Optionnel)* : Type de jointure SQL (Par défaut : `'INNER'`).
195
+
196
+ ---
169
197
 
170
198
  ## Exemple find
171
199
 
@@ -197,6 +225,73 @@ await User.find({
197
225
  })
198
226
  ```
199
227
 
228
+ ### Exemples avancés avec `find`
229
+
230
+ #### 1. Comptage standard, distinct et multi-colonnes
231
+
232
+ Comptez les lignes globales parallèlement à des combinaisons uniques multi-colonnes, comme l'identification des couples uniques d'équipes et de sources de pipelines :
233
+
234
+ ```js
235
+ const { MyTable } = require("./models");
236
+
237
+ const stats = await MyTable.find({
238
+ select: [
239
+ { count: 'id', as: 'total_pipelines' },
240
+ { count: ['cteam', 'csource'], as: 'unique_groups' } // COUNT DISTINCT multi-colonnes
241
+ ],
242
+ where: { csource: 'web' }
243
+ });
244
+ ```
245
+
246
+ #### 2. Agrégations conditionnelles (Indicateurs KPIs Actifs / Inactifs)
247
+
248
+ 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 :
249
+
250
+ ```js
251
+ const { ProjectPipeline } = require("./models");
252
+
253
+ const ppiStats = await ProjectPipeline.find({
254
+ select: [
255
+ { count: { deletedAt: null }, as: 'active' }, // Compte les lignes où deletedAt = NULL
256
+ { count: { status: 'SUCCESS' }, as: 'total_success' } // Compte les lignes où status = 'SUCCESS'
257
+ ],
258
+ where: {
259
+ source: 'jenkins',
260
+ team: 'GROUP-1'
261
+ }
262
+ });
263
+
264
+ // Format du tableau de sortie retourné : [{ active: 6, total_success: 42 }]
265
+ ```
266
+
267
+ #### 3. Jointures de tables, groupement temporel et tri multi-colonnes
268
+
269
+ Une orchestration de requête avancée combinant une jointure gauche (LEFT JOIN), des conversions de formats de date et des tris :
270
+
271
+ ```js
272
+ const { ProjectPipeline } = require("./models");
273
+
274
+ const history = await ProjectPipeline.find({
275
+ select: [
276
+ { col: 'Projects.name', as: 'project_name' },
277
+ { dateFormat: ['MyTable.created_at', '%Y-%m'], as: 'period' },
278
+ { count: 'MyTable.id', as: 'pipelines_count' }
279
+ ],
280
+ where: "MyTable.deletedAt IS NULL", // Les chaînes de conditions brutes sont autorisées
281
+ join: {
282
+ table: 'Projects',
283
+ on: 'MyTable.project_id = Projects.id',
284
+ type: 'LEFT'
285
+ },
286
+ groupBy: ['project_name', 'period'],
287
+ orderBy: [
288
+ { field: 'period', direction: 'DESC' },
289
+ { field: 'project_name', direction: 'ASC' }
290
+ ],
291
+ limit: 50
292
+ });
293
+ ```
294
+
200
295
  ## Fonction count
201
296
 
202
297
  Compte le nombre d'enregistrements correspondant au filtre donné.
package/package.json CHANGED
@@ -1,18 +1,23 @@
1
1
  {
2
2
  "name": "@mlagie/sql-connector",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Le module sql-connector permet de gérer les connexions à une base de données MySQL, de définir des schémas de tables, et d'interagir avec les données de manière simple et efficace.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
7
+ "test": "rm -r coverage ; node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
8
+ "test:publish": "npm publish --dry-run",
8
9
  "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
9
- "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
10
+ "test:coverage": "rm -r coverage ; node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
10
11
  "security": "npx eslint . --max-warnings 0 --ignore-pattern './tests/*'"
11
12
  },
12
13
  "repository": {
13
14
  "type": "git",
14
15
  "url": "git+https://github.com/lagie-marin/sql-connector.git"
15
16
  },
17
+ "publishConfig": {
18
+ "registry": "https://registry.npmjs.org/",
19
+ "access": "public"
20
+ },
16
21
  "keywords": [
17
22
  "sql",
18
23
  "schema",
@@ -0,0 +1,80 @@
1
+ # Release v2.0.6 — Architectural Property Isolation, Query Defusal & Test Coverage Expansion
2
+
3
+ This emergency structural release addresses a critical data-shadowing bug within the `ModelInstance` hydration cycle where active database column values (e.g., fields named `name`, `schema`, or `data`) overrode the core internal engine structures, mangling runtime query generation. It also introduces a comprehensive overhaul of the test suite to secure maximum coverage.
4
+
5
+ ## 🛠️ The Bug Defusal (Why this was critical)
6
+
7
+ When `ModelInstance` mapped rows dynamically into getters/setters, a column named `name` from the database would mask `this.name` (the structural reference to the SQL Table Name).
8
+
9
+ As a result, an injection or ingestion update would mistakenly compile syntax failures like:
10
+
11
+ ```sql
12
+ UPDATE my_value SET `deletedAt` = ... -- Mangled: Used row data instead of Table Name
13
+ ```
14
+
15
+ By migrating all runtime configuration tokens to protected underscored properties (`_tableName`, `_data`, `_schema`), the instance context is now completely hermetic against database row schema payloads.
16
+
17
+ ## Massive Test Coverage Expansion
18
+
19
+ To ensure the stability and robustness of this release, the unit test suites for both `Model` and `ModelInstance` have been significantly hardened:
20
+
21
+ - **Edge-Case & Fallback Testing**: Added specialized tests to force controlled failures in deep `try/catch` blocks, guaranteeing that the global engine fallback workflows function flawlessly under corrupted data scenarios.
22
+
23
+ - **SQL Mechanics Validation**: Built targeted unit tests covering schema dictionary mismatches, complex conditional mapping (e.g., table joins filtering mixed array types), and secondary logic paths inside `updateOne()`, `delete()`, and `deleteOne()`.
24
+
25
+ - `Database Simulation Coverage`: Implemented strict simulation sequences verifying UUID collision rejections (ensuring `generate_uuid()` handles duplicates properly) and missing response payloads (such as undefined `affectedRows`).
26
+
27
+ - `Zero-Crash Guarantees`: Hardened utility methods like `getFieldType()` against historical JavaScript edge cases (such as parsing `null` values safely).
28
+
29
+ ## What Was Improved
30
+
31
+ - `Protected Context Isolation`: Migrated core metadata targets in `ModelInstance.js` to `_tableName`, `_data`, and `_schema`. Database columns can no longer shadow engine properties.
32
+
33
+ - `Tiret/Hyphen Database Safety`: Wrapped table calls explicitly in SQL backticks (\`) inside `updateOne`, `delete`, and `deleteOne` routines. Tables named with hyphens (like my_value) no longer trigger syntax violations.
34
+
35
+ - **Rehydration Mapping Consistency**: Standardized downstream sub-instantiations (like `customRequest`) to feed data strictly using the isolated `_tableName` descriptor.
36
+
37
+ ## File Blueprint Updates
38
+
39
+ `ModelInstance.js`
40
+
41
+ The updated architecture encapsulates the internal core parameters like this:
42
+
43
+ ```js
44
+ class ModelInstance {
45
+ constructor(name, data, schema = null) {
46
+ Object.defineProperties(this, {
47
+ _tableName: { // 🔒 Fully protected from row data collisions
48
+ value: name,
49
+ writable: true,
50
+ configurable: true,
51
+ enumerable: false
52
+ },
53
+ _data: {
54
+ value: data,
55
+ writable: true,
56
+ configurable: true,
57
+ enumerable: true
58
+ },
59
+ _schema: {
60
+ value: schema,
61
+ writable: true,
62
+ configurable: true,
63
+ enumerable: false
64
+ }
65
+ });
66
+
67
+ // Dynamic row mapping safely bound without property leakage...
68
+ }
69
+
70
+ async updateOne(model) {
71
+ const setClause = generateCondition(formatObject(model), true);
72
+ let whereClause = /* ... evaluation matrix using this._schema & this.getRecordData() ... */;
73
+
74
+ // Encapsulated Table execution with explicit backticks
75
+ const sql_request = `UPDATE \`${this._tableName}\` SET ${setClause} WHERE ${whereClause}`;
76
+
77
+ // ... execute transaction safely
78
+ }
79
+ }
80
+ ```
@@ -0,0 +1,38 @@
1
+ # Release v2.0.7 — COUNT Polymorphism, Aggregation Alias Security & Test Coverage Overhaul
2
+
3
+ This major release upgrades the query engine by introducing full polymorphic support for the `COUNT` aggregation function (supporting standard, multi-column distinct, and conditional `CASE WHEN` operations). It also fixes a key-aliasing limitation during raw aggregations and strengthens query-building validation boundaries through an extensive unit testing overhaul.
4
+
5
+ ## COUNT Polymorphism (Why this evolution matters)
6
+
7
+ Previously, the query engine only accepted a raw string parameter for `COUNT` instructions. The engine now dynamically evaluates three distinct structures (String, Array, Object) passed directly from your selection attributes:
8
+
9
+ - **Multi-Column Distinct Counting**: Passing an array automatically compiles a unified multi-column unique combination statement, removing the need for manual raw SQL workarounds.
10
+
11
+ - **Conditional Counting (`CASE WHEN`)**: Passing a key/value object cleanly compiles a conditional statement. This allows you to isolate state and status indicators (KPIs) in a single database round-trip without twisting your global query filters.
12
+
13
+ As a result, complex nullity checks or status distributions are generated natively:
14
+
15
+ ```js
16
+ -- Automated nullity indicator compilation example:
17
+ COUNT(CASE WHEN `deletedAt` = NULL THEN 1 END)
18
+ ```
19
+
20
+ ## Test Coverage Expansion
21
+
22
+ To guarantee the stability of these new mechanics and prevent structural regressions, the global unit testing footprint has been exhaustively expanded:
23
+
24
+ - **Multi-Count Validation**: Added dedicated test pipelines ensuring the syntax of `COUNT(DISTINCT column1, column2)` is perfectly isolated from standard counters.
25
+
26
+ - **Temporal Nullity Filtering**: Implemented test scenarios covering both `{ deletedAt: null }` and `{ deletedAt: "NOT NULL" }` parameters to verify string state evaluations.
27
+
28
+ - **Unsupported Input Interception**: Added strict `throw new Error` type boundaries to guarantee that injecting non-compliant values (like booleans or numbers) results in a clean, controlled application crash.
29
+
30
+ - **Zero-Leak Key Assurances**: Added strict payload assertions ensuring no raw unmapped backticked SQL expressions can escape into the final Node.js JSON output object.
31
+
32
+ ## What Was Improved
33
+
34
+ - **Automatic Aggregation Fallback Alias**: Enhanced the `buildField` logic inside `buildQuery.js`. If an explicit `as` descriptor is omitted during a column sum (sum), the engine automatically fallbacks to using the targeted column name as the extraction property, blocking raw SQL formula leaks into the final row keys.
35
+
36
+ - **Orchestrated Order Sorting**: Hardened the `options.orderBy` evaluator array loop to cleanly isolate and support both raw column identifier strings and structured sorting configuration objects `{ field, direction }`.
37
+
38
+ Documentation Cleanup: Fully aligned the core developer usage guide and documentation files within `README.md` to reflect the updated parameter boundaries and capabilities.
package/socket.yml ADDED
@@ -0,0 +1,4 @@
1
+ version: 2
2
+ project:
3
+ # Masquer les alertes des outils de dev/test dans le rapport
4
+ ignoreDevDependencies: true
@@ -4,8 +4,7 @@ const { getConnexion } = require("../db/connexion");
4
4
  const generateCondition = require("../utils/generateCondition");
5
5
  const formatObject = require("../utils/formatObject");
6
6
  const { ModelInstance } = require("./ModelInstance");
7
- const { buildSelect, buildQueryParts } = require("../utils/buildQuery");
8
- const util = require("util");
7
+ const { buildSelect, buildQueryParts } = require("../utils/buildQuery/buildQuery");
9
8
  const { getSafe, setSafe } = require("../utils/security/safe");
10
9
  const { escapeIdentifier, escapeIdentifierList } = require("../utils/sql");
11
10
 
@@ -14,10 +13,8 @@ function getFieldType(field) {
14
13
  if (field.type && field.type.name !== undefined) return field.type.name;
15
14
  else if (field.type !== undefined) return field.type;
16
15
  return undefined;
17
- } else {
18
- if (field && field.name !== undefined) return field.name;
19
- else return field;
20
16
  }
17
+ if (field && field.name !== undefined) return field.name;
21
18
  }
22
19
 
23
20
  function isDateLikeType(fieldType) {
@@ -99,7 +96,7 @@ function getColumnDefinition(fieldName, field) {
99
96
  if (defaultDefinition !== null) colDef += ` ${defaultDefinition}`;
100
97
  if (field.unique) colDef += ' UNIQUE';
101
98
  if (field.auto_increment) colDef += ' AUTO_INCREMENT';
102
- if (field.primary_key) colDef += ' PRIMARY KEY';
99
+ if (field.primary_key) colDef += ' PRIMARY KEY'
103
100
  if (typeof field.customize === 'string' && field.customize.length != 0) colDef += ` ${field.customize}`;
104
101
  return `${escapeIdentifier(fieldName)} ${colDef}`;
105
102
  }
@@ -213,18 +210,6 @@ class Model {
213
210
  return `CREATE TABLE IF NOT EXISTS ${escapeIdentifier(this.name)} (${columns.join(', ')}${foreignKey.length > 0 ? ", " + foreignKey.join(', ') : ""}) ENGINE=InnoDB`;
214
211
  }
215
212
 
216
- getRecordData() {
217
- return Array.isArray(this.data) ? this.data[0] ?? this.data : this.data;
218
- }
219
-
220
- toJSON() {
221
- return this.getRecordData()?.data;
222
- }
223
-
224
- [util.inspect.custom]() {
225
- return this.getRecordData();
226
- }
227
-
228
213
  /**
229
214
  * Saves data to the database table.
230
215
  * @param {Object} data The data to insert into the table.
@@ -246,23 +231,40 @@ class Model {
246
231
 
247
232
  /**
248
233
  * @typedef {Object} SelectAggregation
249
- * @property {string} [sum] - The name of the column to sum (e.g., "total_runs").
250
- * @property {string} [count] - The name of the column to count.
251
- * @property {string[]} [dateFormat] - Array with [column, format] (e.g., ["date_day", "%Y-%m-%d"]).
252
- * @property {string} as - The output alias for the SQL field (e.g., "total_runs" or "period").
234
+ * @property {string} [col] - Le nom brut de la colonne à récupérer sans agrégation.
235
+ * @property {string} [sum] - Le nom de la colonne à additionner (ex: "total_runs").
236
+ * @property {string} [distinct] - Applique le mot-clé DISTINCT sur la colonne spécifiée.
237
+ * @property {string | string[] | Object} [count] - Compte les lignes selon le format fourni :
238
+ * - `string`: Compte toutes les valeurs non-nulles de cette colonne (ex: "id").
239
+ * - `string[]`: Compte les combinaisons uniques de plusieurs colonnes (COUNT DISTINCT col1, col2).
240
+ * - `Object`: Comptage conditionnel (CASE WHEN clé = valeur THEN 1 END). Ex: { deletedAt: null }.
241
+ * @property {string[]} [dateFormat] - Tableau au format [colonne, format_mysql] (ex: ["date_day", "%Y-%m-%d"]).
242
+ * @property {string} [as] - Alias d'extraction SQL pour le champ (ex: "total_runs" ou "period").
243
+ */
244
+
245
+ /**
246
+ * @typedef {Object} OrderByOption
247
+ * @property {string} field - Le nom de la colonne sur laquelle appliquer le tri.
248
+ * @property {'ASC'|'DESC'} [direction] - La direction du tri (Par défaut : 'ASC').
249
+ */
250
+
251
+ /**
252
+ * @typedef {Object} JoinOption
253
+ * @property {string} table - Le nom de la table cible avec laquelle effectuer la jointure.
254
+ * @property {string} on - La condition de correspondance de la jointure (ex: "ProjectPipelines.project_id = Projects.id").
255
+ * @property {string} [alias] - Alias SQL optionnel à donner à la table jointe.
256
+ * @property {'INNER'|'LEFT'|'RIGHT'} [type] - Le type de jointure SQL à appliquer (Par défaut : 'INNER').
253
257
  */
254
258
 
255
259
  /**
256
- * Retrieves multiple entries from the table.
257
- * @param {Object} [options] - Query options (attributes, where, order, limit).
258
- * @param {Array<string|SelectAggregation>} [options.select] - Fields to return.
259
- * @param {Object} [options.where] - Filters (key/value).
260
- * @param {Array} [options.order] - Example: [['points', 'DESC']]
261
- * @param {number} [options.limit] - Result limit.
262
- * @param {Object} [options.join] - Join options.
263
- * @param {String} [options.join.table] - Table to join.
264
- * @param {String} [options.join.on] - Join condition.
265
- * @param {String} [options.join.alias] - Alias for the joined table.
260
+ * Récupère plusieurs entrées de la table correspondante.
261
+ * @param {Object} [options] - Options de configuration de la requête SQL.
262
+ * @param {Array<string|SelectAggregation>} [options.select] - Liste des champs, transformations ou agrégations à retourner.
263
+ * @param {Object|string} [options.where] - Filtres structurés (objet clé/valeur) ou clause WHERE brute sous forme de chaîne.
264
+ * @param {string[]} [options.groupBy] - Tableau de colonnes ou d'expressions pour grouper les résultats (ex: ["period"]).
265
+ * @param {Array<string|OrderByOption>} [options.orderBy] - Règles de tri des résultats.
266
+ * @param {number} [options.limit] - Limite maximale du nombre de lignes à retourner.
267
+ * @param {JoinOption | JoinOption[]} [options.join] - Option(s) de jointure avec d'autres tables de la base de données.
266
268
  * @returns {Promise<Array<ModelInstance>>}
267
269
  */
268
270
  async find(options = {}) {
@@ -346,7 +348,7 @@ class Model {
346
348
  return resolve(1);
347
349
  }).catch((err) => {
348
350
  error(`Error executing query delete: ${err}`);
349
- return 0;
351
+ reject(err)
350
352
  });
351
353
  });
352
354
  }