@fluojs/http 2.0.1 → 3.0.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 (125) hide show
  1. package/README.ko.md +417 -16
  2. package/README.md +424 -16
  3. package/dist/access-log-observer.d.ts +77 -0
  4. package/dist/access-log-observer.d.ts.map +1 -0
  5. package/dist/access-log-observer.js +171 -0
  6. package/dist/adapter.d.ts +9 -0
  7. package/dist/adapter.d.ts.map +1 -1
  8. package/dist/adapter.js +15 -1
  9. package/dist/adapters/binding.d.ts.map +1 -1
  10. package/dist/adapters/binding.js +18 -16
  11. package/dist/adapters/dto-binding-plan.d.ts +2 -2
  12. package/dist/adapters/dto-binding-plan.d.ts.map +1 -1
  13. package/dist/adapters/dto-binding-plan.js +13 -4
  14. package/dist/adapters/dto-validation-adapter.d.ts +2 -1
  15. package/dist/adapters/dto-validation-adapter.d.ts.map +1 -1
  16. package/dist/adapters/dto-validation-adapter.js +2 -2
  17. package/dist/byte-range-response.d.ts +71 -0
  18. package/dist/byte-range-response.d.ts.map +1 -0
  19. package/dist/byte-range-response.js +347 -0
  20. package/dist/client-identity.d.ts +8 -4
  21. package/dist/client-identity.d.ts.map +1 -1
  22. package/dist/client-identity.js +12 -93
  23. package/dist/compiled-route-identity.d.ts +17 -0
  24. package/dist/compiled-route-identity.d.ts.map +1 -0
  25. package/dist/compiled-route-identity.js +29 -0
  26. package/dist/connection.d.ts +67 -0
  27. package/dist/connection.d.ts.map +1 -0
  28. package/dist/connection.js +480 -0
  29. package/dist/context/request-context-node-store.d.ts +6 -0
  30. package/dist/context/request-context-node-store.d.ts.map +1 -1
  31. package/dist/context/request-context-node-store.js +14 -0
  32. package/dist/context/request-context.d.ts +5 -5
  33. package/dist/context/request-context.d.ts.map +1 -1
  34. package/dist/context/request-context.js +8 -83
  35. package/dist/context/request-id.d.ts +10 -0
  36. package/dist/context/request-id.d.ts.map +1 -0
  37. package/dist/context/request-id.js +27 -0
  38. package/dist/context/sse.d.ts +13 -0
  39. package/dist/context/sse.d.ts.map +1 -1
  40. package/dist/context/sse.js +36 -6
  41. package/dist/cookie-helpers.d.ts +47 -0
  42. package/dist/cookie-helpers.d.ts.map +1 -0
  43. package/dist/cookie-helpers.js +166 -0
  44. package/dist/decorators.d.ts +52 -19
  45. package/dist/decorators.d.ts.map +1 -1
  46. package/dist/decorators.js +57 -10
  47. package/dist/dispatch/conditional-request-policy.d.ts +67 -0
  48. package/dist/dispatch/conditional-request-policy.d.ts.map +1 -0
  49. package/dist/dispatch/conditional-request-policy.js +297 -0
  50. package/dist/dispatch/dispatch-content-negotiation.d.ts +8 -0
  51. package/dist/dispatch/dispatch-content-negotiation.d.ts.map +1 -1
  52. package/dist/dispatch/dispatch-content-negotiation.js +89 -42
  53. package/dist/dispatch/dispatch-error-negotiation.d.ts +26 -0
  54. package/dist/dispatch/dispatch-error-negotiation.d.ts.map +1 -0
  55. package/dist/dispatch/dispatch-error-negotiation.js +110 -0
  56. package/dist/dispatch/dispatch-error-policy.d.ts +1 -10
  57. package/dist/dispatch/dispatch-error-policy.d.ts.map +1 -1
  58. package/dist/dispatch/dispatch-error-policy.js +1 -33
  59. package/dist/dispatch/dispatch-error-representation.d.ts +18 -0
  60. package/dist/dispatch/dispatch-error-representation.d.ts.map +1 -0
  61. package/dist/dispatch/dispatch-error-representation.js +125 -0
  62. package/dist/dispatch/dispatch-response-metadata.d.ts +10 -0
  63. package/dist/dispatch/dispatch-response-metadata.d.ts.map +1 -0
  64. package/dist/dispatch/dispatch-response-metadata.js +12 -0
  65. package/dist/dispatch/dispatch-response-policy.d.ts +30 -3
  66. package/dist/dispatch/dispatch-response-policy.d.ts.map +1 -1
  67. package/dist/dispatch/dispatch-response-policy.js +104 -15
  68. package/dist/dispatch/dispatcher.d.ts +5 -1
  69. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  70. package/dist/dispatch/dispatcher.js +140 -100
  71. package/dist/dispatch/fast-path/debug-visibility.d.ts.map +1 -1
  72. package/dist/dispatch/fast-path/debug-visibility.js +6 -3
  73. package/dist/dispatch/fast-path/dispatcher-state.d.ts +10 -0
  74. package/dist/dispatch/fast-path/dispatcher-state.d.ts.map +1 -0
  75. package/dist/dispatch/fast-path/dispatcher-state.js +52 -0
  76. package/dist/dispatch/fast-path/eligibility-checker.d.ts.map +1 -1
  77. package/dist/dispatch/fast-path/eligibility-checker.js +27 -15
  78. package/dist/dispatch/fast-path/eligibility.d.ts +16 -10
  79. package/dist/dispatch/fast-path/eligibility.d.ts.map +1 -1
  80. package/dist/dispatch/fast-path/eligibility.js +10 -4
  81. package/dist/dispatch/fast-path/fast-path-executor.d.ts +1 -1
  82. package/dist/dispatch/fast-path/fast-path-executor.d.ts.map +1 -1
  83. package/dist/dispatch/fast-path/fast-path-executor.js +8 -5
  84. package/dist/dispatch/request-abort.d.ts +22 -0
  85. package/dist/dispatch/request-abort.d.ts.map +1 -0
  86. package/dist/dispatch/request-abort.js +30 -0
  87. package/dist/dispatch/response-integration.d.ts +55 -0
  88. package/dist/dispatch/response-integration.d.ts.map +1 -0
  89. package/dist/dispatch/response-integration.js +57 -0
  90. package/dist/errors.d.ts +10 -0
  91. package/dist/errors.d.ts.map +1 -1
  92. package/dist/errors.js +19 -0
  93. package/dist/guards.d.ts +1 -1
  94. package/dist/guards.d.ts.map +1 -1
  95. package/dist/header-helpers.d.ts +59 -0
  96. package/dist/header-helpers.d.ts.map +1 -0
  97. package/dist/header-helpers.js +185 -0
  98. package/dist/index.d.ts +1 -16
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +4 -15
  101. package/dist/index.portable.d.ts +25 -0
  102. package/dist/index.portable.d.ts.map +1 -0
  103. package/dist/index.portable.js +21 -0
  104. package/dist/internal.d.ts +3 -1
  105. package/dist/internal.d.ts.map +1 -1
  106. package/dist/internal.js +2 -0
  107. package/dist/mapping.d.ts.map +1 -1
  108. package/dist/mapping.js +100 -15
  109. package/dist/middleware/correlation.d.ts +9 -1
  110. package/dist/middleware/correlation.d.ts.map +1 -1
  111. package/dist/middleware/correlation.js +25 -14
  112. package/dist/middleware/cors.d.ts.map +1 -1
  113. package/dist/middleware/cors.js +5 -8
  114. package/dist/middleware/middleware.d.ts +3 -3
  115. package/dist/middleware/middleware.d.ts.map +1 -1
  116. package/dist/middleware/rate-limit.d.ts +13 -2
  117. package/dist/middleware/rate-limit.d.ts.map +1 -1
  118. package/dist/middleware/rate-limit.js +1 -0
  119. package/dist/static-assets.d.ts +77 -0
  120. package/dist/static-assets.d.ts.map +1 -0
  121. package/dist/static-assets.js +270 -0
  122. package/dist/types.d.ts +209 -16
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/types.js +69 -1
  125. package/package.json +19 -9
@@ -1,31 +1,52 @@
1
1
  import { NotAcceptableException } from '../exceptions.js';
2
+ import { readJoinedNonEmptyRequestHeaderValues } from '../header-helpers.js';
2
3
 
3
4
  /**
4
5
  * Describes the resolved content negotiation contract.
5
6
  */
6
7
 
7
8
  const NO_ACCEPTABLE_REPRESENTATION_MESSAGE = 'No acceptable response representation found.';
9
+ class ContentNegotiationNotAcceptableException extends NotAcceptableException {}
8
10
  function normalizeMediaType(value) {
9
11
  return value.split(';')[0]?.trim().toLowerCase() ?? '';
10
12
  }
11
13
  function readAcceptHeader(request) {
12
- const raw = request.headers.accept ?? request.headers.Accept;
13
- const value = Array.isArray(raw) ? raw.join(',') : raw;
14
- const normalized = value?.trim();
15
- return normalized ? normalized : undefined;
14
+ return readJoinedNonEmptyRequestHeaderValues(request, 'accept');
16
15
  }
17
16
  function parseQuality(value) {
18
- if (!value) {
19
- return 1;
17
+ if (!/^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/.test(value)) {
18
+ return undefined;
20
19
  }
21
- const parsed = Number(value);
22
- if (!Number.isFinite(parsed) || parsed < 0) {
23
- return 0;
20
+ return Number(value);
21
+ }
22
+
23
+ /**
24
+ * Determines whether an error originated from response content negotiation.
25
+ *
26
+ * @param error Candidate error emitted while selecting a response formatter.
27
+ * @returns Whether the error requires `Vary: Accept` on its representation.
28
+ */
29
+ export function isContentNegotiationNotAcceptableException(error) {
30
+ return error instanceof ContentNegotiationNotAcceptableException;
31
+ }
32
+ function isValidMediaRange(mediaRange) {
33
+ const [type, subtype, extra] = mediaRange.split('/');
34
+ if (!type || !subtype || extra !== undefined) {
35
+ return false;
24
36
  }
25
- if (parsed > 1) {
26
- return 1;
37
+ if (type === '*') {
38
+ return subtype === '*';
39
+ }
40
+ if (type.includes('*')) {
41
+ return false;
27
42
  }
28
- return parsed;
43
+ if (subtype === '*') {
44
+ return true;
45
+ }
46
+ if (subtype.startsWith('*+')) {
47
+ return subtype.length > 2 && !subtype.slice(2).includes('*');
48
+ }
49
+ return !subtype.includes('*');
29
50
  }
30
51
  function getMediaRangeSpecificity(mediaRange) {
31
52
  if (mediaRange === '*/*') {
@@ -34,39 +55,46 @@ function getMediaRangeSpecificity(mediaRange) {
34
55
  if (mediaRange.endsWith('/*')) {
35
56
  return 1;
36
57
  }
37
- return 2;
58
+ return mediaRange.includes('/*+') ? 2 : 3;
38
59
  }
39
60
  function parseAcceptHeader(acceptHeader) {
40
61
  const tokens = [];
41
- for (const token of acceptHeader.split(',')) {
62
+ for (const [order, token] of acceptHeader.split(',').entries()) {
42
63
  const [rawMediaRange, ...parameterParts] = token.trim().split(';');
43
64
  const mediaRange = normalizeMediaType(rawMediaRange ?? '');
44
- if (!mediaRange || !mediaRange.includes('/')) {
65
+ if (!isValidMediaRange(mediaRange)) {
45
66
  continue;
46
67
  }
47
68
  let quality = 1;
69
+ let qualityDefined = false;
70
+ let malformed = false;
48
71
  for (const parameterPart of parameterParts) {
49
- const [name, value] = parameterPart.trim().split('=');
72
+ const [name, ...rawValues] = parameterPart.trim().split('=');
50
73
  if (name?.toLowerCase() === 'q') {
51
- quality = parseQuality(value?.trim());
52
- break;
74
+ if (qualityDefined || rawValues.length !== 1) {
75
+ malformed = true;
76
+ break;
77
+ }
78
+ const parsedQuality = parseQuality(rawValues[0]?.trim() ?? '');
79
+ if (parsedQuality === undefined) {
80
+ malformed = true;
81
+ break;
82
+ }
83
+ quality = parsedQuality;
84
+ qualityDefined = true;
53
85
  }
54
86
  }
55
- if (quality <= 0) {
87
+ if (malformed) {
56
88
  continue;
57
89
  }
58
90
  tokens.push({
59
91
  mediaRange,
92
+ order,
60
93
  quality,
61
94
  specificity: getMediaRangeSpecificity(mediaRange)
62
95
  });
63
96
  }
64
- return tokens.sort((left, right) => {
65
- if (right.quality !== left.quality) {
66
- return right.quality - left.quality;
67
- }
68
- return right.specificity - left.specificity;
69
- });
97
+ return tokens;
70
98
  }
71
99
  function matchesMediaRange(mediaRange, mediaType) {
72
100
  if (mediaRange === '*/*') {
@@ -80,7 +108,25 @@ function matchesMediaRange(mediaRange, mediaType) {
80
108
  if (rangeType !== '*' && rangeType !== mediaTypeType) {
81
109
  return false;
82
110
  }
83
- return rangeSubtype === '*' || rangeSubtype === mediaTypeSubtype;
111
+ if (rangeSubtype === '*') {
112
+ return true;
113
+ }
114
+ if (rangeSubtype.startsWith('*+')) {
115
+ return mediaTypeSubtype.endsWith(rangeSubtype.slice(1));
116
+ }
117
+ return rangeSubtype === mediaTypeSubtype;
118
+ }
119
+ function selectMostSpecificAcceptToken(mediaType, acceptTokens) {
120
+ let selected;
121
+ for (const token of acceptTokens) {
122
+ if (!matchesMediaRange(token.mediaRange, mediaType)) {
123
+ continue;
124
+ }
125
+ if (!selected || token.specificity > selected.specificity || token.specificity === selected.specificity && token.order < selected.order) {
126
+ selected = token;
127
+ }
128
+ }
129
+ return selected;
84
130
  }
85
131
 
86
132
  /**
@@ -155,7 +201,7 @@ export function selectResponseFormatter(handler, request, contentNegotiation) {
155
201
  normalizedMediaTypes: allowedNormalizedMediaTypes
156
202
  } = resolveAllowedFormatters(handler, contentNegotiation);
157
203
  if (!allowedFormatters.length) {
158
- throw new NotAcceptableException(NO_ACCEPTABLE_REPRESENTATION_MESSAGE);
204
+ throw new ContentNegotiationNotAcceptableException(NO_ACCEPTABLE_REPRESENTATION_MESSAGE);
159
205
  }
160
206
  const defaultFormatter = resolveDefaultFormatter(allowedFormatters, allowedNormalizedMediaTypes, contentNegotiation);
161
207
  const acceptHeader = readAcceptHeader(request);
@@ -164,22 +210,23 @@ export function selectResponseFormatter(handler, request, contentNegotiation) {
164
210
  }
165
211
  const acceptTokens = parseAcceptHeader(acceptHeader);
166
212
  if (!acceptTokens.length) {
167
- throw new NotAcceptableException(NO_ACCEPTABLE_REPRESENTATION_MESSAGE);
168
- }
169
- for (const token of acceptTokens) {
170
- if (token.mediaRange === '*/*') {
171
- return defaultFormatter;
172
- }
173
- let matchedFormatter;
174
- for (let i = 0; i < allowedFormatters.length; i++) {
175
- if (matchesMediaRange(token.mediaRange, allowedNormalizedMediaTypes[i])) {
176
- matchedFormatter = allowedFormatters[i];
177
- break;
178
- }
213
+ throw new ContentNegotiationNotAcceptableException(NO_ACCEPTABLE_REPRESENTATION_MESSAGE);
214
+ }
215
+ let selectedFormatter;
216
+ let selectedToken;
217
+ for (let i = 0; i < allowedFormatters.length; i++) {
218
+ const formatter = allowedFormatters[i];
219
+ const token = selectMostSpecificAcceptToken(allowedNormalizedMediaTypes[i], acceptTokens);
220
+ if (!token || token.quality === 0 || selectedToken && (token.quality < selectedToken.quality || token.quality === selectedToken.quality && token.specificity < selectedToken.specificity)) {
221
+ continue;
179
222
  }
180
- if (matchedFormatter) {
181
- return matchedFormatter;
223
+ if (!selectedToken || token.quality > selectedToken.quality || token.specificity > selectedToken.specificity || formatter === defaultFormatter) {
224
+ selectedFormatter = formatter;
225
+ selectedToken = token;
182
226
  }
183
227
  }
184
- throw new NotAcceptableException(NO_ACCEPTABLE_REPRESENTATION_MESSAGE);
228
+ if (selectedFormatter) {
229
+ return selectedFormatter;
230
+ }
231
+ throw new ContentNegotiationNotAcceptableException(NO_ACCEPTABLE_REPRESENTATION_MESSAGE);
185
232
  }
@@ -0,0 +1,26 @@
1
+ import type { RequestContext } from '../types.js';
2
+ /** Error representation selected by HTTP content negotiation. */
3
+ export type ErrorRepresentationKind = 'html' | 'json';
4
+ /**
5
+ * Reads the normalized Accept header from one request context.
6
+ *
7
+ * @param context Active HTTP request context.
8
+ * @returns The normalized header value when present.
9
+ */
10
+ export declare function readAcceptHeader(context: RequestContext): string | undefined;
11
+ /**
12
+ * Determines whether an Accept header permits the HTML representation.
13
+ *
14
+ * @param acceptHeader Normalized Accept header value.
15
+ * @returns Whether HTML has a positive matching quality.
16
+ */
17
+ export declare function canNegotiateHtml(acceptHeader: string | undefined): boolean;
18
+ /**
19
+ * Selects the deterministic error representation for one Accept header.
20
+ *
21
+ * @param acceptHeader Normalized Accept header value.
22
+ * @param htmlAvailable Whether the application HTML provider is available.
23
+ * @returns The selected representation, or `undefined` when no offer is acceptable.
24
+ */
25
+ export declare function selectErrorRepresentation(acceptHeader: string | undefined, htmlAvailable: boolean): ErrorRepresentationKind | undefined;
26
+ //# sourceMappingURL=dispatch-error-negotiation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-error-negotiation.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-error-negotiation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMlD,iEAAiE;AACjE,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,MAAM,CAAC;AA2EtD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAO1E;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,aAAa,EAAE,OAAO,GACrB,uBAAuB,GAAG,SAAS,CAoCrC"}
@@ -0,0 +1,110 @@
1
+ import { readFirstNonEmptyRequestHeaderValue } from '../header-helpers.js';
2
+ const HTML_MEDIA_TYPE = 'text/html';
3
+ const JSON_MEDIA_TYPE = 'application/json';
4
+
5
+ /** Error representation selected by HTTP content negotiation. */
6
+
7
+ function parseQuality(value) {
8
+ if (value === undefined) {
9
+ return 1;
10
+ }
11
+ const parsed = Number(value);
12
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? parsed : 0;
13
+ }
14
+ function mediaRangeSpecificity(mediaRange) {
15
+ if (mediaRange === '*/*') {
16
+ return 0;
17
+ }
18
+ return mediaRange.endsWith('/*') ? 1 : 2;
19
+ }
20
+ function parseAcceptHeader(value) {
21
+ const ranges = [];
22
+ for (const [order, token] of value.split(',').entries()) {
23
+ const [rawMediaRange, ...parameters] = token.trim().split(';');
24
+ const mediaRange = rawMediaRange?.trim().toLowerCase() ?? '';
25
+ if (!mediaRange.includes('/')) {
26
+ continue;
27
+ }
28
+ const qualityParameter = parameters.map(parameter => parameter.trim().split('=')).find(([name]) => name?.toLowerCase() === 'q');
29
+ ranges.push({
30
+ mediaRange,
31
+ order,
32
+ quality: parseQuality(qualityParameter?.[1]?.trim()),
33
+ specificity: mediaRangeSpecificity(mediaRange)
34
+ });
35
+ }
36
+ return ranges;
37
+ }
38
+ function mediaRangeMatches(mediaRange, mediaType) {
39
+ const [rangeType, rangeSubtype] = mediaRange.split('/');
40
+ const [type, subtype] = mediaType.split('/');
41
+ return rangeType !== undefined && rangeSubtype !== undefined && type !== undefined && subtype !== undefined && (rangeType === '*' || rangeType === type) && (rangeSubtype === '*' || rangeSubtype === subtype);
42
+ }
43
+ function bestRangeForMediaType(ranges, mediaType) {
44
+ return ranges.filter(range => mediaRangeMatches(range.mediaRange, mediaType)).sort((left, right) => right.specificity - left.specificity || left.order - right.order)[0];
45
+ }
46
+
47
+ /**
48
+ * Reads the normalized Accept header from one request context.
49
+ *
50
+ * @param context Active HTTP request context.
51
+ * @returns The normalized header value when present.
52
+ */
53
+ export function readAcceptHeader(context) {
54
+ return readFirstNonEmptyRequestHeaderValue(context.request, 'accept');
55
+ }
56
+
57
+ /**
58
+ * Determines whether an Accept header permits the HTML representation.
59
+ *
60
+ * @param acceptHeader Normalized Accept header value.
61
+ * @returns Whether HTML has a positive matching quality.
62
+ */
63
+ export function canNegotiateHtml(acceptHeader) {
64
+ if (acceptHeader === undefined) {
65
+ return false;
66
+ }
67
+ const range = bestRangeForMediaType(parseAcceptHeader(acceptHeader), HTML_MEDIA_TYPE);
68
+ return range !== undefined && range.quality > 0;
69
+ }
70
+
71
+ /**
72
+ * Selects the deterministic error representation for one Accept header.
73
+ *
74
+ * @param acceptHeader Normalized Accept header value.
75
+ * @param htmlAvailable Whether the application HTML provider is available.
76
+ * @returns The selected representation, or `undefined` when no offer is acceptable.
77
+ */
78
+ export function selectErrorRepresentation(acceptHeader, htmlAvailable) {
79
+ if (acceptHeader === undefined) {
80
+ return 'json';
81
+ }
82
+ const ranges = parseAcceptHeader(acceptHeader);
83
+ const offers = htmlAvailable ? [{
84
+ kind: 'json',
85
+ mediaType: JSON_MEDIA_TYPE,
86
+ priority: 0
87
+ }, {
88
+ kind: 'html',
89
+ mediaType: HTML_MEDIA_TYPE,
90
+ priority: 1
91
+ }] : [{
92
+ kind: 'json',
93
+ mediaType: JSON_MEDIA_TYPE,
94
+ priority: 0
95
+ }];
96
+ const candidates = [];
97
+ for (const offer of offers) {
98
+ const range = bestRangeForMediaType(ranges, offer.mediaType);
99
+ if (range !== undefined && range.quality > 0) {
100
+ candidates.push({
101
+ kind: offer.kind,
102
+ priority: offer.priority,
103
+ quality: range.quality,
104
+ specificity: range.specificity
105
+ });
106
+ }
107
+ }
108
+ candidates.sort((left, right) => right.quality - left.quality || right.specificity - left.specificity || left.priority - right.priority);
109
+ return candidates[0]?.kind;
110
+ }
@@ -1,11 +1,2 @@
1
- import type { FrameworkResponse } from '../types.js';
2
- /**
3
- * Write error response.
4
- *
5
- * @param error The error.
6
- * @param response The response.
7
- * @param requestId The request id.
8
- * @returns The write error response result.
9
- */
10
- export declare function writeErrorResponse(error: unknown, response: FrameworkResponse, requestId?: string): Promise<void>;
1
+ export { writeErrorResponse } from './dispatch-error-representation.js';
11
2
  //# sourceMappingURL=dispatch-error-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch-error-policy.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-error-policy.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAiBrD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQvH"}
1
+ {"version":3,"file":"dispatch-error-policy.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-error-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC"}
@@ -1,33 +1 @@
1
- import { HandlerNotFoundError } from '../errors.js';
2
- import { HttpException, InternalServerErrorException, NotFoundException, createErrorResponse } from '../exceptions.js';
3
- function toHttpException(error) {
4
- if (error instanceof HttpException) {
5
- return error;
6
- }
7
- if (error instanceof HandlerNotFoundError) {
8
- const message = error instanceof Error ? error.message : 'Resource not found.';
9
- return new NotFoundException(message, {
10
- cause: error
11
- });
12
- }
13
- return new InternalServerErrorException('Internal server error.', {
14
- cause: error
15
- });
16
- }
17
-
18
- /**
19
- * Write error response.
20
- *
21
- * @param error The error.
22
- * @param response The response.
23
- * @param requestId The request id.
24
- * @returns The write error response result.
25
- */
26
- export async function writeErrorResponse(error, response, requestId) {
27
- if (response.committed) {
28
- return;
29
- }
30
- const httpError = toHttpException(error);
31
- response.setStatus(httpError.status);
32
- await response.send(createErrorResponse(httpError, requestId));
33
- }
1
+ export { writeErrorResponse } from './dispatch-error-representation.js';
@@ -0,0 +1,18 @@
1
+ import type { DispatcherLogger, HandlerDescriptor, HttpErrorRepresentationOptions, RequestContext } from '../types.js';
2
+ type WriteErrorResponseOptions = {
3
+ readonly handler?: HandlerDescriptor;
4
+ readonly logger?: DispatcherLogger;
5
+ readonly representation?: HttpErrorRepresentationOptions;
6
+ readonly varyAccept?: boolean;
7
+ };
8
+ /**
9
+ * Writes one HTTP-owned error representation when the response remains writable.
10
+ *
11
+ * @param error Failure classified by the HTTP dispatch pipeline.
12
+ * @param requestContext Active request and response context.
13
+ * @param options Matched-handler, logger, and optional HTML provider settings.
14
+ * @returns A promise that settles after the selected response is written or skipped.
15
+ */
16
+ export declare function writeErrorResponse(error: unknown, requestContext: RequestContext, options?: WriteErrorResponseOptions): Promise<void>;
17
+ export {};
18
+ //# sourceMappingURL=dispatch-error-representation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-error-representation.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-error-representation.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,gBAAgB,EAEhB,iBAAiB,EAGjB,8BAA8B,EAC9B,cAAc,EACf,MAAM,aAAa,CAAC;AAcrB,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,8BAA8B,CAAC;IACzD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAsEF;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,OAAO,EACd,cAAc,EAAE,cAAc,EAC9B,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,IAAI,CAAC,CAoEf"}
@@ -0,0 +1,125 @@
1
+ import { HandlerNotFoundError } from '../errors.js';
2
+ import { createErrorResponse, HttpException, InternalServerErrorException, NotAcceptableException, NotFoundException } from '../exceptions.js';
3
+ import { canNegotiateHtml, readAcceptHeader, selectErrorRepresentation } from './dispatch-error-negotiation.js';
4
+ import { appendVaryHeader } from '../header-helpers.js';
5
+ import { isRequestAborted } from './request-abort.js';
6
+ const HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
7
+ const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
8
+ const NOT_ACCEPTABLE_MESSAGE = 'No acceptable response representation found.';
9
+ const PROVIDER_FAILURE_MESSAGE = 'HTML error representation provider threw before response commit; falling back to canonical JSON.';
10
+ function toHttpException(error) {
11
+ if (error instanceof HttpException) {
12
+ return error;
13
+ }
14
+ if (error instanceof HandlerNotFoundError) {
15
+ const message = error instanceof Error ? error.message : 'Resource not found.';
16
+ return new NotFoundException(message, {
17
+ cause: error
18
+ });
19
+ }
20
+ return new InternalServerErrorException('Internal server error.', {
21
+ cause: error
22
+ });
23
+ }
24
+ function isHttpRepresentationEligible(error) {
25
+ return error instanceof HttpException || error instanceof HandlerNotFoundError;
26
+ }
27
+ function createRepresentationContext(error, requestContext, handler) {
28
+ return {
29
+ container: requestContext.container,
30
+ error,
31
+ ...(handler === undefined ? {} : {
32
+ handler
33
+ }),
34
+ json: createErrorResponse(error, requestContext.requestId),
35
+ request: requestContext.request,
36
+ ...(requestContext.requestId === undefined ? {} : {
37
+ requestId: requestContext.requestId
38
+ })
39
+ };
40
+ }
41
+ async function isHtmlAvailable(provider, context) {
42
+ return provider.canRender === undefined || (await provider.canRender(context));
43
+ }
44
+ function setNegotiatedHeaders(response, contentType) {
45
+ response.setHeader('Content-Type', contentType);
46
+ appendVaryHeader(response, 'Accept');
47
+ }
48
+ async function writeBody(requestContext, status, contentType, body) {
49
+ if (requestContext.response.committed || isRequestAborted(requestContext.request)) {
50
+ return;
51
+ }
52
+ requestContext.response.setStatus(status);
53
+ setNegotiatedHeaders(requestContext.response, contentType);
54
+ await requestContext.response.send(requestContext.request.method.toUpperCase() === 'HEAD' ? undefined : body);
55
+ }
56
+ async function writeCanonicalJson(error, requestContext) {
57
+ await writeBody(requestContext, error.status, JSON_CONTENT_TYPE, createErrorResponse(error, requestContext.requestId));
58
+ }
59
+
60
+ /**
61
+ * Writes one HTTP-owned error representation when the response remains writable.
62
+ *
63
+ * @param error Failure classified by the HTTP dispatch pipeline.
64
+ * @param requestContext Active request and response context.
65
+ * @param options Matched-handler, logger, and optional HTML provider settings.
66
+ * @returns A promise that settles after the selected response is written or skipped.
67
+ */
68
+ export async function writeErrorResponse(error, requestContext, options = {}) {
69
+ if (requestContext.response.committed || isRequestAborted(requestContext.request)) {
70
+ return;
71
+ }
72
+ if (options.varyAccept) {
73
+ appendVaryHeader(requestContext.response, 'Accept');
74
+ }
75
+ const httpError = toHttpException(error);
76
+ const provider = options.representation?.html;
77
+ if (provider === undefined || !isHttpRepresentationEligible(error)) {
78
+ requestContext.response.setStatus(httpError.status);
79
+ if (requestContext.request.method.toUpperCase() === 'HEAD') {
80
+ requestContext.response.setHeader('Content-Type', JSON_CONTENT_TYPE);
81
+ await requestContext.response.send(undefined);
82
+ return;
83
+ }
84
+ await requestContext.response.send(createErrorResponse(httpError, requestContext.requestId));
85
+ return;
86
+ }
87
+ const acceptHeader = readAcceptHeader(requestContext);
88
+ const representationContext = createRepresentationContext(httpError, requestContext, options.handler);
89
+ let htmlAvailable = false;
90
+ try {
91
+ htmlAvailable = canNegotiateHtml(acceptHeader) && (await isHtmlAvailable(provider, representationContext));
92
+ } catch (providerError) {
93
+ if (isRequestAborted(requestContext.request)) {
94
+ return;
95
+ }
96
+ options.logger?.error(PROVIDER_FAILURE_MESSAGE, providerError, 'HttpDispatcher');
97
+ await writeCanonicalJson(httpError, requestContext);
98
+ return;
99
+ }
100
+ const selected = selectErrorRepresentation(acceptHeader, htmlAvailable);
101
+ if (selected === undefined) {
102
+ await writeCanonicalJson(new NotAcceptableException(NOT_ACCEPTABLE_MESSAGE), requestContext);
103
+ return;
104
+ }
105
+ if (selected === 'json') {
106
+ await writeCanonicalJson(httpError, requestContext);
107
+ return;
108
+ }
109
+ if (requestContext.request.method.toUpperCase() === 'HEAD') {
110
+ await writeBody(requestContext, httpError.status, HTML_CONTENT_TYPE, undefined);
111
+ return;
112
+ }
113
+ let body;
114
+ try {
115
+ body = await provider.render(representationContext);
116
+ } catch (providerError) {
117
+ if (isRequestAborted(requestContext.request)) {
118
+ return;
119
+ }
120
+ options.logger?.error(PROVIDER_FAILURE_MESSAGE, providerError, 'HttpDispatcher');
121
+ await writeCanonicalJson(httpError, requestContext);
122
+ return;
123
+ }
124
+ await writeBody(requestContext, httpError.status, HTML_CONTENT_TYPE, body);
125
+ }
@@ -0,0 +1,10 @@
1
+ import type { FrameworkResponse, HandlerDescriptor } from '../types.js';
2
+ /**
3
+ * Applies route-declared headers without setting success response metadata.
4
+ *
5
+ * @param handler Matched route descriptor that declares response headers.
6
+ * @param response Mutable adapter-normalized response.
7
+ * @returns Nothing.
8
+ */
9
+ export declare function applyRouteHeaders(handler: HandlerDescriptor, response: FrameworkResponse): void;
10
+ //# sourceMappingURL=dispatch-response-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-response-metadata.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-response-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAI/F"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Applies route-declared headers without setting success response metadata.
3
+ *
4
+ * @param handler Matched route descriptor that declares response headers.
5
+ * @param response Mutable adapter-normalized response.
6
+ * @returns Nothing.
7
+ */
8
+ export function applyRouteHeaders(handler, response) {
9
+ for (const header of handler.route.headers ?? []) {
10
+ response.setHeader(header.name, header.value);
11
+ }
12
+ }
@@ -1,6 +1,21 @@
1
- import type { FrameworkRequest, FrameworkResponse, HandlerDescriptor, RequestContext } from '../types.js';
1
+ import type { FrameworkRequest, FrameworkResponse, HandlerDescriptor, RequestContext, ResponseFormatter, ResponseValidators } from '../types.js';
2
+ import { type ConditionalRequestOutcome } from './conditional-request-policy.js';
2
3
  import { type ResolvedContentNegotiation, resolveContentNegotiation } from './dispatch-content-negotiation.js';
3
4
  import { writeErrorResponse } from './dispatch-error-policy.js';
5
+ /** Selected representation metadata shared by success and conditional response writers. */
6
+ export interface ResolvedResponsePolicy {
7
+ readonly formatter: ResponseFormatter | undefined;
8
+ readonly variesByAccept: boolean;
9
+ }
10
+ /**
11
+ * Resolves the representation policy before conditional request evaluation.
12
+ *
13
+ * @param handler Matched route descriptor.
14
+ * @param request Adapter-normalized request.
15
+ * @param contentNegotiation Configured response formatters.
16
+ * @returns Formatter selection and representation variance metadata.
17
+ */
18
+ export declare function resolveResponsePolicy(handler: HandlerDescriptor, request: FrameworkRequest, contentNegotiation: ResolvedContentNegotiation | undefined): ResolvedResponsePolicy;
4
19
  /**
5
20
  * Write success response.
6
21
  *
@@ -8,11 +23,23 @@ import { writeErrorResponse } from './dispatch-error-policy.js';
8
23
  * @param request The request.
9
24
  * @param response The response.
10
25
  * @param value The value.
11
- * @param contentNegotiation The content negotiation.
26
+ * @param contentNegotiation The configured response formatters.
12
27
  * @param requestContext The active request context passed to custom response writers.
28
+ * @param validators Validators resolved before the route handler executes.
29
+ * @param conditionalOutcome Matched conditional outcome for formatter-managed responses.
13
30
  * @returns The write success response result.
14
31
  */
15
- export declare function writeSuccessResponse(handler: HandlerDescriptor, request: FrameworkRequest, response: FrameworkResponse, value: unknown, contentNegotiation: ResolvedContentNegotiation | undefined, requestContext: RequestContext): ReturnType<FrameworkResponse['send']> | void;
32
+ export declare function writeSuccessResponse(handler: HandlerDescriptor, request: FrameworkRequest, response: FrameworkResponse, value: unknown, contentNegotiation: ResolvedContentNegotiation | undefined, requestContext: RequestContext, validators?: ResponseValidators, conditionalOutcome?: Exclude<ConditionalRequestOutcome, 'proceed'>): Promise<void>;
33
+ /**
34
+ * Writes a bodyless conditional response through every supported adapter facade.
35
+ *
36
+ * @param response Mutable adapter-normalized response.
37
+ * @param outcome Selected non-proceed conditional request outcome.
38
+ * @param validators Current representation validators.
39
+ * @param responsePolicy Selected representation metadata.
40
+ * @returns A promise that settles after the adapter accepts the bodyless response.
41
+ */
42
+ export declare function writeConditionalResponse(response: FrameworkResponse, outcome: Exclude<ConditionalRequestOutcome, 'proceed'>, validators: ResponseValidators | undefined, responsePolicy: ResolvedResponsePolicy): Promise<void>;
16
43
  export type { ResolvedContentNegotiation };
17
44
  export { resolveContentNegotiation, writeErrorResponse };
18
45
  //# sourceMappingURL=dispatch-response-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch-response-policy.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-response-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,0BAA0B,EAC/B,yBAAyB,EAE1B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AA8GhE;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,iBAAiB,EAC3B,KAAK,EAAE,OAAO,EACd,kBAAkB,EAAE,0BAA0B,GAAG,SAAS,EAC1D,cAAc,EAAE,cAAc,GAC7B,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CA+C9C;AAED,YAAY,EAAE,0BAA0B,EAAE,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"dispatch-response-policy.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-response-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,KAAK,0BAA0B,EAC/B,yBAAyB,EAE1B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AA8BhE,2FAA2F;AAC3F,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,gBAAgB,EACzB,kBAAkB,EAAE,0BAA0B,GAAG,SAAS,GACzD,sBAAsB,CASxB;AA2GD;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,iBAAiB,EAC3B,KAAK,EAAE,OAAO,EACd,kBAAkB,EAAE,0BAA0B,GAAG,SAAS,EAC1D,cAAc,EAAE,cAAc,EAC9B,UAAU,CAAC,EAAE,kBAAkB,EAC/B,kBAAkB,CAAC,EAAE,OAAO,CAAC,yBAAyB,EAAE,SAAS,CAAC,iBA6EnE;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,OAAO,CAAC,yBAAyB,EAAE,SAAS,CAAC,EACtD,UAAU,EAAE,kBAAkB,GAAG,SAAS,EAC1C,cAAc,EAAE,sBAAsB,GACrC,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,YAAY,EAAE,0BAA0B,EAAE,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,CAAC"}