@johntalton/http-util 6.0.0 → 6.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.
Files changed (43) hide show
  1. package/package.json +16 -2
  2. package/src/defs.js +34 -1
  3. package/src/headers/accept-encoding.js +9 -12
  4. package/src/headers/accept-language.js +10 -4
  5. package/src/headers/accept.js +0 -28
  6. package/src/headers/cache-control.js +0 -6
  7. package/src/headers/clear-site-data.js +4 -10
  8. package/src/headers/client-hints.js +9 -2
  9. package/src/headers/conditional.js +169 -105
  10. package/src/headers/content-disposition.js +0 -14
  11. package/src/headers/content-range.js +0 -17
  12. package/src/headers/content-type.js +1 -1
  13. package/src/headers/forwarded.js +2 -33
  14. package/src/headers/link.js +10 -11
  15. package/src/headers/multipart.js +17 -9
  16. package/src/headers/preference.js +0 -43
  17. package/src/headers/range.js +3 -31
  18. package/src/headers/rate-limit.js +6 -1
  19. package/src/headers/server-timing.js +1 -14
  20. package/src/headers/strict-transport-security.js +1 -0
  21. package/src/headers/util/mime.js +2 -2
  22. package/src/headers/util/whitespace.js +3 -1
  23. package/src/response/2xx/bytes.js +41 -6
  24. package/src/response/2xx/created.js +26 -5
  25. package/src/response/2xx/json.js +36 -4
  26. package/src/response/2xx/no-content.js +25 -5
  27. package/src/response/2xx/partial-content.js +38 -8
  28. package/src/response/2xx/preflight.js +26 -7
  29. package/src/response/3xx/found.js +23 -0
  30. package/src/response/3xx/not-modified.js +27 -3
  31. package/src/response/4xx/bad-request.js +19 -0
  32. package/src/response/4xx/not-acceptable.js +12 -1
  33. package/src/response/4xx/not-allowed.js +15 -4
  34. package/src/response/4xx/payment-required.js +17 -0
  35. package/src/response/4xx/precondition-failed.js +32 -3
  36. package/src/response/4xx/range-not-satisfiable.js +12 -1
  37. package/src/response/4xx/too-many-requests.js +18 -1
  38. package/src/response/4xx/unauthorized.js +1 -1
  39. package/src/response/4xx/unsupported-media.js +19 -2
  40. package/src/response/5xx/unavailable.js +13 -1
  41. package/src/response/index.js +4 -0
  42. package/src/response/response.js +6 -0
  43. package/src/response/send-util.js +23 -10
@@ -9,7 +9,7 @@ import {
9
9
  import { send } from '../send-util.js'
10
10
 
11
11
  /** @import { ServerHttp2Stream } from 'node:http2' */
12
- /** @import { Metadata } from '../../defs.js' */
12
+ /** @import { AcceptRangeUnits, SendInfo, Metadata } from '../../defs.js' */
13
13
 
14
14
  const {
15
15
  HTTP2_HEADER_CONTENT_TYPE,
@@ -27,18 +27,37 @@ const { HTTP_STATUS_OK } = http2.constants
27
27
 
28
28
  /**
29
29
  * @param {ServerHttp2Stream} stream
30
- * @param {Array<string>} methods
30
+ * @param {Array<string>} supportedMethods
31
31
  * @param {Array<string>|undefined} supportedQueryTypes
32
- * @param {'byte'|'none'|undefined} acceptRanges
32
+ * @param {AcceptRangeUnits|undefined} acceptRanges
33
33
  * @param {Metadata} meta
34
34
  */
35
- export function sendPreflight(stream, methods, supportedQueryTypes, acceptRanges, meta) {
36
- const supportsQuery = methods.includes(HTTP_METHOD_QUERY) && supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
35
+ export function sendPreflight(stream, supportedMethods, supportedQueryTypes, acceptRanges, meta) {
36
+ _sendPreflight(stream, {
37
+ supportedMethods,
38
+ supportedQueryTypes,
39
+ acceptRanges
40
+ }, meta)
41
+ }
42
+
43
+ /**
44
+ * @param {ServerHttp2Stream} stream
45
+ * @param {Pick<SendInfo, 'supportedMethods' | 'supportedQueryTypes' | 'acceptRanges'>} info
46
+ * @param {Metadata} meta
47
+ */
48
+ export function _sendPreflight(stream, info, meta) {
49
+ const {
50
+ supportedMethods,
51
+ supportedQueryTypes,
52
+ acceptRanges
53
+ } = info
54
+
55
+ const supportsQuery = supportedMethods.includes(HTTP_METHOD_QUERY) && supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
37
56
  const exposedHeadersAcceptQuery = supportsQuery ? [ HTTP_HEADER_ACCEPT_QUERY ] : []
38
57
  const exposedHeaders = acceptRanges === undefined ? exposedHeadersAcceptQuery : [ HTTP2_HEADER_ACCEPT_RANGES, ...exposedHeadersAcceptQuery ]
39
58
 
40
59
  send(stream, HTTP_STATUS_OK, {
41
- [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]: methods.join(','),
60
+ [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]: supportedMethods.join(','),
42
61
  [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS]: [
43
62
  HTTP2_HEADER_IF_MATCH,
44
63
  HTTP2_HEADER_IF_NONE_MATCH,
@@ -49,7 +68,7 @@ export function sendPreflight(stream, methods, supportedQueryTypes, acceptRanges
49
68
  ].join(','),
50
69
  [HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE]: PREFLIGHT_AGE_SECONDS,
51
70
  [HTTP2_HEADER_ACCEPT_RANGES]: acceptRanges,
52
- [HTTP_HEADER_ACCEPT_QUERY]: supportedQueryTypes?.join(',')
71
+ [HTTP_HEADER_ACCEPT_QUERY]: supportedQueryTypes?.join(',') // todo should empty array return undef
53
72
  // Access-Control-Allow-Credentials
54
73
  }, exposedHeaders, undefined, undefined, meta)
55
74
  }
@@ -0,0 +1,23 @@
1
+ import http2 from 'node:http2'
2
+
3
+ import { send } from '../send-util.js'
4
+
5
+ /** @import { ServerHttp2Stream } from 'node:http2' */
6
+ /** @import { Metadata } from '../../defs.js' */
7
+
8
+ const {
9
+ HTTP2_HEADER_LOCATION
10
+ } = http2.constants
11
+
12
+ const { HTTP_STATUS_FOUND } = http2.constants
13
+
14
+ /**
15
+ * @param {ServerHttp2Stream} stream
16
+ * @param {URL} location
17
+ * @param {Metadata} meta
18
+ */
19
+ export function sendFound(stream, location, meta) {
20
+ send(stream, HTTP_STATUS_FOUND, {
21
+ [HTTP2_HEADER_LOCATION]: location.href
22
+ }, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
23
+ }
@@ -5,13 +5,14 @@ import { Conditional } from '../../headers/conditional.js'
5
5
  import { send } from '../send-util.js'
6
6
 
7
7
  /** @import { ServerHttp2Stream } from 'node:http2' */
8
- /** @import { Metadata } from '../../defs.js' */
9
- /** @import { EtagItem } from '../../headers/conditional.js' */
8
+ /** @import { SendContent, Metadata } from '../../defs.js' */
9
+ /** @import { EtagItem, IMFFixDateInput } from '../../headers/conditional.js' */
10
10
  /** @import { CacheControlOptions } from '../../headers/cache-control.js' */
11
11
 
12
12
  const {
13
13
  HTTP2_HEADER_AGE,
14
14
  HTTP2_HEADER_ETAG,
15
+ HTTP2_HEADER_LAST_MODIFIED,
15
16
  HTTP2_HEADER_VARY,
16
17
  HTTP2_HEADER_CACHE_CONTROL
17
18
  } = http2.constants
@@ -21,15 +22,38 @@ const { HTTP_STATUS_NOT_MODIFIED } = http2.constants
21
22
  /**
22
23
  * @param {ServerHttp2Stream} stream
23
24
  * @param {EtagItem|undefined} etag
25
+ * @param {IMFFixDateInput|string|undefined} lastModified
24
26
  * @param {number|undefined} age
25
27
  * @param {CacheControlOptions} cacheControl
26
28
  * @param {Metadata} meta
27
29
  */
28
- export function sendNotModified(stream, etag, age, cacheControl, meta) {
30
+ export function sendNotModified(stream, etag, lastModified, age, cacheControl, meta) {
31
+ _sendNotModified(stream, {
32
+ etag,
33
+ lastModified,
34
+ age,
35
+ cacheControl
36
+ }, meta)
37
+ }
38
+
39
+ /**
40
+ * @param {ServerHttp2Stream} stream
41
+ * @param {Pick<SendContent, 'etag' | 'lastModified' | 'age' | 'cacheControl'>} content
42
+ * @param {Metadata} meta
43
+ */
44
+ export function _sendNotModified(stream, content, meta) {
45
+ const {
46
+ etag,
47
+ lastModified,
48
+ age,
49
+ cacheControl
50
+ } = content
51
+
29
52
  send(stream, HTTP_STATUS_NOT_MODIFIED, {
30
53
  [HTTP2_HEADER_VARY]: 'Accept, Accept-Encoding',
31
54
  [HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
32
55
  [HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
56
+ [HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified),
33
57
  [HTTP2_HEADER_AGE]: age === undefined ? undefined : `${age}`
34
58
  }, [ HTTP2_HEADER_AGE ], undefined, undefined, meta)
35
59
  }
@@ -0,0 +1,19 @@
1
+ import http2 from 'node:http2'
2
+
3
+ import { CONTENT_TYPE_TEXT } from '../../headers/content-type.js'
4
+ import { send } from '../send-util.js'
5
+
6
+ /** @import { ServerHttp2Stream } from 'node:http2' */
7
+ /** @import { Metadata } from '../../defs.js' */
8
+
9
+ const { HTTP_STATUS_BAD_REQUEST } = http2.constants
10
+
11
+ /**
12
+ * @param {ServerHttp2Stream} stream
13
+ * @param {string} message
14
+ * @param {Metadata} meta
15
+ */
16
+ export function sendBadRequest(stream, message, meta) {
17
+ send(stream, HTTP_STATUS_BAD_REQUEST, {
18
+ }, [ ], CONTENT_TYPE_TEXT, message, meta)
19
+ }
@@ -4,7 +4,7 @@ import { CONTENT_TYPE_JSON } from '../../headers/content-type.js'
4
4
  import { send } from '../send-util.js'
5
5
 
6
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
- /** @import { Metadata } from '../../defs.js' */
7
+ /** @import { SendInfo, Metadata } from '../../defs.js' */
8
8
 
9
9
  const { HTTP_STATUS_NOT_ACCEPTABLE } = http2.constants
10
10
 
@@ -14,6 +14,17 @@ const { HTTP_STATUS_NOT_ACCEPTABLE } = http2.constants
14
14
  * @param {Metadata} meta
15
15
  */
16
16
  export function sendNotAcceptable(stream, supportedTypes, meta) {
17
+ _sendNotAcceptable(stream, { supportedTypes }, meta)
18
+ }
19
+
20
+ /**
21
+ * @param {ServerHttp2Stream} stream
22
+ * @param {Pick<SendInfo, 'supportedTypes'>} info
23
+ * @param {Metadata} meta
24
+ */
25
+ export function _sendNotAcceptable(stream, info, meta) {
26
+ const { supportedTypes } = info
27
+
17
28
  const supportedTypesList = Array.isArray(supportedTypes) ? supportedTypes : [ supportedTypes ]
18
29
  const has = supportedTypesList.length > 0
19
30
 
@@ -3,7 +3,7 @@ import http2 from 'node:http2'
3
3
  import { send } from '../send-util.js'
4
4
 
5
5
  /** @import { ServerHttp2Stream } from 'node:http2' */
6
- /** @import { Metadata } from '../../defs.js' */
6
+ /** @import { SendInfo, Metadata } from '../../defs.js' */
7
7
 
8
8
  const {
9
9
  HTTP_STATUS_METHOD_NOT_ALLOWED
@@ -13,11 +13,22 @@ const { HTTP2_HEADER_ALLOW } = http2.constants
13
13
 
14
14
  /**
15
15
  * @param {ServerHttp2Stream} stream
16
- * @param {Array<string>} methods
16
+ * @param {Array<string>} supportedMethods
17
17
  * @param {Metadata} meta
18
18
  */
19
- export function sendNotAllowed(stream, methods, meta) {
19
+ export function sendNotAllowed(stream, supportedMethods, meta) {
20
+ _sendNotAllowed(stream, { supportedMethods }, meta)
21
+ }
22
+
23
+ /**
24
+ * @param {ServerHttp2Stream} stream
25
+ * @param {Pick<SendInfo, 'supportedMethods'>} info
26
+ * @param {Metadata} meta
27
+ */
28
+ export function _sendNotAllowed(stream, info, meta) {
29
+ const { supportedMethods } = info
30
+
20
31
  send(stream, HTTP_STATUS_METHOD_NOT_ALLOWED, {
21
- [HTTP2_HEADER_ALLOW]: methods.join(',')
32
+ [HTTP2_HEADER_ALLOW]: supportedMethods.join(',')
22
33
  }, [ HTTP2_HEADER_ALLOW ], undefined, undefined, meta)
23
34
  }
@@ -0,0 +1,17 @@
1
+ import http2 from 'node:http2'
2
+
3
+ import { send } from '../send-util.js'
4
+
5
+ /** @import { ServerHttp2Stream } from 'node:http2' */
6
+ /** @import { Metadata } from '../../defs.js' */
7
+
8
+ const { HTTP_STATUS_PAYMENT_REQUIRED } = http2.constants
9
+
10
+ /**
11
+ * @param {ServerHttp2Stream} stream
12
+ * @param {Metadata} meta
13
+ */
14
+ export function sendPaymentRequired(stream, meta) {
15
+ send(stream, HTTP_STATUS_PAYMENT_REQUIRED, {
16
+ }, [ ], undefined, undefined, meta)
17
+ }
@@ -1,16 +1,45 @@
1
1
  import http2 from 'node:http2'
2
2
 
3
+ import { Conditional } from '../../headers/conditional.js'
3
4
  import { send } from '../send-util.js'
4
5
 
5
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
6
- /** @import { Metadata } from '../../defs.js' */
7
+ /** @import { SendContent, Metadata } from '../../defs.js' */
8
+ /** @import { EtagItem ,IMFFixDateInput } from '../../headers/conditional.js' */
9
+
10
+ const {
11
+ HTTP2_HEADER_ETAG,
12
+ HTTP2_HEADER_LAST_MODIFIED
13
+ } = http2.constants
7
14
 
8
15
  const { HTTP_STATUS_PRECONDITION_FAILED } = http2.constants
9
16
 
10
17
  /**
11
18
  * @param {ServerHttp2Stream} stream
19
+ * @param {EtagItem|undefined} etag
20
+ * @param {IMFFixDateInput|string|undefined} lastModified
21
+ * @param {Metadata} meta
22
+ */
23
+ export function sendPreconditionFailed(stream, etag, lastModified, meta) {
24
+ _sendPreconditionFailed(stream, {
25
+ etag,
26
+ lastModified
27
+ }, meta)
28
+ }
29
+
30
+ /**
31
+ * @param {ServerHttp2Stream} stream
32
+ * @param {Pick<SendContent, 'etag' | 'lastModified'>} content
12
33
  * @param {Metadata} meta
13
34
  */
14
- export function sendPreconditionFailed(stream, meta) {
15
- send(stream, HTTP_STATUS_PRECONDITION_FAILED, {}, [], undefined, undefined, meta)
35
+ export function _sendPreconditionFailed(stream, content, meta) {
36
+ const {
37
+ etag,
38
+ lastModified
39
+ } = content
40
+
41
+ send(stream, HTTP_STATUS_PRECONDITION_FAILED, {
42
+ [HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
43
+ [HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified)
44
+ }, [], undefined, undefined, meta)
16
45
  }
@@ -4,7 +4,7 @@ import { CONTENT_RANGE_UNKNOWN, ContentRange } from '../../headers/content-range
4
4
  import { send } from '../send-util.js'
5
5
 
6
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
- /** @import { Metadata } from '../../defs.js' */
7
+ /** @import { SendContent, Metadata } from '../../defs.js' */
8
8
  /** @import { ContentRangeDirective} from '../../headers/content-range.js' */
9
9
 
10
10
  const {
@@ -19,6 +19,17 @@ const { HTTP_STATUS_RANGE_NOT_SATISFIABLE } = http2.constants
19
19
  * @param {Metadata} meta
20
20
  */
21
21
  export function sendRangeNotSatisfiable(stream, rangeDirective, meta) {
22
+ _sendRangeNotSatisfiable(stream, { rangeDirective }, meta)
23
+ }
24
+
25
+ /**
26
+ * @param {ServerHttp2Stream} stream
27
+ * @param {Pick<SendContent, 'rangeDirective'>} content
28
+ * @param {Metadata} meta
29
+ */
30
+ export function _sendRangeNotSatisfiable(stream, content, meta) {
31
+ const { rangeDirective } = content
32
+
22
33
  /** @type {ContentRangeDirective} */
23
34
  const invalidRange = { size: rangeDirective.size, range: CONTENT_RANGE_UNKNOWN }
24
35
 
@@ -10,7 +10,7 @@ import {
10
10
  import { send } from '../send-util.js'
11
11
 
12
12
  /** @import { ServerHttp2Stream } from 'node:http2' */
13
- /** @import { Metadata } from '../../defs.js' */
13
+ /** @import { SendInfo, Metadata } from '../../defs.js' */
14
14
  /** @import { RateLimitInfo, RateLimitPolicyInfo } from '../../headers/rate-limit.js' */
15
15
 
16
16
  const {
@@ -26,6 +26,23 @@ const { HTTP_STATUS_TOO_MANY_REQUESTS } = http2.constants
26
26
  * @param {Metadata} meta
27
27
  */
28
28
  export function sendTooManyRequests(stream, limitInfo, policies, meta) {
29
+ _sendTooManyRequests(stream, {
30
+ limitInfo,
31
+ policies
32
+ }, meta)
33
+ }
34
+
35
+ /**
36
+ * @param {ServerHttp2Stream} stream
37
+ * @param {Pick<SendInfo, 'limitInfo' | 'policies'>} info
38
+ * @param {Metadata} meta
39
+ */
40
+ export function _sendTooManyRequests(stream, info, meta) {
41
+ const {
42
+ limitInfo,
43
+ policies
44
+ } = info
45
+
29
46
  send(stream, HTTP_STATUS_TOO_MANY_REQUESTS, {
30
47
  [HTTP2_HEADER_RETRY_AFTER]: `${limitInfo.resetSeconds}`,
31
48
  [HTTP_HEADER_RATE_LIMIT]: RateLimit.from(limitInfo),
@@ -20,6 +20,6 @@ const { HTTP_STATUS_UNAUTHORIZED } = http2.constants
20
20
  */
21
21
  export function sendUnauthorized(stream, challenge, meta) {
22
22
  send(stream, HTTP_STATUS_UNAUTHORIZED, {
23
- [HTTP2_HEADER_WWW_AUTHENTICATE]: challenge?.map(Challenge.encode),
23
+ [HTTP2_HEADER_WWW_AUTHENTICATE]: challenge?.map(Challenge.encode), // todo stringify ?
24
24
  }, [ HTTP2_HEADER_WWW_AUTHENTICATE ], undefined, undefined, meta)
25
25
  }
@@ -4,7 +4,7 @@ import { HTTP_HEADER_ACCEPT_PATCH, HTTP_HEADER_ACCEPT_POST, HTTP_HEADER_ACCEPT_Q
4
4
  import { send } from '../send-util.js'
5
5
 
6
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
- /** @import { Metadata } from '../../defs.js' */
7
+ /** @import { SendInfo, Metadata } from '../../defs.js' */
8
8
 
9
9
  const { HTTP2_METHOD_POST, HTTP2_METHOD_PATCH } = http2.constants
10
10
 
@@ -17,11 +17,28 @@ const { HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE } = http2.constants
17
17
  * @param {Metadata} meta
18
18
  */
19
19
  export function sendUnsupportedMediaType(stream, acceptableMediaType, supportedQueryTypes, meta) {
20
+ _sendUnsupportedMediaType(stream, {
21
+ acceptableMediaType,
22
+ supportedQueryTypes
23
+ }, meta)
24
+ }
25
+
26
+ /**
27
+ * @param {ServerHttp2Stream} stream
28
+ * @param {Pick<SendInfo, 'acceptableMediaType' | 'supportedQueryTypes'>} info
29
+ * @param {Metadata} meta
30
+ */
31
+ export function _sendUnsupportedMediaType(stream, info, meta) {
32
+ const {
33
+ supportedQueryTypes,
34
+ acceptableMediaType
35
+ } = info
36
+
20
37
  const supportsQuery = supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
21
38
  const exposedHeaders = supportsQuery ? [ HTTP_HEADER_ACCEPT_QUERY, HTTP_HEADER_ACCEPT_POST ] : [ HTTP_HEADER_ACCEPT_POST ]
22
39
 
23
40
  const method = HTTP2_METHOD_POST // todo pass in as parameter or split acceptable to post and patch types
24
- const acceptable = Array.isArray(acceptableMediaType) ? acceptableMediaType : [ acceptableMediaType ]
41
+ const acceptable = Array.isArray(acceptableMediaType) ? acceptableMediaType : [ acceptableMediaType ] // todo undefined creates array of one item
25
42
  const acceptHeader = (method === HTTP2_METHOD_POST) ? HTTP_HEADER_ACCEPT_POST : HTTP_HEADER_ACCEPT_PATCH
26
43
  const acceptValue = ((method === HTTP2_METHOD_POST) || (method === HTTP2_METHOD_PATCH)) ? acceptable.join(',') : undefined
27
44
 
@@ -4,7 +4,7 @@ import { CONTENT_TYPE_TEXT } from '../../headers/content-type.js'
4
4
  import { send } from '../send-util.js'
5
5
 
6
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
- /** @import { Metadata } from '../../defs.js' */
7
+ /** @import { SendInfo, Metadata } from '../../defs.js' */
8
8
 
9
9
  const {
10
10
  HTTP2_HEADER_RETRY_AFTER
@@ -19,6 +19,18 @@ const { HTTP_STATUS_SERVICE_UNAVAILABLE } = http2.constants
19
19
  * @param {Metadata} meta
20
20
  */
21
21
  export function sendUnavailable(stream, message, retryAfter, meta) {
22
+ _sendUnavailable(stream, message, { retryAfter }, meta)
23
+ }
24
+
25
+ /**
26
+ * @param {ServerHttp2Stream} stream
27
+ * @param {string|undefined} message
28
+ * @param {Pick<SendInfo, 'retryAfter'>} info
29
+ * @param {Metadata} meta
30
+ */
31
+ export function _sendUnavailable(stream, message, info, meta) {
32
+ const { retryAfter } = info
33
+
22
34
  send(stream, HTTP_STATUS_SERVICE_UNAVAILABLE, {
23
35
  [HTTP2_HEADER_RETRY_AFTER]: Number.isInteger(retryAfter) ? `${retryAfter}` : undefined
24
36
  }, [ HTTP2_HEADER_RETRY_AFTER ], CONTENT_TYPE_TEXT, message, meta)
@@ -1,6 +1,7 @@
1
1
  /** biome-ignore-all lint/performance/noBarrelFile: entry point */
2
2
  /** biome-ignore-all lint/performance/noReExportAll: entry point */
3
3
  export * from '../defs.js'
4
+ export * from './header-util.js'
4
5
  export * from './send-util.js'
5
6
  // end common headers
6
7
 
@@ -13,12 +14,14 @@ export * from './2xx/partial-content.js'
13
14
  export * from './2xx/preflight.js'
14
15
  export * from './2xx/sse.js'
15
16
  export * from './2xx/trace.js'
17
+ export * from './3xx/found.js'
16
18
  export * from './3xx/moved-permanently.js'
17
19
  export * from './3xx/multiple-choices.js'
18
20
  export * from './3xx/not-modified.js'
19
21
  export * from './3xx/permanent-redirect.js'
20
22
  export * from './3xx/see-other.js'
21
23
  export * from './3xx/temporary-redirect.js'
24
+ export * from './4xx/bad-request.js'
22
25
  export * from './4xx/conflict.js'
23
26
  export * from './4xx/content-too-large.js'
24
27
  export * from './4xx/forbidden.js'
@@ -27,6 +30,7 @@ export * from './4xx/im-a-teapot.js'
27
30
  export * from './4xx/not-acceptable.js'
28
31
  export * from './4xx/not-allowed.js'
29
32
  export * from './4xx/not-found.js'
33
+ export * from './4xx/payment-required.js'
30
34
  export * from './4xx/precondition-failed.js'
31
35
  export * from './4xx/range-not-satisfiable.js'
32
36
  export * from './4xx/timeout.js'
@@ -7,12 +7,14 @@ import { sendPartialContent } from './2xx/partial-content.js'
7
7
  import { sendPreflight } from './2xx/preflight.js'
8
8
  import { sendSSE } from './2xx/sse.js'
9
9
  import { sendTrace } from './2xx/trace.js'
10
+ import { sendFound } from './3xx/found.js'
10
11
  import { sendMovedPermanently } from './3xx/moved-permanently.js'
11
12
  import { sendMultipleChoices } from './3xx/multiple-choices.js'
12
13
  import { sendNotModified } from './3xx/not-modified.js'
13
14
  import { sendPermanentRedirect } from './3xx/permanent-redirect.js'
14
15
  import { sendSeeOther } from './3xx/see-other.js'
15
16
  import { sendTemporaryRedirect } from './3xx/temporary-redirect.js'
17
+ import { sendBadRequest } from './4xx/bad-request.js'
16
18
  import { sendConflict } from './4xx/conflict.js'
17
19
  import { sendContentTooLarge } from './4xx/content-too-large.js'
18
20
  import { sendForbidden } from './4xx/forbidden.js'
@@ -21,6 +23,7 @@ import { sendImATeapot } from './4xx/im-a-teapot.js'
21
23
  import { sendNotAcceptable } from './4xx/not-acceptable.js'
22
24
  import { sendNotAllowed } from './4xx/not-allowed.js'
23
25
  import { sendNotFound } from './4xx/not-found.js'
26
+ import { sendPaymentRequired } from './4xx/payment-required.js'
24
27
  import { sendPreconditionFailed } from './4xx/precondition-failed.js'
25
28
  import { sendRangeNotSatisfiable } from './4xx/range-not-satisfiable.js'
26
29
  import { sendTimeout } from './4xx/timeout.js'
@@ -35,12 +38,14 @@ import { sendUnavailable } from './5xx/unavailable.js'
35
38
 
36
39
  export const Response = {
37
40
  accepted: sendAccepted,
41
+ badRequest: sendBadRequest,
38
42
  bytes: sendBytes,
39
43
  conflict: sendConflict,
40
44
  contentTooLarge: sendContentTooLarge,
41
45
  created: sendCreated,
42
46
  error: sendError,
43
47
  forbidden: sendForbidden,
48
+ found: sendFound,
44
49
  gone: sendGone,
45
50
  imATeapot: sendImATeapot,
46
51
  insufficientStorage: sendInsufficientStorage,
@@ -54,6 +59,7 @@ export const Response = {
54
59
  notImplemented: sendNotImplemented,
55
60
  notModified: sendNotModified,
56
61
  partialContent: sendPartialContent,
62
+ paymentRequired: sendPaymentRequired,
57
63
  permanentRedirect: sendPermanentRedirect,
58
64
  preconditionFailed: sendPreconditionFailed,
59
65
  preflight: sendPreflight,
@@ -1,5 +1,5 @@
1
1
  import http2 from 'node:http2'
2
- import { Readable } from 'node:stream'
2
+ import { pipeline, Readable } from 'node:stream'
3
3
  import { ReadableStream } from 'node:stream/web'
4
4
  import {
5
5
  brotliCompressSync,
@@ -22,8 +22,8 @@ import {
22
22
  /** @import { ServerHttp2Stream } from 'node:http2' */
23
23
  /** @import { OutgoingHttpHeaders } from 'node:http2' */
24
24
  /** @import { InputType } from 'node:zlib' */
25
- /** @import { Metadata, SendBody } from '../defs.js' */
26
- /** @import { EtagItem } from '../headers/conditional.js' */
25
+ /** @import { AcceptRangeUnits, Metadata, SendBody } from '../defs.js' */
26
+ /** @import { EtagItem, IMFFixDateInput } from '../headers/conditional.js' */
27
27
  /** @import { CacheControlOptions } from '../headers/cache-control.js' */
28
28
  /** @import { ContentRangeDirective } from '../headers/content-range.js' */
29
29
 
@@ -38,6 +38,7 @@ const {
38
38
  HTTP2_HEADER_VARY,
39
39
  HTTP2_HEADER_CACHE_CONTROL,
40
40
  HTTP2_HEADER_ETAG,
41
+ HTTP2_HEADER_LAST_MODIFIED,
41
42
  HTTP2_HEADER_AGE,
42
43
  HTTP2_HEADER_ACCEPT_RANGES,
43
44
  HTTP2_HEADER_CONTENT_RANGE,
@@ -47,7 +48,7 @@ const {
47
48
  HTTP2_HEADER_RANGE
48
49
  } = http2.constants
49
50
 
50
- /** @typedef { (data: InputType) => Buffer } EncoderFun */
51
+ /** @typedef { (data: InputType) => Buffer<ArrayBuffer> } EncoderFun */
51
52
 
52
53
  /** @type {Map<string, EncoderFun>} */
53
54
  export const ENCODER_MAP = new Map([
@@ -64,13 +65,14 @@ export const ENCODER_MAP = new Map([
64
65
  * @param {SendBody} body
65
66
  * @param {string|undefined} encoding
66
67
  * @param {EtagItem|undefined} etag
68
+ * @param {IMFFixDateInput|string|undefined} lastModified
67
69
  * @param {number|undefined} age
68
70
  * @param {CacheControlOptions|undefined} cacheControl
69
- * @param {'bytes'|'none'|undefined} acceptRanges
71
+ * @param {AcceptRangeUnits|undefined} acceptRanges
70
72
  * @param {Array<string>|undefined} supportedQueryTypes
71
73
  * @param {Metadata} meta
72
74
  */
73
- export function send_encoded(stream, status, contentType, body, encoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
75
+ export function send_encoded(stream, status, contentType, body, encoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
74
76
  const obj = (typeof body === 'string') ? Buffer.from(body, CHARSET_UTF8) : body
75
77
 
76
78
  const useIdentity = encoding === 'identity'
@@ -86,7 +88,7 @@ export function send_encoded(stream, status, contentType, body, encoding, etag,
86
88
  { name: 'encode', duration: encodeEnd - encodeStart }
87
89
  )
88
90
 
89
- send_bytes(stream, status, contentType, encodedData, undefined, undefined, actualEncoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta )
91
+ send_bytes(stream, status, contentType, encodedData, undefined, undefined, actualEncoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta )
90
92
  }
91
93
 
92
94
 
@@ -99,13 +101,14 @@ export function send_encoded(stream, status, contentType, body, encoding, etag,
99
101
  * @param {number|undefined} contentLength
100
102
  * @param {string|undefined} encoding
101
103
  * @param {EtagItem|undefined} etag
104
+ * @param {IMFFixDateInput|string|undefined} lastModified
102
105
  * @param {number|undefined} age
103
106
  * @param {CacheControlOptions|undefined} cacheControl
104
- * @param {'bytes'|'none'|undefined} acceptRanges
107
+ * @param {AcceptRangeUnits|undefined} acceptRanges
105
108
  * @param {Array<string>|undefined} supportedQueryTypes
106
109
  * @param {Metadata} meta
107
110
  */
108
- export function send_bytes(stream, status, contentType, obj, range, contentLength, encoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
111
+ export function send_bytes(stream, status, contentType, obj, range, contentLength, encoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
109
112
  const contentLen = Number.isInteger(contentLength) ? `${contentLength}` : undefined
110
113
  const supportsQuery = supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
111
114
 
@@ -123,6 +126,7 @@ export function send_bytes(stream, status, contentType, obj, range, contentLengt
123
126
  [HTTP2_HEADER_VARY]: varyHeaders.join(','),
124
127
  [HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
125
128
  [HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
129
+ [HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified),
126
130
  [HTTP2_HEADER_AGE]: age === undefined ? undefined : `${age}`,
127
131
  [HTTP2_HEADER_CONTENT_LENGTH]: contentLen,
128
132
  [HTTP2_HEADER_CONTENT_RANGE]: ContentRange.encode(range),
@@ -164,7 +168,16 @@ export function send(stream, status, headers, exposedHeaders, contentType, body,
164
168
 
165
169
  if(stream.writable && body !== undefined) {
166
170
  if(body instanceof ReadableStream) {
167
- Readable.fromWeb(body).pipe(stream)
171
+ const signal = undefined // AbortSignal.timeout(1000)
172
+ pipeline(
173
+ Readable.fromWeb(body, { signal }),
174
+ stream,
175
+ err => {
176
+ if(err !== null) {
177
+ console.warn('pipeline error')
178
+ }
179
+ })
180
+
168
181
  return
169
182
  }
170
183