@johntalton/http-util 6.0.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +1 -1
  2. package/package.json +17 -2
  3. package/src/defs.js +34 -1
  4. package/src/headers/accept-encoding.js +9 -12
  5. package/src/headers/accept-language.js +10 -4
  6. package/src/headers/accept.js +0 -28
  7. package/src/headers/cache-control.js +0 -6
  8. package/src/headers/clear-site-data.js +4 -10
  9. package/src/headers/client-hints.js +9 -2
  10. package/src/headers/conditional.js +170 -106
  11. package/src/headers/content-disposition.js +0 -14
  12. package/src/headers/content-range.js +0 -17
  13. package/src/headers/content-type.js +1 -1
  14. package/src/headers/forwarded.js +2 -33
  15. package/src/headers/link.js +11 -12
  16. package/src/headers/multipart.js +19 -18
  17. package/src/headers/preference.js +0 -43
  18. package/src/headers/range.js +5 -31
  19. package/src/headers/rate-limit.js +7 -2
  20. package/src/headers/server-timing.js +1 -14
  21. package/src/headers/strict-transport-security.js +1 -0
  22. package/src/headers/util/mime.js +3 -3
  23. package/src/headers/util/whitespace.js +3 -1
  24. package/src/headers/www-authenticate.js +0 -1
  25. package/src/response/2xx/bytes.js +19 -13
  26. package/src/response/2xx/created.js +16 -8
  27. package/src/response/2xx/json.js +29 -10
  28. package/src/response/2xx/no-content.js +12 -6
  29. package/src/response/2xx/partial-content.js +17 -14
  30. package/src/response/2xx/preflight.js +22 -10
  31. package/src/response/3xx/found.js +25 -0
  32. package/src/response/3xx/moved-permanently.js +7 -5
  33. package/src/response/3xx/not-modified.js +13 -8
  34. package/src/response/3xx/permanent-redirect.js +4 -2
  35. package/src/response/3xx/see-other.js +7 -5
  36. package/src/response/3xx/temporary-redirect.js +4 -2
  37. package/src/response/4xx/bad-request.js +19 -0
  38. package/src/response/4xx/content-too-large.js +1 -1
  39. package/src/response/4xx/gone.js +1 -1
  40. package/src/response/4xx/im-a-teapot.js +1 -1
  41. package/src/response/4xx/not-acceptable.js +6 -4
  42. package/src/response/4xx/not-allowed.js +6 -4
  43. package/src/response/4xx/payment-required.js +17 -0
  44. package/src/response/4xx/precondition-failed.js +18 -3
  45. package/src/response/4xx/range-not-satisfiable.js +5 -3
  46. package/src/response/4xx/too-many-requests.js +8 -5
  47. package/src/response/4xx/unauthorized.js +1 -1
  48. package/src/response/4xx/unsupported-media.js +9 -5
  49. package/src/response/5xx/error.js +2 -3
  50. package/src/response/5xx/insufficient-storage.js +2 -2
  51. package/src/response/5xx/not-implemented.js +2 -3
  52. package/src/response/5xx/unavailable.js +7 -12
  53. package/src/response/header-util.js +1 -1
  54. package/src/response/index.js +4 -0
  55. package/src/response/response.js +8 -2
  56. package/src/response/send-util.js +46 -14
package/README.md CHANGED
@@ -64,7 +64,7 @@ All responders take in a `stream` as well as a metadata object to hint on server
64
64
  - [`sendGone`](#responsegone)
65
65
  - [`sendImATeapot`](#)
66
66
  - [`sendInsufficientStorage`](#)
67
- - [`sendJSON_Encoded`](#responsejson) - Standard Ok response with encoding
67
+ - [`sendJSON`](#responsejson) - Standard Ok response with encoding
68
68
  - [`sendMovedPermanently`](#responsemovedpermanently)
69
69
  - [`sendMultipleChoices`](#)
70
70
  - [`sendNoContent`](#responsenocontent)
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@johntalton/http-util",
3
- "version": "6.0.0",
3
+ "version": "7.0.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
+ "sideEffects": false,
6
7
  "imports": {
7
8
  "#util": "./src/headers/util/index.js"
8
9
  },
@@ -20,10 +21,24 @@
20
21
  "src/headers/*.js",
21
22
  "src/headers/util/*.js"
22
23
  ],
24
+ "scripts": {
25
+ "lint": "biome check",
26
+ "test": "node --test test/**/*.test.js",
27
+ "coverage": "c8 --src src --check-coverage --statements 90 --branches 90 --functions 90 --lines 90 -r lcovonly -r text node --test test/**/*.test.js"
28
+ },
23
29
  "repository": {
24
30
  "url": "https://github.com/johntalton/http-util"
25
31
  },
26
32
  "dependencies": {
27
33
  "@johntalton/sse-util": "^1.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@biomejs/biome": "^2.4.15",
37
+ "c8": "^11.0.0"
38
+ },
39
+ "overrides": {
40
+ "c8": {
41
+ "yargs": "^18.0.0"
42
+ }
28
43
  }
29
- }
44
+ }
package/src/defs.js CHANGED
@@ -26,7 +26,14 @@ export const RANGE_UNITS_BYTES = 'bytes'
26
26
  /** @type {'none'} */
27
27
  export const RANGE_UNITS_NONE = 'none'
28
28
 
29
+
29
30
  /** @import { TimingsInfo } from './headers/server-timing.js' */
31
+ /** @import { EtagItem, IMFFixDateInput } from './headers/conditional.js' */
32
+ /** @import { CacheControlOptions } from './headers/cache-control.js' */
33
+ /** @import { ContentRangeDirective } from './headers/content-range.js' */
34
+ /** @import { RateLimitPolicyInfo, RateLimitInfo } from './headers/rate-limit.js' */
35
+
36
+ /** @typedef {RANGE_UNITS_BYTES | RANGE_UNITS_NONE} AcceptRangeUnits */
30
37
 
31
38
  /**
32
39
  * @typedef {`X-${string}`} CustomHeaderKey
@@ -46,4 +53,30 @@ export const RANGE_UNITS_NONE = 'none'
46
53
  * @property {boolean} [bom]
47
54
  */
48
55
 
49
- /** @typedef {ArrayBufferLike|ArrayBufferView|ReadableStream|string} SendBody */
56
+ /** @typedef {ArrayBufferLike|ArrayBufferView|ReadableStream|string} SendBody */
57
+
58
+ /**
59
+ * @typedef {Object} SendContent
60
+ * @property {string|undefined} contentType
61
+ * @property {number|undefined} contentLength
62
+ * @property {string|undefined} encoding
63
+ * @property {EtagItem|undefined} etag
64
+ * @property {IMFFixDateInput|string|undefined} lastModified
65
+ * @property {number|undefined} age
66
+ * @property {CacheControlOptions} cacheControl
67
+ * @property {ContentRangeDirective} rangeDirective
68
+ */
69
+
70
+ /**
71
+ * @typedef {Object} SendInfo
72
+ * @property {Array<string>} supportedMethods
73
+ * @property {Array<string>|string} supportedTypes
74
+ * @property {Array<string>|string} acceptableMediaType
75
+ * @property {AcceptRangeUnits|undefined} acceptRanges
76
+ * @property {Array<string>|undefined} supportedQueryTypes
77
+ * @property {RateLimitInfo} limitInfo
78
+ * @property {Array<RateLimitPolicyInfo>} policies
79
+ * @property {number|undefined} retryAfter
80
+ */
81
+
82
+
@@ -2,6 +2,8 @@ import { parseAcceptStyleHeader } from './util/accept-util.js'
2
2
 
3
3
  /** @import { AcceptStyleItem } from './util/accept-util.js' */
4
4
 
5
+ export const ENCODING_ANY = '*'
6
+
5
7
  export const WELL_KNOWN_ENCODINGS = new Map([
6
8
  [ 'gzip, deflate, br, zstd', [ { name: 'gzip' }, { name: 'deflate' }, { name: 'br' }, { name: 'zstd' } ] ],
7
9
  [ 'gzip, deflate, br', [ { name: 'gzip' }, { name: 'deflate' }, { name: 'br' } ] ]
@@ -29,6 +31,8 @@ export class AcceptEncoding {
29
31
  * @param {Array<string>} supportedTypes
30
32
  */
31
33
  static selectFrom(acceptEncodings, supportedTypes) {
34
+ if(supportedTypes === undefined) { return undefined }
35
+
32
36
  for(const acceptEncoding of acceptEncodings) {
33
37
  const { name } = acceptEncoding
34
38
  if(supportedTypes.includes(name)) {
@@ -36,18 +40,11 @@ export class AcceptEncoding {
36
40
  }
37
41
  }
38
42
 
43
+ //
44
+ if(acceptEncodings.some(item => item.name === ENCODING_ANY)) {
45
+ return supportedTypes.at(0)
46
+ }
47
+
39
48
  return undefined
40
49
  }
41
50
  }
42
-
43
-
44
- // console.log(AcceptEncoding.parse(''))
45
- // console.log(AcceptEncoding.parse(' '))
46
- // console.log(AcceptEncoding.parse('zstd'))
47
- // console.log(AcceptEncoding.parse('identity'))
48
- // console.log(AcceptEncoding.parse('*'))
49
- // console.log(AcceptEncoding.parse('gzip, deflate, br, zstd'))
50
- // console.log(AcceptEncoding.parse('br;q=1.0, gzip;q=0.8, *;q=0.1'))
51
- // console.log(AcceptEncoding.parse('deflate, gzip;q=1.0, *;q=0.5'))
52
- // console.log(AcceptEncoding.parse('identity;q=0'))
53
- // console.log(AcceptEncoding.parse('*;q=0'))
@@ -2,6 +2,8 @@ import { parseAcceptStyleHeader } from './util/accept-util.js'
2
2
 
3
3
  /** @import { AcceptStyleItem } from './util/accept-util.js' */
4
4
 
5
+ export const LANGUAGE_ANY = '*'
6
+
5
7
  export const WELL_KNOWN_LANGUAGES = new Map([
6
8
  [ 'en-US,en;q=0.5', [ { name: 'en-US', quality: 1 }, { name: 'en', quality: 0.5 } ] ],
7
9
  [ 'en-US,en;q=0.9', [ { name: 'en-US', quality: 1 }, { name: 'en', quality: 0.9 } ] ],
@@ -30,16 +32,20 @@ export class AcceptLanguage {
30
32
  * @param {Array<string>} supportedTypes
31
33
  */
32
34
  static selectFrom(acceptLanguages, supportedTypes) {
35
+ if(supportedTypes === undefined) { return undefined }
36
+
33
37
  for(const acceptLanguage of acceptLanguages) {
34
38
  const { name } = acceptLanguage
35
39
  if(supportedTypes.includes(name)) {
36
40
  return name
37
- }
41
+ }
42
+ }
43
+
44
+ //
45
+ if(acceptLanguages.some(item => item.name === LANGUAGE_ANY)) {
46
+ return supportedTypes.at(0)
38
47
  }
39
48
 
40
49
  return undefined
41
50
  }
42
51
  }
43
-
44
- // console.log(AcceptLanguage.parse('en-US,en;q=0.9'))
45
- // console.log(AcceptLanguage.select('foo;q=0.2, bar-BZ', [ 'bang', 'foo' ]))
@@ -83,32 +83,4 @@ export class Accept {
83
83
  }
84
84
  }
85
85
 
86
- // console.log(Accept.parse('text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, text/*;q=.8, */*;q=0.7'))
87
- // console.log(Accept.select('text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, text/*;q=.8, */*;q=0.7', [ 'application/json', 'text/plain' ]))
88
- // console.log(Accept.parse('*'))
89
-
90
-
91
- // const tests = [
92
- // undefined,
93
- // '',
94
- // ' ',
95
- // ' fake',
96
- // ' application/json',
97
- // ' application/xml,',
98
- // ' ,application/xml ,,',
99
- // ' audio/*; q=0.2, audio/basic',
100
- // ' text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8',
101
- // ' text/*;q=0.3, text/plain;q=0.7, text/plain;format=flowed,\ntext/plain;format=fixed;q=0.4, */*;q=0.5',
102
-
103
- // ' */*, foo/bar, foo/*, biz/bang, */*;q=.2, quix/quak;q=.1',
104
- // 'foo / bar ; q = .5'
105
- // ]
106
-
107
- // tests.forEach(test => {
108
- // const result = Accept.parse(test)
109
- // console.log('=============================')
110
- // console.log({ test })
111
- // console.log('---')
112
- // console.log(result)
113
- // })
114
86
 
@@ -54,9 +54,3 @@ export class CacheControl {
54
54
  return result.join(', ')
55
55
  }
56
56
  }
57
-
58
- // console.log(CacheControl.encode({
59
- // priv: true,
60
- // maxAge: 60,
61
- // directives: ['must-revalidate', 'no-transform']
62
- // }))
@@ -42,18 +42,12 @@ export class SiteData {
42
42
  if(directives.prerenderCache === true) { result.push(CSD_DIRECTIVE_PRERENDER_CACHE) }
43
43
  if(directives.storage === true) { result.push(CSD_DIRECTIVE_STORAGE) }
44
44
 
45
+ if(result.length === 0) {
46
+ return undefined
47
+ }
48
+
45
49
  return result
46
50
  .map(item => `${CSD_QUOTE}${item}${CSD_QUOTE}`)
47
51
  .join(CSD_DIRECTIVE_SEPARATOR)
48
52
  }
49
53
  }
50
-
51
- // console.log(SiteData.encode())
52
- // console.log(SiteData.encode({}))
53
- // console.log(SiteData.encode(false))
54
- // console.log(SiteData.encode('true'))
55
- // console.log(SiteData.encode(true))
56
- // console.log(SiteData.encode('*'))
57
- // console.log(SiteData.encode({ storage: false }))
58
- // console.log(SiteData.encode({ storage: true }))
59
- // console.log(SiteData.encode({ storage: true, cookies: true }))
@@ -71,9 +71,16 @@ export class ClientHints {
71
71
  * @param {Array<String>} hints
72
72
  */
73
73
  static encode(hints) {
74
- return hints
74
+ if(hints === undefined) { return undefined }
75
+ if(!Array.isArray(hints)) { return undefined }
76
+ if(hints.length === 0) { return undefined }
77
+
78
+ const remaining = hints
75
79
  .filter(hint => KNOWN_CLIENT_HINTS.includes(hint))
76
- .join(', ')
80
+
81
+ if(remaining.length === 0) { return undefined }
82
+
83
+ return remaining.join(', ')
77
84
  }
78
85
  }
79
86
 
@@ -1,3 +1,5 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: includes helper classes */
2
+ /** biome-ignore-all lint/nursery/noExcessiveLinesPerFile: includes temporal and date support */
1
3
  import { isQuoted, stripQuotes } from './util/quote.js'
2
4
 
3
5
  /**
@@ -24,7 +26,7 @@ import { isQuoted, stripQuotes } from './util/quote.js'
24
26
  /** @typedef {WeakEtagItem | NotWeakEtagItem | AnyEtagItem } EtagItem */
25
27
 
26
28
  /**
27
- * @typedef {Object} IMFFixDate
29
+ * @typedef {Object} IMFFixDateItem
28
30
  * @property {typeof DATE_DAYS[number]} dayName
29
31
  * @property {number} day
30
32
  * @property {typeof DATE_MONTHS[number]} month
@@ -32,8 +34,39 @@ import { isQuoted, stripQuotes } from './util/quote.js'
32
34
  * @property {number} hour
33
35
  * @property {number} minute
34
36
  * @property {number} second
37
+ */
38
+
39
+ /**
40
+ * @typedef {Object} IMFFixDateItemExtension
35
41
  * @property {Date|undefined} [date]
42
+ * @property {Temporal.Instant|undefined} [instant]
43
+ */
44
+
45
+ /** @typedef {IMFFixDateItem & IMFFixDateItemExtension} IMFFixDate */
46
+ /** @typedef {IMFFixDate|Date|Temporal.Instant|undefined} IMFFixDateInput */
47
+
48
+ export const FEATURE_TEMPORAL = typeof Temporal !== 'undefined'
49
+
50
+ export const IMF_FIX_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
51
+ timeZone: 'UTC',
52
+ weekday: 'short',
53
+ year: 'numeric',
54
+ month: 'short',
55
+ day: '2-digit',
56
+ hour: '2-digit',
57
+ minute: '2-digit',
58
+ second: '2-digit',
59
+ hour12: false
60
+ })
61
+
62
+ /**
63
+ * @param {IMFFixDate|Date|Temporal.Instant|undefined} reference
64
+ * @returns {reference is Temporal.Instant}
36
65
  */
66
+ export function isTemporalInstant(reference) {
67
+ if(!FEATURE_TEMPORAL) { return false }
68
+ return reference instanceof Temporal.Instant
69
+ }
37
70
 
38
71
  export const CONDITION_ETAG_SEPARATOR = ','
39
72
  export const CONDITION_ETAG_ANY = '*'
@@ -67,7 +100,9 @@ export class ETag {
67
100
  * @param {string} etag
68
101
  */
69
102
  static isValid(etag) {
70
- // %x21 / %x23-7E and %x80-FF
103
+ if(etag === undefined) { return false }
104
+
105
+ // %x21 / %x23-7E and %x80-FF
71
106
  for(const c of etag) {
72
107
  if(c.charCodeAt(0) < 0x21) { return false }
73
108
  if(c.charCodeAt(0) > 0xFF) { return false }
@@ -115,8 +150,9 @@ export class ETag {
115
150
  if(!isQuoted(quotedEtag)) { return undefined }
116
151
  const etag = stripQuotes(quotedEtag)
117
152
  if(etag === undefined) { return undefined }
153
+ if(etag === '') { return undefined }
154
+ if(etag === CONDITION_ETAG_ANY) { return ANY_ETAG_ITEM } // todo: should this return undefined?
118
155
  if(!ETag.isValid(etag)) { return undefined }
119
- if(etag === CONDITION_ETAG_ANY) { return undefined }
120
156
 
121
157
  return {
122
158
  weak,
@@ -126,6 +162,100 @@ export class ETag {
126
162
  }
127
163
  }
128
164
 
165
+ export class FixDate {
166
+
167
+ /**
168
+ * True if {@link test} is after (exclusive) the {@link reference}
169
+ * (compares are seconds precision)
170
+ * @param {IMFFixDateInput} reference
171
+ * @param {IMFFixDateInput} test
172
+ * @returns {boolean}
173
+ */
174
+ static isAfter(reference, test) {
175
+ if(reference === undefined) { return false }
176
+ if(test === undefined) { return false }
177
+
178
+ if(FEATURE_TEMPORAL) {
179
+ /** @type {Temporal.RoundingOptions<Temporal.TimeUnit>} */
180
+ const precision = {
181
+ smallestUnit: 'second',
182
+ roundingMode: 'trunc'
183
+ }
184
+
185
+ const referenceInstant = FixDate.toInstant(reference)?.round(precision)
186
+ const testInstant = FixDate.toInstant(test)?.round(precision)
187
+
188
+ if(referenceInstant === undefined) { return false }
189
+ if(testInstant === undefined) { return false }
190
+
191
+ return Temporal.Instant.compare(referenceInstant, testInstant) === -1
192
+ }
193
+
194
+ //
195
+ const referenceDate = FixDate.toDate(reference)
196
+ const testDate = FixDate.toDate(test)
197
+
198
+ if(referenceDate === undefined) { return false }
199
+ if(testDate === undefined) { return false }
200
+
201
+ // this effectively rounds to seconds
202
+ referenceDate.setMilliseconds(0)
203
+ testDate.setMilliseconds(0)
204
+
205
+ return testDate > referenceDate
206
+ }
207
+
208
+ /**
209
+ * @param {IMFFixDateInput} reference
210
+ * @returns {Temporal.Instant|undefined}
211
+ */
212
+ static toInstant(reference) {
213
+ if(!FEATURE_TEMPORAL) { return undefined }
214
+ if(reference === undefined) { return undefined }
215
+
216
+ if(isTemporalInstant(reference)) { return reference }
217
+ if(reference instanceof Date) { return reference.toTemporalInstant() }
218
+
219
+ if(reference.instant !== undefined) { return reference.instant }
220
+ if(reference.date !== undefined) { return reference.date.toTemporalInstant() }
221
+
222
+ const { year, month: monthName, day, hour, minute, second } = reference
223
+
224
+ const zeroMonth = DATE_MONTHS.indexOf(monthName)
225
+ if(zeroMonth === -1) { return undefined }
226
+ const month = zeroMonth + 1
227
+
228
+ const zdt = Temporal.ZonedDateTime.from({
229
+ year,
230
+ month,
231
+ day,
232
+ hour,
233
+ minute,
234
+ second,
235
+ timeZone: 'UTC'
236
+ })
237
+
238
+ return zdt.toInstant()
239
+ }
240
+
241
+ /**
242
+ * @param {IMFFixDateInput} reference
243
+ * @returns {Date|undefined}
244
+ */
245
+ static toDate(reference) {
246
+ if(reference === undefined) { return undefined }
247
+
248
+ if(reference instanceof Date) { return reference }
249
+ if(isTemporalInstant(reference)) { return new Date(reference.epochMilliseconds) }
250
+
251
+ if(FEATURE_TEMPORAL && (reference.instant !== undefined)) { return new Date(reference.instant.epochMilliseconds) }
252
+ if(reference.date !== undefined) { return reference.date }
253
+
254
+ const { year, month, day, hour, minute, second } = reference
255
+ return new Date(Date.UTC(year, DATE_MONTHS.indexOf(month), day, hour, minute, second))
256
+ }
257
+ }
258
+
129
259
  export class Conditional {
130
260
  /**
131
261
  * @param {EtagItem|undefined} etagItem
@@ -158,29 +288,47 @@ export class Conditional {
158
288
  }
159
289
 
160
290
  /**
161
- * @param {Array<EtagItem>} etagItemList
291
+ * @param {Array<EtagItem>|undefined} etagItemList
162
292
  */
163
293
  static hasAny(etagItemList) {
164
- return etagItemList.find(item => item.any) !== undefined
294
+ return etagItemList?.find(item => item.any) !== undefined
165
295
  }
166
296
 
167
297
  /**
168
- * @param {Array<EtagItem>} etagItemList
169
- * @param {string} etag
298
+ * @param {Array<EtagItem>|undefined} etagItemList
299
+ * @param {string|undefined} etag
170
300
  */
171
301
  static hasEtag(etagItemList, etag) {
172
- return etagItemList.find(item => item.etag === etag) !== undefined
302
+ return etagItemList?.find(item => item.etag === etag) !== undefined
173
303
  }
174
304
 
175
305
  /**
176
- * @param {IMFFixDate|undefined} fixDate
306
+ * @param {IMFFixDateInput|string|undefined} reference
177
307
  * @returns {string|undefined}
178
308
  */
179
- static encodeFixDate(fixDate) {
180
- if(fixDate === undefined) { return undefined }
181
- if(fixDate.date !== undefined) { return fixDate.date.toUTCString() }
309
+ static encodeFixDate(reference) {
310
+ if(reference === undefined) { return undefined }
311
+
312
+ if(typeof reference === 'string') { return reference }
313
+ if(reference instanceof Date) { return reference.toUTCString() }
314
+ if(isTemporalInstant(reference)) {
315
+
316
+ const parts = IMF_FIX_DATE_FORMATTER.formatToParts(reference)
317
+ const m = new Map(parts.map(p => [ p.type, p.value ]))
318
+ const weekday = m.get('weekday') ?? 'ERR'
319
+ const day = m.get('day') ?? '00'
320
+ const month = m.get('month') ?? '00'
321
+ const year = m.get('year') ?? '0000'
322
+ const hour = m.get('hour') ?? '00'
323
+ const minute = m.get('minute') ?? '00'
324
+ const second = m.get('second') ?? '00'
325
+
326
+ return `${weekday}, ${day} ${month} ${year} ${hour}:${minute}:${second} ${DATE_ZONE}`
327
+ }
328
+
329
+ if(reference.date !== undefined) { return reference.date.toUTCString() }
182
330
 
183
- const { year, month, day, hour, minute, second } = fixDate
331
+ const { year, month, day, hour, minute, second } = reference
184
332
  const d = new Date(Date.UTC(year, DATE_MONTHS.indexOf(month), day, hour, minute, second))
185
333
  return d.toUTCString()
186
334
  }
@@ -250,105 +398,21 @@ export class Conditional {
250
398
  if(minute > 60 || minute < 0) { return undefined }
251
399
  if(second > 60 || second < 0) { return undefined }
252
400
 
253
- //
254
- return {
401
+ const fixDate = {
255
402
  dayName,
256
403
  day,
257
404
  month,
258
405
  year,
259
406
  hour,
260
407
  minute,
261
- second,
262
- date: new Date(Date.UTC(year, DATE_MONTHS.indexOf(month), day, hour, minute, second)),
263
- // temporal:
408
+ second
409
+ }
410
+
411
+ //
412
+ return {
413
+ ...fixDate,
414
+ date: FixDate.toDate(fixDate),
415
+ instant: FixDate.toInstant(fixDate)
264
416
  }
265
417
  }
266
418
  }
267
-
268
- // Ok
269
- // console.log(Conditional.encodeEtag({ any: true, weak: false, etag: '*' }))
270
- // console.log(Conditional.encodeEtag({ any: true, weak: true, etag: '*' }))
271
- // console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo' }))
272
- // console.log(Conditional.encodeEtag({ any: false, weak: true, etag: 'WeakFoo' }))
273
-
274
- // Error
275
- // console.log(Conditional.encodeEtag(undefined))
276
- // console.log(Conditional.encodeEtag({ any: true, weak: false, etag: 'NotAsterisk' }))
277
- // console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo\tBar' }))
278
- // console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo"Bar' }))
279
- // console.log(Conditional.encodeEtag({ any: false, weak: false, etag: '*' }))
280
-
281
-
282
- // Ok
283
- // console.log(Conditional.parseEtagList('"bfc13a64729c4290ef5b2c2730249c88ca92d82d"'))
284
- // console.log(Conditional.parseEtagList('W/"67ab43", "54ed21", "7892dd"'))
285
- // console.log(Conditional.parseEtagList('*'))
286
- // console.log(Conditional.parseEtagList('"!ÿ©"'))
287
- // console.log(Conditional.parseEtagList('"!","ÿ", "©"'))
288
- // console.log(Conditional.parseEtagList('"!","ÿ" ,\t"©"'))
289
-
290
- // Error
291
- // console.log(Conditional.parseEtagList('"*"'))
292
- // console.log(Conditional.parseEtagList('W/'))
293
- // console.log(Conditional.parseEtagList('W/"'))
294
- // console.log(Conditional.parseEtagList('W/""'))
295
- // console.log(Conditional.parseEtagList(''))
296
- // console.log(Conditional.parseEtagList('"'))
297
- // console.log(Conditional.parseEtagList('""'))
298
- // console.log(Conditional.parseEtagList('"""'))
299
- // console.log(Conditional.parseEtagList('" "'))
300
- // console.log(Conditional.parseEtagList('"\n"'))
301
- // console.log(Conditional.parseEtagList('"\t"'))
302
-
303
- //
304
- // const testsOk = [
305
- // 'Sun, 06 Nov 1994 08:49:37 GMT',
306
- // 'Sun, 06 Nov 1994 00:00:00 GMT',
307
- // 'Tue, 01 Nov 1994 00:00:00 GMT',
308
- // 'Thu, 06 Nov 3000 08:49:37 GMT',
309
- // 'Sun, 06 Nov 1994 23:59:59 GMT',
310
- // new String('Sun, 06 Nov 1994 08:49:37 GMT'),
311
- // ]
312
- // for(const test of testsOk) {
313
- // const result = Conditional.parseFixDate(test)
314
- // if(result?.date.toUTCString() !== test.toString()) {
315
- // console.log('🛑', test, result, result?.date.toUTCString())
316
- // break
317
- // }
318
- // }
319
-
320
-
321
-
322
- // const testBad = [
323
- // undefined,
324
- // null,
325
- // {},
326
- // new String(),
327
- // '',
328
- // 'Anything',
329
- // ' , : : GMT',
330
- // 'Sun, Nov : : GMT',
331
- // 'Sun, 00 Nov 0000 00:00:00 GMT',
332
- // 'Sun, 06 Nov 1994 08-49-37 GMT',
333
- // 'Sun 06 Nov 1994 08:49:37 GMT',
334
- // 'FOO, 06 Nov 1994 08:49:37 GMT',
335
- // 'Sun, 32 Nov 1994 08:49:37 GMT',
336
- // 'Sun, 00 Nov 1994 08:49:37 GMT',
337
- // 'Sun, 06 Nov 0900 08:49:37 GMT',
338
- // 'Sun, 06 Nov 1994 08:49:37 UTC',
339
- // 'Sun, 06 Nov 1994 30:49:37 GMT',
340
- // 'Sun,\t06 Nov 1994 08:49:37 GMT',
341
-
342
- // 'Sunday, 06-Nov-94 08:49:37 GMT',
343
- // 'Sun Nov 6 08:49:37 1994',
344
- // 'Sun Nov 6 08:49:37 1994 ',
345
-
346
- // ]
347
- // for(const test of testBad) {
348
- // const result = Conditional.parseFixDate(test)
349
- // if(result !== undefined) {
350
- // console.log('🛑', test, result)
351
- // break
352
- // }
353
- // }
354
-
@@ -42,17 +42,3 @@ export class ContentDisposition {
42
42
  }
43
43
  }
44
44
  }
45
-
46
- // console.log(parseContentDisposition())
47
- // // console.log(parseContentDisposition(null))
48
- // console.log(parseContentDisposition(''))
49
- // console.log(parseContentDisposition('form-data'))
50
- // console.log(parseContentDisposition(' form-data ; name'))
51
- // console.log(parseContentDisposition('form-data; name="key"'))
52
-
53
- // console.log(parseContentDisposition('inline'))
54
- // console.log(parseContentDisposition('attachment'))
55
- // console.log(parseContentDisposition('attachment; filename="file name.jpg"'))
56
- // console.log(parseContentDisposition('attachment; filename*=UTF-8\'\'file%20name.jpg'))
57
- // console.log(parseContentDisposition('attachment; filename*=UTF-8\'\'file%20name.jpg'))
58
- // console.log(parseContentDisposition('form-data;title*=us-ascii\'en-us\'This%20is%20%2A%2A%2Afun%2A%2A%2A'))
@@ -40,20 +40,3 @@ export class ContentRange {
40
40
  return `${units} ${rangeStr}${CONTENT_RANGE_SIZE_SEPARATOR}${size}`
41
41
  }
42
42
  }
43
-
44
- // console.log(ContentRange.encode({}))
45
- // console.log(ContentRange.encode({ size: '*' }))
46
- // console.log(ContentRange.encode({ units: 'bytes' }))
47
- // console.log(ContentRange.encode({ range: '*' }))
48
- // console.log(ContentRange.encode({ range: '*', size: '*' }))
49
-
50
- // console.log()
51
- // console.log(ContentRange.encode({ range: { start: 0, end: 1024 } }))
52
- // console.log(ContentRange.encode({ range: { start: 0, end: 1024 }, size: 1024 }))
53
- // console.log(ContentRange.encode({ range: '*', size: 1024 }))
54
- // console.log(ContentRange.encode({ size: 1024 }))
55
-
56
- // console.log()
57
- // console.log(ContentRange.encode({ units: 'bob' }))
58
- // console.log(ContentRange.encode({ range: 'bob' }))
59
- // console.log(ContentRange.encode({ size: 'bob' }))
@@ -98,4 +98,4 @@ export class ContentType {
98
98
  parameters
99
99
  }
100
100
  }
101
- }
101
+ }