@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/qust.all.js ADDED
@@ -0,0 +1,328 @@
1
+ const DEFAULT_OPTIONS = {
2
+ arrayFormat: 'bracket',
3
+ arraySeparator: ',',
4
+ skipNull: true,
5
+ skipEmptyString: false,
6
+ encode: true,
7
+ decode: true,
8
+ depth: 10
9
+ };
10
+ class Qust {
11
+ options;
12
+ constructor(options = {}) {
13
+ this.options = { ...DEFAULT_OPTIONS, ...options };
14
+ }
15
+ /**
16
+ * Convertit un objet en query string
17
+ */
18
+ stringify(obj) {
19
+ if (!obj || typeof obj !== 'object') {
20
+ return '';
21
+ }
22
+ const parts = [];
23
+ this.processObject('', obj, parts, 0);
24
+ return parts.length > 0 ? `?${parts.join('&')}` : '';
25
+ }
26
+ /**
27
+ * Convertit une query string en objet
28
+ */
29
+ parse(queryString) {
30
+ if (!queryString || typeof queryString !== 'string') {
31
+ return {};
32
+ }
33
+ // Supprime le '?' initial si présent
34
+ const cleanString = queryString.startsWith('?')
35
+ ? queryString.slice(1)
36
+ : queryString;
37
+ if (!cleanString) {
38
+ return {};
39
+ }
40
+ const result = {};
41
+ const pairs = cleanString.split('&');
42
+ for (const pair of pairs) {
43
+ if (!pair)
44
+ continue;
45
+ const [encodedKey, encodedValue] = pair.split('=');
46
+ if (!encodedKey)
47
+ continue;
48
+ const key = this.options.decode
49
+ ? decodeURIComponent(encodedKey)
50
+ : encodedKey;
51
+ const value = this.options.decode && encodedValue !== undefined
52
+ ? decodeURIComponent(encodedValue)
53
+ : encodedValue;
54
+ this.setValue(result, key, value);
55
+ }
56
+ return this.transformArrays(result);
57
+ }
58
+ /**
59
+ * Traite récursivement un objet pour la stringification
60
+ */
61
+ processObject(prefix, value, parts, depth) {
62
+ if (depth > this.options.depth) {
63
+ console.warn('Qust: Profondeur maximale atteinte, arrêt de la récursion');
64
+ return;
65
+ }
66
+ if (value === null || value === undefined) {
67
+ if (!this.options.skipNull) {
68
+ parts.push(this.encodeKey(prefix));
69
+ }
70
+ return;
71
+ }
72
+ if (typeof value === 'string' && value === '' && this.options.skipEmptyString) {
73
+ return;
74
+ }
75
+ if (Array.isArray(value)) {
76
+ this.processArray(prefix, value, parts, depth + 1);
77
+ return;
78
+ }
79
+ if (typeof value === 'object' && !this.isPrimitive(value)) {
80
+ for (const [key, val] of Object.entries(value)) {
81
+ const newPrefix = prefix ? `${prefix}[${key}]` : key;
82
+ this.processObject(newPrefix, val, parts, depth + 1);
83
+ }
84
+ return;
85
+ }
86
+ // Valeur primitive
87
+ const encodedKey = this.encodeKey(prefix);
88
+ const encodedValue = this.options.encode && typeof value === 'string'
89
+ ? encodeURIComponent(value)
90
+ : String(value);
91
+ parts.push(`${encodedKey}=${encodedValue}`);
92
+ }
93
+ /**
94
+ * Traite les tableaux selon le format spécifié
95
+ */
96
+ processArray(prefix, array, parts, depth) {
97
+ if (array.length === 0)
98
+ return;
99
+ const encodedKey = this.encodeKey(prefix);
100
+ switch (this.options.arrayFormat) {
101
+ case 'comma':
102
+ const values = array
103
+ .map(item => this.options.encode && typeof item === 'string'
104
+ ? encodeURIComponent(item)
105
+ : String(item))
106
+ .join(this.options.arraySeparator);
107
+ parts.push(`${encodedKey}=${values}`);
108
+ break;
109
+ case 'index':
110
+ array.forEach((item, index) => {
111
+ const arrayPrefix = `${prefix}[${index}]`;
112
+ this.processObject(arrayPrefix, item, parts, depth);
113
+ });
114
+ break;
115
+ case 'separator':
116
+ array.forEach(item => {
117
+ const encodedValue = this.options.encode && typeof item === 'string'
118
+ ? encodeURIComponent(item)
119
+ : String(item);
120
+ parts.push(`${encodedKey}=${encodedValue}`);
121
+ });
122
+ break;
123
+ case 'none':
124
+ array.forEach(item => {
125
+ this.processObject(prefix, item, parts, depth);
126
+ });
127
+ break;
128
+ case 'bracket':
129
+ default:
130
+ array.forEach(item => {
131
+ const arrayPrefix = `${prefix}[]`;
132
+ this.processObject(arrayPrefix, item, parts, depth);
133
+ });
134
+ break;
135
+ }
136
+ }
137
+ /**
138
+ * Encode une clé de manière sélective
139
+ * N'encode pas les caractères [] pour préserver la lisibilité
140
+ */
141
+ encodeKey(key) {
142
+ if (!this.options.encode) {
143
+ return key;
144
+ }
145
+ // Sépare la clé en parties (texte normal et crochets)
146
+ const parts = [];
147
+ let currentPart = '';
148
+ for (let i = 0; i < key.length; i++) {
149
+ const char = key[i];
150
+ if (char === '[' || char === ']') {
151
+ // Encode la partie actuelle si elle n'est pas vide
152
+ if (currentPart) {
153
+ parts.push(encodeURIComponent(currentPart));
154
+ currentPart = '';
155
+ }
156
+ // Ajoute le crochet sans l'encoder
157
+ parts.push(char);
158
+ }
159
+ else {
160
+ currentPart += char;
161
+ }
162
+ }
163
+ // Encode la dernière partie si elle existe
164
+ if (currentPart) {
165
+ parts.push(encodeURIComponent(currentPart));
166
+ }
167
+ return parts.join('');
168
+ }
169
+ /**
170
+ * Définit une valeur dans l'objet résultat lors du parsing
171
+ */
172
+ setValue(obj, key, value) {
173
+ const matches = key.match(/([^\[\]]+)|(\[\])/g);
174
+ if (!matches) {
175
+ if (value !== undefined) {
176
+ obj[key] = this.parseValue(value);
177
+ }
178
+ return;
179
+ }
180
+ let current = obj;
181
+ for (let i = 0; i < matches.length; i++) {
182
+ const match = matches[i];
183
+ const isLast = i === matches.length - 1;
184
+ if (match === '[]') {
185
+ if (!Array.isArray(current)) {
186
+ current = [];
187
+ }
188
+ if (isLast) {
189
+ if (!!value) {
190
+ current.push(this.parseValue(value));
191
+ }
192
+ }
193
+ else {
194
+ const nextMatch = matches[i + 1];
195
+ const nextIndex = parseInt(nextMatch, 10);
196
+ if (!isNaN(nextIndex)) {
197
+ if (current[nextIndex] === undefined) {
198
+ current[nextIndex] = {};
199
+ }
200
+ current = current[nextIndex];
201
+ i++; // Skip the next match since we used it as index
202
+ }
203
+ else {
204
+ const newObj = {};
205
+ current.push(newObj);
206
+ current = newObj;
207
+ }
208
+ }
209
+ }
210
+ else {
211
+ if (isLast) {
212
+ if (value !== undefined) {
213
+ current[match] = this.parseValue(value);
214
+ }
215
+ }
216
+ else {
217
+ if (current[match] === undefined || typeof current[match] !== 'object') {
218
+ const nextMatch = matches[i + 1];
219
+ current[match] = nextMatch === '[]' ? [] : {};
220
+ }
221
+ current = current[match];
222
+ }
223
+ }
224
+ }
225
+ }
226
+ /**
227
+ * Transforme les structures en tableaux lorsque nécessaire
228
+ */
229
+ transformArrays(obj) {
230
+ if (Array.isArray(obj)) {
231
+ return obj.map(item => typeof item === 'object' && item !== null
232
+ ? this.transformArrays(item)
233
+ : item);
234
+ }
235
+ if (typeof obj !== 'object' || obj === null) {
236
+ return obj;
237
+ }
238
+ const result = {};
239
+ const keys = Object.keys(obj);
240
+ const isArrayLike = keys.every(key => {
241
+ const num = parseInt(key, 10);
242
+ return !isNaN(num) && num >= 0 && num.toString() === key;
243
+ });
244
+ if (isArrayLike && keys.length > 0) {
245
+ const array = [];
246
+ keys.forEach(key => {
247
+ const index = parseInt(key, 10);
248
+ array[index] = this.transformArrays(obj[key]);
249
+ });
250
+ return array;
251
+ }
252
+ keys.forEach(key => {
253
+ const value = obj[key];
254
+ result[key] =
255
+ typeof value === 'object' && value !== null
256
+ ? this.transformArrays(value)
257
+ : value;
258
+ });
259
+ return result;
260
+ }
261
+ /**
262
+ * Parse une valeur string en type approprié
263
+ */
264
+ parseValue(value) {
265
+ if (value === 'true')
266
+ return true;
267
+ if (value === 'false')
268
+ return false;
269
+ if (value === 'null')
270
+ return null;
271
+ if (value === 'undefined')
272
+ return undefined;
273
+ const num = Number(value);
274
+ if (!isNaN(num) && value.trim() !== '')
275
+ return num;
276
+ return value;
277
+ }
278
+ /**
279
+ * Vérifie si une valeur est primitive
280
+ */
281
+ isPrimitive(value) {
282
+ return value === null ||
283
+ typeof value !== 'object' &&
284
+ typeof value !== 'function';
285
+ }
286
+ /**
287
+ * Met à jour les options
288
+ */
289
+ setOptions(newOptions) {
290
+ this.options = { ...this.options, ...newOptions };
291
+ }
292
+ /**
293
+ * Retourne les options actuelles
294
+ */
295
+ getOptions() {
296
+ return { ...this.options };
297
+ }
298
+ /**
299
+ * Exposition globale de la classe
300
+ */
301
+ static exposeToGlobal() {
302
+ if (typeof window !== "undefined") {
303
+ window.Qust = Qust;
304
+ }
305
+ }
306
+ }
307
+ // Fonctions utilitaires statiques
308
+ const qust = {
309
+ /**
310
+ * Convertit un objet en query string avec options par défaut
311
+ */
312
+ stringify(obj, options) {
313
+ return new Qust(options).stringify(obj);
314
+ },
315
+ /**
316
+ * Convertit une query string en objet avec options par défaut
317
+ */
318
+ parse(queryString, options) {
319
+ return new Qust(options).parse(queryString);
320
+ }
321
+ };
322
+ // Exposition automatique en environnement browser
323
+ if (typeof window !== "undefined") {
324
+ window.Qust = Qust;
325
+ window.qust = qust;
326
+ }
327
+
328
+
@@ -0,0 +1 @@
1
+ let DEFAULT_OPTIONS={arrayFormat:"bracket",arraySeparator:",",skipNull:!0,skipEmptyString:!1,encode:!0,decode:!0,depth:10};class Qust{options;constructor(e={}){this.options={...DEFAULT_OPTIONS,...e}}stringify(e){return e&&"object"==typeof e&&(this.processObject("",e,e=[],0),0<e.length)?"?"+e.join("&"):""}parse(e){if(!e||"string"!=typeof e)return{};e=e.startsWith("?")?e.slice(1):e;if(!e)return{};var t,r,s,o={};for(t of e.split("&"))t&&([r,s]=t.split("="),r)&&(r=this.options.decode?decodeURIComponent(r):r,s=this.options.decode&&void 0!==s?decodeURIComponent(s):s,this.setValue(o,r,s));return this.transformArrays(o)}processObject(e,t,r,s){if(!(s>this.options.depth))if(null==t)this.options.skipNull||r.push(this.encodeKey(e));else if("string"!=typeof t||""!==t||!this.options.skipEmptyString)if(Array.isArray(t))this.processArray(e,t,r,s+1);else if("object"!=typeof t||this.isPrimitive(t)){var o=this.encodeKey(e),i=(this.options.encode&&"string"==typeof t?encodeURIComponent:String)(t);r.push(o+"="+i)}else for(var[n,a]of Object.entries(t)){n=e?`${e}[${n}]`:n;this.processObject(n,a,r,s+1)}}processArray(r,e,s,o){if(0!==e.length){let t=this.encodeKey(r);switch(this.options.arrayFormat){case"comma":var i=e.map(e=>(this.options.encode&&"string"==typeof e?encodeURIComponent:String)(e)).join(this.options.arraySeparator);s.push(t+"="+i);break;case"index":e.forEach((e,t)=>{this.processObject(r+`[${t}]`,e,s,o)});break;case"separator":e.forEach(e=>{e=(this.options.encode&&"string"==typeof e?encodeURIComponent:String)(e);s.push(t+"="+e)});break;case"none":e.forEach(e=>{this.processObject(r,e,s,o)});break;default:e.forEach(e=>{this.processObject(r+"[]",e,s,o)})}}}encodeKey(t){if(!this.options.encode)return t;var r=[];let s="";for(let e=0;e<t.length;e++){var o=t[e];"["===o||"]"===o?(s&&(r.push(encodeURIComponent(s)),s=""),r.push(o)):s+=o}return s&&r.push(encodeURIComponent(s)),r.join("")}setValue(e,t,r){var s=t.match(/([^\[\]]+)|(\[\])/g);if(s){let t=e;for(let e=0;e<s.length;e++){var o,i,n=s[e],a=e===s.length-1;"[]"===n?(Array.isArray(t)||(t=[]),a?r&&t.push(this.parseValue(r)):(o=s[e+1],o=parseInt(o,10),isNaN(o)?(i={},t.push(i),t=i):(void 0===t[o]&&(t[o]={}),t=t[o],e++))):a?void 0!==r&&(t[n]=this.parseValue(r)):(void 0!==t[n]&&"object"==typeof t[n]||(i=s[e+1],t[n]="[]"===i?[]:{}),t=t[n])}}else void 0!==r&&(e[t]=this.parseValue(r))}transformArrays(s){if(Array.isArray(s))return s.map(e=>"object"==typeof e&&null!==e?this.transformArrays(e):e);if("object"!=typeof s||null===s)return s;let r={};var e=Object.keys(s);if(e.every(e=>{var t=parseInt(e,10);return!isNaN(t)&&0<=t&&t.toString()===e})&&0<e.length){let r=[];return e.forEach(e=>{var t=parseInt(e,10);r[t]=this.transformArrays(s[e])}),r}return e.forEach(e=>{var t=s[e];r[e]="object"==typeof t&&null!==t?this.transformArrays(t):t}),r}parseValue(e){var t;return"true"===e||"false"!==e&&("null"===e?null:"undefined"!==e?(t=Number(e),isNaN(t)||""===e.trim()?e:t):void 0)}isPrimitive(e){return null===e||"object"!=typeof e&&"function"!=typeof e}setOptions(e){this.options={...this.options,...e}}getOptions(){return{...this.options}}static exposeToGlobal(){"undefined"!=typeof window&&(window.Qust=Qust)}}let qust={stringify(e,t){return new Qust(t).stringify(e)},parse(e,t){return new Qust(t).parse(e)}};"undefined"!=typeof window&&(window.Qust=Qust,window.qust=qust);
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable", "ESNext"],
6
+ "strict": false,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "declaration": false,
10
+ "sourceMap": false,
11
+ "allowSyntheticDefaultImports": true,
12
+ "noEmit": false
13
+ },
14
+ "include": [],
15
+ "exclude": [
16
+ "node_modules",
17
+ "**/*.d.ts"
18
+ ]
19
+ }