@ndiinginc/router 4.4.0 → 4.4.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 +177 -53
- package/package.json +1 -1
- package/src/app.js +5 -1
- package/src/router.js +10 -3
- package/src/util.js +15 -3
package/README.md
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# @ndiinginc/router
|
|
2
2
|
|
|
3
|
-
Router HTTP kecil, cepat, dan **zero-dependency** untuk Node.js. Dibangun langsung di atas `node:http`, dengan routing berbasis tree, nested router, middleware async, error handler,
|
|
3
|
+
Router HTTP kecil, cepat, dan **zero-dependency** untuk Node.js. Dibangun langsung di atas `node:http`, dengan routing berbasis tree, LRU cache untuk lookup route, nested router, middleware async, error handler, timeout per request, serta middleware `json` dan `cors` bawaan.
|
|
4
4
|
|
|
5
5
|
## Fitur
|
|
6
6
|
|
|
7
7
|
- Zero dependency, murni `node:http`
|
|
8
|
-
- Routing berbasis tree per
|
|
8
|
+
- Routing berbasis tree per segmen (static match diprioritaskan, lalu dynamic)
|
|
9
9
|
- Parameter dinamis `:id` dan wildcard `*`
|
|
10
|
+
- LRU cache untuk hasil lookup route
|
|
10
11
|
- Nested router dengan `use(path, router)`
|
|
11
12
|
- Middleware global dan per-path, mendukung `async`
|
|
12
13
|
- Error handler dengan signature `(err, req, res)`
|
|
14
|
+
- Timeout per request (`req.timeout`) yang menghasilkan `504`
|
|
13
15
|
- `req.path`, `req.query`, `req.params`, `req.body`
|
|
14
16
|
- Helper `res.send()` dan `res.json()`
|
|
15
|
-
-
|
|
17
|
+
- Middleware bawaan: `json()` (dengan limit dan proteksi prototype pollution) dan `cors()`
|
|
16
18
|
- Semua HTTP method, termasuk `QUERY`
|
|
17
19
|
|
|
18
20
|
## Instalasi
|
|
@@ -21,14 +23,16 @@ Router HTTP kecil, cepat, dan **zero-dependency** untuk Node.js. Dibangun langsu
|
|
|
21
23
|
npm install @ndiinginc/router
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
Butuh
|
|
26
|
+
Butuh Node.js 18.3 atau lebih baru.
|
|
25
27
|
|
|
26
28
|
## Quick Start
|
|
27
29
|
|
|
28
30
|
```js
|
|
29
|
-
const
|
|
31
|
+
const App = require("@ndiinginc/router");
|
|
30
32
|
|
|
31
|
-
const app = new
|
|
33
|
+
const app = new App();
|
|
34
|
+
|
|
35
|
+
app.use(App.json());
|
|
32
36
|
|
|
33
37
|
app.get("/", (req, res) => {
|
|
34
38
|
res.json({ hello: "world" });
|
|
@@ -38,19 +42,44 @@ app.get("/users/:id", (req, res) => {
|
|
|
38
42
|
res.json({ id: req.params.id });
|
|
39
43
|
});
|
|
40
44
|
|
|
45
|
+
app.post("/users", (req, res) => {
|
|
46
|
+
res.json({ received: req.body });
|
|
47
|
+
});
|
|
48
|
+
|
|
41
49
|
app.listen(3000);
|
|
42
50
|
```
|
|
43
51
|
|
|
52
|
+
Yang diekspor adalah class `App`, dengan tiga properti tambahan:
|
|
53
|
+
|
|
54
|
+
| Export | Keterangan |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `App` | Router + HTTP server (`listen`) |
|
|
57
|
+
| `App.Router` | Router tanpa server, untuk nested router |
|
|
58
|
+
| `App.json` | Middleware JSON body parser |
|
|
59
|
+
| `App.cors` | Middleware CORS |
|
|
60
|
+
|
|
44
61
|
## Routing
|
|
45
62
|
|
|
46
63
|
Semua method mengembalikan `this`, jadi bisa di-chain.
|
|
47
64
|
|
|
48
65
|
```js
|
|
49
|
-
app.get("/users", handler)
|
|
66
|
+
app.get("/users", handler)
|
|
67
|
+
.post("/users", handler)
|
|
68
|
+
.put("/users/:id", handler)
|
|
69
|
+
.patch("/users/:id", handler)
|
|
70
|
+
.delete("/users/:id", handler);
|
|
50
71
|
```
|
|
51
72
|
|
|
52
73
|
Method yang tersedia: `get`, `post`, `put`, `patch`, `delete`, `head`, `options`, `trace`, `connect`, `query`, dan `use` (semua method).
|
|
53
74
|
|
|
75
|
+
Satu route boleh punya beberapa handler:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
app.get("/profile", authenticate, loadUser, (req, res) => {
|
|
79
|
+
res.json(req.user);
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
54
83
|
### Parameter dinamis
|
|
55
84
|
|
|
56
85
|
```js
|
|
@@ -62,7 +91,7 @@ app.get("/posts/:postId/comments/:commentId", (req, res) => {
|
|
|
62
91
|
|
|
63
92
|
### Wildcard
|
|
64
93
|
|
|
65
|
-
`*` cocok dengan karakter apa pun di dalam satu segmen
|
|
94
|
+
`*` cocok dengan karakter apa pun **di dalam satu segmen**. Wildcard tidak melintasi `/`.
|
|
66
95
|
|
|
67
96
|
```js
|
|
68
97
|
app.get("/files/report-*", (req, res) => {
|
|
@@ -70,13 +99,15 @@ app.get("/files/report-*", (req, res) => {
|
|
|
70
99
|
});
|
|
71
100
|
```
|
|
72
101
|
|
|
73
|
-
###
|
|
102
|
+
### Aturan matching
|
|
74
103
|
|
|
75
|
-
Untuk setiap segmen, router mencoba **static match dulu**, baru pattern dinamis. Jadi `/users/me` menang atas `/users/:id` bila keduanya terdaftar.
|
|
104
|
+
- Untuk setiap segmen, router mencoba **static match dulu**, baru pattern dinamis. Jadi `/users/me` menang atas `/users/:id` bila keduanya terdaftar.
|
|
105
|
+
- Jika ada beberapa pattern dinamis di level yang sama, yang **terdaftar lebih dulu** yang dipakai (tanpa backtracking).
|
|
106
|
+
- Trailing slash dan slash ganda diabaikan: `/users`, `/users/`, dan `//users` dianggap sama.
|
|
76
107
|
|
|
77
108
|
## Middleware
|
|
78
109
|
|
|
79
|
-
Middleware didaftarkan dengan `use()
|
|
110
|
+
Middleware didaftarkan dengan `use()`. Semua handler (middleware dan route) dijalankan **berurutan sesuai urutan pendaftaran**.
|
|
80
111
|
|
|
81
112
|
```js
|
|
82
113
|
// Global
|
|
@@ -93,15 +124,17 @@ app.use("/admin", async (req, res) => {
|
|
|
93
124
|
});
|
|
94
125
|
```
|
|
95
126
|
|
|
96
|
-
Chain berhenti otomatis begitu response dikirim (`res.
|
|
127
|
+
Chain berhenti otomatis begitu response selesai dikirim (`res.writableEnded`), jadi handler berikutnya tidak dipanggil.
|
|
97
128
|
|
|
98
|
-
> Middleware
|
|
129
|
+
> Middleware tetap dijalankan walaupun tidak ada route yang cocok, sebelum respons 404 dikirim.
|
|
99
130
|
|
|
100
131
|
## Nested Router
|
|
101
132
|
|
|
102
|
-
Pecah aplikasi
|
|
133
|
+
Pecah aplikasi menjadi beberapa router, lalu pasang dengan `use(path, router)`. Nesting bisa berlapis.
|
|
103
134
|
|
|
104
135
|
```js
|
|
136
|
+
const { Router } = App;
|
|
137
|
+
|
|
105
138
|
const users = new Router();
|
|
106
139
|
users.get("/", (req, res) => res.json({ list: [] }));
|
|
107
140
|
users.get("/:id", (req, res) => res.json({ id: req.params.id }));
|
|
@@ -109,16 +142,18 @@ users.get("/:id", (req, res) => res.json({ id: req.params.id }));
|
|
|
109
142
|
const admin = new Router();
|
|
110
143
|
admin.use("/users", users);
|
|
111
144
|
|
|
112
|
-
const app = new
|
|
145
|
+
const app = new App();
|
|
113
146
|
app.use("/admin", admin);
|
|
114
147
|
|
|
115
|
-
// GET /admin/users -> list
|
|
148
|
+
// GET /admin/users -> { list: [] }
|
|
116
149
|
// GET /admin/users/42 -> { id: "42" }
|
|
117
150
|
```
|
|
118
151
|
|
|
152
|
+
> Tree sub-router digabungkan saat `use()` dipanggil. Daftarkan semua route ke sub-router **sebelum** di-mount; route yang ditambahkan setelahnya tidak ikut. Satu instance sub-router sebaiknya hanya di-mount satu kali.
|
|
153
|
+
|
|
119
154
|
## Error Handling
|
|
120
155
|
|
|
121
|
-
Handler dengan **tiga argumen** `(err, req, res)` dikenali sebagai error handler.
|
|
156
|
+
Handler dengan **tiga argumen** `(err, req, res)` dikenali sebagai error handler. Dipanggil saat handler atau middleware melempar error (sync maupun async).
|
|
122
157
|
|
|
123
158
|
```js
|
|
124
159
|
app.get("/boom", () => {
|
|
@@ -127,7 +162,28 @@ app.get("/boom", () => {
|
|
|
127
162
|
|
|
128
163
|
app.use((err, req, res) => {
|
|
129
164
|
res.statusCode = err.statusCode || 500;
|
|
130
|
-
res.json({ error: err.message });
|
|
165
|
+
res.json({ error: { message: err.message } });
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> Deteksi error handler memakai `handler.length === 3`. Jangan memakai default parameter atau rest parameter di signature handler, karena mengubah `length`.
|
|
170
|
+
|
|
171
|
+
### Menentukan status code
|
|
172
|
+
|
|
173
|
+
Ada dua cara:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
// 1. Properti statusCode pada error
|
|
177
|
+
app.get("/admin", () => {
|
|
178
|
+
const err = new Error("Forbidden");
|
|
179
|
+
err.statusCode = 403;
|
|
180
|
+
throw err;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// 2. Set res.statusCode sebelum throw
|
|
184
|
+
app.get("/limit", (req, res) => {
|
|
185
|
+
res.statusCode = 429;
|
|
186
|
+
throw new Error("Too Many Requests");
|
|
131
187
|
});
|
|
132
188
|
```
|
|
133
189
|
|
|
@@ -136,21 +192,39 @@ app.use((err, req, res) => {
|
|
|
136
192
|
Jika tidak ada handler yang mengirim response, router mengirim JSON otomatis:
|
|
137
193
|
|
|
138
194
|
```json
|
|
139
|
-
{ "error": { "
|
|
195
|
+
{ "error": { "message": "Not Found" } }
|
|
140
196
|
```
|
|
141
197
|
|
|
142
|
-
- Tidak ada route cocok:
|
|
143
|
-
- Error
|
|
144
|
-
-
|
|
198
|
+
- Tidak ada route cocok: `404 Not Found`
|
|
199
|
+
- Error tanpa status code: `500`
|
|
200
|
+
- Jika `res.statusCode` sudah `>= 400`, nilainya dipertahankan
|
|
201
|
+
- Jika error handler sendiri melempar error, error baru dibuat dengan `cause` menunjuk error asli
|
|
202
|
+
|
|
203
|
+
- Field `stack` hanya disertakan saat `NODE_ENV=development`
|
|
204
|
+
|
|
205
|
+
## Timeout
|
|
206
|
+
|
|
207
|
+
Set `req.timeout` (milidetik) untuk membatasi waktu eksekusi handler. Jika terlampaui, error `504 Gateway Timeout` dilempar dan diteruskan ke error handler.
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
app.use((req) => {
|
|
211
|
+
req.timeout = 5000;
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- Nilai default `0` (tanpa timeout)
|
|
216
|
+
- Harus diset **secara sinkron di middleware pertama** pada chain, karena dibaca sesaat setelah chain mulai berjalan
|
|
217
|
+
- Timeout tidak membatalkan handler yang sedang berjalan; hanya menghentikan penantian dan mengirim error
|
|
145
218
|
|
|
146
219
|
## Request
|
|
147
220
|
|
|
148
|
-
| Properti
|
|
149
|
-
|
|
|
150
|
-
| `req.path`
|
|
151
|
-
| `req.query`
|
|
152
|
-
| `req.params` | Parameter dari route dinamis
|
|
153
|
-
| `req.body`
|
|
221
|
+
| Properti | Keterangan |
|
|
222
|
+
| --- | --- |
|
|
223
|
+
| `req.path` | Pathname tanpa query string |
|
|
224
|
+
| `req.query` | Object query string, di-parse secara lazy. Key yang berulang menjadi array |
|
|
225
|
+
| `req.params` | Parameter dari route dinamis (sudah di-decode dengan `decodeURIComponent`) |
|
|
226
|
+
| `req.body` | Hasil parse body (butuh middleware `json()`) |
|
|
227
|
+
| `req.timeout` | Batas waktu handler dalam ms (default `0`) |
|
|
154
228
|
|
|
155
229
|
```js
|
|
156
230
|
// GET /search?tag=a&tag=b&page=2
|
|
@@ -159,61 +233,99 @@ app.get("/search", (req, res) => {
|
|
|
159
233
|
});
|
|
160
234
|
```
|
|
161
235
|
|
|
236
|
+
`Request` adalah turunan `http.IncomingMessage`, jadi `req.headers`, `req.method`, dan lainnya tetap tersedia.
|
|
237
|
+
|
|
162
238
|
## Response
|
|
163
239
|
|
|
164
|
-
| Method
|
|
165
|
-
|
|
|
166
|
-
| `res.send(
|
|
167
|
-
| `res.json(
|
|
240
|
+
| Method | Keterangan |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `res.send(chunk)` | Kirim response dan akhiri |
|
|
243
|
+
| `res.json(value)` | Set `Content-Type: application/json`, serialize, lalu kirim |
|
|
168
244
|
|
|
169
|
-
|
|
245
|
+
`Response` adalah turunan `http.ServerResponse`, jadi `res.statusCode`, `res.setHeader()`, dan lainnya tetap tersedia.
|
|
170
246
|
|
|
171
|
-
##
|
|
247
|
+
## Middleware Bawaan
|
|
248
|
+
|
|
249
|
+
### `json([options])`
|
|
172
250
|
|
|
173
251
|
```js
|
|
174
|
-
|
|
175
|
-
|
|
252
|
+
app.use(App.json({ limit: 100 * 1024 })); // default 100 KB
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
| Opsi | Default | Keterangan |
|
|
256
|
+
| --- | --- | --- |
|
|
257
|
+
| `limit` | `102400` | Ukuran body maksimum dalam byte |
|
|
176
258
|
|
|
177
|
-
|
|
259
|
+
Perilaku:
|
|
178
260
|
|
|
179
|
-
|
|
261
|
+
- Hanya memproses method `POST`, `PUT`, `PATCH`, dan `QUERY` dengan `Content-Type: application/json`
|
|
262
|
+
- Body melebihi `limit` menghasilkan `413 Payload Too Large` (dicek lewat `Content-Length` dan saat streaming)
|
|
263
|
+
- JSON tidak valid menghasilkan `400` dengan pesan `Invalid JSON body`
|
|
264
|
+
- Body kosong menghasilkan `req.body = null`
|
|
265
|
+
- Key `__proto__`, `constructor`, dan `prototype` dibuang untuk mencegah prototype pollution
|
|
180
266
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
267
|
+
### `cors([options])`
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
app.use(
|
|
271
|
+
App.cors({
|
|
272
|
+
origin: "https://example.com",
|
|
273
|
+
credentials: true,
|
|
274
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
275
|
+
headers: ["Content-Type", "Authorization"],
|
|
276
|
+
maxAge: 600,
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
184
279
|
```
|
|
185
280
|
|
|
281
|
+
| Opsi | Default | Keterangan |
|
|
282
|
+
| --- | --- | --- |
|
|
283
|
+
| `origin` | `"*"` | `"*"`, string origin, atau `null` |
|
|
284
|
+
| `credentials` | `false` | Mengirim `Access-Control-Allow-Credentials: true` |
|
|
285
|
+
| `headers` | - | `"*"` atau array nama header |
|
|
286
|
+
| `methods` | - | `"*"` atau array nama method |
|
|
287
|
+
| `maxAge` | `0` | Lama cache preflight (detik). `0` berarti header tidak dikirim |
|
|
288
|
+
|
|
186
289
|
Perilaku:
|
|
187
290
|
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
|
|
291
|
+
- Request `OPTIONS` dijawab langsung dengan `204` dan chain berhenti
|
|
292
|
+
- Dengan `origin: "*"` dan `credentials: true`, origin dari request dipantulkan dan header `Vary: Origin` ditambahkan
|
|
293
|
+
- Nilai `"*"` untuk `headers` dan `methods` diabaikan saat `credentials: true`, sesuai spesifikasi CORS
|
|
294
|
+
- Pada request non-preflight, `headers` (jika array) dikirim sebagai `Access-Control-Expose-Headers`
|
|
295
|
+
|
|
296
|
+
## Opsi Router
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
const app = new App({ size: 1000 });
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
| Opsi | Default | Keterangan |
|
|
303
|
+
| --- | --- | --- |
|
|
304
|
+
| `size` | `1000` | Jumlah maksimum entri LRU cache untuk hasil lookup `METHOD path` |
|
|
193
305
|
|
|
194
306
|
## API
|
|
195
307
|
|
|
196
|
-
### `new Router()`
|
|
308
|
+
### `new App([options])` / `new Router([options])`
|
|
197
309
|
|
|
198
|
-
Membuat instance
|
|
310
|
+
Membuat instance baru. `App` adalah `Router` ditambah `listen()`.
|
|
199
311
|
|
|
200
|
-
### `router.use([path],
|
|
312
|
+
### `router.use([path], ...handlers | router)`
|
|
201
313
|
|
|
202
314
|
Mendaftarkan middleware, error handler, atau nested router untuk semua method.
|
|
203
315
|
|
|
204
|
-
### `router.<method>([path],
|
|
316
|
+
### `router.<method>([path], ...handlers)`
|
|
205
317
|
|
|
206
|
-
Mendaftarkan handler untuk method tertentu. `path` opsional; jika dihilangkan, handler dipasang di root
|
|
318
|
+
Mendaftarkan handler untuk method tertentu. `path` opsional; jika dihilangkan, handler dipasang di root.
|
|
207
319
|
|
|
208
320
|
### `router.find(method, path)`
|
|
209
321
|
|
|
210
|
-
Mencari route tanpa menjalankannya. Berguna untuk testing.
|
|
322
|
+
Mencari route tanpa menjalankannya. Hasilnya di-cache. Berguna untuk testing.
|
|
211
323
|
|
|
212
324
|
```js
|
|
213
325
|
const { params, handlers, errorHandlers } = app.find("GET", "/users/1");
|
|
214
326
|
```
|
|
215
327
|
|
|
216
|
-
### `
|
|
328
|
+
### `app.listen(...args)`
|
|
217
329
|
|
|
218
330
|
Membuat `http.Server` dengan `Request` dan `Response` milik router, lalu memanggil `server.listen(...args)`. Mengembalikan instance `http.Server`.
|
|
219
331
|
|
|
@@ -222,6 +334,18 @@ const server = app.listen(0); // port acak
|
|
|
222
334
|
console.log(server.address().port);
|
|
223
335
|
```
|
|
224
336
|
|
|
337
|
+
## Benchmark
|
|
338
|
+
|
|
339
|
+
Hello world dengan satu route `GET /`, diuji memakai `autocannon -c 100 -d 40 -p 10` di mesin lokal.
|
|
340
|
+
|
|
341
|
+
| | Req/sec (avg) | Latency (avg) |
|
|
342
|
+
| --- | --- | --- |
|
|
343
|
+
| Fastify | 31.475 | 31,27 ms |
|
|
344
|
+
| **@ndiinginc/router** | 29.735 | 33,12 ms |
|
|
345
|
+
| Express | 20.211 | 48,93 ms |
|
|
346
|
+
|
|
347
|
+
Hasil bervariasi tergantung mesin, versi Node.js, dan jumlah route. Benchmark ini hanya menggambarkan overhead dasar, bukan performa aplikasi nyata.
|
|
348
|
+
|
|
225
349
|
## Testing
|
|
226
350
|
|
|
227
351
|
```bash
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -59,7 +59,11 @@ class App extends Router {
|
|
|
59
59
|
res.statusCode = err.statusCode || 500;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
const error = { message: err.message };
|
|
63
|
+
if (process.env.NODE_ENV === "development") {
|
|
64
|
+
error.stack = err.stack;
|
|
65
|
+
}
|
|
66
|
+
res.json({ error });
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
|
package/src/router.js
CHANGED
|
@@ -107,7 +107,7 @@ class Router {
|
|
|
107
107
|
|
|
108
108
|
let node = this.root;
|
|
109
109
|
|
|
110
|
-
let params =
|
|
110
|
+
let params = Object.create(null);
|
|
111
111
|
const handlers = {};
|
|
112
112
|
const errorHandlers = {};
|
|
113
113
|
|
|
@@ -120,7 +120,14 @@ class Router {
|
|
|
120
120
|
const match = child.pattern.exec(input);
|
|
121
121
|
if (match) {
|
|
122
122
|
matched = child;
|
|
123
|
-
|
|
123
|
+
for (const name in match.groups) {
|
|
124
|
+
const raw = match.groups[name];
|
|
125
|
+
try {
|
|
126
|
+
params[name] = decodeURIComponent(raw);
|
|
127
|
+
} catch {
|
|
128
|
+
params[name] = raw;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
124
131
|
break;
|
|
125
132
|
}
|
|
126
133
|
}
|
|
@@ -128,7 +135,7 @@ class Router {
|
|
|
128
135
|
|
|
129
136
|
if (!matched) {
|
|
130
137
|
node = null;
|
|
131
|
-
params =
|
|
138
|
+
params = Object.create(null);
|
|
132
139
|
break;
|
|
133
140
|
}
|
|
134
141
|
|
package/src/util.js
CHANGED
|
@@ -2,13 +2,25 @@ const URL_REGEX = /^([^?#]*)(\?([^#]*))?(#.*)?$/;
|
|
|
2
2
|
const QUERY_STRING_REGEX = /([^=&]*)?=([^&]*)?/g;
|
|
3
3
|
|
|
4
4
|
function parseQueryString(queryString) {
|
|
5
|
-
const result =
|
|
5
|
+
const result = Object.create(null);
|
|
6
6
|
|
|
7
7
|
let match;
|
|
8
8
|
while ((match = QUERY_STRING_REGEX.exec(queryString)) !== null) {
|
|
9
|
-
const [,
|
|
9
|
+
const [, rawName = "", rawValue = ""] = match;
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
let name
|
|
12
|
+
try {
|
|
13
|
+
name = decodeURIComponent(rawName)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
name=rawName
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let value
|
|
19
|
+
try {
|
|
20
|
+
value = decodeURIComponent(rawValue)
|
|
21
|
+
} catch (error) {
|
|
22
|
+
value=rawValue
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
if (result[name]) {
|
|
14
26
|
if (Array.isArray(result[name])) result[name].push(value);
|