xcraft-core-utils 4.20.0 → 4.21.2

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Aperçu
4
4
 
5
- Le module `xcraft-core-utils` est une bibliothèque utilitaire centrale du framework Xcraft qui fournit un ensemble complet d'outils et de classes pour diverses opérations courantes. Il regroupe des utilitaires pour la manipulation de données, la cryptographie, la gestion de fichiers, les opérations asynchrones, la mise en cache, et bien plus encore.
5
+ Le module `xcraft-core-utils` est une bibliothèque utilitaire centrale du framework Xcraft qui fournit un ensemble complet d'outils et de classes pour diverses opérations courantes. Il regroupe des utilitaires pour la manipulation de données, la cryptographie, la gestion de fichiers, les opérations asynchrones, la synchronisation, la mise en cache, les files d'attente de jobs, et bien plus encore.
6
6
 
7
7
  ## Sommaire
8
8
 
@@ -28,11 +28,12 @@ Le module expose une collection d'utilitaires organisés par domaine fonctionnel
28
28
  - **Cache et performance** : `RankedCache`
29
29
  - **Manipulation de chaînes** : `string`, `regex`
30
30
  - **Logging** : `log`
31
- - **Opérations asynchrones** : `async`
32
31
 
33
32
  ## Fonctionnement global
34
33
 
35
- Ce module agit comme une boîte à outils centralisée pour l'écosystème Xcraft. Chaque utilitaire est conçu pour être autonome tout en s'intégrant parfaitement avec les autres composants du framework. Les utilitaires supportent les patterns asynchrones avec `gigawatts` et sont optimisés pour les performances dans un environnement distribué.
34
+ Ce module agit comme une boîte à outils centralisée pour l'écosystème Xcraft. Chaque utilitaire est conçu pour être autonome tout en s'intégrant parfaitement avec les autres composants du framework. Les utilitaires supportent les patterns asynchrones avec [gigawatts] et sont optimisés pour les performances dans un environnement distribué.
35
+
36
+ Le module ne contient pas d'acteurs Elf ou Goblin : il s'agit uniquement d'une librairie utilitaire importée par d'autres modules.
36
37
 
37
38
  ## Exemples d'utilisation
38
39
 
@@ -41,14 +42,17 @@ Ce module agit comme une boîte à outils centralisée pour l'écosystème Xcraf
41
42
  ```javascript
42
43
  const {ArrayCollector} = require('xcraft-core-utils');
43
44
 
44
- const collector = new ArrayCollector(resp, 100, (entries, resp) => {
45
- // Traitement des données collectées
46
- console.log('Données collectées:', entries);
45
+ const collector = new ArrayCollector(resp, 100, async (entries, resp) => {
46
+ // Traitement des données collectées par clé
47
+ for (const [key, values] of Object.entries(entries)) {
48
+ console.log(`Clé: ${key}`, values);
49
+ }
47
50
  });
48
51
 
49
- // Ajout de données
50
- collector.grab('key1', ['data1', 'data2']);
51
- collector.grab('key2', ['data3']);
52
+ collector.grab('entityA', [{id: 1}]);
53
+ collector.grab('entityA', [{id: 2}]);
54
+ collector.grab('entityB', [{id: 3}]);
55
+ // La fonction onCollect est déclenchée après 100ms maximum
52
56
  ```
53
57
 
54
58
  ### Gestion de files d'attente avec JobQueue
@@ -61,8 +65,11 @@ const runner = function* (job, next) {
61
65
  yield setTimeout(next, 1000);
62
66
  };
63
67
 
64
- const queue = new JobQueue('processing', runner, 3);
65
- queue.push({id: 1, data: 'example'});
68
+ const queue = new JobQueue('processing', runner, 3, {
69
+ priorityGroup: 'highPrio',
70
+ waitOn: ['lowPrio'],
71
+ });
72
+ queue.push({id: 'job-1', payload: 'example'});
66
73
  ```
67
74
 
68
75
  ### Cryptographie et hachage
@@ -70,20 +77,38 @@ queue.push({id: 1, data: 'example'});
70
77
  ```javascript
71
78
  const {crypto, hash} = require('xcraft-core-utils');
72
79
 
73
- // Génération de mots de passe
80
+ // Génération de mots de passe sécurisés
74
81
  const password = crypto.randomPassword(16);
75
82
 
76
- // Hachage d'objets
83
+ // Entier aléatoire cryptographiquement sûr
84
+ const roll = crypto.randomInt(1, 6);
85
+
86
+ // Hachage déterministe d'un objet
77
87
  const objectHash = hash.computeHash({user: 'john', role: 'admin'});
78
88
  ```
79
89
 
80
- ### API REST
90
+ ### Client REST
81
91
 
82
92
  ```javascript
83
93
  const {RestAPI} = require('xcraft-core-utils');
84
94
 
85
- const api = new RestAPI(30000);
86
- const data = await api._get('https://api.example.com/users');
95
+ class MyApi extends RestAPI {
96
+ constructor() {
97
+ super(30000, {Authorization: 'Bearer token'});
98
+ }
99
+
100
+ async getUsers() {
101
+ return this._get('https://api.example.com/users');
102
+ }
103
+
104
+ async streamResults() {
105
+ const stream = await this._getStream('https://api.example.com/stream');
106
+ if (stream.error) throw stream.error;
107
+ for await (const item of stream) {
108
+ console.log(item);
109
+ }
110
+ }
111
+ }
87
112
  ```
88
113
 
89
114
  ### Synchronisation avec locks
@@ -91,61 +116,99 @@ const data = await api._get('https://api.example.com/users');
91
116
  ```javascript
92
117
  const {locks} = require('xcraft-core-utils');
93
118
 
119
+ // Mutex simple
94
120
  const mutex = new locks.Mutex();
95
- await mutex.lock();
121
+ yield mutex.lock();
96
122
  try {
97
123
  // Section critique
98
124
  } finally {
99
125
  mutex.unlock();
100
126
  }
127
+
128
+ // Mutex par clé (GetMutex global)
129
+ yield locks.getMutex.lock('resource-id');
130
+ try {
131
+ // Accès exclusif à la ressource 'resource-id'
132
+ } finally {
133
+ locks.getMutex.unlock('resource-id');
134
+ }
135
+
136
+ // Exécution coalescente : seul le premier et le dernier appel concurrent s'exécutent
137
+ const executor = new locks.CoalescingExecutor();
138
+ await executor.run(async () => expensiveOperation());
101
139
  ```
102
140
 
103
- ### Opérations asynchrones avec mapReduce
141
+ ### Traitement par lots avec Batcher
104
142
 
105
143
  ```javascript
106
- const {async} = require('xcraft-core-utils');
144
+ const {Batcher} = require('xcraft-core-utils');
107
145
 
108
- const data = ['item1', 'item2', 'item3'];
109
- const result = yield async.mapReduce(
110
- item => item.id,
111
- item => processItem(item),
112
- data
146
+ const batcher = new Batcher(
147
+ async () => db.beginTransaction(),
148
+ async (count) => db.commit(),
149
+ 500, // commit tous les 500 éléments
150
+ 5000 // ou après 5s d'inactivité
113
151
  );
152
+
153
+ await batcher.start();
154
+ for (const record of records) {
155
+ await db.insert(record);
156
+ const shouldContinue = await batcher.bump();
157
+ if (!shouldContinue) break;
158
+ }
159
+ await batcher.stop();
160
+ ```
161
+
162
+ ### Cache LRU avec RankedCache
163
+
164
+ ```javascript
165
+ const {RankedCache} = require('xcraft-core-utils');
166
+
167
+ const cache = new RankedCache(100); // max 100 entrées
168
+ cache.on('out', (item) => {
169
+ console.log('Élément évincé:', item.payload);
170
+ });
171
+
172
+ const item = cache.rank({id: 'style-1', css: '...'});
173
+ // Promouvoir un élément existant
174
+ cache.rank(item);
114
175
  ```
115
176
 
116
177
  ## Interactions avec d'autres modules
117
178
 
118
- - **[xcraft-core-log]** : Utilisé pour le logging avancé dans plusieurs utilitaires
119
- - **[xcraft-core-fs]** : Intégré pour les opérations sur le système de fichiers
120
- - **[xcraft-core-busclient]** : Utilisé par JobQueue pour les notifications d'événements
121
- - **[xcraft-traverse]** : Utilisé pour la traversée d'objets complexes
122
- - **[gigawatts]** : Support des générateurs et fonctions asynchrones
179
+ - **[xcraft-core-log]** : Utilisé pour le logging dans `JobQueue` et d'autres utilitaires
180
+ - **[xcraft-core-fs]** : Intégré pour les opérations sur le système de fichiers dans `batch` et `modules`
181
+ - **[xcraft-core-busclient]** : Utilisé par `JobQueue` pour les notifications d'événements sur le bus Xcraft
182
+ - **[xcraft-traverse]** : Utilisé pour la traversée d'objets complexes dans `hash`, `json` et `modules`
183
+ - **[gigawatts]** : Support des générateurs et fonctions asynchrones dans les classes principales
123
184
 
124
185
  ### Variables d'environnement
125
186
 
126
- | Variable | Description | Exemple | Valeur par défaut |
127
- | --------- | ------------------------------------------------------------------- | ------------------------------- | ----------------- |
128
- | `PATH` | Chemin système utilisé par `whereIs` pour localiser les exécutables | `/usr/bin:/bin` | Variable système |
129
- | `APPDATA` | Répertoire de données d'application sur Windows | `C:\Users\User\AppData\Roaming` | Variable système |
187
+ | Variable | Description | Exemple | Valeur par défaut |
188
+ | --------- | ------------------------------------------------------------------------------- | ------------------------------- | ----------------- |
189
+ | `PATH` | Chemin système utilisé par `whereIs` pour localiser les exécutables | `/usr/bin:/bin` | Variable système |
190
+ | `APPDATA` | Répertoire de données d'application sur Windows (utilisé par `os.getAppData()`) | `C:\Users\User\AppData\Roaming` | Variable système |
130
191
 
131
192
  ## Détails des sources
132
193
 
133
194
  ### `arrayCollector.js`
134
195
 
135
- Classe pour collecter des données par clés avec un mécanisme de throttling. Permet d'accumuler des données et de les traiter par lots à intervalles réguliers. Supporte les modes synchrone et asynchrone.
196
+ Classe pour collecter des données par clés avec un mécanisme de throttling. Permet d'accumuler des données et de les traiter par lots à intervalles réguliers. Supporte les modes synchrone et asynchrone via le paramètre `async` du constructeur.
197
+
198
+ En mode synchrone, le callback `onCollect` est wrappé avec `gigawatts` pour supporter les générateurs. En mode asynchrone, il est appelé avec `await` directement.
136
199
 
137
200
  #### Méthodes publiques
138
201
 
139
- - **`grab(key, data)`** — Ajoute des données à la collection sous une clé spécifique et déclenche le traitement si nécessaire.
140
- - **`cancel()`** — Annule le traitement en attente.
202
+ - **`grab(key, data)`** — Ajoute des données à la collection sous une clé spécifique (par concaténation) et déclenche le traitement throttlé.
203
+ - **`cancel()`** — Annule le traitement en attente (throttle en cours).
141
204
 
142
205
  ### `async.js`
143
206
 
144
- Classe utilitaire pour les opérations asynchrones avancées qui évitent de bloquer la boucle d'événements principale.
207
+ Module exportant une instance singleton avec des utilitaires pour les opérations asynchrones qui évitent de bloquer la boucle d'événements principale.
145
208
 
146
209
  #### Méthodes publiques
147
210
 
148
- - **`mapReduce(keyFunc, valueFunc, list)`** — Réduit un tableau en map avec itération asynchrone pour éviter de bloquer la boucle d'événements sur de gros volumes de données.
211
+ - **`mapReduce(keyFunc, valueFunc, list)`** — Réduit un tableau en map avec itération asynchrone. Utile pour les très grandes collections où chaque item est traité via une `Promise`, libérant la boucle d'événements entre les itérations.
149
212
 
150
213
  ### `batch.js`
151
214
 
@@ -153,92 +216,100 @@ Fonction utilitaire pour exécuter une action sur tous les fichiers d'un répert
153
216
 
154
217
  #### Méthodes publiques
155
218
 
156
- - **`run(filter, location, callbackAction)`** — Parcourt récursivement un répertoire et exécute une action sur les fichiers correspondant au filtre.
219
+ - **`run(filter, location, callbackAction)`** — Parcourt récursivement un répertoire et exécute `callbackAction` sur les fichiers dont le nom correspond au filtre regex. Les sous-répertoires sont traités récursivement.
157
220
 
158
221
  ### `batcher.js`
159
222
 
160
- Gestionnaire de traitement par lots avec support de timeout et de seuils de déclenchement. Idéal pour optimiser les opérations de base de données ou les écritures sur disque.
223
+ Gestionnaire de traitement par lots avec support de timeout et de seuils de déclenchement. Idéal pour optimiser les opérations coûteuses comme les transactions de base de données ou les écritures disque en regroupant les appels.
224
+
225
+ Le Batcher maintient une session entre `start()` et `stop()`. La méthode `bump()` incrémente le compteur et déclenche automatiquement un `stop()`/`start()` quand le seuil `batch` est atteint ou quand le timeout expire. La méthode `pump()` vérifie uniquement le timeout via un `setImmediate`.
161
226
 
162
227
  #### Méthodes publiques
163
228
 
164
- - **`start()`** — Démarre une nouvelle session de traitement par lots.
165
- - **`bump()`** — Incrémente le compteur et déclenche le commit si le seuil est atteint.
166
- - **`pump()`** — Force le commit si le timeout est atteint.
167
- - **`stop()`** — Termine la session et exécute le commit final.
168
- - **`dispose()`** — Marque le batcher pour destruction.
229
+ - **`start()`** — Démarre une nouvelle session : appelle `begin()` et arme le timer de timeout.
230
+ - **`bump()`** — Incrémente le compteur et déclenche un cycle commit/restart si le seuil ou le timeout est atteint. Retourne `false` si le batcher est en cours de destruction.
231
+ - **`pump()`** — Cède le contrôle via `setImmediate` puis déclenche un cycle si le timeout est atteint. Retourne `false` si destruction en cours.
232
+ - **`stop()`** — Termine la session courante en appelant `commit(count)` avec le nombre d'éléments traités.
233
+ - **`dispose()`** — Marque le batcher pour destruction ; les prochains appels à `bump()`/`pump()` déclencheront l'arrêt.
169
234
 
170
235
  ### `crypto.js`
171
236
 
172
- Utilitaires cryptographiques pour le hachage, la génération de tokens et de mots de passe sécurisés.
237
+ Utilitaires cryptographiques pour le hachage, la génération de tokens et de mots de passe sécurisés. Utilise le module natif `node:crypto` pour garantir la qualité cryptographique des nombres aléatoires.
238
+
239
+ L'algorithme de génération d'entiers aléatoires (`randomInt`) utilise un rejet des valeurs hors plage pour garantir une distribution uniforme sans biais statistique.
173
240
 
174
241
  #### Méthodes publiques
175
242
 
176
- - **`md5(data)`** — Calcule le hash MD5 des données.
177
- - **`sha256(data)`** — Calcule le hash SHA256 des données.
178
- - **`genToken()`** — Génère un token UUID sans tirets.
179
- - **`randomInt(min, max)`** — Génère un entier aléatoire cryptographiquement sûr dans la plage spécifiée.
180
- - **`randomChar(chars)`** — Génère un caractère aléatoire à partir d'un ensemble de caractères.
181
- - **`randomPassword(length=12, chars)`** — Génère un mot de passe aléatoire sécurisé.
243
+ - **`md5(data)`** — Calcule le hash MD5 des données (retourne une chaîne hexadécimale).
244
+ - **`sha256(data)`** — Calcule le hash SHA256 des données (retourne une chaîne hexadécimale).
245
+ - **`genToken()`** — Génère un token UUID v4 sans tirets (32 caractères hexadécimaux).
246
+ - **`randomInt(min, max)`** — Génère un entier aléatoire cryptographiquement sûr dans `[min, max]`. Lève une erreur si la plage dépasse `2^31 - 1`.
247
+ - **`randomChar(chars)`** — Génère un caractère aléatoire à partir d'un ensemble (défaut : alphanumérique + symboles sans caractères ambigus).
248
+ - **`randomPassword(length=12, chars)`** — Génère un mot de passe aléatoire sécurisé de la longueur spécifiée.
182
249
 
183
250
  ### `cursorPump.js`
184
251
 
185
- Wrapper pour les curseurs de base de données permettant de pomper les données de manière asynchrone.
252
+ Wrapper pour les curseurs de base de données permettant de pomper les données de manière asynchrone avec [gigawatts]. Le curseur doit implémenter une méthode `next()` qui rejette sa promesse en fin de données.
186
253
 
187
254
  #### Méthodes publiques
188
255
 
189
- - **`toArray()`** — Convertit toutes les données du curseur en tableau.
190
- - **`pump()`** — Récupère le prochain élément du curseur.
256
+ - **`toArray()`** — Consomme intégralement le curseur et retourne toutes les lignes dans un tableau.
257
+ - **`pump()`** — Récupère le prochain élément du curseur (lève une exception en fin de curseur).
191
258
 
192
259
  ### `eventDebouncer.js`
193
260
 
194
- Classe pour débouncer l'envoi d'événements, évitant les envois trop fréquents du même type d'événement.
261
+ Classe pour débouncer l'envoi d'événements sur le bus Xcraft, évitant les envois trop fréquents pour un même topic. Un debouncer distinct est créé par topic à la première utilisation.
195
262
 
196
263
  #### Méthodes publiques
197
264
 
198
- - **`publish(topic, data)`** — Publie un événement avec debouncing automatique.
265
+ - **`publish(topic, data)`** — Publie un événement avec debouncing automatique. Les appels répétés dans la fenêtre `wait` (défaut 1000ms) sont fusionnés, seul le dernier est envoyé.
199
266
 
200
267
  ### `file-crypto.js`
201
268
 
202
- Utilitaires cryptographiques pour les fichiers.
269
+ Utilitaires cryptographiques pour les fichiers, utilisant des streams pour éviter de charger le fichier entier en mémoire.
203
270
 
204
271
  #### Méthodes publiques
205
272
 
206
- - **`fileChecksum(filePath, options)`** — Calcule la somme de contrôle d'un fichier avec l'algorithme spécifié.
273
+ - **`fileChecksum(filePath, options)`** — Calcule la somme de contrôle d'un fichier. `options.algorithm` (défaut `sha1`) et `options.encoding` (défaut `hex`) sont configurables. Lève une erreur si le chemin ne désigne pas un fichier.
207
274
 
208
275
  ### `files.js`
209
276
 
210
- Utilitaires pour la détection de types de fichiers et l'analyse MIME avec une base de données complète d'extensions.
277
+ Utilitaires pour la détection de types de fichiers. Contient une base de données statique d'extensions couvrant audio, archives, images, documents, code source, vidéos, etc.
211
278
 
212
279
  #### Méthodes publiques
213
280
 
214
- - **`getFileFilter(filePath)`** — Retourne le filtre de fichier basé sur l'extension.
215
- - **`getMimeType(filePath)`** — Détecte le type MIME et l'encodage d'un fichier en utilisant libmagic.
281
+ - **`getFileFilter(filePath)`** — Retourne `{name, extensions}` basé sur l'extension du fichier. Retourne `{name: '?', extensions: [ext]}` si l'extension est inconnue.
282
+ - **`getMimeType(filePath)`** — Détecte le type MIME et le charset d'un fichier en utilisant libmagic (`@npcz/magic`). Retourne `{mime, charset}`. Préserve l'atime sur macOS et Linux.
216
283
 
217
284
  ### `hash.js`
218
285
 
219
- Calcul de hash SHA256 pour des objets JavaScript complexes avec traversée récursive.
286
+ Calcul de hash SHA256 déterministe pour des objets JavaScript complexes en traversant récursivement toutes les feuilles de l'arbre d'objets.
287
+
288
+ Seules les valeurs de type `number`, `string` et `boolean` contribuent au hash. Les chaînes vides sont remplacées par `\0` pour les distinguer de `null`/`undefined` (qui sont ignorés).
220
289
 
221
290
  #### Méthodes publiques
222
291
 
223
- - **`computeHash(payload)`** — Calcule un hash déterministe d'un objet JavaScript en traversant récursivement ses propriétés.
292
+ - **`computeHash(payload)`** — Calcule un hash SHA256 hexadécimal d'un objet JavaScript en traversant récursivement ses propriétés feuilles.
224
293
 
225
- ### `jobQueue.js`
294
+ ### `job-queue.js`
226
295
 
227
- Système de files d'attente avancé avec support de priorités, limitations de parallélisme, gestion des dépendances entre groupes et mécanismes de retry.
296
+ Système de files d'attente avancé avec support de priorités, limitations de parallélisme, gestion des dépendances entre groupes et mécanismes de retry. Les jobs sont stockés dans une `Map` (préservation d'ordre d'insertion). L'exécution réelle est déléguée au singleton `runnerInstance`.
297
+
298
+ Les notifications de débit sont émises sur l'événement `<job-queue.sampled>` via le bus Xcraft, avec throttling à 500ms.
228
299
 
229
300
  #### Méthodes publiques
230
301
 
231
- - **`push(job)`** — Ajoute un job à la file d'attente.
232
- - **`dispose()`** — Nettoie la file d'attente et envoie les événements de fin.
302
+ - **`push(job)`** — Ajoute un job à la file d'attente (le job doit avoir une propriété `id`). Planifie une exécution via `setTimeout(..., 0)`.
303
+ - **`dispose()`** — Nettoie la file : émet l'événement `<job-queue.disposed>` et remet le compteur de samples à zéro.
233
304
 
234
305
  ### `js.js`
235
306
 
236
- Utilitaires pour l'introspection de fonctions JavaScript.
307
+ Utilitaires légers pour l'introspection de fonctions JavaScript.
237
308
 
238
309
  #### Méthodes publiques
239
310
 
240
311
  - **`isFunction(fn)`** — Vérifie si l'argument est une fonction.
241
- - **`isGenerator(fn)`** — Vérifie si l'argument est une fonction générateur.
312
+ - **`isGenerator(fn)`** — Vérifie si l'argument est une fonction générateur (`function*`).
242
313
  - **`isAsync(fn)`** — Vérifie si l'argument est une fonction async.
243
314
 
244
315
  ### `json.js`
@@ -249,161 +320,174 @@ Utilitaires pour la manipulation de fichiers JSON et la transformation de struct
249
320
 
250
321
  - **`fromFile(jsonFile)`** — ⚠️ Déprécié : utiliser `fse.readJSONSync` à la place.
251
322
  - **`toFile(json, destFile)`** — ⚠️ Déprécié : utiliser `fse.writeJSONSync` à la place.
252
- - **`dotKeysToObject(json)`** — Convertit les clés avec points en objets imbriqués.
323
+ - **`dotKeysToObject(json)`** — Convertit les clés avec points en objets imbriqués. Exemple : `{"foo.bar": true}` devient `{foo: {bar: true}}`. Supporte les chemins imbriqués arbitrairement profonds et les tableaux.
253
324
 
254
325
  ### `locks.js`
255
326
 
256
- Implémentations de primitives de synchronisation : mutex, sémaphores et mutex récursifs avec support des générateurs.
257
-
258
- #### Classes et méthodes
327
+ Implémentations de primitives de synchronisation compatibles avec le pattern générateur [gigawatts].
259
328
 
260
- - **`Mutex`** — Mutex simple avec méthodes `lock()` et `unlock()`.
261
- - **`RecursiveMutex`** — Mutex récursif supportant les verrous imbriqués par le même propriétaire.
262
- - **`Semaphore`** — Sémaphore avec méthodes `wait()` et `signal()`.
263
- - **`getMutex`** — Gestionnaire global de mutex par clé avec méthodes `lock(key)` et `unlock(key)`.
329
+ - **`Mutex`** — Mutex simple. Méthodes : `*lock(next)` et `unlock()`. Propriété `isLocked`.
330
+ - **`RecursiveMutex`** — Mutex récursif : un même `owner` peut verrouiller plusieurs fois sans deadlock. `unlock(owner)` doit être appelé autant de fois que `lock(owner)`.
331
+ - **`Semaphore`** — Sémaphore avec valeur initiale configurable. Méthodes : `*wait(next)` et `signal()`.
332
+ - **`GetMutex`** (instance exportée comme `getMutex`) — Gestionnaire de mutex nommés : crée et détruit automatiquement les mutex par clé. Méthodes : `*lock(key)` et `unlock(key)`.
333
+ - **`CoalescingExecutor`** — Exécuteur coalescent : si plusieurs appels concurrents arrivent, seuls le premier et le dernier s'exécutent (les intermédiaires sont abandonnés). Méthode : `async run(work)`.
264
334
 
265
335
  ### `log.js`
266
336
 
267
- Utilitaires de formatage et de décoration pour les logs avec support des couleurs, de l'indentation et de la détection de logs imbriqués.
337
+ Utilitaires de formatage et de décoration pour les logs avec support des couleurs ANSI, de l'indentation adaptative et de la détection de logs imbriqués. Utilisé par `xcraft-core-log`.
268
338
 
269
339
  #### Méthodes publiques
270
340
 
271
- - **`decorate(mode, prefix, mod, log, maxWidth, stripBegin)`** — Formate un message de log avec couleurs et indentation intelligente.
272
- - **`graffiti(text, callback)`** — Génère du texte ASCII art avec la police Graffiti et colorisation.
273
- - **`getIndent()`** — Retourne l'indentation actuelle.
274
- - **`computeIndent(prefix, mod)`** — Calcule l'indentation nécessaire pour un préfixe et module donnés.
341
+ - **`decorate(mode, prefix, mod, log, maxWidth, stripBegin)`** — Formate un message de log avec alignement dynamique, retour à la ligne automatique selon la largeur du terminal, et détection des logs embarqués.
342
+ - **`graffiti(text, callback)`** — Génère du texte ASCII art avec la police Graffiti (via `figlet`) et colorise les caractères `_`, `/`, `\` en vert/gris.
343
+ - **`getIndent()`** — Retourne la valeur d'indentation globale courante.
344
+ - **`computeIndent(prefix, mod)`** — Calcule le nombre d'espaces nécessaires pour aligner les messages et met à jour l'indentation globale si nécessaire.
275
345
 
276
346
  ### `mapAggregator.js`
277
347
 
278
- Classe pour agréger des données dans une structure de map avec throttling.
348
+ Classe pour agréger des données dans une structure de map imbriquée avec throttling. Contrairement à `ArrayCollector`, `MapAggregator` écrase les valeurs existantes plutôt que de les concaténer.
279
349
 
280
350
  #### Méthodes publiques
281
351
 
282
- - **`put(keys, data)`** — Ajoute des données à la map en utilisant un chemin de clés.
283
- - **`release()`** — Force la libération immédiate des données agrégées.
352
+ - **`put(keys, data)`** — Ajoute `data` dans la map en suivant le chemin `keys` (tableau ou chaîne avec points). Déclenche le throttle de libération.
353
+ - **`release()`** — Force la libération immédiate des données agrégées vers le callback `onCollect`.
284
354
 
285
355
  ### `modules.js`
286
356
 
287
- Utilitaires pour la gestion des modules et configurations d'applications Xcraft avec support des variantes et surcharges.
357
+ Utilitaires pour la gestion des modules et configurations d'applications Xcraft avec support des variantes de déploiement et des surcharges de configuration.
358
+
359
+ La fonction `extractForEtc` gère les surcharges `@appId` dans `app.json` pour fusionner les configurations multi-applications. Les valeurs `-0` dans les JSON de configuration sont des marqueurs spéciaux pour revenir aux valeurs par défaut du `config.js` de chaque module.
288
360
 
289
361
  #### Méthodes publiques
290
362
 
291
- - **`extractForEtc(appDir, appId, variantId)`** — Extrait la configuration pour xcraft-core-etc.
292
- - **`loadAppConfig(appId, appDir, configJson, variantId)`** — Charge la configuration d'une application avec ses dépendances.
293
- - **`extractAllDeps(appId, libDir, configJson)`** — Extrait toutes les dépendances d'une application récursivement.
294
- - **`extractAllJs(libDir, modules)`** — Extrait tous les fichiers JavaScript des modules spécifiés.
363
+ - **`mergeOverloads(obj, overloads)`** — Fusionne `overloads` dans `obj` en préservant les tableaux (non-fusion récursive des arrays).
364
+ - **`extractForEtc(appDir, appId, variantId)`** — Extrait et fusionne la configuration d'une application pour `xcraft-core-etc`, en appliquant les surcharges de variante et les configs `@appId`.
365
+ - **`loadAppConfig(appId, appDir, configJson, variantId)`** — Charge récursivement la configuration d'une application et de toutes ses hordes.
366
+ - **`extractConfigDeps(libDir, configJson)`** — Extrait les dépendances de modules déclarées dans les configs serveur.
367
+ - **`extractAllDeps(appId, libDir, configJson)`** — Résout récursivement toutes les dépendances Xcraft d'une application.
368
+ - **`extractAllJs(libDir, modules)`** — Retourne la liste de tous les fichiers `.js` des modules spécifiés (hors `node_modules`, `test`, `species`).
295
369
 
296
370
  ### `os.js`
297
371
 
298
- Utilitaires pour les opérations système multi-plateformes.
372
+ Utilitaire pour les chemins de données d'application multi-plateformes.
299
373
 
300
374
  #### Méthodes publiques
301
375
 
302
- - **`getAppData()`** — Retourne le répertoire de données d'application approprié selon la plateforme.
376
+ - **`getAppData()`** — Retourne `%APPDATA%` sur Windows, `~/Library/Application Support` sur macOS, `~/.local/share` sur Linux.
303
377
 
304
378
  ### `prop-types.js`
305
379
 
306
- Générateurs de PropTypes pour les composants React avec support des types étendus Xcraft.
380
+ Générateurs de PropTypes React avec support des types étendus du système de design Xcraft (couleurs, espacements, glyphes, types Nabu, etc.).
381
+
382
+ Le type `nabu` valide que la prop est soit une chaîne, un nombre, soit un objet avec `nabuId` ou `_type: 'translatableString'/'translatableMarkdown'` (supportant aussi les `Map`/`OrderedMap` Immutable.js).
307
383
 
308
384
  #### Méthodes publiques
309
385
 
310
- - **`makePropTypes(props)`** — Génère un objet PropTypes à partir d'une définition de propriétés.
311
- - **`makeDefaultProps(props)`** — Génère un objet de propriétés par défaut.
386
+ - **`makePropTypes(props)`** — Génère un objet `propTypes` React à partir d'une définition de propriétés Xcraft. Applique `.isRequired` si `prop.required` est vrai.
387
+ - **`makeDefaultProps(props)`** — Génère un objet `defaultProps` React à partir des `defaultValue` de la définition.
312
388
 
313
- ### `rankedCache.js`
389
+ ### `ranked-cache.js`
314
390
 
315
- Cache LRU (Least Recently Used) basé sur une liste chaînée avec émission d'événements lors de l'éviction.
391
+ Cache LRU (Least Recently Used) basé sur une liste doublement chaînée avec émission d'événements lors de l'éviction. Les éléments les moins récemment utilisés migrent vers la tête de liste et sont évincés en premier quand la limite est atteinte.
392
+
393
+ Un `RankedCache` avec `limit <= 0` retourne toujours `null` sans stocker quoi que ce soit.
316
394
 
317
395
  #### Méthodes publiques
318
396
 
319
- - **`rank(item)`** — Ajoute ou met à jour un élément dans le cache avec promotion automatique.
320
- - **`clear()`** — Vide complètement le cache en émettant des événements 'out'.
397
+ - **`rank(item)`** — Ajoute un nouvel item (valeur brute ou `LinkedList.Item`) ou promeut un item existant d'un cran dans la liste. Émet `'out'` avec l'item évincé si la limite est atteinte.
398
+ - **`clear()`** — Vide entièrement le cache en émettant `'out'` pour chaque item.
321
399
 
322
400
  ### `reflect.js`
323
401
 
324
- Utilitaires de réflexion pour l'introspection de fonctions JavaScript.
402
+ Utilitaires de réflexion pour l'introspection de code JavaScript.
403
+
404
+ `funcParams` gère correctement les valeurs par défaut complexes (expressions imbriquées, chaînes avec parenthèses), les commentaires, et les fonctions fléchées avec ou sans parenthèses.
325
405
 
326
406
  #### Méthodes publiques
327
407
 
328
- - **`funcParams(func)`** — Extrait les noms des paramètres d'une fonction en gérant les valeurs par défaut et les commentaires.
408
+ - **`funcParams(func)`** — Extrait les noms des paramètres d'une fonction sous forme de tableau de chaînes, en supprimant les valeurs par défaut et les commentaires.
409
+ - **`parseOptions(args)`** — Parse une chaîne d'options CLI en tableau en gérant les espaces échappés, les guillemets simples et doubles, et les guillemets imbriqués.
329
410
 
330
411
  ### `regex.js`
331
412
 
332
- Utilitaires pour la manipulation d'expressions régulières.
413
+ Utilitaires pour la manipulation d'expressions régulières avec support des patterns Axon et Xcraft.
333
414
 
334
415
  #### Méthodes publiques
335
416
 
336
- - **`toRegexp(value)`** — Convertit une valeur en expression régulière.
337
- - **`toAxonRegExpStr(str)`** — Convertit une chaîne avec wildcards en pattern regex pour Axon.
338
- - **`toXcraftRegExpStr(str)`** — Convertit une chaîne avec wildcards en pattern regex pour Xcraft.
417
+ - **`toRegexp(value)`** — Convertit une chaîne en `RegExp` ancrée (`^...$`) ou retourne la valeur si c'est déjà un `RegExp`.
418
+ - **`toAxonRegExpStr(str)`** — Convertit une chaîne avec wildcards `*` en pattern regex pour le système de messagerie Axon.
419
+ - **`toXcraftRegExpStr(str)`** — Convertit une chaîne avec wildcards `*` et groupes `(...)` en pattern regex pour le bus Xcraft.
339
420
 
340
421
  ### `rest.js`
341
422
 
342
- Client REST avancé avec support des streams, retry automatique, gestion d'erreurs enrichie et throttling des requêtes.
423
+ Client REST avancé basé sur [got] avec support des streams JSON, retry automatique sur HTTP 429 (avec respect du header `Retry-After`), et enrichissement des erreurs avec l'URL et le corps de la réponse.
424
+
425
+ Les méthodes de stream utilisent `JSONStream.parse('*')` pour émettre chaque élément d'un tableau JSON au fil de la réception. Les erreurs de stream sont capturées dans `stream.error` et doivent être testées explicitement après la consommation du stream.
343
426
 
344
427
  #### Méthodes publiques
345
428
 
346
- - **`_get(query)`** — Effectue une requête GET et retourne le body.
347
- - **`_getWithHeaders(query)`** — Effectue une requête GET et retourne body et headers.
348
- - **`_post(query, payload, options)`** — Effectue une requête POST avec payload JSON.
349
- - **`_delete(query)`** — Effectue une requête DELETE.
350
- - **`_patch(query, payload)`** — Effectue une requête PATCH.
351
- - **`_putForm(query, formData)`** — Effectue une requête PUT avec données de formulaire.
352
- - **`_getStream(query, json=true)`** — Retourne un stream pour une requête GET.
353
- - **`_postStream(query, payload, json=true)`** — Retourne un stream pour une requête POST.
354
- - **`_streamPostStream(query, stream, json=true)`** — Envoie un stream et retourne un stream.
429
+ - **`_get(query)`** — Requête GET, retourne le body parsé.
430
+ - **`_getWithHeaders(query)`** — Requête GET, retourne `{body, headers}`.
431
+ - **`_post(query, payload, options={})`** — Requête POST avec payload JSON.
432
+ - **`_delete(query)`** — Requête DELETE.
433
+ - **`_patch(query, payload)`** — Requête PATCH avec payload JSON.
434
+ - **`_putForm(query, formData)`** — Requête PUT avec données de formulaire (sans header `content-type`).
435
+ - **`_getStream(query, json=true)`** — Retourne un stream GET ; si `json=true`, parse le JSON à la volée.
436
+ - **`_postStream(query, payload, json=true)`** — Retourne un stream POST avec payload JSON.
437
+ - **`_streamPostStream(query, stream, json=true)`** — Envoie un stream en corps de requête POST et retourne un stream de réponse.
355
438
 
356
439
  ### `runnerInstance.js`
357
440
 
358
- Singleton global pour la gestion des files d'attente de jobs avec support des priorités et dépendances.
359
-
360
- #### Fonctionnalités
441
+ Singleton global (via `Symbol.for`) qui orchestre l'exécution des `JobQueue`. Il gère le parallélisme, les groupes de priorité et les dépendances entre groupes. Son unicité est garantie même si le module est chargé plusieurs fois dans des contextes différents.
361
442
 
362
- - Gestion centralisée de l'exécution des jobs
363
- - Support des groupes de priorité
364
- - Gestion des dépendances entre groupes
365
- - Limitation du parallélisme par queue
443
+ L'algorithme de priorité suspend les jobs d'une queue si un groupe listé dans `waitOn` est actuellement en cours d'exécution, avec retry configurable (`maxAttempt`, `waitDelay`).
366
444
 
367
445
  ### `string.js`
368
446
 
369
- Utilitaires de manipulation de chaînes de caractères.
447
+ Utilitaires légers de manipulation de chaînes.
370
448
 
371
449
  #### Méthodes publiques
372
450
 
373
- - **`camelcasify(str)`** — Convertit les points en camelCase.
374
- - **`capitalize(str)`** — Met en forme la première lettre en majuscule.
375
- - **`jsify(str)`** — Convertit les tirets en camelCase.
451
+ - **`camelcasify(str)`** — Convertit les segments séparés par des points en camelCase (ex: `foo.bar` → `fooBar`).
452
+ - **`capitalize(str)`** — Met la première lettre en majuscule et le reste en minuscules.
453
+ - **`jsify(str)`** — Convertit les tirets en camelCase (ex: `foo-bar` → `fooBar`).
376
454
 
377
455
  ### `whereIs.js`
378
456
 
379
- Utilitaire pour localiser des exécutables dans le PATH système.
457
+ Utilitaire pour localiser des exécutables dans le `PATH` système.
380
458
 
381
459
  #### Méthodes publiques
382
460
 
383
- - **`whereIs(bin)`** — Recherche un exécutable dans le PATH et retourne son chemin complet.
461
+ - **`whereIs(bin)`** — Recherche un exécutable dans chaque répertoire du `PATH` et retourne le premier chemin complet trouvé, ou `null` si absent.
384
462
 
385
463
  ### `yaml.js`
386
464
 
387
- Utilitaires pour la manipulation de fichiers YAML.
465
+ Utilitaires pour la manipulation de fichiers YAML via `js-yaml`.
388
466
 
389
467
  #### Méthodes publiques
390
468
 
391
- - **`fromFile(yamlFile)`** — Charge et parse un fichier YAML.
469
+ - **`fromFile(yamlFile)`** — Charge et parse un fichier YAML, retourne l'objet JavaScript correspondant.
470
+ - **`toFile(data, yamlFile)`** — Sérialise un objet en YAML et l'écrit dans un fichier (largeur de ligne 999 pour limiter les retours à la ligne).
392
471
 
393
472
  ### `zippy.js`
394
473
 
395
- Utilitaire pour créer des archives ZIP avec support du chiffrement.
474
+ Utilitaire pour créer des archives ZIP à partir d'une liste de fichiers vers un stream de sortie Node.js, avec support du chiffrement par mot de passe.
475
+
476
+ Utilise `@zip.js/zip.js` avec conversion des streams Node.js en Web Streams API via `Readable.toWeb` et `Writable.toWeb`.
396
477
 
397
478
  #### Méthodes publiques
398
479
 
399
- - **`zippy(files, outputStream, options)`** — Crée une archive ZIP à partir d'une liste de fichiers vers un stream de sortie avec support optionnel du chiffrement par mot de passe.
480
+ - **`zippy(files, outputStream, options)`** — Crée une archive ZIP. `files` est un tableau de chemins absolus. `options` peut contenir `password` (string) et `zipCrypto` (boolean) pour le chiffrement.
400
481
 
401
- ---
482
+ ## Licence
402
483
 
403
- _Ce document a été mis à jour pour refléter l'état actuel du code source._
484
+ Ce module est distribué sous [licence MIT](./LICENSE).
404
485
 
405
486
  [xcraft-core-log]: https://github.com/Xcraft-Inc/xcraft-core-log
406
487
  [xcraft-core-fs]: https://github.com/Xcraft-Inc/xcraft-core-fs
407
488
  [xcraft-core-busclient]: https://github.com/Xcraft-Inc/xcraft-core-busclient
408
489
  [xcraft-traverse]: https://github.com/Xcraft-Inc/xcraft-traverse
409
- [gigawatts]: https://github.com/Xcraft-Inc/gigawatts
490
+ [gigawatts]: https://github.com/Xcraft-Inc/gigawatts
491
+ [got]: https://github.com/sindresorhus/got
492
+
493
+ _Ce contenu a été généré par IA_
package/lib/batcher.js CHANGED
@@ -28,6 +28,10 @@ class Batcher {
28
28
  }
29
29
  }
30
30
 
31
+ #tick() {
32
+ return new Promise((resolve) => setImmediate(resolve));
33
+ }
34
+
31
35
  async start() {
32
36
  this.#running = true;
33
37
  await this.#begin();
@@ -51,6 +55,8 @@ class Batcher {
51
55
  }
52
56
 
53
57
  async pump() {
58
+ await this.#tick();
59
+
54
60
  if (this.#disposing) {
55
61
  await this.stop();
56
62
  return false;
package/lib/locks.js CHANGED
@@ -100,9 +100,39 @@ class GetMutex {
100
100
  }
101
101
  }
102
102
 
103
+ class CoalescingExecutor {
104
+ #running;
105
+ #pending;
106
+
107
+ async run(work) {
108
+ if (this.#running) {
109
+ /* Keep the last one */
110
+ this.#pending = work;
111
+ await this.#running;
112
+ return;
113
+ }
114
+
115
+ /* The first */
116
+ this.#running = (async () => {
117
+ await work();
118
+
119
+ /* Run the last if exists */
120
+ while (this.#pending) {
121
+ const last = this.#pending;
122
+ this.#pending = null;
123
+ await last();
124
+ }
125
+ })();
126
+
127
+ await this.#running;
128
+ this.#running = null;
129
+ }
130
+ }
131
+
103
132
  module.exports = {
104
133
  Mutex,
105
134
  RecursiveMutex,
106
135
  Semaphore,
136
+ CoalescingExecutor,
107
137
  getMutex: new GetMutex(),
108
138
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xcraft-core-utils",
3
- "version": "4.20.0",
3
+ "version": "4.21.2",
4
4
  "description": "Xcraft utils",
5
5
  "main": "index.js",
6
6
  "engines": {
package/test/hash.spec.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const {expect} = require('chai');
4
4
  const {computeHash} = require('../lib/hash.js');
5
5
 
6
- describe('goblin.yennefer.hash', function () {
6
+ describe('xcraft.utils.hash', function () {
7
7
  it('string', function () {
8
8
  const objL = {string: 'Roger'};
9
9
  const objR = {string: 'Wilco'};
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const {expect} = require('chai');
4
+ const {CoalescingExecutor} = require('../lib/locks.js');
5
+ const {setTimeout: setTimeoutAsync} = require('node:timers/promises');
6
+
7
+ describe('xcraft.utils.locks', function () {
8
+ it('barrier', async function () {
9
+ const results = [];
10
+ const promises = [];
11
+ const barrier = new CoalescingExecutor();
12
+
13
+ promises.push(
14
+ barrier.run(async () => {
15
+ await setTimeoutAsync(100);
16
+ results.push(0);
17
+ })
18
+ );
19
+ promises.push(
20
+ barrier.run(async () => {
21
+ await setTimeoutAsync(20);
22
+ results.push(1);
23
+ })
24
+ );
25
+ promises.push(
26
+ barrier.run(async () => {
27
+ await setTimeoutAsync(100);
28
+ results.push(2);
29
+ })
30
+ );
31
+
32
+ await Promise.all(promises);
33
+ expect(results).to.deep.equal([0, 2]);
34
+ });
35
+ });