@johntalton/http-core 1.0.7 → 1.1.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@johntalton/http-core",
3
3
  "type": "module",
4
- "version": "1.0.7",
4
+ "version": "1.1.1",
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": "^6.1.0",
19
+ "@johntalton/http-util": "^7.0.2",
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
 
@@ -52,12 +51,14 @@ export function epilogue(state) {
52
51
  switch(type) {
53
52
  //
54
53
  case 'trace': { Response.trace(stream, state.method, state.url, state.headers, meta) } break
54
+ // case 'im-a-teapot': { Response.imATeapot(stream, meta) } break
55
55
  //
56
- case 'preflight': { Response.preflight(stream, state.methods, state.supportedQueryTypes, undefined, meta) } break
57
- case 'no-content': { Response.noContent(stream, state.etag, state.lastModified, meta)} break
58
56
  // case 'accepted': { Response.accepted(stream, meta) } break
59
- case 'created': { Response.created(stream, new URL(state.location, meta.origin), state.etag, state.lastModified, meta) } break
60
- case 'not-modified': { Response.notModified(stream, state.etag, state.lastModified, state.age, state.cacheControl ?? {}, meta) } break
57
+ case 'created': { Response.created(stream, new URL(state.location, meta.origin), { etag: state.etag, lastModified: state.lastModified }, meta) } break
58
+ case 'preflight': { Response.preflight(stream, { supportedMethods: state.methods, supportedQueryTypes: state.supportedQueryTypes, acceptRanges: undefined }, meta) } break
59
+ case 'no-content': { Response.noContent(stream, { etag: state.etag, lastModified: state.lastModified }, meta)} break
60
+ case 'not-modified': { Response.notModified(stream, { etag: state.etag, lastModified: state.lastModified, age: state.age, cacheControl: state.cacheControl ?? {} }, meta) } break
61
+ case 'found': { Response.found(stream, state.location, meta) } break
61
62
 
62
63
  //
63
64
  // case 'multiple-choices': { Response.multipleChoices(stream, meta) } break
@@ -69,21 +70,23 @@ export function epilogue(state) {
69
70
 
70
71
  //
71
72
  case '404': { Response.notFound(stream, state.message, meta) } break
73
+ // case 'bad-request': { Response.badRequest(stream, meta) } break
72
74
  case 'conflict': { Response.conflict(stream, meta) } break
73
- case 'not-allowed': { Response.notAllowed(stream, state.methods, meta) } break
74
- case 'not-acceptable': { Response.notAcceptable(stream, state.acceptableMediaTypes ?? [], meta)} break
75
- case 'unsupported-media': { Response.unsupportedMediaType(stream, state.acceptableMediaTypes, state.supportedQueryTypes, meta) } break
76
- case 'unprocessable': { Response.unprocessable(stream, meta) } break
77
- case 'precondition-failed': { Response.preconditionFailed(stream, state.etag, state.lastModified, meta) } break
78
- case 'not-satisfiable': { Response.rangeNotSatisfiable(stream, { size: state.contentLength }, meta) } break
79
75
  case 'content-too-large': { Response.contentTooLarge(stream, meta) } break
80
- case 'insufficient-storage': { Response.insufficientStorage(stream, meta) } break
81
- case 'too-many-requests': { Response.tooManyRequests(stream, state.limit, state.policies, meta) } break
82
- case 'unauthorized': { Response.unauthorized(stream, state.challenge, meta) } break
83
76
  case 'forbidden': { Response.forbidden(stream, meta) } break
84
- case 'unavailable': { Response.unavailable(stream, state.message, state.retryAfter, meta)} break
85
- case 'not-implemented': { Response.notImplemented(stream, state.message, meta)} break
77
+ case 'not-acceptable': { Response.notAcceptable(stream, { supportedTypes: state.acceptableMediaTypes ?? [] }, meta) } break
78
+ case 'not-allowed': { Response.notAllowed(stream, { supportedMethods: state.methods }, meta) } break
79
+ // case 'payment-required': {} break
80
+ case 'precondition-failed': { Response.preconditionFailed(stream, { etag: state.etag, lastModified: state.lastModified }, meta) } break
81
+ case 'not-satisfiable': { Response.rangeNotSatisfiable(stream, { rangeDirective: { size: state.contentLength } }, meta) } break
86
82
  case 'timeout': { Response.timeout(stream, meta) } break
83
+ case 'too-many-requests': { Response.tooManyRequests(stream, { limitInfo: state.limit, policies: state.policies }, meta) } break
84
+ case 'unauthorized': { Response.unauthorized(stream, state.challenge, meta) } break
85
+ case 'unprocessable': { Response.unprocessable(stream, meta) } break
86
+ case 'unsupported-media': { Response.unsupportedMediaType(stream, { acceptableMediaType: state.acceptableMediaTypes, supportedQueryTypes: state.supportedQueryTypes }, meta) } break
87
+ case 'insufficient-storage': { Response.insufficientStorage(stream, meta) } break
88
+ case 'not-implemented': { Response.notImplemented(stream, state.message, meta) } break
89
+ case 'unavailable': { Response.unavailable(stream, state.message, { retryAfter: state.retryAfter }, meta) } break
87
90
 
88
91
  //
89
92
  case 'sse': {
@@ -91,22 +94,38 @@ export function epilogue(state) {
91
94
 
92
95
  Response.sse(stream, { ...meta, active, bom })
93
96
  if(active) { addSSEPortHandler(stream, port, state.streamId, state.shutdownSignal) }
94
- }
95
- break
97
+ } break
96
98
  case 'json': {
97
- const { obj, accept, etag, lastModified } = state
98
-
99
- if(accept.type === MIME_TYPE_JSON) {
100
- Response.json(stream, obj, accept.encoding, etag, lastModified, state.age, state.cacheControl ?? {}, state.supportedQueryTypes, meta)
101
- }
102
- else {
103
- // todo: but we did process the request - is that ok?
104
- Response.notAcceptable(stream, [ MIME_TYPE_JSON ], meta)
105
- }
106
- }
107
- break
108
- case 'partial-bytes': { Response.partialContent(stream, state.contentType, state.objs, state.contentLength, undefined, state.etag, state.lastModified, state.age, state.cacheControl ?? {}, meta) } break
109
- case 'bytes': { Response.bytes(stream, state.contentType, state.obj, state.contentLength, 'identity', state.etag, state.lastModified, state.age, state.cacheControl ?? {}, state.acceptRanges, meta) } break
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 'partial-bytes': {
103
+ const { contentType, contentLength, etag, lastModified, age } = state
104
+
105
+ Response.partialContent(stream, state.objs, {
106
+ contentType,
107
+ contentLength,
108
+ encoding: undefined,
109
+ etag,
110
+ lastModified,
111
+ age,
112
+ cacheControl: state.cacheControl ?? {} }, meta)
113
+ } break
114
+ case 'bytes': {
115
+ const { contentType, contentLength, encoding, etag, lastModified, age, acceptRanges } = state
116
+
117
+ Response.bytes(stream, state.obj, {
118
+ contentType,
119
+ contentLength,
120
+ encoding: encoding ?? 'identity',
121
+ etag,
122
+ lastModified,
123
+ age,
124
+ cacheControl: state.cacheControl ?? {}
125
+ }, {
126
+ acceptRanges
127
+ }, meta)
128
+ } break
110
129
 
111
130
  //
112
131
  case 'error': {
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,6 +53,7 @@ export const KNOWN_METHODS = [
52
53
  ChallengeItem,
53
54
  CacheControlOptions
54
55
  } from '@johntalton/http-util/headers' */
56
+ /** @import { AcceptStyleItem } from '@johntalton/http-util/util' */
55
57
  /** @import { SendBody } from '@johntalton/http-util/response' */
56
58
  /** @import { SecFetchSite, SecFetchMode, SecFetchDest } from '@johntalton/http-util/headers' */
57
59
 
@@ -60,37 +62,45 @@ export const KNOWN_METHODS = [
60
62
 
61
63
  /** @typedef {'request'} RouteTypeRequest */
62
64
  /** @typedef {
63
- 'partial-bytes' |
64
- 'bytes' |
65
- 'json' |
66
- '404' |
67
- 'sse' |
68
- 'error' |
69
- 'preflight' |
70
- 'not-allowed' |
71
65
  'trace' |
66
+ 'im-a-teapot' |
67
+ 'accepted' |
72
68
  'created' |
73
- 'unsupported-media' |
69
+ 'preflight' |
70
+ 'no-content' |
74
71
  'not-modified' |
75
- 'precondition-failed' |
76
- 'unprocessable' |
77
- 'not-acceptable' |
78
- 'conflict' |
79
- 'not-implemented' |
80
- 'unavailable' |
81
- 'not-satisfiable' |
72
+ 'found' |
73
+
74
+ 'gone' |
75
+ 'moved-permanently' |
82
76
  'see-other' |
83
77
  'temporary-redirect' |
84
78
  'permanent-redirect' |
85
- 'moved-permanently' |
86
- 'gone' |
87
- 'no-content' |
79
+
80
+ '404' |
81
+ 'bad-request' |
82
+ 'conflict' |
88
83
  'content-too-large' |
89
- 'insufficient-storage' |
84
+ 'forbidden' |
85
+ 'not-acceptable' |
86
+ 'not-allowed' |
87
+ 'payment-required' |
88
+ 'precondition-failed' |
89
+ 'not-satisfiable' |
90
+ 'timeout' |
90
91
  'too-many-requests' |
91
92
  'unauthorized' |
92
- 'forbidden' |
93
- 'timeout'
93
+ 'unprocessable' |
94
+ 'unsupported-media' |
95
+ 'insufficient-storage' |
96
+ 'not-implemented' |
97
+ 'unavailable' |
98
+
99
+ 'sse' |
100
+ 'bytes' |
101
+ 'partial-bytes' |
102
+ 'json' |
103
+ 'error'
94
104
  } RouteType */
95
105
  /** @typedef {'GET'|'HEAD'|'POST'|'PUT'|'PATCH'|'OPTIONS'|'DELETE'|'TRACE'|'QUERY'} RouteMethod */
96
106
 
@@ -111,8 +121,24 @@ export const KNOWN_METHODS = [
111
121
  * @property {AbortSignal} shutdownSignal
112
122
  */
113
123
 
124
+ /**
125
+ * @typedef {Object} RouteRequestAcceptParsed
126
+ * @property {Array<AcceptItem> | undefined} type
127
+ * @property {Array<AcceptStyleItem> | undefined} encoding
128
+ * @property {Array<AcceptStyleItem>} language
129
+ */
130
+
131
+ /**
132
+ * @typedef {Object} RouteRequestAcceptFn
133
+ * @property {(a: Array<string>|undefined) => AcceptItem|undefined} type
134
+ * @property {(a: Array<string>|undefined) => AcceptStyleItem|undefined} encoding
135
+ * @property {(a: Array<string>|undefined) => AcceptStyleItem|undefined} language
136
+ */
137
+
114
138
  /**
115
139
  * @typedef {Object} RouteRequestAccept
140
+ * @property {RouteRequestAcceptParsed} parsed
141
+ * @property {RouteRequestAcceptFn} select
116
142
  * @property {string|undefined} type
117
143
  * @property {string|undefined} encoding
118
144
  * @property {string|undefined} language
@@ -125,6 +151,22 @@ export const KNOWN_METHODS = [
125
151
  * @property {number|undefined} port
126
152
  */
127
153
 
154
+ /**
155
+ * @typedef {Object} RouteConditions
156
+ * @property {Array<EtagItem>} match
157
+ * @property {Array<EtagItem>} noneMatch
158
+ * @property {IMFFixDate|undefined} modifiedSince
159
+ * @property {IMFFixDate|undefined} unmodifiedSince
160
+ * @property {IMFFixDate|EtagItem|undefined} [range]
161
+ */
162
+
163
+ /**
164
+ * @typedef {Object} SecFetchMetadata
165
+ * @property {SecFetchSite|undefined} site
166
+ * @property {SecFetchMode|undefined} mode
167
+ * @property {SecFetchDest|undefined} dest
168
+ */
169
+
128
170
  /**
129
171
  * @typedef {Object} RouteRequestBase
130
172
  * @property {'request'} type
@@ -141,21 +183,20 @@ export const KNOWN_METHODS = [
141
183
  /** @typedef {RouteBase & RouteRequestBase} RouteRequest */
142
184
 
143
185
  /**
144
- * @typedef {Object} RouteErrorBase
145
- * @property {'error'} type
146
- * @property {string} cause
147
- * @property {Error|undefined} [error]
186
+ * @template T
187
+ * @typedef {[ T, ...T[] ]} NonEmptyArray
148
188
  */
149
- /** @typedef {RouteBase & RouteErrorBase } RouteError */
150
189
 
151
190
  /**
152
- * @typedef {Object} RouteNotAllowedBase
153
- * @property {'not-allowed'} type
154
- * @property {RouteMethod} method
155
- * @property {URL} url
156
- * @property {Array<RouteMethod>} methods
191
+ * @typedef {Object} PartialBytes
192
+ * @property {SendBody} obj
193
+ * @property {ContentRangeDirective} range
157
194
  */
158
- /** @typedef {RouteBase & RouteNotAllowedBase} RouteNotAllowed */
195
+
196
+
197
+
198
+
199
+
159
200
 
160
201
  /**
161
202
  * @typedef {Object} RouteTraceBase
@@ -164,25 +205,17 @@ export const KNOWN_METHODS = [
164
205
  * @property {URL} url
165
206
  * @property {IncomingHttpHeaders} headers
166
207
  * @property {number} maxForwards
167
- */
208
+ */
168
209
  /** @typedef {RouteBase & RouteTraceBase} RouteTrace */
169
210
 
170
211
  /**
171
- * @typedef {Object} RouteConditions
172
- * @property {Array<EtagItem>} match
173
- * @property {Array<EtagItem>} noneMatch
174
- * @property {IMFFixDate|undefined} modifiedSince
175
- * @property {IMFFixDate|undefined} unmodifiedSince
176
- * @property {IMFFixDate|EtagItem|undefined} [range]
177
- */
178
-
179
- /**
180
- * @typedef {Object} SecFetchMetadata
181
- * @property {SecFetchSite|undefined} site
182
- * @property {SecFetchMode|undefined} mode
183
- * @property {SecFetchDest|undefined} dest
212
+ * @typedef {Object} RouteCreatedBase
213
+ * @property {'created'} type
214
+ * @property {URL|string} location
215
+ * @property {EtagItem|undefined} [etag]
216
+ * @property {IMFFixDateInput|string|undefined} [lastModified]
184
217
  */
185
-
218
+ /** @typedef {RouteBase & RouteCreatedBase} RouteCreated */
186
219
 
187
220
  /**
188
221
  * @typedef {Object} RoutePreflightBase
@@ -195,78 +228,77 @@ export const KNOWN_METHODS = [
195
228
  /** @typedef {RouteBase & RoutePreflightBase} RoutePreflight */
196
229
 
197
230
  /**
198
- * @typedef {Object} RouteJSONBase
199
- * @property {'json'} type
200
- * @property {RouteRequestAccept} accept
201
- * @property {Record<any, any>} obj
231
+ * @typedef {Object} RouteNoContentBase
232
+ * @property {'no-content'} type
233
+ * @property {EtagItem|undefined} [etag]
202
234
  * @property {IMFFixDateInput|string|undefined} [lastModified]
235
+ */
236
+ /** @typedef {RouteBase & RouteNoContentBase} RouteNoContent */
237
+
238
+ /**
239
+ * @typedef {Object} RouteNotModifiedBase
240
+ * @property {'not-modified'} type
241
+ * @property {number} age
203
242
  * @property {EtagItem|undefined} [etag]
243
+ * @property {IMFFixDateInput|string|undefined} [lastModified]
204
244
  * @property {number|undefined} [age]
205
245
  * @property {CacheControlOptions|undefined} [cacheControl]
206
- * @property {Array<string>|undefined} [supportedQueryTypes]
207
246
  */
208
- /** @typedef {RouteBase & RouteJSONBase} RouteJSON */
247
+ /** @typedef {RouteBase & RouteNotModifiedBase} RouteNotModified */
209
248
 
210
249
  /**
211
- * @typedef {Object} Route404Base
212
- * @property {'404'} type
213
- * @property {string} method
214
- * @property {URL} url
215
- * @property {string} message
250
+ * @typedef {Object} RouteFoundBase
251
+ * @property {'found'} type
252
+ * @property {URL|string} location
216
253
  */
217
- /** @typedef {RouteBase & Route404Base} Route404 */
254
+ /** @typedef {RouteBase & RouteFoundBase} RouteFound */
255
+
256
+
218
257
 
219
258
  /**
220
- * @typedef {Object} RouteCreatedBase
221
- * @property {'created'} type
222
- * @property {URL|string} location
223
- * @property {EtagItem|undefined} [etag]
224
- * @property {IMFFixDateInput|string|undefined} [lastModified]
259
+ * @typedef {Object} RouteGoneBase
260
+ * @property {'gone'} type
225
261
  */
226
- /** @typedef {RouteBase & RouteCreatedBase} RouteCreated */
262
+ /** @typedef {RouteBase & RouteGoneBase} RouteGone */
227
263
 
228
264
  /**
229
- * @typedef {Object} RouteUnsupportedMediaTypeBase
230
- * @property {'unsupported-media'} type
231
- * @property {Array<string>|string} acceptableMediaTypes
232
- * @property {Array<string>|undefined} [supportedQueryTypes]
265
+ * @typedef {Object} RouteMovedPermanentlyBase
266
+ * @property {'moved-permanently'} type
267
+ * @property {URL|string} location
233
268
  */
234
- /** @typedef {RouteBase & RouteUnsupportedMediaTypeBase} RouteUnsupportedMediaType */
269
+ /** @typedef {RouteBase & RouteMovedPermanentlyBase} RouteMovedPermanently */
235
270
 
236
271
  /**
237
- * @typedef {Object} RouteNotModifiedBase
238
- * @property {'not-modified'} type
239
- * @property {number} age
240
- * @property {EtagItem|undefined} [etag]
241
- * @property {IMFFixDateInput|string|undefined} [lastModified]
242
- * @property {number|undefined} [age]
243
- * @property {CacheControlOptions|undefined} [cacheControl]
272
+ * @typedef {Object} RouteSeeOtherBase
273
+ * @property {'see-other'} type
274
+ * @property {URL|string} location
244
275
  */
245
- /** @typedef {RouteBase & RouteNotModifiedBase} RouteNotModified */
276
+ /** @typedef {RouteBase & RouteSeeOtherBase} RouteSeeOther */
246
277
 
247
278
  /**
248
- * @typedef {Object} RoutePreconditionFailedBase
249
- * @property {'precondition-failed'} type
250
- * @property {EtagItem|undefined} [etag]
251
- * @property {IMFFixDateInput|string|undefined} [lastModified]
279
+ * @typedef {Object} RouteTemporaryRedirectBase
280
+ * @property {'temporary-redirect'} type
281
+ * @property {URL|string} location
252
282
  */
253
- /** @typedef {RouteBase & RoutePreconditionFailedBase} RoutePreconditionFailed */
283
+ /** @typedef {RouteBase & RouteTemporaryRedirectBase} RouteTemporaryRedirect */
254
284
 
255
285
  /**
256
- * @typedef {Object} RouteNotAcceptableBase
257
- * @property {'not-acceptable'} type
258
- * @property {Array<string>|undefined} [acceptableMediaTypes]
259
- * @property {Array<string>|undefined} [acceptableEncodings]
260
- * @property {Array<string>|undefined} [acceptableLanguages]
286
+ * @typedef {Object} RoutePermanentRedirectBase
287
+ * @property {'permanent-redirect'} type
288
+ * @property {URL|string} location
261
289
  */
262
- /** @typedef {RouteBase & RouteNotAcceptableBase} RouteNotAcceptable */
290
+ /** @typedef {RouteBase & RoutePermanentRedirectBase} RoutePermanentRedirect */
291
+
292
+
293
+
263
294
 
264
295
  /**
265
- * @typedef {Object} RouteUnprocessableBase
266
- * @property {'unprocessable'} type
296
+ * @typedef {Object} Route404Base
297
+ * @property {'404'} type
298
+ * @property {string} method
267
299
  * @property {string} message
268
300
  */
269
- /** @typedef {RouteBase & RouteUnprocessableBase} RouteUnprocessable */
301
+ /** @typedef {RouteBase & Route404Base} Route404 */
270
302
 
271
303
  /**
272
304
  * @typedef {Object} RouteConflictBase
@@ -276,57 +308,41 @@ export const KNOWN_METHODS = [
276
308
  /** @typedef {RouteBase & RouteConflictBase} RouteConflict */
277
309
 
278
310
  /**
279
- * @typedef {Object} RouteNotImplementedBase
280
- * @property {'not-implemented'} type
281
- * @property {string|undefined} [message]
282
- */
283
- /** @typedef {RouteBase & RouteNotImplementedBase} RouteNotImplemented */
284
-
285
- /**
286
- * @typedef {Object} RouteUnavailableBase
287
- * @property {'unavailable'} type
288
- * @property {string|undefined} [message]
289
- * @property {number|undefined} [retryAfter]
311
+ * @typedef {Object} RouteContentTooLargeBase
312
+ * @property {'content-too-large'} type
290
313
  */
291
- /** @typedef {RouteBase & RouteUnavailableBase} RouteUnavailable */
314
+ /** @typedef {RouteBase & RouteContentTooLargeBase} RouteContentTooLarge */
292
315
 
293
316
  /**
294
- * @typedef {Object} RouteBytesBase
295
- * @property {'bytes'} type
296
- * @property {string} contentType
297
- * @property {number|undefined} [contentLength]
298
- * @property {SendBody|undefined} obj
299
- * @property {IMFFixDateInput|string|undefined} [lastModified]
300
- * @property {EtagItem|undefined} [etag]
301
- * @property {number|undefined} [age]
302
- * @property {CacheControlOptions|undefined} [cacheControl]
303
- * @property {'bytes'|'none'|undefined} [acceptRanges]
317
+ * @typedef {Object} RouteForbiddenBase
318
+ * @property {'forbidden'} type
304
319
  */
305
- /** @typedef {RouteBase & RouteBytesBase} RouteBytes */
320
+ /** @typedef {RouteBase & RouteForbiddenBase} RouteForbidden */
306
321
 
307
322
  /**
308
- * @typedef {Object} PartialBytes
309
- * @property {SendBody} obj
310
- * @property {ContentRangeDirective} range
323
+ * @typedef {Object} RouteNotAcceptableBase
324
+ * @property {'not-acceptable'} type
325
+ * @property {Array<string>|undefined} [acceptableMediaTypes]
326
+ * @property {Array<string>|undefined} [acceptableEncodings]
327
+ * @property {Array<string>|undefined} [acceptableLanguages]
311
328
  */
329
+ /** @typedef {RouteBase & RouteNotAcceptableBase} RouteNotAcceptable */
312
330
 
313
331
  /**
314
- * @template T
315
- * @typedef {[ T, ...T[] ]} NonEmptyArray
332
+ * @typedef {Object} RouteNotAllowedBase
333
+ * @property {'not-allowed'} type
334
+ * @property {RouteMethod} method
335
+ * @property {Array<RouteMethod>} methods
316
336
  */
337
+ /** @typedef {RouteBase & RouteNotAllowedBase} RouteNotAllowed */
317
338
 
318
339
  /**
319
- * @typedef {Object} RoutePartialBytesBase
320
- * @property {'partial-bytes'} type
321
- * @property {NonEmptyArray<PartialBytes>} objs
322
- * @property {string} contentType
323
- * @property {number|undefined} [contentLength]
340
+ * @typedef {Object} RoutePreconditionFailedBase
341
+ * @property {'precondition-failed'} type
324
342
  * @property {EtagItem|undefined} [etag]
325
343
  * @property {IMFFixDateInput|string|undefined} [lastModified]
326
- * @property {number|undefined} [age]
327
- * @property {CacheControlOptions|undefined} [cacheControl]
328
344
  */
329
- /** @typedef {RouteBase & RoutePartialBytesBase} RoutePartialBytes */
345
+ /** @typedef {RouteBase & RoutePreconditionFailedBase} RoutePreconditionFailed */
330
346
 
331
347
  /**
332
348
  * @typedef {Object} RouteNotSatisfiableBase
@@ -336,52 +352,40 @@ export const KNOWN_METHODS = [
336
352
  /** @typedef {RouteBase & RouteNotSatisfiableBase} RouteNotSatisfiable */
337
353
 
338
354
  /**
339
- * @typedef {Object} RouteSeeOtherBase
340
- * @property {'see-other'} type
341
- * @property {URL} location
342
- */
343
- /** @typedef {RouteBase & RouteSeeOtherBase} RouteSeeOther */
344
-
345
- /**
346
- * @typedef {Object} RouteTemporaryRedirectBase
347
- * @property {'temporary-redirect'} type
348
- * @property {URL} location
349
- */
350
- /** @typedef {RouteBase & RouteTemporaryRedirectBase} RouteTemporaryRedirect */
351
-
352
- /**
353
- * @typedef {Object} RoutePermanentRedirectBase
354
- * @property {'permanent-redirect'} type
355
- * @property {URL} location
355
+ * @typedef {Object} RouteTimeoutBase
356
+ * @property {'timeout'} type
356
357
  */
357
- /** @typedef {RouteBase & RoutePermanentRedirectBase} RoutePermanentRedirect */
358
+ /** @typedef {RouteBase & RouteTimeoutBase} RouteTimeout */
358
359
 
359
360
  /**
360
- * @typedef {Object} RouteMovedPermanentlyBase
361
- * @property {'moved-permanently'} type
362
- * @property {URL} location
361
+ * @typedef {Object} RouteTooManyRequestsBase
362
+ * @property {'too-many-requests'} type
363
+ * @property {RateLimitInfo} limit
364
+ * @property {Array<RateLimitPolicyInfo>} policies
363
365
  */
364
- /** @typedef {RouteBase & RouteMovedPermanentlyBase} RouteMovedPermanently */
366
+ /** @typedef {RouteBase & RouteTooManyRequestsBase} RouteTooManyRequests */
365
367
 
366
368
  /**
367
- * @typedef {Object} RouteNoContentBase
368
- * @property {'no-content'} type
369
- * @property {EtagItem|undefined} [etag]
370
- * @property {IMFFixDateInput|string|undefined} [lastModified]
369
+ * @typedef {Object} RouteUnauthorizedBase
370
+ * @property {'unauthorized'} type
371
+ * @property {Array<ChallengeItem>} challenge
371
372
  */
372
- /** @typedef {RouteBase & RouteNoContentBase} RouteNoContent */
373
+ /** @typedef {RouteBase & RouteUnauthorizedBase} RouteUnauthorized */
373
374
 
374
375
  /**
375
- * @typedef {Object} RouteGoneBase
376
- * @property {'gone'} type
376
+ * @typedef {Object} RouteUnprocessableBase
377
+ * @property {'unprocessable'} type
378
+ * @property {string} message
377
379
  */
378
- /** @typedef {RouteBase & RouteGoneBase} RouteGone */
380
+ /** @typedef {RouteBase & RouteUnprocessableBase} RouteUnprocessable */
379
381
 
380
382
  /**
381
- * @typedef {Object} RouteContentTooLargeBase
382
- * @property {'content-too-large'} type
383
+ * @typedef {Object} RouteUnsupportedMediaTypeBase
384
+ * @property {'unsupported-media'} type
385
+ * @property {Array<string>|string} acceptableMediaTypes
386
+ * @property {Array<string>|undefined} [supportedQueryTypes]
383
387
  */
384
- /** @typedef {RouteBase & RouteContentTooLargeBase} RouteContentTooLarge */
388
+ /** @typedef {RouteBase & RouteUnsupportedMediaTypeBase} RouteUnsupportedMediaType */
385
389
 
386
390
  /**
387
391
  * @typedef {Object} RouteInsufficientStorageBase
@@ -390,31 +394,21 @@ export const KNOWN_METHODS = [
390
394
  /** @typedef {RouteBase & RouteInsufficientStorageBase} RouteInsufficientStorage */
391
395
 
392
396
  /**
393
- * @typedef {Object} RouteTooManyRequestsBase
394
- * @property {'too-many-requests'} type
395
- * @property {RateLimitInfo} limit
396
- * @property {Array<RateLimitPolicyInfo>} policies
397
+ * @typedef {Object} RouteNotImplementedBase
398
+ * @property {'not-implemented'} type
399
+ * @property {string|undefined} [message]
397
400
  */
398
- /** @typedef {RouteBase & RouteTooManyRequestsBase} RouteTooManyRequests */
401
+ /** @typedef {RouteBase & RouteNotImplementedBase} RouteNotImplemented */
399
402
 
400
403
  /**
401
- * @typedef {Object} RouteUnauthorizedBase
402
- * @property {'unauthorized'} type
403
- * @property {Array<ChallengeItem>} challenge
404
+ * @typedef {Object} RouteUnavailableBase
405
+ * @property {'unavailable'} type
406
+ * @property {string|undefined} [message]
407
+ * @property {number|undefined} [retryAfter]
404
408
  */
405
- /** @typedef {RouteBase & RouteUnauthorizedBase} RouteUnauthorized */
409
+ /** @typedef {RouteBase & RouteUnavailableBase} RouteUnavailable */
406
410
 
407
- /**
408
- * @typedef {Object} RouteForbiddenBase
409
- * @property {'forbidden'} type
410
- */
411
- /** @typedef {RouteBase & RouteForbiddenBase} RouteForbidden */
412
411
 
413
- /**
414
- * @typedef {Object} RouteTimeoutBase
415
- * @property {'timeout'} type
416
- */
417
- /** @typedef {RouteBase & RouteTimeoutBase} RouteTimeout */
418
412
 
419
413
  /**
420
414
  * @typedef {Object} RouteSSEBase
@@ -425,38 +419,92 @@ export const KNOWN_METHODS = [
425
419
  */
426
420
  /** @typedef {RouteBase & RouteSSEBase} RouteSSE */
427
421
 
422
+ /**
423
+ * @typedef {Object} RouteBytesBase
424
+ * @property {'bytes'} type
425
+ * @property {string} contentType
426
+ * @property {SendBody|undefined} obj
427
+ * @property {number|undefined} [contentLength]
428
+ * @property {string | undefined} [encoding]
429
+ * @property {IMFFixDateInput|string|undefined} [lastModified]
430
+ * @property {EtagItem|undefined} [etag]
431
+ * @property {number|undefined} [age]
432
+ * @property {CacheControlOptions|undefined} [cacheControl]
433
+ * @property {'bytes'|'none'|undefined} [acceptRanges]
434
+ */
435
+ /** @typedef {RouteBase & RouteBytesBase} RouteBytes */
436
+
437
+ /**
438
+ * @typedef {Object} RoutePartialBytesBase
439
+ * @property {'partial-bytes'} type
440
+ * @property {NonEmptyArray<PartialBytes>} objs
441
+ * @property {string} contentType
442
+ * @property {number|undefined} [contentLength]
443
+ * @property {EtagItem|undefined} [etag]
444
+ * @property {IMFFixDateInput|string|undefined} [lastModified]
445
+ * @property {number|undefined} [age]
446
+ * @property {CacheControlOptions|undefined} [cacheControl]
447
+ */
448
+ /** @typedef {RouteBase & RoutePartialBytesBase} RoutePartialBytes */
449
+
450
+ /**
451
+ * @typedef {Object} RouteJSONBase
452
+ * @property {'json'} type
453
+ * @property {Record<any, any>} obj
454
+ * @property {string | undefined} [encoding]
455
+ * @property {IMFFixDateInput|string|undefined} [lastModified]
456
+ * @property {EtagItem|undefined} [etag]
457
+ * @property {number|undefined} [age]
458
+ * @property {CacheControlOptions|undefined} [cacheControl]
459
+ * @property {Array<string>|undefined} [supportedQueryTypes]
460
+ */
461
+ /** @typedef {RouteBase & RouteJSONBase} RouteJSON */
462
+
463
+ /**
464
+ * @typedef {Object} RouteErrorBase
465
+ * @property {'error'} type
466
+ * @property {string} cause
467
+ * @property {Error|undefined} [error]
468
+ */
469
+ /** @typedef {RouteBase & RouteErrorBase } RouteError */
470
+
471
+
428
472
  /** @typedef {
429
- RouteError |
430
- RouteNotAllowed |
431
- RoutePreflight |
432
- RouteBytes |
433
- RouteJSON |
434
- Route404 |
435
- RouteSSE |
436
473
  RouteTrace |
437
474
  RouteCreated |
438
- RouteUnsupportedMediaType |
475
+ RoutePreflight |
476
+ RouteNoContent |
439
477
  RouteNotModified |
440
- RoutePreconditionFailed |
441
- RouteUnprocessable |
442
- RouteNotAcceptable |
443
- RouteConflict |
444
- RouteNotImplemented |
445
- RouteUnavailable |
446
- RoutePartialBytes |
447
- RouteNotSatisfiable |
478
+ RouteFound |
479
+
480
+ RouteGone |
481
+ RouteMovedPermanently |
448
482
  RouteSeeOther |
449
483
  RouteTemporaryRedirect |
450
484
  RoutePermanentRedirect |
451
- RouteMovedPermanently |
452
- RouteNoContent |
453
- RouteGone |
485
+
486
+ Route404 |
487
+ RouteConflict |
454
488
  RouteContentTooLarge |
455
- RouteInsufficientStorage |
489
+ RouteForbidden |
490
+ RouteNotAcceptable |
491
+ RouteNotAllowed |
492
+ RoutePreconditionFailed |
493
+ RouteNotSatisfiable |
494
+ RouteTimeout |
456
495
  RouteTooManyRequests |
457
496
  RouteUnauthorized |
458
- RouteForbidden |
459
- RouteTimeout
497
+ RouteUnprocessable |
498
+ RouteUnsupportedMediaType |
499
+ RouteInsufficientStorage |
500
+ RouteNotImplemented |
501
+ RouteUnavailable |
502
+
503
+ RouteSSE |
504
+ RouteBytes |
505
+ RoutePartialBytes |
506
+ RouteJSON |
507
+ RouteError
460
508
  } RouteAction */
461
509
 
462
510
  /** @typedef {Record<string, string|undefined>} RouteMatches */
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
 
@@ -162,7 +162,6 @@ export function preamble(preState, headers, servername) {
162
162
  //
163
163
  const allowedOrigin = (ALLOWED_ORIGINS.includes('*') || ((origin !== undefined) && URL.canParse(origin) && ALLOWED_ORIGINS.includes(origin))) ? origin : undefined
164
164
 
165
-
166
165
  /** @type {RouteRequest|RouteAction} */
167
166
  const state = {
168
167
  type: 'error',
@@ -259,21 +258,34 @@ export function preamble(preState, headers, servername) {
259
258
  //
260
259
  // content negotiation
261
260
  //
262
- const contentType = ContentType.parse(fullContentType)
263
- const acceptedEncoding = AcceptEncoding.select(fullAcceptEncoding, DEFAULT_SUPPORTED_ENCODINGS)
264
- const accept = Accept.select(fullAccept, DEFAULT_SUPPORTED_MIME_TYPES)
265
- 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} */
266
266
  const acceptObject = {
267
- type: accept,
268
- encoding: acceptedEncoding,
269
- 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
+ }
270
282
  }
271
283
 
272
284
  //
273
285
  // Trace
274
286
  //
275
287
  if(method === HTTP2_METHOD_TRACE) {
276
- if(!ALLOW_TRACE) { return { ...state, type: 'not-allowed', method, methods: [], url: requestUrl }}
288
+ if(!ALLOW_TRACE) { return { ...state, type: 'not-allowed', method, methods: [] }}
277
289
  const maxForwardsValue = maxForwards === undefined ? 0 : Number.parseInt(maxForwards)
278
290
  const preambleEnd = performance.now()
279
291
  state.meta.performance.push({ name: 'preamble-trace', duration: preambleEnd - preambleStart })
@@ -284,6 +296,7 @@ export function preamble(preState, headers, servername) {
284
296
  //
285
297
  // setup future body
286
298
  //
299
+ const contentType = ContentType.parse(fullContentType)
287
300
  const contentLength = fullContentLength === undefined ? undefined : Number.parseInt(fullContentLength, 10)
288
301
  const body = requestBody(stream, {
289
302
  byteLimit: BODY_BYTE_LENGTH,