@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/index.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
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
|
+
stringify(obj) {
|
|
16
|
+
if (!obj || typeof obj !== 'object') {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
const parts = [];
|
|
20
|
+
this.processObject('', obj, parts, 0);
|
|
21
|
+
return parts.length > 0 ? `?${parts.join('&')}` : '';
|
|
22
|
+
}
|
|
23
|
+
parse(queryString) {
|
|
24
|
+
if (!queryString || typeof queryString !== 'string') {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
const cleanString = queryString.startsWith('?')
|
|
28
|
+
? queryString.slice(1)
|
|
29
|
+
: queryString;
|
|
30
|
+
if (!cleanString) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
const result = {};
|
|
34
|
+
const pairs = cleanString.split('&');
|
|
35
|
+
for (const pair of pairs) {
|
|
36
|
+
if (!pair)
|
|
37
|
+
continue;
|
|
38
|
+
const [encodedKey, encodedValue] = pair.split('=');
|
|
39
|
+
if (!encodedKey)
|
|
40
|
+
continue;
|
|
41
|
+
const key = this.options.decode
|
|
42
|
+
? decodeURIComponent(encodedKey)
|
|
43
|
+
: encodedKey;
|
|
44
|
+
const value = this.options.decode && encodedValue !== undefined
|
|
45
|
+
? decodeURIComponent(encodedValue)
|
|
46
|
+
: encodedValue;
|
|
47
|
+
this.setValue(result, key, value);
|
|
48
|
+
}
|
|
49
|
+
return this.transformArrays(result);
|
|
50
|
+
}
|
|
51
|
+
processObject(prefix, value, parts, depth) {
|
|
52
|
+
if (depth > this.options.depth) {
|
|
53
|
+
console.warn('Qust: Profondeur maximale atteinte, arrêt de la récursion');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (value === null || value === undefined) {
|
|
57
|
+
if (!this.options.skipNull) {
|
|
58
|
+
parts.push(this.encodeKey(prefix));
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === 'string' && value === '' && this.options.skipEmptyString) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
this.processArray(prefix, value, parts, depth + 1);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === 'object' && !this.isPrimitive(value)) {
|
|
70
|
+
for (const [key, val] of Object.entries(value)) {
|
|
71
|
+
const newPrefix = prefix ? `${prefix}[${key}]` : key;
|
|
72
|
+
this.processObject(newPrefix, val, parts, depth + 1);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const encodedKey = this.encodeKey(prefix);
|
|
77
|
+
const encodedValue = this.options.encode && typeof value === 'string'
|
|
78
|
+
? encodeURIComponent(value)
|
|
79
|
+
: String(value);
|
|
80
|
+
parts.push(`${encodedKey}=${encodedValue}`);
|
|
81
|
+
}
|
|
82
|
+
processArray(prefix, array, parts, depth) {
|
|
83
|
+
if (array.length === 0)
|
|
84
|
+
return;
|
|
85
|
+
const encodedKey = this.encodeKey(prefix);
|
|
86
|
+
switch (this.options.arrayFormat) {
|
|
87
|
+
case 'comma':
|
|
88
|
+
const values = array
|
|
89
|
+
.map(item => this.options.encode && typeof item === 'string'
|
|
90
|
+
? encodeURIComponent(item)
|
|
91
|
+
: String(item))
|
|
92
|
+
.join(this.options.arraySeparator);
|
|
93
|
+
parts.push(`${encodedKey}=${values}`);
|
|
94
|
+
break;
|
|
95
|
+
case 'index':
|
|
96
|
+
array.forEach((item, index) => {
|
|
97
|
+
const arrayPrefix = `${prefix}[${index}]`;
|
|
98
|
+
this.processObject(arrayPrefix, item, parts, depth);
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
case 'separator':
|
|
102
|
+
array.forEach(item => {
|
|
103
|
+
const encodedValue = this.options.encode && typeof item === 'string'
|
|
104
|
+
? encodeURIComponent(item)
|
|
105
|
+
: String(item);
|
|
106
|
+
parts.push(`${encodedKey}=${encodedValue}`);
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
case 'none':
|
|
110
|
+
array.forEach(item => {
|
|
111
|
+
this.processObject(prefix, item, parts, depth);
|
|
112
|
+
});
|
|
113
|
+
break;
|
|
114
|
+
case 'bracket':
|
|
115
|
+
default:
|
|
116
|
+
array.forEach(item => {
|
|
117
|
+
const arrayPrefix = `${prefix}[]`;
|
|
118
|
+
this.processObject(arrayPrefix, item, parts, depth);
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
encodeKey(key) {
|
|
124
|
+
if (!this.options.encode) {
|
|
125
|
+
return key;
|
|
126
|
+
}
|
|
127
|
+
const parts = [];
|
|
128
|
+
let currentPart = '';
|
|
129
|
+
for (let i = 0; i < key.length; i++) {
|
|
130
|
+
const char = key[i];
|
|
131
|
+
if (char === '[' || char === ']') {
|
|
132
|
+
if (currentPart) {
|
|
133
|
+
parts.push(encodeURIComponent(currentPart));
|
|
134
|
+
currentPart = '';
|
|
135
|
+
}
|
|
136
|
+
parts.push(char);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
currentPart += char;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (currentPart) {
|
|
143
|
+
parts.push(encodeURIComponent(currentPart));
|
|
144
|
+
}
|
|
145
|
+
return parts.join('');
|
|
146
|
+
}
|
|
147
|
+
setValue(obj, key, value) {
|
|
148
|
+
const matches = key.match(/([^\[\]]+)|(\[\])/g);
|
|
149
|
+
if (!matches) {
|
|
150
|
+
if (value !== undefined) {
|
|
151
|
+
obj[key] = this.parseValue(value);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let current = obj;
|
|
156
|
+
for (let i = 0; i < matches.length; i++) {
|
|
157
|
+
const match = matches[i];
|
|
158
|
+
const isLast = i === matches.length - 1;
|
|
159
|
+
if (match === '[]') {
|
|
160
|
+
if (!Array.isArray(current)) {
|
|
161
|
+
current = [];
|
|
162
|
+
}
|
|
163
|
+
if (isLast) {
|
|
164
|
+
if (!!value) {
|
|
165
|
+
current.push(this.parseValue(value));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const nextMatch = matches[i + 1];
|
|
170
|
+
const nextIndex = parseInt(nextMatch, 10);
|
|
171
|
+
if (!isNaN(nextIndex)) {
|
|
172
|
+
if (current[nextIndex] === undefined) {
|
|
173
|
+
current[nextIndex] = {};
|
|
174
|
+
}
|
|
175
|
+
current = current[nextIndex];
|
|
176
|
+
i++;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
const newObj = {};
|
|
180
|
+
current.push(newObj);
|
|
181
|
+
current = newObj;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
if (isLast) {
|
|
187
|
+
if (value !== undefined) {
|
|
188
|
+
current[match] = this.parseValue(value);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
if (current[match] === undefined || typeof current[match] !== 'object') {
|
|
193
|
+
const nextMatch = matches[i + 1];
|
|
194
|
+
current[match] = nextMatch === '[]' ? [] : {};
|
|
195
|
+
}
|
|
196
|
+
current = current[match];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
transformArrays(obj) {
|
|
202
|
+
if (Array.isArray(obj)) {
|
|
203
|
+
return obj.map(item => typeof item === 'object' && item !== null
|
|
204
|
+
? this.transformArrays(item)
|
|
205
|
+
: item);
|
|
206
|
+
}
|
|
207
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
208
|
+
return obj;
|
|
209
|
+
}
|
|
210
|
+
const result = {};
|
|
211
|
+
const keys = Object.keys(obj);
|
|
212
|
+
const isArrayLike = keys.every(key => {
|
|
213
|
+
const num = parseInt(key, 10);
|
|
214
|
+
return !isNaN(num) && num >= 0 && num.toString() === key;
|
|
215
|
+
});
|
|
216
|
+
if (isArrayLike && keys.length > 0) {
|
|
217
|
+
const array = [];
|
|
218
|
+
keys.forEach(key => {
|
|
219
|
+
const index = parseInt(key, 10);
|
|
220
|
+
array[index] = this.transformArrays(obj[key]);
|
|
221
|
+
});
|
|
222
|
+
return array;
|
|
223
|
+
}
|
|
224
|
+
keys.forEach(key => {
|
|
225
|
+
const value = obj[key];
|
|
226
|
+
result[key] =
|
|
227
|
+
typeof value === 'object' && value !== null
|
|
228
|
+
? this.transformArrays(value)
|
|
229
|
+
: value;
|
|
230
|
+
});
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
parseValue(value) {
|
|
234
|
+
if (value === 'true')
|
|
235
|
+
return true;
|
|
236
|
+
if (value === 'false')
|
|
237
|
+
return false;
|
|
238
|
+
if (value === 'null')
|
|
239
|
+
return null;
|
|
240
|
+
if (value === 'undefined')
|
|
241
|
+
return undefined;
|
|
242
|
+
const num = Number(value);
|
|
243
|
+
if (!isNaN(num) && value.trim() !== '')
|
|
244
|
+
return num;
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
isPrimitive(value) {
|
|
248
|
+
return value === null ||
|
|
249
|
+
typeof value !== 'object' &&
|
|
250
|
+
typeof value !== 'function';
|
|
251
|
+
}
|
|
252
|
+
setOptions(newOptions) {
|
|
253
|
+
this.options = { ...this.options, ...newOptions };
|
|
254
|
+
}
|
|
255
|
+
getOptions() {
|
|
256
|
+
return { ...this.options };
|
|
257
|
+
}
|
|
258
|
+
static exposeToGlobal() {
|
|
259
|
+
if (typeof window !== "undefined") {
|
|
260
|
+
window.Qust = Qust;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const qust = {
|
|
265
|
+
stringify(obj, options) {
|
|
266
|
+
return new Qust(options).stringify(obj);
|
|
267
|
+
},
|
|
268
|
+
parse(queryString, options) {
|
|
269
|
+
return new Qust(options).parse(queryString);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
if (typeof window !== "undefined") {
|
|
273
|
+
window.Qust = Qust;
|
|
274
|
+
window.qust = qust;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export { Qust, qust as default, qust };
|
package/index.min.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 };
|
package/index.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(t={}){this.options={...DEFAULT_OPTIONS,...t}}stringify(t){return t&&"object"==typeof t&&(this.processObject("",t,t=[],0),0<t.length)?"?"+t.join("&"):""}parse(t){if(!t||"string"!=typeof t)return{};t=t.startsWith("?")?t.slice(1):t;if(!t)return{};var e,s,r,o={};for(e of t.split("&"))e&&([s,r]=e.split("="),s)&&(s=this.options.decode?decodeURIComponent(s):s,r=this.options.decode&&void 0!==r?decodeURIComponent(r):r,this.setValue(o,s,r));return this.transformArrays(o)}processObject(t,e,s,r){if(!(r>this.options.depth))if(null==e)this.options.skipNull||s.push(this.encodeKey(t));else if("string"!=typeof e||""!==e||!this.options.skipEmptyString)if(Array.isArray(e))this.processArray(t,e,s,r+1);else if("object"!=typeof e||this.isPrimitive(e)){var o=this.encodeKey(t),i=(this.options.encode&&"string"==typeof e?encodeURIComponent:String)(e);s.push(o+"="+i)}else for(var[n,a]of Object.entries(e)){n=t?`${t}[${n}]`:n;this.processObject(n,a,s,r+1)}}processArray(s,t,r,o){if(0!==t.length){let e=this.encodeKey(s);switch(this.options.arrayFormat){case"comma":var i=t.map(t=>(this.options.encode&&"string"==typeof t?encodeURIComponent:String)(t)).join(this.options.arraySeparator);r.push(e+"="+i);break;case"index":t.forEach((t,e)=>{this.processObject(s+`[${e}]`,t,r,o)});break;case"separator":t.forEach(t=>{t=(this.options.encode&&"string"==typeof t?encodeURIComponent:String)(t);r.push(e+"="+t)});break;case"none":t.forEach(t=>{this.processObject(s,t,r,o)});break;default:t.forEach(t=>{this.processObject(s+"[]",t,r,o)})}}}encodeKey(e){if(!this.options.encode)return e;var s=[];let r="";for(let t=0;t<e.length;t++){var o=e[t];"["===o||"]"===o?(r&&(s.push(encodeURIComponent(r)),r=""),s.push(o)):r+=o}return r&&s.push(encodeURIComponent(r)),s.join("")}setValue(t,e,s){var r=e.match(/([^\[\]]+)|(\[\])/g);if(r){let e=t;for(let t=0;t<r.length;t++){var o,i,n=r[t],a=t===r.length-1;"[]"===n?(Array.isArray(e)||(e=[]),a?s&&e.push(this.parseValue(s)):(o=r[t+1],o=parseInt(o,10),isNaN(o)?(i={},e.push(i),e=i):(void 0===e[o]&&(e[o]={}),e=e[o],t++))):a?void 0!==s&&(e[n]=this.parseValue(s)):(void 0!==e[n]&&"object"==typeof e[n]||(i=r[t+1],e[n]="[]"===i?[]:{}),e=e[n])}}else void 0!==s&&(t[e]=this.parseValue(s))}transformArrays(r){if(Array.isArray(r))return r.map(t=>"object"==typeof t&&null!==t?this.transformArrays(t):t);if("object"!=typeof r||null===r)return r;let s={};var t=Object.keys(r);if(t.every(t=>{var e=parseInt(t,10);return!isNaN(e)&&0<=e&&e.toString()===t})&&0<t.length){let s=[];return t.forEach(t=>{var e=parseInt(t,10);s[e]=this.transformArrays(r[t])}),s}return t.forEach(t=>{var e=r[t];s[t]="object"==typeof e&&null!==e?this.transformArrays(e):e}),s}parseValue(t){var e;return"true"===t||"false"!==t&&("null"===t?null:"undefined"!==t?(e=Number(t),isNaN(e)||""===t.trim()?t:e):void 0)}isPrimitive(t){return null===t||"object"!=typeof t&&"function"!=typeof t}setOptions(t){this.options={...this.options,...t}}getOptions(){return{...this.options}}static exposeToGlobal(){"undefined"!=typeof window&&(window.Qust=Qust)}}let qust={stringify(t,e){return new Qust(e).stringify(t)},parse(t,e){return new Qust(e).parse(t)}};"undefined"!=typeof window&&(window.Qust=Qust,window.qust=qust);export{Qust,qust as default,qust};
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arc-js/qust",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.0.1",
|
|
7
|
+
"description": "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.",
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "INICODE <contact@inicode@gmail.com>",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"init": "npm init --scope=@arc-js/qust",
|
|
14
|
+
"login": "npm login"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {},
|
|
17
|
+
"dependencies": {}
|
|
18
|
+
}
|