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.
- package/dist/fetch-router.d.ts +7 -0
- package/dist/fetch-router.d.ts.map +1 -1
- package/dist/node-tsx/load-module.d.ts +2 -0
- package/dist/node-tsx/load-module.d.ts.map +1 -0
- package/dist/node-tsx/load-module.js +2 -0
- package/dist/node-tsx.d.ts +3 -0
- package/dist/node-tsx.d.ts.map +1 -0
- package/{src/node-serve.ts → dist/node-tsx.js} +2 -1
- package/dist/render-middleware.d.ts +2 -0
- package/dist/render-middleware.d.ts.map +1 -0
- package/dist/render-middleware.js +2 -0
- package/dist/route-pattern/href.d.ts +2 -0
- package/dist/route-pattern/href.d.ts.map +1 -0
- package/dist/route-pattern/href.js +2 -0
- package/dist/route-pattern/join.d.ts +2 -0
- package/dist/route-pattern/join.d.ts.map +1 -0
- package/dist/route-pattern/join.js +2 -0
- package/dist/route-pattern/match.d.ts +2 -0
- package/dist/route-pattern/match.d.ts.map +1 -0
- package/dist/route-pattern/match.js +2 -0
- package/package.json +158 -44
- package/src/assert/README.md +109 -0
- package/src/assets/README.md +539 -0
- package/src/async-context-middleware/README.md +100 -0
- package/src/auth/README.md +445 -0
- package/src/auth-middleware/README.md +246 -0
- package/src/cli/README.md +78 -0
- package/src/compression-middleware/README.md +176 -0
- package/src/cookie/README.md +106 -0
- package/src/cop-middleware/README.md +117 -0
- package/src/cors-middleware/README.md +174 -0
- package/src/csrf-middleware/README.md +99 -0
- package/src/data-schema/README.md +422 -0
- package/src/data-table/README.md +552 -0
- package/src/data-table-mysql/README.md +97 -0
- package/src/data-table-postgres/README.md +74 -0
- package/src/data-table-sqlite/README.md +84 -0
- package/src/fetch-proxy/README.md +46 -0
- package/src/fetch-router/README.md +902 -0
- package/src/fetch-router.ts +7 -0
- package/src/file-storage/README.md +57 -0
- package/src/file-storage-s3/README.md +47 -0
- package/src/form-data-middleware/README.md +109 -0
- package/src/form-data-parser/README.md +160 -0
- package/src/fs/README.md +60 -0
- package/src/headers/README.md +629 -0
- package/src/html-template/README.md +101 -0
- package/src/lazy-file/README.md +109 -0
- package/src/logger-middleware/README.md +132 -0
- package/src/method-override-middleware/README.md +71 -0
- package/src/mime/README.md +110 -0
- package/src/multipart-parser/README.md +241 -0
- package/src/node-fetch-server/README.md +352 -0
- package/src/node-tsx/README.md +79 -0
- package/src/node-tsx/load-module.ts +2 -0
- package/{dist/node-serve.js → src/node-tsx.ts} +2 -1
- package/src/render-middleware/README.md +99 -0
- package/src/render-middleware.ts +2 -0
- package/src/route-pattern/README.md +291 -0
- package/src/route-pattern/href.ts +2 -0
- package/src/route-pattern/join.ts +2 -0
- package/src/route-pattern/match.ts +2 -0
- package/src/session/README.md +171 -0
- package/src/session-middleware/README.md +109 -0
- package/src/session-storage-memcache/README.md +37 -0
- package/src/session-storage-redis/README.md +37 -0
- package/src/static-middleware/README.md +89 -0
- package/src/tar-parser/README.md +74 -0
- package/src/terminal/README.md +92 -0
- package/src/test/README.md +430 -0
- package/src/ui/README.md +219 -0
- package/src/ui/accordion/README.md +166 -0
- package/src/ui/anchor/README.md +153 -0
- package/src/ui/animation/README.md +316 -0
- package/src/ui/breadcrumbs/README.md +55 -0
- package/src/ui/button/README.md +44 -0
- package/src/ui/combobox/README.md +145 -0
- package/src/ui/glyph/README.md +72 -0
- package/src/ui/listbox/README.md +115 -0
- package/src/ui/menu/README.md +96 -0
- package/src/ui/popover/README.md +122 -0
- package/src/ui/scroll-lock/README.md +33 -0
- package/src/ui/select/README.md +107 -0
- package/src/ui/server/README.md +90 -0
- package/src/ui/test/README.md +107 -0
- package/src/ui/theme/README.md +103 -0
- package/dist/node-serve.d.ts +0 -2
- 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)
|