@johntalton/http-core 1.1.0 → 1.1.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@johntalton/http-core",
3
3
  "type": "module",
4
- "version": "1.1.0",
4
+ "version": "1.1.2",
5
5
  "license": "MIT",
6
6
  "exports": {
7
7
  ".": "./src/index.js"
@@ -16,7 +16,7 @@
16
16
  "lint": "biome check"
17
17
  },
18
18
  "dependencies": {
19
- "@johntalton/http-util": "^7.0.1",
19
+ "@johntalton/http-util": "^7.0.3",
20
20
  "@johntalton/sse-util": "^1.0.0"
21
21
  },
22
22
  "devDependencies": {
package/src/epilogue.js CHANGED
@@ -1,4 +1,3 @@
1
- import { MIME_TYPE_JSON } from '@johntalton/http-util/headers'
2
1
  import { Response } from '@johntalton/http-util/response/object'
3
2
  import { ServerSentEvents } from '@johntalton/sse-util'
4
3
 
@@ -18,11 +17,11 @@ function addSSEPortHandler(stream, port, streamId, shutdownSignal) {
18
17
  stream.end()
19
18
  }
20
19
 
21
- stream.once('close', (() => {
20
+ stream.once('close', () => {
22
21
  console.log('stream close in sse handler', streamId)
23
22
  shutdownSignal.removeEventListener('abort', signalHandler)
24
23
  port.close()
25
- }))
24
+ })
26
25
 
27
26
  shutdownSignal.addEventListener('abort', signalHandler, { once: true })
28
27
 
@@ -97,15 +96,22 @@ export function epilogue(state) {
97
96
  if(active) { addSSEPortHandler(stream, port, state.streamId, state.shutdownSignal) }
98
97
  } break
99
98
  case 'json': {
100
- const { obj, accept, etag, lastModified, age, supportedQueryTypes } = state
101
-
102
- if(accept.type === MIME_TYPE_JSON) {
103
- Response.json(stream, obj, { encoding: accept.encoding, etag, lastModified, age, cacheControl: state.cacheControl ?? {} }, { supportedQueryTypes }, meta)
104
- }
105
- else {
106
- // todo: but we did process the request - is that ok?
107
- Response.notAcceptable(stream, { supportedTypes: [ MIME_TYPE_JSON ] }, meta)
108
- }
99
+ const { obj, encoding, etag, lastModified, age, supportedQueryTypes } = state
100
+ Response.json(stream, obj, { encoding, etag, lastModified, age, cacheControl: state.cacheControl ?? {} }, { supportedQueryTypes }, meta)
101
+ } break
102
+ case 'encoded': {
103
+ const { contentType, encoding, etag, lastModified, age, supportedQueryTypes } = state
104
+
105
+ Response.encoded(stream, state.obj, {
106
+ contentType,
107
+ encoding,
108
+ etag,
109
+ lastModified,
110
+ age,
111
+ cacheControl: state.cacheControl ?? {}
112
+ }, {
113
+ supportedQueryTypes
114
+ }, meta)
109
115
  } break
110
116
  case 'partial-bytes': {
111
117
  const { contentType, contentLength, etag, lastModified, age } = state
@@ -125,7 +131,6 @@ export function epilogue(state) {
125
131
  Response.bytes(stream, state.obj, {
126
132
  contentType,
127
133
  contentLength,
128
- encoding: 'identity',
129
134
  etag,
130
135
  lastModified,
131
136
  age,
package/src/index.js CHANGED
@@ -43,6 +43,7 @@ export const KNOWN_METHODS = [
43
43
  /** @import { Metadata } from '@johntalton/http-util/response' */
44
44
  /** @import { BodyFuture } from '@johntalton/http-util/body' */
45
45
  /** @import {
46
+ AcceptItem,
46
47
  EtagItem,
47
48
  IMFFixDate,
48
49
  IMFFixDateInput,
@@ -52,7 +53,8 @@ export const KNOWN_METHODS = [
52
53
  ChallengeItem,
53
54
  CacheControlOptions
54
55
  } from '@johntalton/http-util/headers' */
55
- /** @import { SendBody } from '@johntalton/http-util/response' */
56
+ /** @import { AcceptStyleItem } from '@johntalton/http-util/util' */
57
+ /** @import { SendBody, NonEmptyArray } from '@johntalton/http-util/response' */
56
58
  /** @import { SecFetchSite, SecFetchMode, SecFetchDest } from '@johntalton/http-util/headers' */
57
59
 
58
60
 
@@ -97,6 +99,7 @@ export const KNOWN_METHODS = [
97
99
  'sse' |
98
100
  'bytes' |
99
101
  'partial-bytes' |
102
+ 'encoded' |
100
103
  'json' |
101
104
  'error'
102
105
  } RouteType */
@@ -119,8 +122,24 @@ export const KNOWN_METHODS = [
119
122
  * @property {AbortSignal} shutdownSignal
120
123
  */
121
124
 
125
+ /**
126
+ * @typedef {Object} RouteRequestAcceptParsed
127
+ * @property {Array<AcceptItem> | undefined} type
128
+ * @property {Array<AcceptStyleItem> | undefined} encoding
129
+ * @property {Array<AcceptStyleItem>} language
130
+ */
131
+
132
+ /**
133
+ * @typedef {Object} RouteRequestAcceptFn
134
+ * @property {(a: Array<string>|undefined) => AcceptItem|undefined} type
135
+ * @property {(a: Array<string>|undefined) => AcceptStyleItem|undefined} encoding
136
+ * @property {(a: Array<string>|undefined) => AcceptStyleItem|undefined} language
137
+ */
138
+
122
139
  /**
123
140
  * @typedef {Object} RouteRequestAccept
141
+ * @property {RouteRequestAcceptParsed} parsed
142
+ * @property {RouteRequestAcceptFn} select
124
143
  * @property {string|undefined} type
125
144
  * @property {string|undefined} encoding
126
145
  * @property {string|undefined} language
@@ -164,11 +183,6 @@ export const KNOWN_METHODS = [
164
183
  */
165
184
  /** @typedef {RouteBase & RouteRequestBase} RouteRequest */
166
185
 
167
- /**
168
- * @template T
169
- * @typedef {[ T, ...T[] ]} NonEmptyArray
170
- */
171
-
172
186
  /**
173
187
  * @typedef {Object} PartialBytes
174
188
  * @property {SendBody} obj
@@ -405,8 +419,8 @@ export const KNOWN_METHODS = [
405
419
  * @typedef {Object} RouteBytesBase
406
420
  * @property {'bytes'} type
407
421
  * @property {string} contentType
422
+ * @property {SendBody} obj
408
423
  * @property {number|undefined} [contentLength]
409
- * @property {SendBody|undefined} obj
410
424
  * @property {IMFFixDateInput|string|undefined} [lastModified]
411
425
  * @property {EtagItem|undefined} [etag]
412
426
  * @property {number|undefined} [age]
@@ -415,8 +429,6 @@ export const KNOWN_METHODS = [
415
429
  */
416
430
  /** @typedef {RouteBase & RouteBytesBase} RouteBytes */
417
431
 
418
-
419
-
420
432
  /**
421
433
  * @typedef {Object} RoutePartialBytesBase
422
434
  * @property {'partial-bytes'} type
@@ -430,11 +442,25 @@ export const KNOWN_METHODS = [
430
442
  */
431
443
  /** @typedef {RouteBase & RoutePartialBytesBase} RoutePartialBytes */
432
444
 
445
+ /**
446
+ * @typedef {Object} RouteEncodedBase
447
+ * @property {'encoded'} type
448
+ * @property {SendBody} obj
449
+ * @property {string} contentType
450
+ * @property {string|undefined} [encoding]
451
+ * @property {EtagItem|undefined} [etag]
452
+ * @property {IMFFixDateInput|string|undefined} [lastModified]
453
+ * @property {number|undefined} [age]
454
+ * @property {CacheControlOptions|undefined} [cacheControl]
455
+ * @property {Array<string>|undefined} [supportedQueryTypes]
456
+ */
457
+ /** @typedef {RouteBase & RouteEncodedBase} RouteEncoded */
458
+
433
459
  /**
434
460
  * @typedef {Object} RouteJSONBase
435
461
  * @property {'json'} type
436
- * @property {RouteRequestAccept} accept
437
462
  * @property {Record<any, any>} obj
463
+ * @property {string | undefined} [encoding]
438
464
  * @property {IMFFixDateInput|string|undefined} [lastModified]
439
465
  * @property {EtagItem|undefined} [etag]
440
466
  * @property {number|undefined} [age]
@@ -486,6 +512,7 @@ export const KNOWN_METHODS = [
486
512
  RouteSSE |
487
513
  RouteBytes |
488
514
  RoutePartialBytes |
515
+ RouteEncoded |
489
516
  RouteJSON |
490
517
  RouteError
491
518
  } RouteAction */
package/src/preamble.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  import { isValidHeader, isValidLikeHeader, isValidMethod } from './index.js'
36
36
 
37
37
  /** @import { ServerHttp2Stream, IncomingHttpHeaders } from 'node:http2' */
38
- /** @import { Config, RouteRequest, RouteAction, StreamID, RouteConditions, SecFetchMetadata } from './index.js' */
38
+ /** @import { Config, RouteRequest, RouteAction, StreamID, RouteConditions, SecFetchMetadata, RouteRequestAccept } from './index.js' */
39
39
 
40
40
  const { HTTP2_METHOD_OPTIONS, HTTP2_METHOD_TRACE } = http2.constants
41
41
 
@@ -258,21 +258,34 @@ export function preamble(preState, headers, servername) {
258
258
  //
259
259
  // content negotiation
260
260
  //
261
- const contentType = ContentType.parse(fullContentType)
262
- const acceptedEncoding = AcceptEncoding.select(fullAcceptEncoding, DEFAULT_SUPPORTED_ENCODINGS)
263
- const accept = Accept.select(fullAccept, DEFAULT_SUPPORTED_MIME_TYPES)
264
- const acceptedLanguage = AcceptLanguage.select(fullAcceptLanguage, DEFAULT_SUPPORTED_LANGUAGES)
261
+ const acceptItem = Accept.parse(fullAccept)
262
+ const acceptEncodingItem = AcceptEncoding.parse(fullAcceptEncoding)
263
+ const acceptLanguageItem = AcceptLanguage.parse(fullAcceptLanguage)
264
+
265
+ /** @type {RouteRequestAccept} */
265
266
  const acceptObject = {
266
- type: accept,
267
- encoding: acceptedEncoding,
268
- language: acceptedLanguage
267
+ parsed: {
268
+ type: acceptItem,
269
+ encoding: acceptEncodingItem,
270
+ language: acceptLanguageItem,
271
+ },
272
+
273
+ get type() { return Accept.selectFrom(acceptItem, DEFAULT_SUPPORTED_MIME_TYPES) },
274
+ get encoding() { return AcceptEncoding.selectFrom(acceptEncodingItem, DEFAULT_SUPPORTED_ENCODINGS) },
275
+ get language() { return AcceptLanguage.selectFrom(acceptLanguageItem, DEFAULT_SUPPORTED_LANGUAGES) },
276
+
277
+ select: {
278
+ type: acceptableTypes => Accept.selectItemFrom(acceptItem, acceptableTypes ?? DEFAULT_SUPPORTED_MIME_TYPES),
279
+ encoding: acceptableEncodings => AcceptEncoding.selectItemFrom(acceptEncodingItem, acceptableEncodings ?? DEFAULT_SUPPORTED_ENCODINGS),
280
+ language: acceptableLanguages => AcceptLanguage.selectItemFrom(acceptLanguageItem, acceptableLanguages ?? DEFAULT_SUPPORTED_LANGUAGES)
281
+ }
269
282
  }
270
283
 
271
284
  //
272
285
  // Trace
273
286
  //
274
287
  if(method === HTTP2_METHOD_TRACE) {
275
- if(!ALLOW_TRACE) { return { ...state, type: 'not-allowed', method, methods: [], url: requestUrl }}
288
+ if(!ALLOW_TRACE) { return { ...state, type: 'not-allowed', method, methods: [] }}
276
289
  const maxForwardsValue = maxForwards === undefined ? 0 : Number.parseInt(maxForwards)
277
290
  const preambleEnd = performance.now()
278
291
  state.meta.performance.push({ name: 'preamble-trace', duration: preambleEnd - preambleStart })
@@ -283,6 +296,7 @@ export function preamble(preState, headers, servername) {
283
296
  //
284
297
  // setup future body
285
298
  //
299
+ const contentType = ContentType.parse(fullContentType)
286
300
  const contentLength = fullContentLength === undefined ? undefined : Number.parseInt(fullContentLength, 10)
287
301
  const body = requestBody(stream, {
288
302
  byteLimit: BODY_BYTE_LENGTH,