@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/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
+ }