@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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/dist/auth.d.ts +139 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +991 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/cache-status.d.ts +15 -0
  8. package/dist/cache-status.d.ts.map +1 -0
  9. package/dist/cache-status.js +152 -0
  10. package/dist/cache-status.js.map +1 -0
  11. package/dist/cache.d.ts +94 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +244 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/client-hints.d.ts +23 -0
  16. package/dist/client-hints.d.ts.map +1 -0
  17. package/dist/client-hints.js +81 -0
  18. package/dist/client-hints.js.map +1 -0
  19. package/dist/conditional.d.ts +97 -0
  20. package/dist/conditional.d.ts.map +1 -0
  21. package/dist/conditional.js +300 -0
  22. package/dist/conditional.js.map +1 -0
  23. package/dist/content-disposition.d.ts +23 -0
  24. package/dist/content-disposition.d.ts.map +1 -0
  25. package/dist/content-disposition.js +122 -0
  26. package/dist/content-disposition.js.map +1 -0
  27. package/dist/cookie.d.ts +43 -0
  28. package/dist/cookie.d.ts.map +1 -0
  29. package/dist/cookie.js +472 -0
  30. package/dist/cookie.js.map +1 -0
  31. package/dist/cors.d.ts +53 -0
  32. package/dist/cors.d.ts.map +1 -0
  33. package/dist/cors.js +170 -0
  34. package/dist/cors.js.map +1 -0
  35. package/dist/datetime.d.ts +53 -0
  36. package/dist/datetime.d.ts.map +1 -0
  37. package/dist/datetime.js +205 -0
  38. package/dist/datetime.js.map +1 -0
  39. package/dist/digest.d.ts +220 -0
  40. package/dist/digest.d.ts.map +1 -0
  41. package/dist/digest.js +355 -0
  42. package/dist/digest.js.map +1 -0
  43. package/dist/encoding.d.ts +14 -0
  44. package/dist/encoding.d.ts.map +1 -0
  45. package/dist/encoding.js +86 -0
  46. package/dist/encoding.js.map +1 -0
  47. package/dist/etag.d.ts +55 -0
  48. package/dist/etag.d.ts.map +1 -0
  49. package/dist/etag.js +182 -0
  50. package/dist/etag.js.map +1 -0
  51. package/dist/ext-value.d.ts +40 -0
  52. package/dist/ext-value.d.ts.map +1 -0
  53. package/dist/ext-value.js +119 -0
  54. package/dist/ext-value.js.map +1 -0
  55. package/dist/forwarded.d.ts +14 -0
  56. package/dist/forwarded.d.ts.map +1 -0
  57. package/dist/forwarded.js +93 -0
  58. package/dist/forwarded.js.map +1 -0
  59. package/dist/header-utils.d.ts +71 -0
  60. package/dist/header-utils.d.ts.map +1 -0
  61. package/dist/header-utils.js +143 -0
  62. package/dist/header-utils.js.map +1 -0
  63. package/dist/headers.d.ts +71 -0
  64. package/dist/headers.d.ts.map +1 -0
  65. package/dist/headers.js +134 -0
  66. package/dist/headers.js.map +1 -0
  67. package/dist/hsts.d.ts +15 -0
  68. package/dist/hsts.d.ts.map +1 -0
  69. package/dist/hsts.js +106 -0
  70. package/dist/hsts.js.map +1 -0
  71. package/dist/http-signatures.d.ts +202 -0
  72. package/dist/http-signatures.d.ts.map +1 -0
  73. package/dist/http-signatures.js +720 -0
  74. package/dist/http-signatures.js.map +1 -0
  75. package/dist/index.d.ts +41 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +125 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/json-pointer.d.ts +97 -0
  80. package/dist/json-pointer.d.ts.map +1 -0
  81. package/dist/json-pointer.js +278 -0
  82. package/dist/json-pointer.js.map +1 -0
  83. package/dist/jsonpath.d.ts +98 -0
  84. package/dist/jsonpath.d.ts.map +1 -0
  85. package/dist/jsonpath.js +1470 -0
  86. package/dist/jsonpath.js.map +1 -0
  87. package/dist/language.d.ts +14 -0
  88. package/dist/language.d.ts.map +1 -0
  89. package/dist/language.js +95 -0
  90. package/dist/language.js.map +1 -0
  91. package/dist/link.d.ts +102 -0
  92. package/dist/link.d.ts.map +1 -0
  93. package/dist/link.js +437 -0
  94. package/dist/link.js.map +1 -0
  95. package/dist/linkset.d.ts +111 -0
  96. package/dist/linkset.d.ts.map +1 -0
  97. package/dist/linkset.js +501 -0
  98. package/dist/linkset.js.map +1 -0
  99. package/dist/negotiate.d.ts +71 -0
  100. package/dist/negotiate.d.ts.map +1 -0
  101. package/dist/negotiate.js +357 -0
  102. package/dist/negotiate.js.map +1 -0
  103. package/dist/pagination.d.ts +80 -0
  104. package/dist/pagination.d.ts.map +1 -0
  105. package/dist/pagination.js +188 -0
  106. package/dist/pagination.js.map +1 -0
  107. package/dist/prefer.d.ts +18 -0
  108. package/dist/prefer.d.ts.map +1 -0
  109. package/dist/prefer.js +93 -0
  110. package/dist/prefer.js.map +1 -0
  111. package/dist/problem.d.ts +54 -0
  112. package/dist/problem.d.ts.map +1 -0
  113. package/dist/problem.js +104 -0
  114. package/dist/problem.js.map +1 -0
  115. package/dist/proxy-status.d.ts +28 -0
  116. package/dist/proxy-status.d.ts.map +1 -0
  117. package/dist/proxy-status.js +220 -0
  118. package/dist/proxy-status.js.map +1 -0
  119. package/dist/range.d.ts +28 -0
  120. package/dist/range.d.ts.map +1 -0
  121. package/dist/range.js +243 -0
  122. package/dist/range.js.map +1 -0
  123. package/dist/response.d.ts +101 -0
  124. package/dist/response.d.ts.map +1 -0
  125. package/dist/response.js +200 -0
  126. package/dist/response.js.map +1 -0
  127. package/dist/sorting.d.ts +66 -0
  128. package/dist/sorting.d.ts.map +1 -0
  129. package/dist/sorting.js +168 -0
  130. package/dist/sorting.js.map +1 -0
  131. package/dist/structured-fields.d.ts +30 -0
  132. package/dist/structured-fields.d.ts.map +1 -0
  133. package/dist/structured-fields.js +468 -0
  134. package/dist/structured-fields.js.map +1 -0
  135. package/dist/types.d.ts +772 -0
  136. package/dist/types.d.ts.map +1 -0
  137. package/dist/types.js +8 -0
  138. package/dist/types.js.map +1 -0
  139. package/dist/uri-template.d.ts +48 -0
  140. package/dist/uri-template.d.ts.map +1 -0
  141. package/dist/uri-template.js +483 -0
  142. package/dist/uri-template.js.map +1 -0
  143. package/dist/uri.d.ts +80 -0
  144. package/dist/uri.d.ts.map +1 -0
  145. package/dist/uri.js +423 -0
  146. package/dist/uri.js.map +1 -0
  147. 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"}
@@ -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"}