@arc-js/qust 0.0.1

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,604 @@
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 JON from '@arc-js/jon'; // Pour la validation
415
+
416
+ // Validation avant sérialisation
417
+ const schema = new JON.Object('fr').struct({
418
+ name: new JON.String('fr').required(),
419
+ age: new JON.Number('fr').min(0).max(150),
420
+ tags: new JON.Array('fr').types(new JON.String('fr'))
421
+ });
422
+
423
+ function safeStringify(obj: any) {
424
+ const validation = schema.check(obj);
425
+ if (validation.valid) {
426
+ return qust.stringify(obj);
427
+ }
428
+ throw new Error(`Validation failed: \${validation.errors}`);
429
+ }
430
+ ```
431
+
432
+ ## 📋 Table des formats de tableaux
433
+
434
+ | Format | Exemple de sortie | Description |
435
+ |--------|-------------------|-------------|
436
+ | **bracket** | `tags[]=a&tags[]=b` | Format standard avec crochets vides |
437
+ | **index** | `tags[0]=a&tags[1]=b` | Avec indices explicites |
438
+ | **comma** | `tags=a,b` | Séparés par des virgules |
439
+ | **separator** | `tags=a&tags=b` | Paramètres multiples avec même clé |
440
+ | **none** | `tags=a&tags=b` | Similaire à separator, traitement interne différent |
441
+
442
+ ## 🚨 Gestion des cas limites
443
+
444
+ ### Tableaux vides
445
+
446
+ ```typescript
447
+ qust.stringify({ items: [] });
448
+ // → "" (par défaut, les tableaux vides sont ignorés)
449
+ ```
450
+
451
+ ### Valeurs spéciales
452
+
453
+ ```typescript
454
+ qust.stringify({
455
+ special: "a&b=c?d#e",
456
+ spaces: "hello world",
457
+ unicode: "🎉"
458
+ });
459
+ // → "?special=a%26b%3Dc%3Fd%23e&spaces=hello%20world&unicode=%F0%9F%8E%89"
460
+ // Tout est correctement encodé
461
+ ```
462
+
463
+ ### Conflits de clés
464
+
465
+ ```typescript
466
+ // Gestion automatique des structures complexes
467
+ const obj = {
468
+ "user[name]": "direct", // Clé avec crochets
469
+ user: { name: "nested" } // Objet imbriqué
470
+ };
471
+
472
+ qust.stringify(obj);
473
+ // Les deux sont correctement gérés
474
+ ```
475
+
476
+ ## 🔬 Performance
477
+
478
+ Qust est optimisé pour la performance :
479
+
480
+ - **Algorithmes récursifs optimisés** avec contrôle de profondeur
481
+ - **Encodage/décodage sélectif** pour éviter les opérations inutiles
482
+ - **Gestion mémoire efficace** avec réutilisation d'objets
483
+ - **Parsing streaming** pour les grandes chaînes
484
+
485
+ ```typescript
486
+ // Benchmark approximatif (sur Node.js v18)
487
+ const largeObj = {
488
+ users: Array(1000).fill(0).map((_, i) => ({
489
+ id: i,
490
+ name: `User\${i}`,
491
+ data: { nested: { value: i * 2 } }
492
+ }))
493
+ };
494
+
495
+ console.time('stringify');
496
+ const query = qust.stringify(largeObj);
497
+ console.timeEnd('stringify'); // ~50ms
498
+
499
+ console.time('parse');
500
+ const parsed = qust.parse(query);
501
+ console.timeEnd('parse'); // ~30ms
502
+ ```
503
+
504
+ ## 🔧 Build et Développement
505
+
506
+ ### Structure du projet
507
+
508
+ ```
509
+ @arc-js/qust/
510
+ ├── qust.all.js
511
+ ├── qust.all.min.js
512
+ ├── index.d.ts
513
+ ├── index.js
514
+ ├── index.min.d.ts
515
+ ├── index.min.js
516
+ ├── package.json
517
+ ├── tsconfig.json
518
+ └── README.md
519
+ ```
520
+
521
+ ## 📋 Compatibilité
522
+
523
+ ### Navigateurs Supportés
524
+ - Chrome 60+
525
+ - Firefox 55+
526
+ - Safari 12+
527
+ - Edge 79+
528
+ - Opera 47+
529
+ - iOS Safari 12+
530
+ - Android Chrome 60+
531
+
532
+ ### Environnements
533
+ - Node.js 18+
534
+ - Deno 1.30+
535
+ - Bun 1.0+
536
+ - React Native
537
+ - Electron
538
+ - Cloudflare Workers
539
+ - Vercel Edge Functions
540
+
541
+ ### Dépendances
542
+ - **Aucune dépendance externe** : Qust est entièrement autonome
543
+ - **TypeScript** : Support natif (types inclus)
544
+ - **ES6+** : Utilise les fonctionnalités modernes JavaScript
545
+
546
+ ## 🛡️ Meilleures Pratiques
547
+
548
+ ### Sécurité
549
+
550
+ 1. **Toujours encoder par défaut** : Protège contre les injections
551
+ 2. **Valider les entrées** : Avant de parser des données non fiables
552
+ 3. **Limiter la profondeur** : Éviter les attaques par récursion
553
+ 4. **Utiliser skipEmptyString** : Pour les formulaires web
554
+
555
+ ```typescript
556
+ // Configuration sécurisée par défaut
557
+ const secureQust = new Qust({
558
+ encode: true, // Toujours encoder
559
+ depth: 10, // Limite raisonnable
560
+ skipNull: true, // Ignorer les valeurs nulles
561
+ skipEmptyString: true // Ignorer les champs vides
562
+ });
563
+ ```
564
+
565
+ ### Performance
566
+
567
+ 1. **Réutiliser les instances** : Pour éviter la recréation d'options
568
+ 2. **Choisir le bon format** : "comma" pour les grands tableaux
569
+ 3. **Filtrer tôt** : skipNull/skipEmptyString réduit la charge
570
+ 4. **Éviter la sur-sérialisation** : Ne pas sérialiser inutilement
571
+
572
+ ### Maintenance
573
+
574
+ 1. **Documenter les schémas** : Utiliser TypeScript pour la documentation
575
+ 2. **Tests unitaires** : Couvrir les cas d'utilisation
576
+ 3. **Versionner les APIs** : Changements de format d'arrayFormat
577
+ 4. **Logging en dev** : Activer les avertissements de profondeur
578
+
579
+ ## 📄 Licence
580
+
581
+ MIT License - Voir le fichier [LICENSE](LICENSE) pour plus de détails.
582
+
583
+ Copyright (c) 2024 INICODE
584
+
585
+ Permission est accordée, gratuitement, à toute personne obtenant une copie
586
+ de ce logiciel et des fichiers de documentation associés (le "Logiciel"), de
587
+ traiter dans le Logiciel sans restriction, y compris sans limitation les
588
+ droits d'utilisation, de copie, de modification, de fusion, de publication,
589
+ de distribution, de sous-licence et/ou de vente de copies du Logiciel, et de
590
+ permettre aux personnes à qui le Logiciel est fourni de le faire, sous réserve
591
+ des conditions suivantes :
592
+
593
+ ## 🐛 Signaler un Bug
594
+
595
+ Envoyez nous un mail à l'adresse `contact.inicode@gmail.com` pour :
596
+ - Signaler un bug
597
+ - Proposer une amélioration
598
+ - Poser une question
599
+
600
+ ---
601
+
602
+ **@arc-js/qust** - La solution ultime pour la manipulation de query strings en TypeScript.
603
+
604
+ *Développé par l'équipe INICODE*
package/index.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ type Primitive = string | number | boolean | null | undefined;
2
+ type QueryValue = Primitive | Primitive[] | {
3
+ [key: string]: QueryValue;
4
+ };
5
+ type QueryObject = {
6
+ [key: string]: QueryValue;
7
+ };
8
+ declare global {
9
+ interface Window {
10
+ Qust: typeof Qust;
11
+ qust: any;
12
+ }
13
+ }
14
+ interface QustOptions {
15
+ arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none';
16
+ arraySeparator?: string;
17
+ skipNull?: boolean;
18
+ skipEmptyString?: boolean;
19
+ encode?: boolean;
20
+ decode?: boolean;
21
+ depth?: number;
22
+ }
23
+ declare class Qust {
24
+ private options;
25
+ constructor(options?: QustOptions);
26
+ /**
27
+ * Convertit un objet en query string
28
+ */
29
+ stringify(obj: QueryObject): string;
30
+ /**
31
+ * Convertit une query string en objet
32
+ */
33
+ parse<T = QueryObject>(queryString: string): T;
34
+ /**
35
+ * Traite récursivement un objet pour la stringification
36
+ */
37
+ private processObject;
38
+ /**
39
+ * Traite les tableaux selon le format spécifié
40
+ */
41
+ private processArray;
42
+ /**
43
+ * Encode une clé de manière sélective
44
+ * N'encode pas les caractères [] pour préserver la lisibilité
45
+ */
46
+ private encodeKey;
47
+ /**
48
+ * Définit une valeur dans l'objet résultat lors du parsing
49
+ */
50
+ private setValue;
51
+ /**
52
+ * Transforme les structures en tableaux lorsque nécessaire
53
+ */
54
+ private transformArrays;
55
+ /**
56
+ * Parse une valeur string en type approprié
57
+ */
58
+ private parseValue;
59
+ /**
60
+ * Vérifie si une valeur est primitive
61
+ */
62
+ private isPrimitive;
63
+ /**
64
+ * Met à jour les options
65
+ */
66
+ setOptions(newOptions: Partial<QustOptions>): void;
67
+ /**
68
+ * Retourne les options actuelles
69
+ */
70
+ getOptions(): Required<QustOptions>;
71
+ /**
72
+ * Exposition globale de la classe
73
+ */
74
+ static exposeToGlobal(): void;
75
+ }
76
+ declare const qust: {
77
+ /**
78
+ * Convertit un objet en query string avec options par défaut
79
+ */
80
+ stringify(obj: QueryObject, options?: QustOptions): string;
81
+ /**
82
+ * Convertit une query string en objet avec options par défaut
83
+ */
84
+ parse<T = QueryObject>(queryString: string, options?: QustOptions): T;
85
+ };
86
+
87
+ export { Qust, qust as default, qust };
88
+ export type { QueryObject, QueryValue, QustOptions };