parse-nested-form-data 1.0.1 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  <div align="center">
2
2
  <h1>parse-nested-form-data</h1>
3
3
 
4
- <p>A tiny node module for parsing FormData by name into objects and arrays</p>
4
+ <p>Parse FormData into nested objects and arrays, based on the field names</p>
5
5
  </div>
6
6
 
7
7
  ---
@@ -10,6 +10,7 @@
10
10
  [![Build Status][build-badge]][build]
11
11
  [![Code Coverage][coverage-badge]][coverage]
12
12
  [![version][version-badge]][package]
13
+ [![Size][size-badge]][size]
13
14
  [![downloads][downloads-badge]][npmtrends]
14
15
  [![MIT License][license-badge]][license]
15
16
  [![All Contributors][all-contributors-badge]](#contributors-)
@@ -17,33 +18,53 @@
17
18
  [![Code of Conduct][coc-badge]][coc]
18
19
  <!-- prettier-ignore-end -->
19
20
 
20
- ## The problem
21
-
22
- 1. You use Forms to upload data to your server (e.g. with remix-run)
23
- 2. You want a way to upload objects and arrays with booleans and numbers
24
- 3. You do not want to transfer the data per hand
21
+ A form sends flat name-value pairs. This library turns them into nested objects
22
+ and arrays, with numbers, booleans and `null`. The field names tell it how.
23
+
24
+ ```html
25
+ <form method="post">
26
+ <input name="user.name" value="Ada" />
27
+ <input name="+user.age" value="36" />
28
+ <input name="&user.admin" type="checkbox" checked />
29
+ <input name="tags[]" value="math" />
30
+ <input name="tags[]" value="code" />
31
+ </form>
32
+ ```
25
33
 
26
- ## This solution
34
+ ```ts
35
+ parseFormData(formData)
36
+ // {
37
+ // user: {name: 'Ada', age: 36, admin: true},
38
+ // tags: ['math', 'code'],
39
+ // }
40
+ ```
27
41
 
28
- A parse functions that uses the name prop to tells the parser how to create an
29
- object out of the values. You can tell the parser to do not parse empty values.
42
+ It's under 2 kB (minified and brotli-compressed), has no dependencies and works
43
+ in all modern JavaScript runtimes (see
44
+ [Supported runtimes](#supported-runtimes)).
30
45
 
31
46
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
32
47
  <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
33
48
 
34
- - [Installation](#installation)
35
- - [Name](#name)
36
- - [Boolean](#boolean)
37
- - [Number](#number)
49
+ - [Install](#install)
50
+ - [Usage](#usage)
51
+ - [Field names](#field-names)
52
+ - [Numbers](#numbers)
53
+ - [Booleans](#booleans)
38
54
  - [Null](#null)
39
- - [Array](#array)
40
- - [Object](#object)
41
- - [Basic Usage](#basic-usage)
42
- - [Transform values](#transform-values)
43
- - [Advanced usage](#advanced-usage)
44
- - [Options](#options)
45
- - [removeEmptyString: `[boolean]`](#removeemptystring-boolean)
46
- - [transformEntry: `(entry: [path: string, value: string | File], defaultTransform: DefaultTransform) => [string, string | JsonLeafValue]`](#transformentry-entry-path-string-value-string--file-defaulttransform-defaulttransform--string-string--jsonleafvalue)
55
+ - [Arrays](#arrays)
56
+ - [Objects](#objects)
57
+ - [Files](#files)
58
+ - [API](#api)
59
+ - [parseFormData(formData, options?)](#parseformdataformdata-options)
60
+ - [formData](#formdata)
61
+ - [options](#options)
62
+ - [removeEmptyString](#removeemptystring)
63
+ - [depth](#depth)
64
+ - [transformEntry](#transformentry)
65
+ - [Errors](#errors)
66
+ - [Security](#security)
67
+ - [Supported runtimes](#supported-runtimes)
47
68
  - [Issues](#issues)
48
69
  - [🐛 Bugs](#-bugs)
49
70
  - [💡 Feature Requests](#-feature-requests)
@@ -53,263 +74,377 @@ object out of the values. You can tell the parser to do not parse empty values.
53
74
 
54
75
  <!-- END doctoc generated TOC please keep comment here to allow auto update -->
55
76
 
56
- ## Installation
57
-
58
- This module is distributed via [npm][npm] which is bundled with [node][node] and
59
- should be installed as one of your project's `dependencies`:
77
+ ## Install
60
78
 
61
79
  ```sh
62
80
  npm install parse-nested-form-data
63
81
  ```
64
82
 
65
- and then import it in your project:
83
+ ## Usage
66
84
 
67
85
  ```ts
68
86
  import {parseFormData} from 'parse-nested-form-data'
87
+
88
+ const data = parseFormData(await request.formData())
69
89
  ```
70
90
 
71
- ## Name
91
+ `parseFormData` reads each entry, puts its value at the place its name tells,
92
+ and returns a new object. The input stays unchanged.
72
93
 
73
- The name prop is used to tell the parser how to create an object out of the
74
- values. The name prop is a string that can contain the following characters:
94
+ ## Field names
75
95
 
76
- - `.`: to create a nested object
77
- - `[]`: to create an array (pushes value)
78
- - `[$order]`: to create an array (sets value at index and squashes array)
79
- - `&`: to transform the value to a boolean
80
- - `-`: to transform the value to null
81
- - `+`: to transform the value to a number
96
+ | In the name | Meaning | Name and value | Result |
97
+ | ---------------- | ------------------------------------ | -------------- | --------------- |
98
+ | `.key` | A key in an object | `a.b` = `x` | `{a: {b: 'x'}}` |
99
+ | `[key]` | A key in an object, same as `.key` | `a[b]` = `x` | `{a: {b: 'x'}}` |
100
+ | `[]` | Add the value to the end of an array | `a[]` = `x` | `{a: ['x']}` |
101
+ | `[0]`, `[1]`, … | Put the value at this array position | `a[0]` = `x` | `{a: ['x']}` |
102
+ | `+` at the start | Change the value to a number | `+a` = `36` | `{a: 36}` |
103
+ | `&` at the start | Change the value to a boolean | `&a` = `on` | `{a: true}` |
104
+ | `-` at the start | Change the value to `null` | `-a` = `x` | `{a: null}` |
82
105
 
83
- ### Boolean
106
+ You can combine them, for example `+orders[0].items[].price`.
84
107
 
85
- If the name prop starts with a `&` the value will be transformed to a boolean.
108
+ In this README, the name without its `+`, `&` or `-` is the **path**. Each
109
+ `key`, `.key`, `[key]`, `[]` and `[0]` in it is a **path part**: `a.b[0]` has
110
+ three path parts.
86
111
 
87
- True if the value has the following values:
112
+ ### Numbers
88
113
 
89
- - `true`
90
- - `1`
91
- - `on`
114
+ A name that starts with `+` changes the value with `Number(value)`. A value that
115
+ isn't a number gives `NaN`. An empty string gives `0`.
92
116
 
93
117
  ```ts
94
118
  const formData = new FormData()
95
- formData.append('&isTrue', 'true')
96
- formData.append('&isFalse', 'false')
97
- formData.append('&isTrue1', '1')
98
- formData.append('&isFalse1', '0')
99
- formData.append('&isTrue2', 'on')
100
- formData.append('&isFalse2', 'off')
119
+ formData.append('+a', '1')
120
+ formData.append('+b', '1.5')
121
+ formData.append('+c', 'x')
101
122
  parseFormData(formData)
102
- // {
103
- // isTrue: true,
104
- // isFalse: false,
105
- // isTrue1: true,
106
- // isFalse1: false,
107
- // isTrue2: true,
108
- // isFalse2: false,
109
- // }
123
+ // {a: 1, b: 1.5, c: NaN}
110
124
  ```
111
125
 
112
- ### Number
126
+ ### Booleans
113
127
 
114
- If the name prop starts with a `+` the value will be transformed to a number.
128
+ A name that starts with `&` changes the value to a boolean. The value `on`,
129
+ `true` or any number other than `0` gives `true`. All other values give `false`.
115
130
 
116
131
  ```ts
117
132
  const formData = new FormData()
118
- formData.append('+number', '1')
119
- formData.append('+number2', '1.1')
120
- formData.append('+number3', 'a')
133
+ formData.append('&a', 'on')
134
+ formData.append('&b', 'true')
135
+ formData.append('&c', '1')
136
+ formData.append('&d', 'off')
137
+ formData.append('&e', 'false')
138
+ formData.append('&f', '0')
121
139
  parseFormData(formData)
122
- // {
123
- // number: 1,
124
- // number2: 1.1,
125
- // number3: NaN,
126
- // }
140
+ // {a: true, b: true, c: true, d: false, e: false, f: false}
127
141
  ```
128
142
 
143
+ A checkbox sends `on` when it's checked. When it isn't checked, the browser
144
+ sends nothing, so the key is missing from the result.
145
+
129
146
  ### Null
130
147
 
131
- If the name prop starts with a `-` the value will be transformed to null.
148
+ A name that starts with `-` changes the value to `null`, whatever the value is.
132
149
 
133
150
  ```ts
134
151
  const formData = new FormData()
135
- formData.append('-null', 'null')
136
- formData.append('-ignored', 'ignored')
152
+ formData.append('-a', 'x')
137
153
  parseFormData(formData)
138
- // {
139
- // null: null,
140
- // ignored: null,
141
- // }
154
+ // {a: null}
142
155
  ```
143
156
 
144
- ### Array
157
+ ### Arrays
145
158
 
146
- If a path of the name prop ends with `[]` the value will be pushed to an array.
147
- If the path of the name prop ends with `[$order]` the value will be set at the
148
- index of the order. Cannot be mixed with `[]`.
159
+ `[]` adds the value to the end of the array, in the order of the entries:
149
160
 
150
161
  ```ts
151
162
  const formData = new FormData()
152
- formData.append('array[0]', '1')
153
- formData.append('array[1]', '2')
154
- formData.append('array[2]', '3')
155
- formData.append('array[3]', '4')
163
+ formData.append('a[]', '1')
164
+ formData.append('a[]', '2')
156
165
  parseFormData(formData)
157
- // {
158
- // array: ['1', '2', '3', '4'],
159
- // }
166
+ // {a: ['1', '2']}
160
167
  ```
161
168
 
169
+ `[0]`, `[1]`, … put the value at that position. The order of the entries doesn't
170
+ matter. Gaps are removed, so `a[1]` and `a[5]` give an array of two values:
171
+
162
172
  ```ts
163
173
  const formData = new FormData()
164
- formData.append('array[]', '1')
165
- formData.append('array[]', '2')
166
- formData.append('array[]', '3')
174
+ formData.append('a[5]', 'second')
175
+ formData.append('a[1]', 'first')
167
176
  parseFormData(formData)
168
- // {
169
- // array: ['1', '2', '3'],
170
- // }
177
+ // {a: ['first', 'second']}
171
178
  ```
172
179
 
173
- Also works with nested objects:
180
+ One array can't use both `[]` and `[0]`. That throws a `MixedArrayError`.
181
+
182
+ The largest position is `4294967294`, the largest index a JavaScript array can
183
+ hold. A larger number throws an `InvalidPathError`.
184
+
185
+ Arrays can hold objects and other arrays:
174
186
 
175
187
  ```ts
176
188
  const formData = new FormData()
177
- formData.append('array[0].a', '1')
178
- formData.append('array[1].a', '2')
179
- formData.append('array[2].a', '3')
189
+ formData.append('a[0].b', '1')
190
+ formData.append('a[1].b', '2')
191
+ formData.append('c[0][]', '3')
192
+ formData.append('c[0][]', '4')
193
+ formData.append('c[1][]', '5')
180
194
  parseFormData(formData)
181
- // {
182
- // array: [{a: '1'}, {a: '2'}, {a: '3'}],
183
- // }
195
+ // {a: [{b: '1'}, {b: '2'}], c: [['3', '4'], ['5']]}
184
196
  ```
185
197
 
186
- Also works with nested arrays:
198
+ ### Objects
199
+
200
+ `.key` and `[key]` mean the same thing, so `a.b` and `a[b]` are the same key.
201
+ Only digits in brackets (`[0]`) make an array. You can mix both forms with each
202
+ other and with arrays:
187
203
 
188
204
  ```ts
189
205
  const formData = new FormData()
190
- formData.append('array[0][]', '1')
191
- formData.append('array[0][]', '2')
192
- formData.append('array[1][]', '3')
193
- formData.append('array[1][]', '4')
206
+ formData.append('a[b]', '1')
207
+ formData.append('a.c[0].d', '2')
208
+ formData.append('a[c][1][d]', '3')
194
209
  parseFormData(formData)
195
- // {
196
- // array: [['1', '2'], ['3', '4']],
197
- // }
210
+ // {a: {b: '1', c: [{d: '2'}, {d: '3'}]}}
198
211
  ```
199
212
 
200
- ### Object
201
-
202
- If the name prop contains a `.` the value will be nested in an object.
213
+ The text in brackets is the key exactly as written. It can contain `.`, `[` and
214
+ `]`, if the brackets inside are balanced:
203
215
 
204
216
  ```ts
205
217
  const formData = new FormData()
206
- formData.append('object.a', '1')
207
- formData.append('object.b', '2')
218
+ formData.append('a[b.c]', '1')
219
+ formData.append('a[d[e]]', '2')
208
220
  parseFormData(formData)
209
- // {
210
- // object: {
211
- // a: '1',
212
- // b: '2',
213
- // },
214
- // }
221
+ // {a: {'b.c': '1', 'd[e]': '2'}}
215
222
  ```
216
223
 
217
- ## Basic Usage
224
+ Brackets in the wrong place don't cause an error:
218
225
 
219
- ### Transform values
226
+ - A `[` without a matching `]` stays in the key. `a[[0]` is the key `[0` in `a`.
227
+ `a[0` is the key `a[0`.
228
+ - A `]` without a matching `[` stays in the key. `a]b` is the key `a]b`.
229
+ - Text after `]` starts the next key. `a[0]b` is the same as `a[0].b`.
230
+ - Empty keys are skipped. `a..b` and `a.[b]` are the same as `a.b`, and `[a]` is
231
+ the same as `a`.
232
+
233
+ ### Files
234
+
235
+ `File` values stay as they are. This includes native files and files from a
236
+ polyfill such as `@remix-run/web-file`. A file is always the end of a path:
220
237
 
221
238
  ```ts
222
239
  const formData = new FormData()
223
- formData.append('+a', '1')
224
- formData.append('&b', 'true')
225
- formData.append('-c', 'null')
226
- formData.append('d', 'foo')
227
- parseFormData(formData, defaultTransform)
228
- // => {a: 1, b: true, c: null, d: 'foo'}
240
+ formData.append('avatar', new File(['...'], 'avatar.png'))
241
+ parseFormData(formData)
242
+ // {avatar: File}
243
+
244
+ formData.append('avatar.name', 'me.png')
245
+ parseFormData(formData)
246
+ // throws DuplicateKeyError('avatar')
229
247
  ```
230
248
 
231
- ## Advanced usage
249
+ ## API
250
+
251
+ ### parseFormData(formData, options?)
252
+
253
+ Returns a new object with the parsed values.
254
+
255
+ #### formData
256
+
257
+ Type: `Iterable<[string, string | File]>`
232
258
 
233
- Complex examples:
259
+ A `FormData` object, or any other list of `[name, value]` pairs, for example
260
+ `[['a.b', 'x']]` or a `URLSearchParams` object.
261
+
262
+ #### options
263
+
264
+ Type: `object`
265
+
266
+ ##### removeEmptyString
267
+
268
+ Type: `boolean`\
269
+ Default: `false`
270
+
271
+ Skip all entries whose value is an empty string (`''`).
234
272
 
235
273
  ```ts
236
274
  const formData = new FormData()
237
- formData.append('a[].b.c', '1')
238
- formData.append('a[].b.d', '2')
239
- formData.append('a[]', '3')
240
- formData.append('b[0]', '4')
241
- formData.append('+b[100]', '5')
242
- parseFormData(formData)
243
- // {
244
- // a: [
245
- // {
246
- // b: {
247
- // c: '1',
248
- // d: '2',
249
- // },
250
- // },
251
- // '3',
252
- // ],
253
- // b: ['4', 5],
254
- // }
275
+ formData.append('a', '')
276
+ formData.append('b', 'x')
277
+ parseFormData(formData, {removeEmptyString: true})
278
+ // {b: 'x'}
255
279
  ```
256
280
 
257
- ### Options
258
-
259
- The second argument is an option object with following properties:
260
-
261
- ### removeEmptyString: `[boolean]`
281
+ ##### depth
262
282
 
263
- _Default: `false`_
283
+ Type: `number`\
284
+ Default: `Infinity`
264
285
 
265
- If `true` empty strings will be removed from the result.
286
+ The maximum number of path parts in one path. All parts count, so `a.b[0]` and
287
+ `a[b][]` both have three. A longer path throws a [`DepthLimitError`](#errors).
288
+ The value must be a positive integer or `Infinity`. Other values throw a
289
+ `TypeError`.
266
290
 
267
291
  ```ts
268
292
  const formData = new FormData()
269
- formData.append('foo[1].foo', '')
270
- formData.append('foo[1].bar', 'test2')
271
- formData.append('foo[0].foo', 'test3')
272
- formData.append('foo[0].baz', '4')
293
+ formData.append('user.address.street', 'Main St')
294
+ parseFormData(formData, {depth: 3})
295
+ // {user: {address: {street: 'Main St'}}}
273
296
 
274
- parseFormData(formData, {removeEmptyString: true})
275
- // {foo: [{foo: 'test3', baz: '4'}, {bar: 'test2'}]}
297
+ formData.append('user.address.geo.lat', '52.5')
298
+ parseFormData(formData, {depth: 3})
299
+ // throws DepthLimitError('user.address.geo.lat')
276
300
  ```
277
301
 
278
- ### transformEntry: `(entry: [path: string, value: string | File], defaultTransform: DefaultTransform) => [string, string | JsonLeafValue]`
302
+ The parser can handle any depth. But a field like `a.a.a…` with thousands of
303
+ parts makes an object that crashes code that walks it recursively, for example
304
+ `JSON.stringify`, validators or deep clones. If the input comes from untrusted
305
+ clients, set a limit. `depth: 32` is enough for almost every form. Version 2.0
306
+ will change the default to a smaller number.
307
+
308
+ The depth is checked after `transformEntry`. An entry that `removeEmptyString`
309
+ skips is never checked.
310
+
311
+ ##### transformEntry
312
+
313
+ Type:
314
+ `(entry: [path: string, value: string | File], defaultTransform) => {path: string, value: string | number | boolean | null | File}`\
315
+ Default:
316
+ the built-in transform for `+`, `&` and `-`
317
+
318
+ Changes each entry before it's parsed. It gets the entry as `[name, value]` and
319
+ the built-in transform. It must return the path and the value for the result.
320
+ Call `defaultTransform(entry)` to keep `+`, `&` and `-` working:
279
321
 
280
322
  ```ts
281
323
  const formData = new FormData()
282
- formData.append('a', 'a')
283
- formData.append('b', 'b')
324
+ formData.append('a', 'x')
325
+ formData.append('+b', '1')
284
326
  parseFormData(formData, {
285
- transformEntry: ([path, value], defaultTransform) => {
327
+ transformEntry: (entry, defaultTransform) => {
328
+ const {path, value} = defaultTransform(entry)
286
329
  return {
287
330
  path,
288
- value:
289
- typeof value === 'string'
290
- ? value.toUpperCase()
291
- : defaultTransform(value),
331
+ value: typeof value === 'string' ? value.toUpperCase() : value,
292
332
  }
293
333
  },
294
334
  })
295
- // => {a: 'A', b: 'B'}
335
+ // {a: 'X', b: 1}
296
336
  ```
297
337
 
338
+ ## Errors
339
+
340
+ `parseFormData` stops at the first error. All error classes are exported:
341
+
342
+ ```ts
343
+ import {
344
+ DepthLimitError,
345
+ DuplicateKeyError,
346
+ ForbiddenKeyError,
347
+ InvalidPathError,
348
+ MixedArrayError,
349
+ } from 'parse-nested-form-data'
350
+ ```
351
+
352
+ | Error | Thrown when | Example names |
353
+ | ------------------- | ------------------------------------------------------------------------ | ---------------------------------------- |
354
+ | `DuplicateKeyError` | Two entries set the same path, or use a path part in two different ways. | `a` and `a`; `a` and `a.b`; `a.b`, `a[]` |
355
+ | `MixedArrayError` | One array uses both `[]` and `[0]`. | `a[]` and `a[0]` |
356
+ | `ForbiddenKeyError` | A path part is `__proto__`, `constructor` or `prototype`. | `__proto__.x`; `a[constructor]` |
357
+ | `DepthLimitError` | A path has more path parts than the `depth` option allows. | `a.b.c` with `depth: 2` |
358
+ | `InvalidPathError` | An array position is larger than `4294967294`. | `a[4294967295]` |
359
+ | `TypeError` | The `depth` option isn't a positive integer or `Infinity`. | `depth: 0` |
360
+
361
+ Each library error has a `key` property. It's the path up to the path part that
362
+ caused the error. For example, `a.b` = `y` and then `a.b[0]` = `x` throw
363
+ `DuplicateKeyError('a.b')`. In `ForbiddenKeyError`, `DepthLimitError` and
364
+ `InvalidPathError`, a `key` longer than 100 characters is cut to 100 characters
365
+ plus `...`.
366
+
367
+ `key` is the path after `transformEntry`, not the field name. So it doesn't have
368
+ the `+`, `&` or `-`: `+a.b.c` with `depth: 2` throws `DepthLimitError('a.b.c')`.
369
+ A custom `transformEntry` can change the path completely. If you use `key` to
370
+ find the form field, undo your own transform first.
371
+
372
+ The entries are parsed in order, so the error of an earlier entry comes first.
373
+ In one entry, the depth is checked first: a path that's too deep throws
374
+ `DepthLimitError`, even if it has `__proto__` in it.
375
+
376
+ ```ts
377
+ try {
378
+ parseFormData(formData)
379
+ } catch (error) {
380
+ if (error instanceof DuplicateKeyError) {
381
+ return badRequest(`The field ${error.key} is used more than once`)
382
+ }
383
+ throw error
384
+ }
385
+ ```
386
+
387
+ ## Security
388
+
389
+ `__proto__`, `constructor` and `prototype` could change `Object.prototype`. So a
390
+ path part with one of these names throws a `ForbiddenKeyError`, in every
391
+ position, with or without brackets:
392
+
393
+ ```ts
394
+ const formData = new FormData()
395
+ formData.append('user.__proto__.isAdmin', 'true')
396
+ parseFormData(formData)
397
+ // throws ForbiddenKeyError('user.__proto__')
398
+ ```
399
+
400
+ Other names that exist on `Object.prototype` at parse time (`toString`,
401
+ `hasOwnProperty`, …, and names that a newer runtime or other code adds) would
402
+ hide a built-in method of the result. So the parser removes every entry with
403
+ such a name in its path. It parses the entry like any other entry first, so the
404
+ entry causes the same errors as any other name. Because of this, an error
405
+ doesn't tell the client which properties exist on the server's
406
+ `Object.prototype`.
407
+
408
+ ```ts
409
+ const formData = new FormData()
410
+ formData.append('user.name', 'Ada')
411
+ formData.append('user.toString', 'x')
412
+ formData.append('settings.valueOf', 'x')
413
+ parseFormData(formData)
414
+ // {user: {name: 'Ada'}}
415
+ ```
416
+
417
+ For input from untrusted clients, also set the [`depth`](#depth) option.
418
+
419
+ ## Supported runtimes
420
+
421
+ The library uses only ES2020 and no runtime APIs. It works in:
422
+
423
+ - browsers
424
+ - Node.js (CI tests versions 22, 24 and 26)
425
+ - Deno
426
+ - Bun
427
+ - Cloudflare Workers
428
+ - Vercel (Edge and Node.js runtimes)
429
+
430
+ The package has ESM, CommonJS and UMD builds. In a `<script>` tag, the UMD build
431
+ sets the global `parseNestedFormData`.
432
+
298
433
  ## Issues
299
434
 
300
- _Looking to contribute? Look for the [Good First Issue][good-first-issue]
301
- label._
435
+ _Looking to contribute? Look for the [Good First Issue][good-first-issue] label,
436
+ and see [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up the project._
302
437
 
303
438
  ### 🐛 Bugs
304
439
 
305
- Please file an issue for bugs, missing documentation, or unexpected behavior.
440
+ Please open an issue for bugs, missing documentation or unexpected behavior.
306
441
 
307
442
  [**See Bugs**][bugs]
308
443
 
309
444
  ### 💡 Feature Requests
310
445
 
311
- Please file an issue to suggest new features. Vote on feature requests by adding
312
- a 👍. This helps maintainers prioritize what to work on.
446
+ Please open an issue to suggest new features. Vote on feature requests with a
447
+ 👍. This helps maintainers decide what to work on.
313
448
 
314
449
  [**See Feature Requests**][requests]
315
450
 
@@ -343,22 +478,22 @@ Special thanks to Kent C. Dodds and his [match-sorter][match-sorter] package
343
478
  where most of the setup is from.
344
479
 
345
480
  <!-- prettier-ignore-start -->
346
- [npm]: https://www.npmjs.com
347
- [node]: https://nodejs.org
348
481
  [build-badge]: https://img.shields.io/github/actions/workflow/status/milamer/parse-nested-form-data/validate.yml?branch=main&logo=github&style=flat-square
349
482
  [build]: https://github.com/milamer/parse-nested-form-data/actions?query=workflow%3Avalidate
350
483
  [coverage-badge]: https://img.shields.io/codecov/c/github/milamer/parse-nested-form-data.svg?style=flat-square
351
484
  [coverage]: https://codecov.io/github/milamer/parse-nested-form-data
352
485
  [version-badge]: https://img.shields.io/npm/v/parse-nested-form-data.svg?style=flat-square
353
486
  [package]: https://www.npmjs.com/package/parse-nested-form-data
487
+ [size-badge]: https://img.shields.io/badge/min%2Bbrotli-%3C%202%20kB-brightgreen?style=flat-square
488
+ [size]: https://github.com/milamer/parse-nested-form-data/blob/main/.size-limit.json
354
489
  [downloads-badge]: https://img.shields.io/npm/dm/parse-nested-form-data.svg?style=flat-square
355
490
  [npmtrends]: https://www.npmtrends.com/parse-nested-form-data
356
491
  [license-badge]: https://img.shields.io/npm/l/parse-nested-form-data.svg?style=flat-square
357
- [license]: https://github.com/milamer/parse-nested-form-data/blob/master/LICENSE
492
+ [license]: https://github.com/milamer/parse-nested-form-data/blob/main/LICENSE
358
493
  [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
359
494
  [prs]: http://makeapullrequest.com
360
495
  [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
361
- [coc]: https://github.com/milamer/parse-nested-form-data/blob/master/CODE_OF_CONDUCT.md
496
+ [coc]: https://github.com/milamer/parse-nested-form-data/blob/main/CODE_OF_CONDUCT.md
362
497
  [emojis]: https://github.com/all-contributors/all-contributors#emoji-key
363
498
  [all-contributors]: https://github.com/all-contributors/all-contributors
364
499
  [all-contributors-badge]: https://img.shields.io/github/all-contributors/milamer/parse-nested-form-data?color=orange&style=flat-square