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