@arc-js/qust 0.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/README.md ADDED
@@ -0,0 +1,630 @@
1
+ # @arc-js/qust
2
+
3
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ ![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-007ACC)
5
+ ![Browser](https://img.shields.io/badge/browser-compatible-green)
6
+ ![Node.js](https://img.shields.io/badge/Node.js-18+-339933)
7
+ ![Bundle Size](https://img.shields.io/badge/bundle_size-3.5KB-green)
8
+
9
+ **@arc-js/qust** est une bibliothèque TypeScript ultra-légère et performante pour la sérialisation et désérialisation d'objets en chaînes de requête (query strings). Elle offre une API simple et puissante pour manipuler les paramètres d'URL avec support des types complexes, des tableaux, des objets imbriqués et des options avancées.
10
+
11
+ ## ✨ Fonctionnalités
12
+
13
+ - **Sérialisation complète** : Conversion d'objets JavaScript/TypeScript en query strings
14
+ - **Parsing intelligent** : Reconstruction d'objets à partir de query strings
15
+ - **Support multi-format** : Tableaux au format bracket, index, comma ou séparateur personnalisé
16
+ - **Objets imbriqués** : Profondeur configurable pour les structures complexes
17
+ - **Filtrage avancé** : Exclusion des valeurs nulles, chaînes vides ou selon vos critères
18
+ - **Encodage/décodage** : Contrôle total sur l'encodage URI
19
+ - **Type-safe** : Typage TypeScript complet avec génériques
20
+ - **Léger** : Seulement ~3.5KB minifié
21
+ - **Universal** : Compatible navigateur, Node.js, Deno, Bun, etc.
22
+
23
+ ## 📦 Installation
24
+
25
+ ### Via npm/yarn/pnpm
26
+
27
+ ```bash
28
+ npm install @arc-js/qust
29
+ # ou
30
+ yarn add @arc-js/qust
31
+ # ou
32
+ pnpm add @arc-js/qust
33
+ ```
34
+
35
+ ### Importation directe (CDN)
36
+
37
+ ```html
38
+ <script src="@arc-js/qust/qust.all.js"></script>
39
+ ```
40
+
41
+ ## 🚀 Démarrage Rapide
42
+
43
+ ### TypeScript/ES Modules
44
+
45
+ ```typescript
46
+ import { qust, Qust } from '@arc-js/qust';
47
+
48
+ // Utilisation avec les fonctions utilitaires
49
+ const query = qust.stringify({ name: "John", age: 30 });
50
+ // → "?name=John&age=30"
51
+
52
+ const obj = qust.parse("?name=John&age=30");
53
+ // → { name: "John", age: 30 }
54
+ ```
55
+
56
+ ### CommonJS
57
+
58
+ ```javascript
59
+ const { qust } = require('@arc-js/qust');
60
+ ```
61
+
62
+ ### Navigateur (global)
63
+
64
+ ```html
65
+ <script src="@arc-js/qust/qust.all.js"></script>
66
+ <script>
67
+ // Disponible globalement
68
+ const query = qust.stringify({ name: "John", age: 30 });
69
+ const obj = qust.parse(query);
70
+ </script>
71
+ ```
72
+
73
+ ## 📚 API Référence
74
+
75
+ ### Types
76
+
77
+ ```typescript
78
+ type Primitive = string | number | boolean | null | undefined;
79
+ type QueryValue = Primitive | Primitive[] | { [key: string]: QueryValue };
80
+ type QueryObject = { [key: string]: QueryValue };
81
+
82
+ interface QustOptions {
83
+ arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none';
84
+ arraySeparator?: string;
85
+ skipNull?: boolean;
86
+ skipEmptyString?: boolean;
87
+ encode?: boolean;
88
+ decode?: boolean;
89
+ depth?: number;
90
+ }
91
+ ```
92
+
93
+ ### Classe Qust
94
+
95
+ #### Constructeur
96
+
97
+ ```typescript
98
+ new Qust(options?: QustOptions)
99
+ ```
100
+
101
+ Crée une nouvelle instance avec des options personnalisées.
102
+
103
+ #### Méthodes
104
+
105
+ - **`stringify(obj: QueryObject): string`** : Convertit un objet en query string
106
+ - **`parse<T = QueryObject>(queryString: string): T`** : Convertit une query string en objet
107
+ - **`setOptions(newOptions: Partial<QustOptions>): void`** : Met à jour les options
108
+ - **`getOptions(): Required<QustOptions>`** : Retourne les options actuelles
109
+
110
+ ### Fonctions utilitaires globales
111
+
112
+ ```typescript
113
+ // stringify avec options par défaut
114
+ qust.stringify(obj: QueryObject, options?: QustOptions): string
115
+
116
+ // parse avec options par défaut
117
+ qust.parse<T = QueryObject>(queryString: string, options?: QustOptions): T
118
+ ```
119
+
120
+ ### Options par défaut
121
+
122
+ ```typescript
123
+ const DEFAULT_OPTIONS = {
124
+ arrayFormat: 'bracket',
125
+ arraySeparator: ',',
126
+ skipNull: true,
127
+ skipEmptyString: false,
128
+ encode: true,
129
+ decode: true,
130
+ depth: 10
131
+ };
132
+ ```
133
+
134
+ ## 🔧 Utilisation Détaillée
135
+
136
+ ### Format des tableaux
137
+
138
+ Qust supporte plusieurs formats pour les tableaux :
139
+
140
+ #### 1. Format "bracket" (défaut)
141
+
142
+ ```typescript
143
+ const obj = { tags: ["js", "ts", "node"] };
144
+ qust.stringify(obj);
145
+ // → "?tags[]=js&tags[]=ts&tags[]=node"
146
+
147
+ qust.parse("?tags[]=js&tags[]=ts&tags[]=node");
148
+ // → { tags: ["js", "ts", "node"] }
149
+ ```
150
+
151
+ #### 2. Format "index"
152
+
153
+ ```typescript
154
+ const obj = { coords: [10, 20, 30] };
155
+ qust.stringify(obj, { arrayFormat: "index" });
156
+ // → "?coords[0]=10&coords[1]=20&coords[2]=30"
157
+
158
+ qust.parse("?coords[0]=10&coords[1]=20&coords[2]=30");
159
+ // → { coords: [10, 20, 30] }
160
+ ```
161
+
162
+ #### 3. Format "comma"
163
+
164
+ ```typescript
165
+ const obj = { values: [1, 2, 3] };
166
+ qust.stringify(obj, { arrayFormat: "comma" });
167
+ // → "?values=1,2,3"
168
+
169
+ qust.parse("?values=1,2,3", { arrayFormat: "comma" });
170
+ // → { values: [1, 2, 3] }
171
+ ```
172
+
173
+ #### 4. Format "separator"
174
+
175
+ ```typescript
176
+ const obj = { items: ["a", "b", "c"] };
177
+ qust.stringify(obj, {
178
+ arrayFormat: "separator",
179
+ arraySeparator: "|"
180
+ });
181
+ // → "?items=a&items=b&items=c"
182
+ // Chaque élément devient un paramètre distinct avec la même clé
183
+ ```
184
+
185
+ #### 5. Format "none"
186
+
187
+ ```typescript
188
+ const obj = { items: ["x", "y"] };
189
+ qust.stringify(obj, { arrayFormat: "none" });
190
+ // → "?items=x&items=y"
191
+ // Similaire à separator mais géré différemment en interne
192
+ ```
193
+
194
+ ### Objets imbriqués
195
+
196
+ ```typescript
197
+ const obj = {
198
+ user: {
199
+ name: "Alice",
200
+ settings: {
201
+ theme: "dark",
202
+ notifications: true
203
+ }
204
+ }
205
+ };
206
+
207
+ const query = qust.stringify(obj);
208
+ // → "?user[name]=Alice&user[settings][theme]=dark&user[settings][notifications]=true"
209
+
210
+ const parsed = qust.parse(query);
211
+ // → { user: { name: "Alice", settings: { theme: "dark", notifications: true } } }
212
+ ```
213
+
214
+ ### Contrôle de la profondeur
215
+
216
+ ```typescript
217
+ // Limite la profondeur de sérialisation/désérialisation
218
+ const q = new Qust({ depth: 2 });
219
+
220
+ const obj = {
221
+ a: {
222
+ b: {
223
+ c: { // Ce niveau sera ignoré (profondeur > 2)
224
+ d: 10
225
+ }
226
+ }
227
+ }
228
+ };
229
+
230
+ q.stringify(obj);
231
+ // Avertissement : "Qust: Profondeur maximale atteinte"
232
+ ```
233
+
234
+ ### Filtrage des valeurs
235
+
236
+ ```typescript
237
+ const obj = {
238
+ a: null,
239
+ b: "",
240
+ c: "value",
241
+ d: 0,
242
+ e: undefined
243
+ };
244
+
245
+ // Ignore null et chaînes vides
246
+ qust.stringify(obj, {
247
+ skipNull: true,
248
+ skipEmptyString: true
249
+ });
250
+ // → "?c=value&d=0"
251
+
252
+ // Garde toutes les valeurs
253
+ qust.stringify(obj, {
254
+ skipNull: false,
255
+ skipEmptyString: false
256
+ });
257
+ // → "?a=null&b=&c=value&d=0"
258
+ ```
259
+
260
+ ### Contrôle de l'encodage
261
+
262
+ ```typescript
263
+ const obj = { city: "Paris & Lyon" };
264
+
265
+ // Avec encodage (défaut)
266
+ qust.stringify(obj);
267
+ // → "?city=Paris%20%26%20Lyon"
268
+
269
+ // Sans encodage
270
+ qust.stringify(obj, { encode: false });
271
+ // → "?city=Paris & Lyon"
272
+
273
+ // Parsing avec/sans décodage
274
+ qust.parse("?city=Paris%20%26%20Lyon", { decode: true });
275
+ // → { city: "Paris & Lyon" }
276
+
277
+ qust.parse("?city=Paris%20%26%20Lyon", { decode: false });
278
+ // → { city: "Paris%20%26%20Lyon" }
279
+ ```
280
+
281
+ ### Types de données supportés
282
+
283
+ ```typescript
284
+ const obj = {
285
+ string: "hello",
286
+ number: 42,
287
+ boolean: true,
288
+ null: null,
289
+ undefined: undefined,
290
+ array: [1, 2, 3],
291
+ nested: { key: "value" }
292
+ };
293
+
294
+ qust.stringify(obj);
295
+ // Les valeurs sont converties automatiquement :
296
+ // - boolean → "true"/"false"
297
+ // - number → chaîne numérique
298
+ // - null → "null"
299
+ // - undefined → "undefined"
300
+
301
+ qust.parse("?string=hello&number=42&boolean=true&null=null");
302
+ // → { string: "hello", number: 42, boolean: true, null: null }
303
+ ```
304
+
305
+ ## 🎯 Cas d'utilisation
306
+
307
+ ### 1. Construction d'URLs
308
+
309
+ ```typescript
310
+ function buildSearchUrl(baseUrl: string, filters: any): string {
311
+ const query = qust.stringify(filters);
312
+ return `\${baseUrl}\${query}`;
313
+ }
314
+
315
+ const filters = {
316
+ q: "laptop",
317
+ category: "electronics",
318
+ price: { min: 100, max: 1000 },
319
+ brands: ["dell", "hp", "lenovo"],
320
+ inStock: true
321
+ };
322
+
323
+ const url = buildSearchUrl("/products", filters);
324
+ // → "/products?q=laptop&category=electronics&price[min]=100&price[max]=1000&brands[]=dell&brands[]=hp&brands[]=lenovo&inStock=true"
325
+ ```
326
+
327
+ ### 2. Récupération des paramètres d'URL
328
+
329
+ ```typescript
330
+ // Dans une application web
331
+ const currentQuery = window.location.search;
332
+ const params = qust.parse(currentQuery);
333
+
334
+ // Utilisation avec React/Vue/Angular
335
+ function useQueryParams() {
336
+ const [params, setParams] = useState(() => {
337
+ return qust.parse(window.location.search);
338
+ });
339
+
340
+ const updateParams = (newParams: any) => {
341
+ const query = qust.stringify({ ...params, ...newParams });
342
+ window.history.pushState({}, '', `?\${query}`);
343
+ setParams(qust.parse(query));
344
+ };
345
+
346
+ return [params, updateParams];
347
+ }
348
+ ```
349
+
350
+ ### 3. Communication API
351
+
352
+ ```typescript
353
+ // Client-side
354
+ async function fetchWithParams(endpoint: string, params: any) {
355
+ const query = qust.stringify(params);
356
+ const response = await fetch(`\${endpoint}\${query}`);
357
+ return response.json();
358
+ }
359
+
360
+ // Server-side (Node.js/Express)
361
+ app.get('/api/data', (req, res) => {
362
+ const params = qust.parse(req.url);
363
+ // Traiter les paramètres...
364
+ res.json({ data: params });
365
+ });
366
+ ```
367
+
368
+ ### 4. Sauvegarde d'état
369
+
370
+ ```typescript
371
+ class FormState {
372
+ private state: any = {};
373
+
374
+ saveToUrl() {
375
+ const query = qust.stringify(this.state, {
376
+ skipEmptyString: true,
377
+ skipNull: true
378
+ });
379
+ window.location.hash = query;
380
+ }
381
+
382
+ loadFromUrl() {
383
+ const query = window.location.hash.substring(1);
384
+ this.state = qust.parse(`?\${query}`) || {};
385
+ }
386
+ }
387
+ ```
388
+
389
+ ## 🔧 Configuration Avancée
390
+
391
+ ### Instance personnalisée
392
+
393
+ ```typescript
394
+ // Création d'une instance avec configuration spécifique
395
+ const customQust = new Qust({
396
+ arrayFormat: 'comma',
397
+ skipNull: true,
398
+ skipEmptyString: true,
399
+ depth: 5
400
+ });
401
+
402
+ // Réutilisation avec la même configuration
403
+ const query1 = customQust.stringify(obj1);
404
+ const query2 = customQust.stringify(obj2);
405
+
406
+ // Modification dynamique
407
+ customQust.setOptions({ arrayFormat: 'index' });
408
+ ```
409
+
410
+ ### Combinaison avec d'autres bibliothèques
411
+
412
+ ```typescript
413
+ import { qust } from '@arc-js/qust';
414
+ import {
415
+ StringSchema,
416
+ NumberSchema,
417
+ BooleanSchema,
418
+ DateSchema,
419
+ EnumSchema,
420
+ NotEnumSchema,
421
+ ArraySchema,
422
+ FileSchema,
423
+ ObjectSchema,
424
+ ChosenTypeSchema,
425
+ AnyTypeSchema,
426
+ } from '@arc-js/jon';
427
+
428
+ const JON = {
429
+ 'String': StringSchema,
430
+ 'Number': NumberSchema,
431
+ 'Boolean': BooleanSchema,
432
+ 'Date': DateSchema,
433
+ 'Enum': EnumSchema,
434
+ 'NotEnum': NotEnumSchema,
435
+ 'Array': ArraySchema,
436
+ 'File': FileSchema,
437
+ 'Object': ObjectSchema,
438
+ 'ChosenType': ChosenTypeSchema,
439
+ 'AnyType': AnyTypeSchema,
440
+ }; // Pour la validation
441
+
442
+ // Validation avant sérialisation
443
+ const schema = new JON.Object('fr').struct({
444
+ name: new JON.String('fr').required(),
445
+ age: new JON.Number('fr').min(0).max(150),
446
+ tags: new JON.Array('fr').types(new JON.String('fr'))
447
+ });
448
+
449
+ function safeStringify(obj: any) {
450
+ const validation = schema.check(obj);
451
+ if (validation.valid) {
452
+ return qust.stringify(obj);
453
+ }
454
+ throw new Error(`Validation failed: \${validation.errors}`);
455
+ }
456
+ ```
457
+
458
+ ## 📋 Table des formats de tableaux
459
+
460
+ | Format | Exemple de sortie | Description |
461
+ |--------|-------------------|-------------|
462
+ | **bracket** | `tags[]=a&tags[]=b` | Format standard avec crochets vides |
463
+ | **index** | `tags[0]=a&tags[1]=b` | Avec indices explicites |
464
+ | **comma** | `tags=a,b` | Séparés par des virgules |
465
+ | **separator** | `tags=a&tags=b` | Paramètres multiples avec même clé |
466
+ | **none** | `tags=a&tags=b` | Similaire à separator, traitement interne différent |
467
+
468
+ ## 🚨 Gestion des cas limites
469
+
470
+ ### Tableaux vides
471
+
472
+ ```typescript
473
+ qust.stringify({ items: [] });
474
+ // → "" (par défaut, les tableaux vides sont ignorés)
475
+ ```
476
+
477
+ ### Valeurs spéciales
478
+
479
+ ```typescript
480
+ qust.stringify({
481
+ special: "a&b=c?d#e",
482
+ spaces: "hello world",
483
+ unicode: "🎉"
484
+ });
485
+ // → "?special=a%26b%3Dc%3Fd%23e&spaces=hello%20world&unicode=%F0%9F%8E%89"
486
+ // Tout est correctement encodé
487
+ ```
488
+
489
+ ### Conflits de clés
490
+
491
+ ```typescript
492
+ // Gestion automatique des structures complexes
493
+ const obj = {
494
+ "user[name]": "direct", // Clé avec crochets
495
+ user: { name: "nested" } // Objet imbriqué
496
+ };
497
+
498
+ qust.stringify(obj);
499
+ // Les deux sont correctement gérés
500
+ ```
501
+
502
+ ## 🔬 Performance
503
+
504
+ Qust est optimisé pour la performance :
505
+
506
+ - **Algorithmes récursifs optimisés** avec contrôle de profondeur
507
+ - **Encodage/décodage sélectif** pour éviter les opérations inutiles
508
+ - **Gestion mémoire efficace** avec réutilisation d'objets
509
+ - **Parsing streaming** pour les grandes chaînes
510
+
511
+ ```typescript
512
+ // Benchmark approximatif (sur Node.js v18)
513
+ const largeObj = {
514
+ users: Array(1000).fill(0).map((_, i) => ({
515
+ id: i,
516
+ name: `User\${i}`,
517
+ data: { nested: { value: i * 2 } }
518
+ }))
519
+ };
520
+
521
+ console.time('stringify');
522
+ const query = qust.stringify(largeObj);
523
+ console.timeEnd('stringify'); // ~50ms
524
+
525
+ console.time('parse');
526
+ const parsed = qust.parse(query);
527
+ console.timeEnd('parse'); // ~30ms
528
+ ```
529
+
530
+ ## 🔧 Build et Développement
531
+
532
+ ### Structure du projet
533
+
534
+ ```
535
+ @arc-js/qust/
536
+ ├── qust.all.js
537
+ ├── qust.all.min.js
538
+ ├── index.d.ts
539
+ ├── index.js
540
+ ├── index.min.d.ts
541
+ ├── index.min.js
542
+ ├── package.json
543
+ ├── tsconfig.json
544
+ └── README.md
545
+ ```
546
+
547
+ ## 📋 Compatibilité
548
+
549
+ ### Navigateurs Supportés
550
+ - Chrome 60+
551
+ - Firefox 55+
552
+ - Safari 12+
553
+ - Edge 79+
554
+ - Opera 47+
555
+ - iOS Safari 12+
556
+ - Android Chrome 60+
557
+
558
+ ### Environnements
559
+ - Node.js 18+
560
+ - Deno 1.30+
561
+ - Bun 1.0+
562
+ - React Native
563
+ - Electron
564
+ - Cloudflare Workers
565
+ - Vercel Edge Functions
566
+
567
+ ### Dépendances
568
+ - **Aucune dépendance externe** : Qust est entièrement autonome
569
+ - **TypeScript** : Support natif (types inclus)
570
+ - **ES6+** : Utilise les fonctionnalités modernes JavaScript
571
+
572
+ ## 🛡️ Meilleures Pratiques
573
+
574
+ ### Sécurité
575
+
576
+ 1. **Toujours encoder par défaut** : Protège contre les injections
577
+ 2. **Valider les entrées** : Avant de parser des données non fiables
578
+ 3. **Limiter la profondeur** : Éviter les attaques par récursion
579
+ 4. **Utiliser skipEmptyString** : Pour les formulaires web
580
+
581
+ ```typescript
582
+ // Configuration sécurisée par défaut
583
+ const secureQust = new Qust({
584
+ encode: true, // Toujours encoder
585
+ depth: 10, // Limite raisonnable
586
+ skipNull: true, // Ignorer les valeurs nulles
587
+ skipEmptyString: true // Ignorer les champs vides
588
+ });
589
+ ```
590
+
591
+ ### Performance
592
+
593
+ 1. **Réutiliser les instances** : Pour éviter la recréation d'options
594
+ 2. **Choisir le bon format** : "comma" pour les grands tableaux
595
+ 3. **Filtrer tôt** : skipNull/skipEmptyString réduit la charge
596
+ 4. **Éviter la sur-sérialisation** : Ne pas sérialiser inutilement
597
+
598
+ ### Maintenance
599
+
600
+ 1. **Documenter les schémas** : Utiliser TypeScript pour la documentation
601
+ 2. **Tests unitaires** : Couvrir les cas d'utilisation
602
+ 3. **Versionner les APIs** : Changements de format d'arrayFormat
603
+ 4. **Logging en dev** : Activer les avertissements de profondeur
604
+
605
+ ## 📄 Licence
606
+
607
+ MIT License - Voir le fichier [LICENSE](LICENSE) pour plus de détails.
608
+
609
+ Copyright (c) 2024 INICODE
610
+
611
+ Permission est accordée, gratuitement, à toute personne obtenant une copie
612
+ de ce logiciel et des fichiers de documentation associés (le "Logiciel"), de
613
+ traiter dans le Logiciel sans restriction, y compris sans limitation les
614
+ droits d'utilisation, de copie, de modification, de fusion, de publication,
615
+ de distribution, de sous-licence et/ou de vente de copies du Logiciel, et de
616
+ permettre aux personnes à qui le Logiciel est fourni de le faire, sous réserve
617
+ des conditions suivantes :
618
+
619
+ ## 🐛 Signaler un Bug
620
+
621
+ Envoyez nous un mail à l'adresse `contact.inicode@gmail.com` pour :
622
+ - Signaler un bug
623
+ - Proposer une amélioration
624
+ - Poser une question
625
+
626
+ ---
627
+
628
+ **@arc-js/qust** - La solution ultime pour la manipulation de query strings en TypeScript.
629
+
630
+ *Développé par l'équipe INICODE*