@irvinebroque/http-rfc-utils 0.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/LICENSE +21 -0
- package/README.md +222 -0
- package/dist/auth.d.ts +139 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +991 -0
- package/dist/auth.js.map +1 -0
- package/dist/cache-status.d.ts +15 -0
- package/dist/cache-status.d.ts.map +1 -0
- package/dist/cache-status.js +152 -0
- package/dist/cache-status.js.map +1 -0
- package/dist/cache.d.ts +94 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +244 -0
- package/dist/cache.js.map +1 -0
- package/dist/client-hints.d.ts +23 -0
- package/dist/client-hints.d.ts.map +1 -0
- package/dist/client-hints.js +81 -0
- package/dist/client-hints.js.map +1 -0
- package/dist/conditional.d.ts +97 -0
- package/dist/conditional.d.ts.map +1 -0
- package/dist/conditional.js +300 -0
- package/dist/conditional.js.map +1 -0
- package/dist/content-disposition.d.ts +23 -0
- package/dist/content-disposition.d.ts.map +1 -0
- package/dist/content-disposition.js +122 -0
- package/dist/content-disposition.js.map +1 -0
- package/dist/cookie.d.ts +43 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cookie.js +472 -0
- package/dist/cookie.js.map +1 -0
- package/dist/cors.d.ts +53 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +170 -0
- package/dist/cors.js.map +1 -0
- package/dist/datetime.d.ts +53 -0
- package/dist/datetime.d.ts.map +1 -0
- package/dist/datetime.js +205 -0
- package/dist/datetime.js.map +1 -0
- package/dist/digest.d.ts +220 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +355 -0
- package/dist/digest.js.map +1 -0
- package/dist/encoding.d.ts +14 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +86 -0
- package/dist/encoding.js.map +1 -0
- package/dist/etag.d.ts +55 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/etag.js +182 -0
- package/dist/etag.js.map +1 -0
- package/dist/ext-value.d.ts +40 -0
- package/dist/ext-value.d.ts.map +1 -0
- package/dist/ext-value.js +119 -0
- package/dist/ext-value.js.map +1 -0
- package/dist/forwarded.d.ts +14 -0
- package/dist/forwarded.d.ts.map +1 -0
- package/dist/forwarded.js +93 -0
- package/dist/forwarded.js.map +1 -0
- package/dist/header-utils.d.ts +71 -0
- package/dist/header-utils.d.ts.map +1 -0
- package/dist/header-utils.js +143 -0
- package/dist/header-utils.js.map +1 -0
- package/dist/headers.d.ts +71 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/headers.js +134 -0
- package/dist/headers.js.map +1 -0
- package/dist/hsts.d.ts +15 -0
- package/dist/hsts.d.ts.map +1 -0
- package/dist/hsts.js +106 -0
- package/dist/hsts.js.map +1 -0
- package/dist/http-signatures.d.ts +202 -0
- package/dist/http-signatures.d.ts.map +1 -0
- package/dist/http-signatures.js +720 -0
- package/dist/http-signatures.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/json-pointer.d.ts +97 -0
- package/dist/json-pointer.d.ts.map +1 -0
- package/dist/json-pointer.js +278 -0
- package/dist/json-pointer.js.map +1 -0
- package/dist/jsonpath.d.ts +98 -0
- package/dist/jsonpath.d.ts.map +1 -0
- package/dist/jsonpath.js +1470 -0
- package/dist/jsonpath.js.map +1 -0
- package/dist/language.d.ts +14 -0
- package/dist/language.d.ts.map +1 -0
- package/dist/language.js +95 -0
- package/dist/language.js.map +1 -0
- package/dist/link.d.ts +102 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +437 -0
- package/dist/link.js.map +1 -0
- package/dist/linkset.d.ts +111 -0
- package/dist/linkset.d.ts.map +1 -0
- package/dist/linkset.js +501 -0
- package/dist/linkset.js.map +1 -0
- package/dist/negotiate.d.ts +71 -0
- package/dist/negotiate.d.ts.map +1 -0
- package/dist/negotiate.js +357 -0
- package/dist/negotiate.js.map +1 -0
- package/dist/pagination.d.ts +80 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +188 -0
- package/dist/pagination.js.map +1 -0
- package/dist/prefer.d.ts +18 -0
- package/dist/prefer.d.ts.map +1 -0
- package/dist/prefer.js +93 -0
- package/dist/prefer.js.map +1 -0
- package/dist/problem.d.ts +54 -0
- package/dist/problem.d.ts.map +1 -0
- package/dist/problem.js +104 -0
- package/dist/problem.js.map +1 -0
- package/dist/proxy-status.d.ts +28 -0
- package/dist/proxy-status.d.ts.map +1 -0
- package/dist/proxy-status.js +220 -0
- package/dist/proxy-status.js.map +1 -0
- package/dist/range.d.ts +28 -0
- package/dist/range.d.ts.map +1 -0
- package/dist/range.js +243 -0
- package/dist/range.js.map +1 -0
- package/dist/response.d.ts +101 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +200 -0
- package/dist/response.js.map +1 -0
- package/dist/sorting.d.ts +66 -0
- package/dist/sorting.d.ts.map +1 -0
- package/dist/sorting.js +168 -0
- package/dist/sorting.js.map +1 -0
- package/dist/structured-fields.d.ts +30 -0
- package/dist/structured-fields.d.ts.map +1 -0
- package/dist/structured-fields.js +468 -0
- package/dist/structured-fields.js.map +1 -0
- package/dist/types.d.ts +772 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/uri-template.d.ts +48 -0
- package/dist/uri-template.d.ts.map +1 -0
- package/dist/uri-template.js +483 -0
- package/dist/uri-template.js.map +1 -0
- package/dist/uri.d.ts +80 -0
- package/dist/uri.d.ts.map +1 -0
- package/dist/uri.js +423 -0
- package/dist/uri.js.map +1 -0
- package/package.json +66 -0
package/dist/range.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Range request utilities per RFC 9110 (13.2.1, 14.2).
|
|
3
|
+
*/
|
|
4
|
+
import { parseETag, compareETags } from './etag.js';
|
|
5
|
+
import { parseHTTPDate } from './datetime.js';
|
|
6
|
+
const RANGE_PREFIX = /^bytes=/i;
|
|
7
|
+
/**
|
|
8
|
+
* Parse a Range header into raw byte ranges.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: Open-ended ranges use end = Infinity.
|
|
11
|
+
* Suffix ranges use start = -N and end = -1.
|
|
12
|
+
*/
|
|
13
|
+
export function parseRange(header) {
|
|
14
|
+
if (!header || !header.trim()) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const trimmed = header.trim();
|
|
18
|
+
if (!RANGE_PREFIX.test(trimmed)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const value = trimmed.replace(RANGE_PREFIX, '');
|
|
22
|
+
if (!value) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const rawRanges = [];
|
|
26
|
+
const parts = value.split(',');
|
|
27
|
+
for (const part of parts) {
|
|
28
|
+
const rangePart = part.trim();
|
|
29
|
+
if (!rangePart) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const dashIndex = rangePart.indexOf('-');
|
|
33
|
+
if (dashIndex === -1) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const startStr = rangePart.slice(0, dashIndex).trim();
|
|
37
|
+
const endStr = rangePart.slice(dashIndex + 1).trim();
|
|
38
|
+
if (startStr === '') {
|
|
39
|
+
const suffixLength = parseInt(endStr, 10);
|
|
40
|
+
if (isNaN(suffixLength) || suffixLength <= 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
rawRanges.push({ start: -suffixLength, end: -1 });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const start = parseInt(startStr, 10);
|
|
47
|
+
if (isNaN(start) || start < 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if (endStr === '') {
|
|
51
|
+
rawRanges.push({ start, end: Number.POSITIVE_INFINITY });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const end = parseInt(endStr, 10);
|
|
55
|
+
if (isNaN(end) || end < start) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
rawRanges.push({ start, end });
|
|
59
|
+
}
|
|
60
|
+
if (rawRanges.length === 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
unit: 'bytes',
|
|
65
|
+
ranges: rawRanges,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse a Content-Range header.
|
|
70
|
+
*/
|
|
71
|
+
export function parseContentRange(header) {
|
|
72
|
+
if (!header || !header.trim()) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const trimmed = header.trim();
|
|
76
|
+
if (!trimmed.toLowerCase().startsWith('bytes')) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const parts = trimmed.split(' ');
|
|
80
|
+
if (parts.length < 2) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const rangeAndSize = parts.slice(1).join(' ').trim();
|
|
84
|
+
const [rangePart, sizePart] = rangeAndSize.split('/');
|
|
85
|
+
if (!rangePart || sizePart === undefined) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const size = sizePart.trim() === '*' ? '*' : parseInt(sizePart.trim(), 10);
|
|
89
|
+
if (size !== '*' && (isNaN(size) || size < 0)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (rangePart.trim() === '*') {
|
|
93
|
+
return {
|
|
94
|
+
unit: 'bytes',
|
|
95
|
+
size,
|
|
96
|
+
unsatisfied: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const dashIndex = rangePart.indexOf('-');
|
|
100
|
+
if (dashIndex === -1) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const start = parseInt(rangePart.slice(0, dashIndex).trim(), 10);
|
|
104
|
+
const end = parseInt(rangePart.slice(dashIndex + 1).trim(), 10);
|
|
105
|
+
if (isNaN(start) || isNaN(end) || start < 0 || end < start) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
unit: 'bytes',
|
|
110
|
+
range: { start, end },
|
|
111
|
+
size,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Format a Content-Range header value.
|
|
116
|
+
*/
|
|
117
|
+
export function formatContentRange(range, size) {
|
|
118
|
+
const total = size === '*' ? '*' : String(size);
|
|
119
|
+
return `bytes ${range.start}-${range.end}/${total}`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Build Accept-Ranges header value.
|
|
123
|
+
*/
|
|
124
|
+
export function acceptRanges(value = 'bytes') {
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
function normalizeRanges(rawRanges, size) {
|
|
128
|
+
const normalized = [];
|
|
129
|
+
for (const range of rawRanges) {
|
|
130
|
+
let start = range.start;
|
|
131
|
+
let end = range.end;
|
|
132
|
+
if (start < 0 && end === -1) {
|
|
133
|
+
const suffixLength = Math.abs(start);
|
|
134
|
+
if (suffixLength === 0) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
start = Math.max(0, size - suffixLength);
|
|
138
|
+
end = size - 1;
|
|
139
|
+
}
|
|
140
|
+
else if (end === Number.POSITIVE_INFINITY) {
|
|
141
|
+
end = size - 1;
|
|
142
|
+
}
|
|
143
|
+
if (start >= size || start < 0) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (end < start) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (end >= size) {
|
|
150
|
+
end = size - 1;
|
|
151
|
+
}
|
|
152
|
+
normalized.push({ start, end });
|
|
153
|
+
}
|
|
154
|
+
if (normalized.length === 0) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
normalized.sort((a, b) => a.start - b.start);
|
|
158
|
+
const merged = [normalized[0]];
|
|
159
|
+
for (let i = 1; i < normalized.length; i++) {
|
|
160
|
+
const current = normalized[i];
|
|
161
|
+
const last = merged[merged.length - 1];
|
|
162
|
+
if (current.start <= last.end + 1) {
|
|
163
|
+
last.end = Math.max(last.end, current.end);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
merged.push({ ...current });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return merged;
|
|
170
|
+
}
|
|
171
|
+
function ifRangeMatches(ifRange, currentETag, lastModified) {
|
|
172
|
+
if (!ifRange.trim()) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const etag = parseETag(ifRange);
|
|
176
|
+
if (etag && currentETag) {
|
|
177
|
+
const current = parseETag(currentETag);
|
|
178
|
+
if (!current) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return compareETags(etag, current, true);
|
|
182
|
+
}
|
|
183
|
+
const date = parseHTTPDate(ifRange);
|
|
184
|
+
if (date && lastModified) {
|
|
185
|
+
return lastModified.getTime() <= date.getTime();
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Evaluate a Range request and determine partial content handling.
|
|
191
|
+
*/
|
|
192
|
+
export function evaluateRange(request, size, etag, lastModified) {
|
|
193
|
+
const method = request.method.toUpperCase();
|
|
194
|
+
const rangeHeader = request.headers.get('Range');
|
|
195
|
+
if (!rangeHeader) {
|
|
196
|
+
return { type: 'none' };
|
|
197
|
+
}
|
|
198
|
+
if (method !== 'GET' && method !== 'HEAD') {
|
|
199
|
+
return { type: 'ignored' };
|
|
200
|
+
}
|
|
201
|
+
const parsed = parseRange(rangeHeader);
|
|
202
|
+
if (!parsed) {
|
|
203
|
+
return { type: 'ignored' };
|
|
204
|
+
}
|
|
205
|
+
const ifRange = request.headers.get('If-Range');
|
|
206
|
+
if (ifRange) {
|
|
207
|
+
const matches = ifRangeMatches(ifRange, etag, lastModified);
|
|
208
|
+
if (!matches) {
|
|
209
|
+
return { type: 'ignored' };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (size <= 0) {
|
|
213
|
+
return {
|
|
214
|
+
type: 'unsatisfiable',
|
|
215
|
+
headers: {
|
|
216
|
+
'Content-Range': 'bytes */0',
|
|
217
|
+
'Accept-Ranges': acceptRanges(),
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const ranges = normalizeRanges(parsed.ranges, size);
|
|
222
|
+
if (ranges.length === 0) {
|
|
223
|
+
return {
|
|
224
|
+
type: 'unsatisfiable',
|
|
225
|
+
headers: {
|
|
226
|
+
'Content-Range': `bytes */${size}`,
|
|
227
|
+
'Accept-Ranges': acceptRanges(),
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const headers = {
|
|
232
|
+
'Accept-Ranges': acceptRanges(),
|
|
233
|
+
};
|
|
234
|
+
if (ranges.length === 1) {
|
|
235
|
+
headers['Content-Range'] = formatContentRange(ranges[0], size);
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
type: 'partial',
|
|
239
|
+
ranges,
|
|
240
|
+
headers,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=range.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"range.js","sourceRoot":"","sources":["../src/range.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response builders for common HTTP patterns.
|
|
3
|
+
* RFC 9110 §8.8.2/§8.8.3, RFC 9111 §5.2.2, RFC 8288 §3, RFC 6266 §4.
|
|
4
|
+
*/
|
|
5
|
+
import type { PaginatedMeta, PaginationLinks, CacheOptions } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Create an OPTIONS response for preflight requests.
|
|
8
|
+
*
|
|
9
|
+
* @param allowedMethods - Array of allowed HTTP methods (default: ['GET', 'HEAD', 'OPTIONS'])
|
|
10
|
+
* @returns Response with 204 No Content and appropriate headers
|
|
11
|
+
*/
|
|
12
|
+
export declare function optionsResponse(allowedMethods?: string[]): Response;
|
|
13
|
+
/**
|
|
14
|
+
* Create a HEAD response with headers but no body.
|
|
15
|
+
*
|
|
16
|
+
* @param headers - Headers to include
|
|
17
|
+
* @returns Response with 200 OK and headers, empty body
|
|
18
|
+
*/
|
|
19
|
+
export declare function headResponse(headers: Record<string, string>): Response;
|
|
20
|
+
/**
|
|
21
|
+
* Create a JSON response with all standard API headers.
|
|
22
|
+
*
|
|
23
|
+
* @param data - Data to serialize as JSON
|
|
24
|
+
* @param meta - Pagination metadata (included in response body)
|
|
25
|
+
* @param links - Pagination links
|
|
26
|
+
* @param etag - ETag value (already formatted with quotes)
|
|
27
|
+
* @param lastModified - Last-Modified date
|
|
28
|
+
* @param totalCount - Total count for X-Total-Count header
|
|
29
|
+
* @param cacheOptions - Optional Cache-Control options (default: revalidate)
|
|
30
|
+
* @returns Response with JSON body and all standard headers
|
|
31
|
+
*
|
|
32
|
+
* Headers included:
|
|
33
|
+
* - Content-Type: application/json
|
|
34
|
+
* - ETag
|
|
35
|
+
* - Last-Modified
|
|
36
|
+
* - Cache-Control
|
|
37
|
+
* - Link (pagination)
|
|
38
|
+
* - X-Total-Count
|
|
39
|
+
* - All CORS headers
|
|
40
|
+
*
|
|
41
|
+
* Response body shape:
|
|
42
|
+
* { data, meta }
|
|
43
|
+
*/
|
|
44
|
+
export declare function jsonResponse<T>(data: T, meta: PaginatedMeta, links: PaginationLinks, etag: string, lastModified: Date, totalCount: number, cacheOptions?: CacheOptions): Response;
|
|
45
|
+
/**
|
|
46
|
+
* Create a CSV response with appropriate headers.
|
|
47
|
+
*
|
|
48
|
+
* @param data - Array of objects to convert to CSV
|
|
49
|
+
* @param etag - ETag value
|
|
50
|
+
* @param lastModified - Last-Modified date
|
|
51
|
+
* @param links - Pagination links
|
|
52
|
+
* @param totalCount - Total count
|
|
53
|
+
* @param cacheOptions - Optional Cache-Control options
|
|
54
|
+
* @returns Response with CSV body and headers
|
|
55
|
+
*
|
|
56
|
+
* Headers included:
|
|
57
|
+
* - Content-Type: text/csv; charset=utf-8
|
|
58
|
+
* - Content-Disposition: attachment; filename="data.csv"
|
|
59
|
+
* - ETag
|
|
60
|
+
* - Last-Modified
|
|
61
|
+
* - Cache-Control
|
|
62
|
+
* - Link
|
|
63
|
+
* - X-Total-Count
|
|
64
|
+
* - All CORS headers
|
|
65
|
+
*/
|
|
66
|
+
export declare function csvResponse<T extends Record<string, unknown>>(data: T[], etag: string, lastModified: Date, links: PaginationLinks, totalCount: number, cacheOptions?: CacheOptions): Response;
|
|
67
|
+
/**
|
|
68
|
+
* Create a redirect response.
|
|
69
|
+
*
|
|
70
|
+
* @param url - Target URL
|
|
71
|
+
* @param status - Status code (301, 302, 303, 307, 308)
|
|
72
|
+
* @returns Redirect response
|
|
73
|
+
*/
|
|
74
|
+
export declare function redirectResponse(url: string, status?: 301 | 302 | 303 | 307 | 308): Response;
|
|
75
|
+
/**
|
|
76
|
+
* Create a simple JSON response without pagination headers.
|
|
77
|
+
* Useful for single-resource endpoints.
|
|
78
|
+
*
|
|
79
|
+
* @param data - Data to serialize
|
|
80
|
+
* @param etag - Optional ETag
|
|
81
|
+
* @param lastModified - Optional Last-Modified date
|
|
82
|
+
* @param cacheOptions - Optional cache options
|
|
83
|
+
* @returns Response with JSON body
|
|
84
|
+
*/
|
|
85
|
+
export declare function simpleJsonResponse<T>(data: T, etag?: string, lastModified?: Date, cacheOptions?: CacheOptions): Response;
|
|
86
|
+
/**
|
|
87
|
+
* Create an empty success response.
|
|
88
|
+
*
|
|
89
|
+
* @param status - Status code (default: 204)
|
|
90
|
+
* @returns Response with no body
|
|
91
|
+
*/
|
|
92
|
+
export declare function noContentResponse(status?: 200 | 201 | 204): Response;
|
|
93
|
+
/**
|
|
94
|
+
* Create a text response.
|
|
95
|
+
*
|
|
96
|
+
* @param text - Text content
|
|
97
|
+
* @param contentType - Content type (default: text/plain)
|
|
98
|
+
* @returns Response with text body
|
|
99
|
+
*/
|
|
100
|
+
export declare function textResponse(text: string, contentType?: string): Response;
|
|
101
|
+
//# sourceMappingURL=response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAO/E;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,cAAc,GAAE,MAAM,EAA+B,GAAG,QAAQ,CAU/F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAQtE;AAED;;;;;;;;;;;;;;;;;;;;;;;EAuBE;AAEF,wBAAgB,YAAY,CAAC,CAAC,EAC1B,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,IAAI,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,GAAE,YAAsC,GACrD,QAAQ,CAmBV;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzD,IAAI,EAAE,CAAC,EAAE,EACT,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,IAAI,EAClB,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,MAAM,EAClB,YAAY,GAAE,YAAsC,GACrD,QAAQ,CAoBV;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAS,GAAG,QAAQ,CAQjG;AAED;;;;;;;;;GASG;AAEH,wBAAgB,kBAAkB,CAAC,CAAC,EAChC,IAAI,EAAE,CAAC,EACP,IAAI,CAAC,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,IAAI,EACnB,YAAY,GAAE,YAAsC,GACrD,QAAQ,CAmBV;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,GAAG,GAAG,GAAG,GAAG,GAAS,GAAG,QAAQ,CAOzE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,GAAE,MAAoC,GAAG,QAAQ,CAQtG"}
|
package/dist/response.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response builders for common HTTP patterns.
|
|
3
|
+
* RFC 9110 §8.8.2/§8.8.3, RFC 9111 §5.2.2, RFC 8288 §3, RFC 6266 §4.
|
|
4
|
+
*/
|
|
5
|
+
import { formatHTTPDate } from './datetime.js';
|
|
6
|
+
import { defaultCorsHeaders } from './cors.js';
|
|
7
|
+
import { cacheControl, CachePresets } from './cache.js';
|
|
8
|
+
import { buildLinkHeader } from './link.js';
|
|
9
|
+
import { toCSV } from './negotiate.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create an OPTIONS response for preflight requests.
|
|
12
|
+
*
|
|
13
|
+
* @param allowedMethods - Array of allowed HTTP methods (default: ['GET', 'HEAD', 'OPTIONS'])
|
|
14
|
+
* @returns Response with 204 No Content and appropriate headers
|
|
15
|
+
*/
|
|
16
|
+
export function optionsResponse(allowedMethods = ['GET', 'HEAD', 'OPTIONS']) {
|
|
17
|
+
const methods = allowedMethods.join(', ');
|
|
18
|
+
return new Response(null, {
|
|
19
|
+
status: 204,
|
|
20
|
+
headers: {
|
|
21
|
+
...defaultCorsHeaders,
|
|
22
|
+
'Allow': methods,
|
|
23
|
+
'Access-Control-Allow-Methods': methods,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a HEAD response with headers but no body.
|
|
29
|
+
*
|
|
30
|
+
* @param headers - Headers to include
|
|
31
|
+
* @returns Response with 200 OK and headers, empty body
|
|
32
|
+
*/
|
|
33
|
+
export function headResponse(headers) {
|
|
34
|
+
return new Response(null, {
|
|
35
|
+
status: 200,
|
|
36
|
+
headers: {
|
|
37
|
+
...defaultCorsHeaders,
|
|
38
|
+
...headers,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a JSON response with all standard API headers.
|
|
44
|
+
*
|
|
45
|
+
* @param data - Data to serialize as JSON
|
|
46
|
+
* @param meta - Pagination metadata (included in response body)
|
|
47
|
+
* @param links - Pagination links
|
|
48
|
+
* @param etag - ETag value (already formatted with quotes)
|
|
49
|
+
* @param lastModified - Last-Modified date
|
|
50
|
+
* @param totalCount - Total count for X-Total-Count header
|
|
51
|
+
* @param cacheOptions - Optional Cache-Control options (default: revalidate)
|
|
52
|
+
* @returns Response with JSON body and all standard headers
|
|
53
|
+
*
|
|
54
|
+
* Headers included:
|
|
55
|
+
* - Content-Type: application/json
|
|
56
|
+
* - ETag
|
|
57
|
+
* - Last-Modified
|
|
58
|
+
* - Cache-Control
|
|
59
|
+
* - Link (pagination)
|
|
60
|
+
* - X-Total-Count
|
|
61
|
+
* - All CORS headers
|
|
62
|
+
*
|
|
63
|
+
* Response body shape:
|
|
64
|
+
* { data, meta }
|
|
65
|
+
*/
|
|
66
|
+
// RFC 9110 §8.8.2/§8.8.3, RFC 9111 §5.2.2, RFC 8288 §3: Validators, Cache-Control, Link.
|
|
67
|
+
export function jsonResponse(data, meta, links, etag, lastModified, totalCount, cacheOptions = CachePresets.revalidate) {
|
|
68
|
+
const headers = {
|
|
69
|
+
...defaultCorsHeaders,
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
'ETag': etag,
|
|
72
|
+
'Last-Modified': formatHTTPDate(lastModified),
|
|
73
|
+
'X-Total-Count': String(totalCount),
|
|
74
|
+
'Cache-Control': cacheControl(cacheOptions),
|
|
75
|
+
};
|
|
76
|
+
const linkHeader = buildLinkHeader(links);
|
|
77
|
+
if (linkHeader) {
|
|
78
|
+
headers['Link'] = linkHeader;
|
|
79
|
+
}
|
|
80
|
+
return new Response(JSON.stringify({ data, meta }, null, 2), {
|
|
81
|
+
status: 200,
|
|
82
|
+
headers,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create a CSV response with appropriate headers.
|
|
87
|
+
*
|
|
88
|
+
* @param data - Array of objects to convert to CSV
|
|
89
|
+
* @param etag - ETag value
|
|
90
|
+
* @param lastModified - Last-Modified date
|
|
91
|
+
* @param links - Pagination links
|
|
92
|
+
* @param totalCount - Total count
|
|
93
|
+
* @param cacheOptions - Optional Cache-Control options
|
|
94
|
+
* @returns Response with CSV body and headers
|
|
95
|
+
*
|
|
96
|
+
* Headers included:
|
|
97
|
+
* - Content-Type: text/csv; charset=utf-8
|
|
98
|
+
* - Content-Disposition: attachment; filename="data.csv"
|
|
99
|
+
* - ETag
|
|
100
|
+
* - Last-Modified
|
|
101
|
+
* - Cache-Control
|
|
102
|
+
* - Link
|
|
103
|
+
* - X-Total-Count
|
|
104
|
+
* - All CORS headers
|
|
105
|
+
*/
|
|
106
|
+
// RFC 9110 §8.8.2/§8.8.3, RFC 9111 §5.2.2, RFC 8288 §3, RFC 6266 §4.
|
|
107
|
+
export function csvResponse(data, etag, lastModified, links, totalCount, cacheOptions = CachePresets.revalidate) {
|
|
108
|
+
const headers = {
|
|
109
|
+
...defaultCorsHeaders,
|
|
110
|
+
'Content-Type': 'text/csv; charset=utf-8',
|
|
111
|
+
'Content-Disposition': 'attachment; filename="data.csv"',
|
|
112
|
+
'ETag': etag,
|
|
113
|
+
'Last-Modified': formatHTTPDate(lastModified),
|
|
114
|
+
'X-Total-Count': String(totalCount),
|
|
115
|
+
'Cache-Control': cacheControl(cacheOptions),
|
|
116
|
+
};
|
|
117
|
+
const linkHeader = buildLinkHeader(links);
|
|
118
|
+
if (linkHeader) {
|
|
119
|
+
headers['Link'] = linkHeader;
|
|
120
|
+
}
|
|
121
|
+
return new Response(toCSV(data), {
|
|
122
|
+
status: 200,
|
|
123
|
+
headers,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create a redirect response.
|
|
128
|
+
*
|
|
129
|
+
* @param url - Target URL
|
|
130
|
+
* @param status - Status code (301, 302, 303, 307, 308)
|
|
131
|
+
* @returns Redirect response
|
|
132
|
+
*/
|
|
133
|
+
export function redirectResponse(url, status = 302) {
|
|
134
|
+
return new Response(null, {
|
|
135
|
+
status,
|
|
136
|
+
headers: {
|
|
137
|
+
...defaultCorsHeaders,
|
|
138
|
+
'Location': url,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create a simple JSON response without pagination headers.
|
|
144
|
+
* Useful for single-resource endpoints.
|
|
145
|
+
*
|
|
146
|
+
* @param data - Data to serialize
|
|
147
|
+
* @param etag - Optional ETag
|
|
148
|
+
* @param lastModified - Optional Last-Modified date
|
|
149
|
+
* @param cacheOptions - Optional cache options
|
|
150
|
+
* @returns Response with JSON body
|
|
151
|
+
*/
|
|
152
|
+
// RFC 9110 §8.8.2/§8.8.3, RFC 9111 §5.2.2: Validators and Cache-Control.
|
|
153
|
+
export function simpleJsonResponse(data, etag, lastModified, cacheOptions = CachePresets.revalidate) {
|
|
154
|
+
const headers = {
|
|
155
|
+
...defaultCorsHeaders,
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
'Cache-Control': cacheControl(cacheOptions),
|
|
158
|
+
};
|
|
159
|
+
if (etag) {
|
|
160
|
+
headers['ETag'] = etag;
|
|
161
|
+
}
|
|
162
|
+
if (lastModified) {
|
|
163
|
+
headers['Last-Modified'] = formatHTTPDate(lastModified);
|
|
164
|
+
}
|
|
165
|
+
return new Response(JSON.stringify(data, null, 2), {
|
|
166
|
+
status: 200,
|
|
167
|
+
headers,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create an empty success response.
|
|
172
|
+
*
|
|
173
|
+
* @param status - Status code (default: 204)
|
|
174
|
+
* @returns Response with no body
|
|
175
|
+
*/
|
|
176
|
+
export function noContentResponse(status = 204) {
|
|
177
|
+
return new Response(null, {
|
|
178
|
+
status,
|
|
179
|
+
headers: {
|
|
180
|
+
...defaultCorsHeaders,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Create a text response.
|
|
186
|
+
*
|
|
187
|
+
* @param text - Text content
|
|
188
|
+
* @param contentType - Content type (default: text/plain)
|
|
189
|
+
* @returns Response with text body
|
|
190
|
+
*/
|
|
191
|
+
export function textResponse(text, contentType = 'text/plain; charset=utf-8') {
|
|
192
|
+
return new Response(text, {
|
|
193
|
+
status: 200,
|
|
194
|
+
headers: {
|
|
195
|
+
...defaultCorsHeaders,
|
|
196
|
+
'Content-Type': contentType,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.js","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sort direction
|
|
3
|
+
*/
|
|
4
|
+
export type SortDirection = 'asc' | 'desc';
|
|
5
|
+
/**
|
|
6
|
+
* Parsed sort field
|
|
7
|
+
*/
|
|
8
|
+
export interface SortField {
|
|
9
|
+
field: string;
|
|
10
|
+
direction: SortDirection;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parse a sort string into field/direction pairs.
|
|
14
|
+
*
|
|
15
|
+
* Format: "field1,-field2,field3"
|
|
16
|
+
* - Prefix with '-' for descending
|
|
17
|
+
* - No prefix for ascending
|
|
18
|
+
* - Multiple fields separated by comma
|
|
19
|
+
*
|
|
20
|
+
* @param sort - The sort string
|
|
21
|
+
* @returns Array of SortField objects
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseSortString(sort: string): SortField[];
|
|
24
|
+
/**
|
|
25
|
+
* Compare two values for sorting.
|
|
26
|
+
* Internal helper exposed for custom comparators.
|
|
27
|
+
*
|
|
28
|
+
* @param a - First value
|
|
29
|
+
* @param b - Second value
|
|
30
|
+
* @param direction - Sort direction
|
|
31
|
+
* @returns Comparison result (-1, 0, 1)
|
|
32
|
+
*/
|
|
33
|
+
export declare function compareValues(a: unknown, b: unknown, direction: SortDirection): number;
|
|
34
|
+
/**
|
|
35
|
+
* Apply sorting to an array of objects.
|
|
36
|
+
*
|
|
37
|
+
* @param data - Array to sort (not mutated, returns new array)
|
|
38
|
+
* @param sort - Sort string (e.g., "-date,title")
|
|
39
|
+
* @returns Sorted array
|
|
40
|
+
*
|
|
41
|
+
* Sorting rules:
|
|
42
|
+
* - undefined/null values sort last
|
|
43
|
+
* - Strings are compared case-insensitively
|
|
44
|
+
* - Numbers compared numerically
|
|
45
|
+
* - Dates compared by timestamp
|
|
46
|
+
* - Booleans: false < true
|
|
47
|
+
* - Multiple sort fields applied in order
|
|
48
|
+
*/
|
|
49
|
+
export declare function applySorting<T extends Record<string, unknown>>(data: T[], sort: string | undefined): T[];
|
|
50
|
+
/**
|
|
51
|
+
* Validate that sort fields exist in the data.
|
|
52
|
+
*
|
|
53
|
+
* @param sort - Sort string
|
|
54
|
+
* @param allowedFields - Array of allowed field names
|
|
55
|
+
* @returns true if all fields are allowed, false otherwise
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateSortFields(sort: string, allowedFields: string[]): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Build a sort string from field/direction pairs.
|
|
60
|
+
* Inverse of parseSortString.
|
|
61
|
+
*
|
|
62
|
+
* @param fields - Array of SortField objects
|
|
63
|
+
* @returns Sort string
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildSortString(fields: SortField[]): string;
|
|
66
|
+
//# sourceMappingURL=sorting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sorting.d.ts","sourceRoot":"","sources":["../src/sorting.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;AAE3C;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,aAAa,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAqBzD;AA0BD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,GAAG,MAAM,CA2CtF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1D,IAAI,EAAE,CAAC,EAAE,EACT,IAAI,EAAE,MAAM,GAAG,SAAS,GACzB,CAAC,EAAE,CA6BL;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAUjF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAM3D"}
|