@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 +604 -0
- package/index.d.ts +88 -0
- package/index.js +277 -0
- package/index.min.d.ts +88 -0
- package/index.min.js +1 -0
- package/package.json +18 -0
- package/qust.all.js +328 -0
- package/qust.all.min.js +1 -0
- package/tsconfig.json +19 -0
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
|
+
|
package/qust.all.min.js
ADDED
|
@@ -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
|
+
}
|