remix 3.0.0-beta.0 → 3.0.0-beta.2

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.
Files changed (88) hide show
  1. package/dist/fetch-router.d.ts +7 -0
  2. package/dist/fetch-router.d.ts.map +1 -1
  3. package/dist/node-tsx/load-module.d.ts +2 -0
  4. package/dist/node-tsx/load-module.d.ts.map +1 -0
  5. package/dist/node-tsx/load-module.js +2 -0
  6. package/dist/node-tsx.d.ts +3 -0
  7. package/dist/node-tsx.d.ts.map +1 -0
  8. package/{src/node-serve.ts → dist/node-tsx.js} +2 -1
  9. package/dist/render-middleware.d.ts +2 -0
  10. package/dist/render-middleware.d.ts.map +1 -0
  11. package/dist/render-middleware.js +2 -0
  12. package/dist/route-pattern/href.d.ts +2 -0
  13. package/dist/route-pattern/href.d.ts.map +1 -0
  14. package/dist/route-pattern/href.js +2 -0
  15. package/dist/route-pattern/join.d.ts +2 -0
  16. package/dist/route-pattern/join.d.ts.map +1 -0
  17. package/dist/route-pattern/join.js +2 -0
  18. package/dist/route-pattern/match.d.ts +2 -0
  19. package/dist/route-pattern/match.d.ts.map +1 -0
  20. package/dist/route-pattern/match.js +2 -0
  21. package/package.json +158 -44
  22. package/src/assert/README.md +109 -0
  23. package/src/assets/README.md +539 -0
  24. package/src/async-context-middleware/README.md +100 -0
  25. package/src/auth/README.md +445 -0
  26. package/src/auth-middleware/README.md +246 -0
  27. package/src/cli/README.md +78 -0
  28. package/src/compression-middleware/README.md +176 -0
  29. package/src/cookie/README.md +106 -0
  30. package/src/cop-middleware/README.md +117 -0
  31. package/src/cors-middleware/README.md +174 -0
  32. package/src/csrf-middleware/README.md +99 -0
  33. package/src/data-schema/README.md +422 -0
  34. package/src/data-table/README.md +552 -0
  35. package/src/data-table-mysql/README.md +97 -0
  36. package/src/data-table-postgres/README.md +74 -0
  37. package/src/data-table-sqlite/README.md +84 -0
  38. package/src/fetch-proxy/README.md +46 -0
  39. package/src/fetch-router/README.md +902 -0
  40. package/src/fetch-router.ts +7 -0
  41. package/src/file-storage/README.md +57 -0
  42. package/src/file-storage-s3/README.md +47 -0
  43. package/src/form-data-middleware/README.md +109 -0
  44. package/src/form-data-parser/README.md +160 -0
  45. package/src/fs/README.md +60 -0
  46. package/src/headers/README.md +629 -0
  47. package/src/html-template/README.md +101 -0
  48. package/src/lazy-file/README.md +109 -0
  49. package/src/logger-middleware/README.md +132 -0
  50. package/src/method-override-middleware/README.md +71 -0
  51. package/src/mime/README.md +110 -0
  52. package/src/multipart-parser/README.md +241 -0
  53. package/src/node-fetch-server/README.md +352 -0
  54. package/src/node-tsx/README.md +79 -0
  55. package/src/node-tsx/load-module.ts +2 -0
  56. package/{dist/node-serve.js → src/node-tsx.ts} +2 -1
  57. package/src/render-middleware/README.md +99 -0
  58. package/src/render-middleware.ts +2 -0
  59. package/src/route-pattern/README.md +291 -0
  60. package/src/route-pattern/href.ts +2 -0
  61. package/src/route-pattern/join.ts +2 -0
  62. package/src/route-pattern/match.ts +2 -0
  63. package/src/session/README.md +171 -0
  64. package/src/session-middleware/README.md +109 -0
  65. package/src/session-storage-memcache/README.md +37 -0
  66. package/src/session-storage-redis/README.md +37 -0
  67. package/src/static-middleware/README.md +89 -0
  68. package/src/tar-parser/README.md +74 -0
  69. package/src/terminal/README.md +92 -0
  70. package/src/test/README.md +430 -0
  71. package/src/ui/README.md +219 -0
  72. package/src/ui/accordion/README.md +166 -0
  73. package/src/ui/anchor/README.md +153 -0
  74. package/src/ui/animation/README.md +316 -0
  75. package/src/ui/breadcrumbs/README.md +55 -0
  76. package/src/ui/button/README.md +44 -0
  77. package/src/ui/combobox/README.md +145 -0
  78. package/src/ui/glyph/README.md +72 -0
  79. package/src/ui/listbox/README.md +115 -0
  80. package/src/ui/menu/README.md +96 -0
  81. package/src/ui/popover/README.md +122 -0
  82. package/src/ui/scroll-lock/README.md +33 -0
  83. package/src/ui/select/README.md +107 -0
  84. package/src/ui/server/README.md +90 -0
  85. package/src/ui/test/README.md +107 -0
  86. package/src/ui/theme/README.md +103 -0
  87. package/dist/node-serve.d.ts +0 -2
  88. package/dist/node-serve.d.ts.map +0 -1
@@ -0,0 +1,629 @@
1
+ # headers
2
+
3
+ Typed utilities for parsing, manipulating, and serializing HTTP header values. `headers` provides focused classes for common HTTP headers.
4
+
5
+ ## Features
6
+
7
+ - **Enhanced `Headers` Class** - Use `SuperHeaders` for lazy, typed property accessors that still work anywhere native `Headers` are expected
8
+ - **Header-Specific Classes** - Purpose-built APIs for `Accept`, `Cache-Control`, `Content-Type`, and more
9
+ - **Round-Trip Safety** - Parse from raw values and serialize back with `.toString()`
10
+ - **Typed Operations** - Work with structured values instead of manual string parsing
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ npm i remix
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ `SuperHeaders` extends the native `Headers` class and adds lazy, typed property accessors for common headers. It is also the default export from this package.
21
+
22
+ ```ts
23
+ import Headers from 'remix/headers'
24
+
25
+ let headers = new Headers(request.headers)
26
+
27
+ headers.contentType = { mediaType: 'text/html', charset: 'utf-8' }
28
+ headers.cacheControl = { public: true, maxAge: 3600 }
29
+ headers.setCookie = { name: 'session', value: 'abc', httpOnly: true }
30
+
31
+ headers.contentType.charset = 'iso-8859-1'
32
+ headers.cacheControl.maxAge = 60
33
+ headers.setCookie.push({ name: 'theme', value: 'dark', path: '/' })
34
+
35
+ return new Response(html, { headers })
36
+ ```
37
+
38
+ Because `SuperHeaders` is a real `Headers` subclass, it can be passed directly to platform APIs:
39
+
40
+ ```ts
41
+ let headers = new Headers({ contentType: 'text/plain' })
42
+
43
+ headers instanceof globalThis.Headers // true
44
+ new Response('Hello', { headers }).headers.get('Content-Type') // 'text/plain'
45
+ ```
46
+
47
+ Typed accessors parse values only when you read them:
48
+
49
+ ```ts
50
+ let headers = new Headers({ 'Content-Type': 'application/json; charset=utf-8' })
51
+
52
+ headers.get('Content-Type') // no typed parse needed
53
+ headers.contentType.mediaType // parses Content-Type lazily
54
+ ```
55
+
56
+ Use `apply()` to apply a `SuperHeadersInit` value to an existing instance with header-aware semantics:
57
+
58
+ ```ts
59
+ let headers = new Headers({
60
+ contentType: 'text/html',
61
+ setCookie: { name: 'session', value: 'abc' },
62
+ vary: 'Accept-Encoding',
63
+ })
64
+
65
+ headers.apply({
66
+ contentType: 'application/json',
67
+ setCookie: { name: 'theme', value: 'dark' },
68
+ vary: ['Accept-Encoding', 'Accept-Language'],
69
+ })
70
+
71
+ headers.get('Content-Type') // 'application/json'
72
+ headers.get('Vary') // 'accept-encoding, accept-language'
73
+ headers.getSetCookie() // ['session=abc', 'theme=dark']
74
+ ```
75
+
76
+ ## Individual Header Utilities
77
+
78
+ Each supported header has a class that represents the header value. Use the static `from()` method to parse header values. Each class has a `toString()` method that returns the header value as a string, which you can either call manually, or will be called automatically when the header class is used in a context that expects a string.
79
+
80
+ The following headers are currently supported:
81
+
82
+ - [Accept](./README.md#accept)
83
+ - [Accept-Encoding](./README.md#accept-encoding)
84
+ - [Accept-Language](./README.md#accept-language)
85
+ - [Cache-Control](./README.md#cache-control)
86
+ - [Content-Disposition](./README.md#content-disposition)
87
+ - [Content-Range](./README.md#content-range)
88
+ - [Content-Type](./README.md#content-type)
89
+ - [Cookie](./README.md#cookie)
90
+ - [If-Match](./README.md#if-match)
91
+ - [If-None-Match](./README.md#if-none-match)
92
+ - [If-Range](./README.md#if-range)
93
+ - [Range](./README.md#range)
94
+ - [Set-Cookie](./README.md#set-cookie)
95
+ - [Vary](./README.md#vary)
96
+
97
+ ### Accept
98
+
99
+ Parse, manipulate and stringify [`Accept` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept).
100
+
101
+ Implements `Map<mediaType, quality>`.
102
+
103
+ ```ts
104
+ import { Accept } from 'remix/headers'
105
+
106
+ // Parse from headers
107
+ let accept = Accept.from(request.headers.get('Accept'))
108
+
109
+ accept.mediaTypes // ['text/html', 'text/*']
110
+ accept.weights // [1, 0.9]
111
+ accept.accepts('text/html') // true
112
+ accept.accepts('text/plain') // true (matches text/*)
113
+ accept.accepts('image/jpeg') // false
114
+ accept.getWeight('text/plain') // 1 (matches text/*)
115
+ accept.getPreferred(['text/html', 'text/plain']) // 'text/html'
116
+
117
+ // Iterate
118
+ for (let [mediaType, quality] of accept) {
119
+ // ...
120
+ }
121
+
122
+ // Modify and set header
123
+ accept.set('application/json', 0.8)
124
+ accept.delete('text/*')
125
+ headers.set('Accept', accept)
126
+
127
+ // Construct directly
128
+ new Accept('text/html, text/*;q=0.9')
129
+ new Accept({ 'text/html': 1, 'text/*': 0.9 })
130
+ new Accept(['text/html', ['text/*', 0.9]])
131
+
132
+ // Use class for type safety when setting Headers values
133
+ // via Accept's `.toString()` method
134
+ let headers = new Headers({
135
+ Accept: new Accept({ 'text/html': 1, 'application/json': 0.8 }),
136
+ })
137
+ headers.set('Accept', new Accept({ 'text/html': 1, 'application/json': 0.8 }))
138
+ ```
139
+
140
+ ### Accept-Encoding
141
+
142
+ Parse, manipulate and stringify [`Accept-Encoding` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding).
143
+
144
+ Implements `Map<encoding, quality>`.
145
+
146
+ ```ts
147
+ import { AcceptEncoding } from 'remix/headers'
148
+
149
+ // Parse from headers
150
+ let acceptEncoding = AcceptEncoding.from(request.headers.get('Accept-Encoding'))
151
+
152
+ acceptEncoding.encodings // ['gzip', 'deflate']
153
+ acceptEncoding.weights // [1, 0.8]
154
+ acceptEncoding.accepts('gzip') // true
155
+ acceptEncoding.accepts('br') // false
156
+ acceptEncoding.getWeight('gzip') // 1
157
+ acceptEncoding.getPreferred(['gzip', 'deflate', 'br']) // 'gzip'
158
+
159
+ // Modify and set header
160
+ acceptEncoding.set('br', 1)
161
+ acceptEncoding.delete('deflate')
162
+ headers.set('Accept-Encoding', acceptEncoding)
163
+
164
+ // Construct directly
165
+ new AcceptEncoding('gzip, deflate;q=0.8')
166
+ new AcceptEncoding({ gzip: 1, deflate: 0.8 })
167
+
168
+ // Use class for type safety when setting Headers values
169
+ // via AcceptEncoding's `.toString()` method
170
+ let headers = new Headers({
171
+ 'Accept-Encoding': new AcceptEncoding({ gzip: 1, br: 0.9 }),
172
+ })
173
+ headers.set('Accept-Encoding', new AcceptEncoding({ gzip: 1, br: 0.9 }))
174
+ ```
175
+
176
+ ### Accept-Language
177
+
178
+ Parse, manipulate and stringify [`Accept-Language` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language).
179
+
180
+ Implements `Map<language, quality>`.
181
+
182
+ ```ts
183
+ import { AcceptLanguage } from 'remix/headers'
184
+
185
+ // Parse from headers
186
+ let acceptLanguage = AcceptLanguage.from(request.headers.get('Accept-Language'))
187
+
188
+ acceptLanguage.languages // ['en-us', 'en']
189
+ acceptLanguage.weights // [1, 0.9]
190
+ acceptLanguage.accepts('en-US') // true
191
+ acceptLanguage.accepts('en-GB') // true (matches en)
192
+ acceptLanguage.getWeight('en-GB') // 1 (matches en)
193
+ acceptLanguage.getPreferred(['en-US', 'en-GB', 'fr']) // 'en-US'
194
+
195
+ // Modify and set header
196
+ acceptLanguage.set('fr', 0.5)
197
+ acceptLanguage.delete('en')
198
+ headers.set('Accept-Language', acceptLanguage)
199
+
200
+ // Construct directly
201
+ new AcceptLanguage('en-US, en;q=0.9')
202
+ new AcceptLanguage({ 'en-US': 1, en: 0.9 })
203
+
204
+ // Use class for type safety when setting Headers values
205
+ // via AcceptLanguage's `.toString()` method
206
+ let headers = new Headers({
207
+ 'Accept-Language': new AcceptLanguage({ 'en-US': 1, fr: 0.5 }),
208
+ })
209
+ headers.set('Accept-Language', new AcceptLanguage({ 'en-US': 1, fr: 0.5 }))
210
+ ```
211
+
212
+ ### Cache-Control
213
+
214
+ Parse, manipulate and stringify [`Cache-Control` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
215
+
216
+ ```ts
217
+ import { CacheControl } from 'remix/headers'
218
+
219
+ // Parse from headers
220
+ let cacheControl = CacheControl.from(response.headers.get('Cache-Control'))
221
+
222
+ cacheControl.public // true
223
+ cacheControl.maxAge // 3600
224
+ cacheControl.sMaxage // 7200
225
+ cacheControl.noCache // undefined
226
+ cacheControl.noStore // undefined
227
+ cacheControl.noTransform // undefined
228
+ cacheControl.mustRevalidate // undefined
229
+ cacheControl.immutable // undefined
230
+
231
+ // Modify and set header
232
+ cacheControl.maxAge = 7200
233
+ cacheControl.immutable = true
234
+ headers.set('Cache-Control', cacheControl)
235
+
236
+ // Construct directly
237
+ new CacheControl('public, max-age=3600')
238
+ new CacheControl({ public: true, maxAge: 3600 })
239
+
240
+ // Use class for type safety when setting Headers values
241
+ // via CacheControl's `.toString()` method
242
+ let headers = new Headers({
243
+ 'Cache-Control': new CacheControl({ public: true, maxAge: 3600 }),
244
+ })
245
+ headers.set('Cache-Control', new CacheControl({ public: true, maxAge: 3600 }))
246
+ ```
247
+
248
+ ### Content-Disposition
249
+
250
+ Parse, manipulate and stringify [`Content-Disposition` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition).
251
+
252
+ ```ts
253
+ import { ContentDisposition } from 'remix/headers'
254
+
255
+ // Parse from headers
256
+ let contentDisposition = ContentDisposition.from(response.headers.get('Content-Disposition'))
257
+
258
+ contentDisposition.type // 'attachment'
259
+ contentDisposition.filename // 'example.pdf'
260
+ contentDisposition.filenameSplat // "UTF-8''%E4%BE%8B%E5%AD%90.pdf"
261
+ contentDisposition.preferredFilename // '例子.pdf' (decoded from filename*)
262
+
263
+ // Modify and set header
264
+ contentDisposition.filename = 'download.pdf'
265
+ headers.set('Content-Disposition', contentDisposition)
266
+
267
+ // Construct directly
268
+ new ContentDisposition('attachment; filename="example.pdf"')
269
+ new ContentDisposition({ type: 'attachment', filename: 'example.pdf' })
270
+
271
+ // Use class for type safety when setting Headers values
272
+ // via ContentDisposition's `.toString()` method
273
+ let headers = new Headers({
274
+ 'Content-Disposition': new ContentDisposition({ type: 'attachment', filename: 'example.pdf' }),
275
+ })
276
+ headers.set(
277
+ 'Content-Disposition',
278
+ new ContentDisposition({ type: 'attachment', filename: 'example.pdf' }),
279
+ )
280
+ ```
281
+
282
+ ### Content-Range
283
+
284
+ Parse, manipulate and stringify [`Content-Range` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range).
285
+
286
+ ```ts
287
+ import { ContentRange } from 'remix/headers'
288
+
289
+ // Parse from headers
290
+ let contentRange = ContentRange.from(response.headers.get('Content-Range'))
291
+
292
+ contentRange.unit // "bytes"
293
+ contentRange.start // 200
294
+ contentRange.end // 1000
295
+ contentRange.size // 67589
296
+
297
+ // Unsatisfied range
298
+ let unsatisfied = ContentRange.from('bytes */67589')
299
+ unsatisfied.start // null
300
+ unsatisfied.end // null
301
+ unsatisfied.size // 67589
302
+
303
+ // Construct directly
304
+ new ContentRange({ unit: 'bytes', start: 0, end: 499, size: 1000 })
305
+
306
+ // Use class for type safety when setting Headers values
307
+ // via ContentRange's `.toString()` method
308
+ let headers = new Headers({
309
+ 'Content-Range': new ContentRange({ unit: 'bytes', start: 0, end: 499, size: 1000 }),
310
+ })
311
+ headers.set('Content-Range', new ContentRange({ unit: 'bytes', start: 0, end: 499, size: 1000 }))
312
+ ```
313
+
314
+ ### Content-Type
315
+
316
+ Parse, manipulate and stringify [`Content-Type` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type).
317
+
318
+ ```ts
319
+ import { ContentType } from 'remix/headers'
320
+
321
+ // Parse from headers
322
+ let contentType = ContentType.from(request.headers.get('Content-Type'))
323
+
324
+ contentType.mediaType // "text/html"
325
+ contentType.charset // "utf-8"
326
+ contentType.boundary // undefined (or boundary string for multipart)
327
+
328
+ // Modify and set header
329
+ contentType.charset = 'iso-8859-1'
330
+ headers.set('Content-Type', contentType)
331
+
332
+ // Construct directly
333
+ new ContentType('text/html; charset=utf-8')
334
+ new ContentType({ mediaType: 'text/html', charset: 'utf-8' })
335
+
336
+ // Use class for type safety when setting Headers values
337
+ // via ContentType's `.toString()` method
338
+ let headers = new Headers({
339
+ 'Content-Type': new ContentType({ mediaType: 'text/html', charset: 'utf-8' }),
340
+ })
341
+ headers.set('Content-Type', new ContentType({ mediaType: 'text/html', charset: 'utf-8' }))
342
+ ```
343
+
344
+ ### Cookie
345
+
346
+ Parse, manipulate and stringify [`Cookie` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie).
347
+
348
+ Implements an ordered list of name/value pairs. Duplicate cookie names are preserved, such as when cookies with the same name were set for different paths.
349
+
350
+ ```ts
351
+ import { Cookie } from 'remix/headers'
352
+
353
+ // Parse from headers
354
+ let cookie = Cookie.from(request.headers.get('Cookie'))
355
+
356
+ cookie.get('session_id') // 'abc123'
357
+ cookie.getAll('session_id') // ['abc123']
358
+ cookie.get('theme') // 'dark'
359
+ cookie.has('session_id') // true
360
+ cookie.size // 2
361
+
362
+ // Iterate
363
+ for (let [name, value] of cookie) {
364
+ // ...
365
+ }
366
+
367
+ // Modify and set header
368
+ cookie.set('theme', 'light')
369
+ cookie.append('session_id', 'def456')
370
+ cookie.delete('session_id')
371
+ headers.set('Cookie', cookie)
372
+
373
+ // Construct directly
374
+ new Cookie('session_id=abc123; theme=dark')
375
+ new Cookie({ session_id: 'abc123', theme: 'dark' })
376
+ new Cookie([
377
+ ['session_id', 'abc123'],
378
+ ['theme', 'dark'],
379
+ ])
380
+
381
+ // Use class for type safety when setting Headers values
382
+ // via Cookie's `.toString()` method
383
+ let headers = new Headers({
384
+ Cookie: new Cookie({ session_id: 'abc123', theme: 'dark' }),
385
+ })
386
+ headers.set('Cookie', new Cookie({ session_id: 'abc123', theme: 'dark' }))
387
+ ```
388
+
389
+ ### If-Match
390
+
391
+ Parse, manipulate and stringify [`If-Match` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match).
392
+
393
+ Implements `Set<etag>`.
394
+
395
+ ```ts
396
+ import { IfMatch } from 'remix/headers'
397
+
398
+ // Parse from headers
399
+ let ifMatch = IfMatch.from(request.headers.get('If-Match'))
400
+
401
+ ifMatch.tags // ['"67ab43"', '"54ed21"']
402
+ ifMatch.has('"67ab43"') // true
403
+ ifMatch.matches('"67ab43"') // true (checks precondition)
404
+ ifMatch.matches('"abc123"') // false
405
+
406
+ // Note: Uses strong comparison only (weak ETags never match)
407
+ let weak = IfMatch.from('W/"67ab43"')
408
+ weak.matches('W/"67ab43"') // false
409
+
410
+ // Modify and set header
411
+ ifMatch.add('"newetag"')
412
+ ifMatch.delete('"67ab43"')
413
+ headers.set('If-Match', ifMatch)
414
+
415
+ // Construct directly
416
+ new IfMatch(['abc123', 'def456'])
417
+
418
+ // Use class for type safety when setting Headers values
419
+ // via IfMatch's `.toString()` method
420
+ let headers = new Headers({
421
+ 'If-Match': new IfMatch(['"abc123"', '"def456"']),
422
+ })
423
+ headers.set('If-Match', new IfMatch(['"abc123"', '"def456"']))
424
+ ```
425
+
426
+ ### If-None-Match
427
+
428
+ Parse, manipulate and stringify [`If-None-Match` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match).
429
+
430
+ Implements `Set<etag>`.
431
+
432
+ ```ts
433
+ import { IfNoneMatch } from 'remix/headers'
434
+
435
+ // Parse from headers
436
+ let ifNoneMatch = IfNoneMatch.from(request.headers.get('If-None-Match'))
437
+
438
+ ifNoneMatch.tags // ['"67ab43"', '"54ed21"']
439
+ ifNoneMatch.has('"67ab43"') // true
440
+ ifNoneMatch.matches('"67ab43"') // true
441
+
442
+ // Supports weak comparison (unlike If-Match)
443
+ let weak = IfNoneMatch.from('W/"67ab43"')
444
+ weak.matches('W/"67ab43"') // true
445
+
446
+ // Modify and set header
447
+ ifNoneMatch.add('"newetag"')
448
+ ifNoneMatch.delete('"67ab43"')
449
+ headers.set('If-None-Match', ifNoneMatch)
450
+
451
+ // Construct directly
452
+ new IfNoneMatch(['abc123'])
453
+
454
+ // Use class for type safety when setting Headers values
455
+ // via IfNoneMatch's `.toString()` method
456
+ let headers = new Headers({
457
+ 'If-None-Match': new IfNoneMatch(['"abc123"']),
458
+ })
459
+ headers.set('If-None-Match', new IfNoneMatch(['"abc123"']))
460
+ ```
461
+
462
+ ### If-Range
463
+
464
+ Parse, manipulate and stringify [`If-Range` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Range).
465
+
466
+ ```ts
467
+ import { IfRange } from 'remix/headers'
468
+
469
+ // Parse from headers
470
+ let ifRange = IfRange.from(request.headers.get('If-Range'))
471
+
472
+ // With HTTP date
473
+ ifRange.matches({ lastModified: 1609459200000 }) // true
474
+ ifRange.matches({ lastModified: new Date('2021-01-01') }) // true
475
+
476
+ // With ETag
477
+ let etagHeader = IfRange.from('"67ab43"')
478
+ etagHeader.matches({ etag: '"67ab43"' }) // true
479
+
480
+ // Empty/null returns empty instance (range proceeds unconditionally)
481
+ let empty = IfRange.from(null)
482
+ empty.matches({ etag: '"any"' }) // true
483
+
484
+ // Construct directly
485
+ new IfRange('"abc123"')
486
+
487
+ // Use class for type safety when setting Headers values
488
+ // via IfRange's `.toString()` method
489
+ let headers = new Headers({
490
+ 'If-Range': new IfRange('"abc123"'),
491
+ })
492
+ headers.set('If-Range', new IfRange('"abc123"'))
493
+ ```
494
+
495
+ ### Range
496
+
497
+ Parse, manipulate and stringify [`Range` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range).
498
+
499
+ ```ts
500
+ import { Range } from 'remix/headers'
501
+
502
+ // Parse from headers
503
+ let range = Range.from(request.headers.get('Range'))
504
+
505
+ range.unit // "bytes"
506
+ range.ranges // [{ start: 200, end: 1000 }]
507
+ range.canSatisfy(2000) // true
508
+ range.canSatisfy(500) // false
509
+ range.normalize(2000) // [{ start: 200, end: 1000 }]
510
+
511
+ // Multiple ranges
512
+ let multi = Range.from('bytes=0-499, 1000-1499')
513
+ multi.ranges.length // 2
514
+
515
+ // Suffix range (last N bytes)
516
+ let suffix = Range.from('bytes=-500')
517
+ suffix.normalize(2000) // [{ start: 1500, end: 1999 }]
518
+
519
+ // Construct directly
520
+ new Range({ unit: 'bytes', ranges: [{ start: 0, end: 999 }] })
521
+
522
+ // Use class for type safety when setting Headers values
523
+ // via Range's `.toString()` method
524
+ let headers = new Headers({
525
+ Range: new Range({ unit: 'bytes', ranges: [{ start: 0, end: 999 }] }),
526
+ })
527
+ headers.set('Range', new Range({ unit: 'bytes', ranges: [{ start: 0, end: 999 }] }))
528
+ ```
529
+
530
+ ### Set-Cookie
531
+
532
+ Parse, manipulate and stringify [`Set-Cookie` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
533
+
534
+ ```ts
535
+ import { SetCookie } from 'remix/headers'
536
+
537
+ // Parse from headers
538
+ let setCookie = SetCookie.from(response.headers.get('Set-Cookie'))
539
+
540
+ setCookie.name // "session_id"
541
+ setCookie.value // "abc"
542
+ setCookie.path // "/"
543
+ setCookie.httpOnly // true
544
+ setCookie.secure // true
545
+ setCookie.domain // undefined
546
+ setCookie.maxAge // undefined
547
+ setCookie.expires // undefined
548
+ setCookie.sameSite // undefined
549
+
550
+ // Modify and set header
551
+ setCookie.maxAge = 3600
552
+ setCookie.sameSite = 'Strict'
553
+ headers.set('Set-Cookie', setCookie)
554
+
555
+ // Construct directly
556
+ new SetCookie('session_id=abc; Path=/; HttpOnly; Secure')
557
+ new SetCookie({
558
+ name: 'session_id',
559
+ value: 'abc',
560
+ path: '/',
561
+ httpOnly: true,
562
+ secure: true,
563
+ })
564
+
565
+ // Use class for type safety when setting Headers values
566
+ // via SetCookie's `.toString()` method
567
+ let headers = new Headers({
568
+ 'Set-Cookie': new SetCookie({ name: 'session_id', value: 'abc', httpOnly: true }),
569
+ })
570
+ headers.set('Set-Cookie', new SetCookie({ name: 'session_id', value: 'abc', httpOnly: true }))
571
+ ```
572
+
573
+ ### Vary
574
+
575
+ Parse, manipulate and stringify [`Vary` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary).
576
+
577
+ Implements `Set<headerName>`.
578
+
579
+ ```ts
580
+ import { Vary } from 'remix/headers'
581
+
582
+ // Parse from headers
583
+ let vary = Vary.from(response.headers.get('Vary'))
584
+
585
+ vary.headerNames // ['accept-encoding', 'accept-language']
586
+ vary.has('Accept-Encoding') // true (case-insensitive)
587
+ vary.size // 2
588
+
589
+ // Modify and set header
590
+ vary.add('User-Agent')
591
+ vary.delete('Accept-Language')
592
+ headers.set('Vary', vary)
593
+
594
+ // Construct directly
595
+ new Vary('Accept-Encoding, Accept-Language')
596
+ new Vary(['Accept-Encoding', 'Accept-Language'])
597
+ new Vary({ headerNames: ['Accept-Encoding', 'Accept-Language'] })
598
+
599
+ // Use class for type safety when setting Headers values
600
+ // via Vary's `.toString()` method
601
+ let headers = new Headers({
602
+ Vary: new Vary(['Accept-Encoding', 'Accept-Language']),
603
+ })
604
+ headers.set('Vary', new Vary(['Accept-Encoding', 'Accept-Language']))
605
+ ```
606
+
607
+ ## Raw Headers
608
+
609
+ Parse and stringify raw HTTP header strings.
610
+
611
+ ```ts
612
+ import { parse, stringify } from 'remix/headers'
613
+
614
+ let headers = parse('Content-Type: text/html\r\nCache-Control: no-cache')
615
+ headers.get('Content-Type') // 'text/html'
616
+ headers.get('Cache-Control') // 'no-cache'
617
+
618
+ stringify(headers)
619
+ // 'Content-Type: text/html\r\nCache-Control: no-cache'
620
+ ```
621
+
622
+ ## Related Packages
623
+
624
+ - [`fetch-proxy`](https://github.com/remix-run/remix/tree/main/packages/fetch-proxy) - Build HTTP proxy servers using the web fetch API
625
+ - [`node-fetch-server`](https://github.com/remix-run/remix/tree/main/packages/node-fetch-server) - Build HTTP servers on Node.js using the web fetch API
626
+
627
+ ## License
628
+
629
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)