@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/linkset.js
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linkset utilities per RFC 9264.
|
|
3
|
+
* API catalog support per RFC 9727.
|
|
4
|
+
* RFC 9264 §4.1, §4.2, §5, §6.
|
|
5
|
+
* RFC 9727 §§2-4, §7.
|
|
6
|
+
* @see https://www.rfc-editor.org/rfc/rfc9264.html
|
|
7
|
+
* @see https://www.rfc-editor.org/rfc/rfc9727.html
|
|
8
|
+
*/
|
|
9
|
+
import { parseLinkHeader, formatLink } from './link.js';
|
|
10
|
+
// RFC 9264 §4.2: Linkset JSON media type.
|
|
11
|
+
export const LINKSET_MEDIA_TYPE = 'application/linkset+json';
|
|
12
|
+
// RFC 9727 §7.3: API catalog profile URI.
|
|
13
|
+
export const API_CATALOG_PROFILE = 'https://www.rfc-editor.org/info/rfc9727';
|
|
14
|
+
// RFC 9727 §2: Well-known URI path for API catalog.
|
|
15
|
+
export const API_CATALOG_PATH = '/.well-known/api-catalog';
|
|
16
|
+
/**
|
|
17
|
+
* Parse an application/linkset document into link definitions.
|
|
18
|
+
* RFC 9264 §4.1: Same as HTTP Link header format but allows newlines as separators.
|
|
19
|
+
*
|
|
20
|
+
* @param document - The linkset document content
|
|
21
|
+
* @returns Array of parsed link definitions, or null if parsing fails
|
|
22
|
+
*/
|
|
23
|
+
// RFC 9264 §4.1: application/linkset uses RFC 8288 Link format with newlines allowed.
|
|
24
|
+
export function parseLinkset(document) {
|
|
25
|
+
if (!document || document.trim() === '') {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
// RFC 9264 §4.1: Newlines are valid separators in addition to commas.
|
|
29
|
+
// Normalize CR/LF to LF, then replace newlines outside quotes with commas.
|
|
30
|
+
const normalized = normalizeNewlines(document);
|
|
31
|
+
return parseLinkHeader(normalized);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Normalize newlines in linkset format for parsing.
|
|
35
|
+
* RFC 9264 §4.1: Newlines allowed as separators between link-values.
|
|
36
|
+
*
|
|
37
|
+
* Handles two patterns:
|
|
38
|
+
* 1. Continuation lines: newline + whitespace + `;` - these continue parameters
|
|
39
|
+
* 2. Link separators: newline followed by `<` or end of document
|
|
40
|
+
*/
|
|
41
|
+
function normalizeNewlines(document) {
|
|
42
|
+
// Replace CR/LF with LF
|
|
43
|
+
let result = document.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
44
|
+
// RFC 9264 §4.1: Newlines with continuation (whitespace + ;) should become spaces
|
|
45
|
+
// This handles the indented parameter continuation pattern from RFC examples
|
|
46
|
+
result = result.replace(/\n[ \t]+;/g, ' ;');
|
|
47
|
+
// Track quote state for remaining newline handling
|
|
48
|
+
let inQuote = false;
|
|
49
|
+
let escaped = false;
|
|
50
|
+
let output = '';
|
|
51
|
+
for (let i = 0; i < result.length; i++) {
|
|
52
|
+
const char = result[i];
|
|
53
|
+
if (escaped) {
|
|
54
|
+
output += char;
|
|
55
|
+
escaped = false;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (char === '\\') {
|
|
59
|
+
output += char;
|
|
60
|
+
escaped = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (char === '"') {
|
|
64
|
+
inQuote = !inQuote;
|
|
65
|
+
output += char;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (char === '\n' && !inQuote) {
|
|
69
|
+
// RFC 9264 §4.1: Newlines between links act as separators
|
|
70
|
+
// Look ahead to determine if this is a link separator
|
|
71
|
+
let j = i + 1;
|
|
72
|
+
while (j < result.length && (result[j] === ' ' || result[j] === '\t')) {
|
|
73
|
+
j++;
|
|
74
|
+
}
|
|
75
|
+
const nextNonWs = result[j];
|
|
76
|
+
// If next non-whitespace is '<', this newline separates links
|
|
77
|
+
if (nextNonWs === '<') {
|
|
78
|
+
const trimmed = output.trimEnd();
|
|
79
|
+
if (trimmed.length > 0 && !trimmed.endsWith(',')) {
|
|
80
|
+
output = trimmed + ', ';
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
output = trimmed + ' ';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Otherwise treat as continuation (just whitespace)
|
|
88
|
+
output += ' ';
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
output += char;
|
|
93
|
+
}
|
|
94
|
+
return output;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Parse an application/linkset+json document.
|
|
98
|
+
* RFC 9264 §4.2: JSON format for link sets.
|
|
99
|
+
*
|
|
100
|
+
* @param json - The JSON string or object
|
|
101
|
+
* @returns Parsed Linkset object, or null if invalid
|
|
102
|
+
*/
|
|
103
|
+
// RFC 9264 §4.2: application/linkset+json parsing.
|
|
104
|
+
export function parseLinksetJson(json) {
|
|
105
|
+
let obj;
|
|
106
|
+
if (typeof json === 'string') {
|
|
107
|
+
try {
|
|
108
|
+
obj = JSON.parse(json);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
obj = json;
|
|
116
|
+
}
|
|
117
|
+
if (!isValidLinkset(obj)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return obj;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Format link definitions as an application/linkset document.
|
|
124
|
+
* RFC 9264 §4.1: Uses Link header format with newlines for readability.
|
|
125
|
+
*
|
|
126
|
+
* @param links - Array of link definitions
|
|
127
|
+
* @returns Formatted linkset document
|
|
128
|
+
*/
|
|
129
|
+
// RFC 9264 §4.1: application/linkset formatting with newline separators.
|
|
130
|
+
export function formatLinkset(links) {
|
|
131
|
+
if (links.length === 0) {
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
// Format each link and join with newlines for readability
|
|
135
|
+
return links.map(formatLink).join(',\n');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Format link definitions as an application/linkset+json object.
|
|
139
|
+
* RFC 9264 §4.2: JSON format for link sets.
|
|
140
|
+
*
|
|
141
|
+
* @param links - Array of link definitions
|
|
142
|
+
* @param options - Formatting options
|
|
143
|
+
* @returns Linkset JSON object
|
|
144
|
+
*/
|
|
145
|
+
// RFC 9264 §4.2: application/linkset+json formatting.
|
|
146
|
+
export function formatLinksetJson(links, options = {}) {
|
|
147
|
+
const { groupByAnchor = true } = options;
|
|
148
|
+
if (!groupByAnchor) {
|
|
149
|
+
// Each link gets its own context object
|
|
150
|
+
const contexts = links.map((link) => {
|
|
151
|
+
const target = linkDefinitionToTarget(link);
|
|
152
|
+
const context = {};
|
|
153
|
+
if (link.anchor) {
|
|
154
|
+
context.anchor = link.anchor;
|
|
155
|
+
}
|
|
156
|
+
context[link.rel] = [target];
|
|
157
|
+
return context;
|
|
158
|
+
});
|
|
159
|
+
return { linkset: contexts };
|
|
160
|
+
}
|
|
161
|
+
// Group by anchor
|
|
162
|
+
const byAnchor = new Map();
|
|
163
|
+
for (const link of links) {
|
|
164
|
+
const anchor = link.anchor ?? '';
|
|
165
|
+
if (!byAnchor.has(anchor)) {
|
|
166
|
+
byAnchor.set(anchor, new Map());
|
|
167
|
+
}
|
|
168
|
+
const relMap = byAnchor.get(anchor);
|
|
169
|
+
if (!relMap.has(link.rel)) {
|
|
170
|
+
relMap.set(link.rel, []);
|
|
171
|
+
}
|
|
172
|
+
relMap.get(link.rel).push(linkDefinitionToTarget(link));
|
|
173
|
+
}
|
|
174
|
+
const contexts = [];
|
|
175
|
+
for (const [anchor, relMap] of byAnchor) {
|
|
176
|
+
const context = {};
|
|
177
|
+
if (anchor) {
|
|
178
|
+
context.anchor = anchor;
|
|
179
|
+
}
|
|
180
|
+
for (const [rel, targets] of relMap) {
|
|
181
|
+
context[rel] = targets;
|
|
182
|
+
}
|
|
183
|
+
contexts.push(context);
|
|
184
|
+
}
|
|
185
|
+
return { linkset: contexts };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Convert a LinkDefinition to a LinksetTarget.
|
|
189
|
+
*/
|
|
190
|
+
function linkDefinitionToTarget(link) {
|
|
191
|
+
const target = { href: link.href };
|
|
192
|
+
if (link.type) {
|
|
193
|
+
target.type = link.type;
|
|
194
|
+
}
|
|
195
|
+
if (link.title) {
|
|
196
|
+
target.title = link.title;
|
|
197
|
+
}
|
|
198
|
+
// RFC 9264 §4.2.4.2: title* with language
|
|
199
|
+
if (link.titleLang && link.title) {
|
|
200
|
+
target['title*'] = [{ value: link.title, language: link.titleLang }];
|
|
201
|
+
}
|
|
202
|
+
if (link.hreflang) {
|
|
203
|
+
// RFC 9264 §4.2.4.1: hreflang is always an array
|
|
204
|
+
target.hreflang = Array.isArray(link.hreflang) ? link.hreflang : [link.hreflang];
|
|
205
|
+
}
|
|
206
|
+
if (link.media) {
|
|
207
|
+
target.media = link.media;
|
|
208
|
+
}
|
|
209
|
+
// Copy extension attributes
|
|
210
|
+
const standardKeys = new Set(['href', 'rel', 'type', 'title', 'titleLang', 'hreflang', 'media', 'anchor', 'rev']);
|
|
211
|
+
for (const [key, value] of Object.entries(link)) {
|
|
212
|
+
if (!standardKeys.has(key) && value !== undefined) {
|
|
213
|
+
// RFC 9264 §4.2.4.3: Extension attributes are always arrays
|
|
214
|
+
target[key] = Array.isArray(value) ? value : [value];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return target;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Convert an application/linkset document to JSON format.
|
|
221
|
+
* RFC 9264 §4.1, §4.2: Format conversion.
|
|
222
|
+
*
|
|
223
|
+
* @param document - The linkset document
|
|
224
|
+
* @returns Linkset JSON object, or null if parsing fails
|
|
225
|
+
*/
|
|
226
|
+
// RFC 9264 §4.1, §4.2: Convert between formats.
|
|
227
|
+
export function linksetToJson(document) {
|
|
228
|
+
const links = parseLinkset(document);
|
|
229
|
+
if (links === null) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
return formatLinksetJson(links);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Convert a JSON linkset to application/linkset format.
|
|
236
|
+
* RFC 9264 §4.1, §4.2: Format conversion.
|
|
237
|
+
*
|
|
238
|
+
* @param linkset - The Linkset object
|
|
239
|
+
* @returns Formatted linkset document
|
|
240
|
+
*/
|
|
241
|
+
// RFC 9264 §4.1, §4.2: Convert between formats.
|
|
242
|
+
export function jsonToLinkset(linkset) {
|
|
243
|
+
const links = linksetJsonToDefinitions(linkset);
|
|
244
|
+
return formatLinkset(links);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Convert a Linkset JSON object to LinkDefinition array.
|
|
248
|
+
*/
|
|
249
|
+
function linksetJsonToDefinitions(linkset) {
|
|
250
|
+
const links = [];
|
|
251
|
+
for (const context of linkset.linkset) {
|
|
252
|
+
const anchor = context.anchor;
|
|
253
|
+
for (const [key, value] of Object.entries(context)) {
|
|
254
|
+
if (key === 'anchor') {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
// key is a relation type, value is an array of targets
|
|
258
|
+
if (!Array.isArray(value)) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const targets = value;
|
|
262
|
+
for (const target of targets) {
|
|
263
|
+
const link = {
|
|
264
|
+
href: target.href,
|
|
265
|
+
rel: key,
|
|
266
|
+
};
|
|
267
|
+
if (anchor) {
|
|
268
|
+
link.anchor = anchor;
|
|
269
|
+
}
|
|
270
|
+
if (target.type) {
|
|
271
|
+
link.type = target.type;
|
|
272
|
+
}
|
|
273
|
+
if (target.title) {
|
|
274
|
+
link.title = target.title;
|
|
275
|
+
}
|
|
276
|
+
// RFC 9264 §4.2.4.2: title* with language
|
|
277
|
+
if (target['title*'] && target['title*'].length > 0) {
|
|
278
|
+
const titleStar = target['title*'][0];
|
|
279
|
+
link.title = titleStar.value;
|
|
280
|
+
if (titleStar.language) {
|
|
281
|
+
link.titleLang = titleStar.language;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (target.hreflang) {
|
|
285
|
+
// Flatten to single value if only one, otherwise keep array
|
|
286
|
+
link.hreflang = target.hreflang.length === 1 ? target.hreflang[0] : target.hreflang;
|
|
287
|
+
}
|
|
288
|
+
if (target.media) {
|
|
289
|
+
link.media = target.media;
|
|
290
|
+
}
|
|
291
|
+
// Copy extension attributes
|
|
292
|
+
const standardKeys = new Set(['href', 'type', 'title', 'title*', 'hreflang', 'media']);
|
|
293
|
+
for (const [attrKey, attrValue] of Object.entries(target)) {
|
|
294
|
+
if (!standardKeys.has(attrKey) && attrValue !== undefined) {
|
|
295
|
+
// Flatten single-element arrays
|
|
296
|
+
if (Array.isArray(attrValue) && attrValue.length === 1) {
|
|
297
|
+
const first = attrValue[0];
|
|
298
|
+
if (typeof first === 'string') {
|
|
299
|
+
link[attrKey] = first;
|
|
300
|
+
}
|
|
301
|
+
else if (typeof first === 'object' && 'value' in first) {
|
|
302
|
+
// Internationalized value
|
|
303
|
+
link[attrKey] = first.value;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else if (Array.isArray(attrValue)) {
|
|
307
|
+
// Keep as array for multi-value extensions
|
|
308
|
+
link[attrKey] = attrValue.map((v) => typeof v === 'object' && 'value' in v ? v.value : v);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
links.push(link);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return links;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Type guard to validate a Linkset object.
|
|
320
|
+
* RFC 9264 §4.2.1: linkset must be the sole top-level member.
|
|
321
|
+
*
|
|
322
|
+
* @param obj - Object to validate
|
|
323
|
+
* @returns True if valid Linkset
|
|
324
|
+
*/
|
|
325
|
+
// RFC 9264 §4.2.1: Validate linkset structure.
|
|
326
|
+
export function isValidLinkset(obj) {
|
|
327
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
const record = obj;
|
|
331
|
+
// RFC 9264 §4.2.1: linkset MUST be the sole member (profile is allowed for api-catalog)
|
|
332
|
+
const keys = Object.keys(record);
|
|
333
|
+
if (!keys.includes('linkset')) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
// Allow 'profile' as additional key for API catalogs
|
|
337
|
+
for (const key of keys) {
|
|
338
|
+
if (key !== 'linkset' && key !== 'profile') {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (!Array.isArray(record.linkset)) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
// Validate each context object
|
|
346
|
+
for (const context of record.linkset) {
|
|
347
|
+
if (typeof context !== 'object' || context === null) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
// Validate each relation type's targets
|
|
351
|
+
for (const [key, value] of Object.entries(context)) {
|
|
352
|
+
if (key === 'anchor') {
|
|
353
|
+
if (typeof value !== 'string') {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
// Value must be an array of target objects
|
|
359
|
+
if (!Array.isArray(value)) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
for (const target of value) {
|
|
363
|
+
if (typeof target !== 'object' || target === null) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
// RFC 9264 §4.2.3: href is required
|
|
367
|
+
if (!('href' in target) || typeof target.href !== 'string') {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
// =============================================================================
|
|
376
|
+
// API Catalog (RFC 9727)
|
|
377
|
+
// =============================================================================
|
|
378
|
+
/**
|
|
379
|
+
* Create an API catalog document.
|
|
380
|
+
* RFC 9727 §4: API catalog document structure.
|
|
381
|
+
*
|
|
382
|
+
* @param options - API catalog configuration
|
|
383
|
+
* @returns API catalog Linkset document
|
|
384
|
+
*/
|
|
385
|
+
// RFC 9727 §4.1, §4.2: Create API catalog with required structure.
|
|
386
|
+
export function createApiCatalog(options) {
|
|
387
|
+
const { anchor, apis = [], items = [], nested = [], profile = true } = options;
|
|
388
|
+
if (!anchor) {
|
|
389
|
+
throw new Error('API catalog requires an anchor URI');
|
|
390
|
+
}
|
|
391
|
+
const contexts = [];
|
|
392
|
+
// RFC 9727 §4.1: Include hyperlinks to API endpoints
|
|
393
|
+
if (items.length > 0 || nested.length > 0) {
|
|
394
|
+
// Create a context for simple item links and nested catalogs
|
|
395
|
+
const mainContext = { anchor };
|
|
396
|
+
// RFC 9727 §3.1: "item" relation identifies API members
|
|
397
|
+
if (items.length > 0) {
|
|
398
|
+
mainContext.item = items.map((item) => {
|
|
399
|
+
const target = { href: item.href };
|
|
400
|
+
if (item.type)
|
|
401
|
+
target.type = item.type;
|
|
402
|
+
if (item.title)
|
|
403
|
+
target.title = item.title;
|
|
404
|
+
if (item.hreflang)
|
|
405
|
+
target.hreflang = [item.hreflang];
|
|
406
|
+
return target;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
// RFC 9727 §4.3: Nested api-catalog links
|
|
410
|
+
if (nested.length > 0) {
|
|
411
|
+
mainContext['api-catalog'] = nested.map((href) => ({ href }));
|
|
412
|
+
}
|
|
413
|
+
contexts.push(mainContext);
|
|
414
|
+
}
|
|
415
|
+
// RFC 9727 Appendix A.1: Full API entries with service relations
|
|
416
|
+
for (const api of apis) {
|
|
417
|
+
const apiContext = { anchor: api.anchor };
|
|
418
|
+
if (api['service-desc']) {
|
|
419
|
+
apiContext['service-desc'] = api['service-desc'];
|
|
420
|
+
}
|
|
421
|
+
if (api['service-doc']) {
|
|
422
|
+
apiContext['service-doc'] = api['service-doc'];
|
|
423
|
+
}
|
|
424
|
+
if (api['service-meta']) {
|
|
425
|
+
apiContext['service-meta'] = api['service-meta'];
|
|
426
|
+
}
|
|
427
|
+
if (api.status) {
|
|
428
|
+
apiContext.status = api.status;
|
|
429
|
+
}
|
|
430
|
+
contexts.push(apiContext);
|
|
431
|
+
}
|
|
432
|
+
const catalog = { linkset: contexts };
|
|
433
|
+
// RFC 9727 §4.2: SHOULD include profile parameter
|
|
434
|
+
if (profile) {
|
|
435
|
+
catalog.profile = API_CATALOG_PROFILE;
|
|
436
|
+
}
|
|
437
|
+
return catalog;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Parse an API catalog document.
|
|
441
|
+
* RFC 9727 §4: Validate and parse API catalog structure.
|
|
442
|
+
*
|
|
443
|
+
* @param json - JSON string or object
|
|
444
|
+
* @returns Parsed API catalog, or null if invalid
|
|
445
|
+
*/
|
|
446
|
+
// RFC 9727 §4: Parse and validate API catalog.
|
|
447
|
+
export function parseApiCatalog(json) {
|
|
448
|
+
const linkset = parseLinksetJson(json);
|
|
449
|
+
if (!linkset) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
// Add profile if present in the input
|
|
453
|
+
const input = typeof json === 'string' ? JSON.parse(json) : json;
|
|
454
|
+
if (input && typeof input === 'object' && 'profile' in input) {
|
|
455
|
+
return { ...linkset, profile: input.profile };
|
|
456
|
+
}
|
|
457
|
+
return linkset;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Check if a Linkset is an API catalog (has the RFC 9727 profile).
|
|
461
|
+
* RFC 9727 §4.2, §7.3: Profile URI indicates api-catalog semantics.
|
|
462
|
+
*
|
|
463
|
+
* @param linkset - Linkset to check
|
|
464
|
+
* @returns True if the linkset has the api-catalog profile
|
|
465
|
+
*/
|
|
466
|
+
// RFC 9727 §7.3: Check for API catalog profile.
|
|
467
|
+
export function isApiCatalog(linkset) {
|
|
468
|
+
if ('profile' in linkset) {
|
|
469
|
+
return linkset.profile === API_CATALOG_PROFILE;
|
|
470
|
+
}
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Convert a Linkset to an array of LinkDefinitions.
|
|
475
|
+
* Useful for converting API catalog entries to Link header format.
|
|
476
|
+
*
|
|
477
|
+
* @param linkset - Linkset to convert
|
|
478
|
+
* @returns Array of LinkDefinition objects
|
|
479
|
+
*/
|
|
480
|
+
// RFC 9264 §4: Convert between linkset and link definitions.
|
|
481
|
+
export function linksetToLinks(linkset) {
|
|
482
|
+
return linksetJsonToDefinitions(linkset);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Convert an array of LinkDefinitions to a Linkset.
|
|
486
|
+
* Useful for creating API catalogs from Link header entries.
|
|
487
|
+
*
|
|
488
|
+
* @param links - Array of link definitions
|
|
489
|
+
* @param anchor - Default anchor URI for links without one
|
|
490
|
+
* @returns Linkset object
|
|
491
|
+
*/
|
|
492
|
+
// RFC 9264 §4: Convert between link definitions and linkset.
|
|
493
|
+
export function linksToLinkset(links, anchor) {
|
|
494
|
+
// Set anchor on links that don't have one
|
|
495
|
+
const linksWithAnchor = links.map((link) => ({
|
|
496
|
+
...link,
|
|
497
|
+
anchor: link.anchor ?? anchor,
|
|
498
|
+
}));
|
|
499
|
+
return formatLinksetJson(linksWithAnchor);
|
|
500
|
+
}
|
|
501
|
+
//# sourceMappingURL=linkset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linkset.js","sourceRoot":"","sources":["../src/linkset.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content negotiation utilities per RFC 7231.
|
|
3
|
+
* RFC 7231 §5.3.1, §5.3.2.
|
|
4
|
+
*/
|
|
5
|
+
import type { AcceptEntry, MediaType } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Media type constants mapping format names to MIME types.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MEDIA_TYPES: Record<MediaType, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Reverse mapping from MIME type to format name.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MIME_TO_FORMAT: Record<string, MediaType>;
|
|
14
|
+
/**
|
|
15
|
+
* Parse an Accept header into a list of entries sorted by preference.
|
|
16
|
+
*
|
|
17
|
+
* RFC 7231 Section 5.3.2 defines Accept header format.
|
|
18
|
+
*
|
|
19
|
+
* @param header - The Accept header value
|
|
20
|
+
* @returns Sorted array of AcceptEntry (highest preference first)
|
|
21
|
+
*
|
|
22
|
+
* Sorting rules:
|
|
23
|
+
* 1. Higher q value first
|
|
24
|
+
* 2. More specific type beats wildcard (text/html > text/star > star/star)
|
|
25
|
+
* 3. More parameters beats fewer
|
|
26
|
+
*
|
|
27
|
+
* Invalid q-values are rejected and the entry is skipped.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* parseAccept("text/html, application/json;q=0.9, text/star;q=0.8")
|
|
31
|
+
* // Returns: [text/html q=1], [application/json q=0.9], [text/star q=0.8]
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseAccept(header: string): AcceptEntry[];
|
|
34
|
+
/**
|
|
35
|
+
* Negotiate the best media type from supported options.
|
|
36
|
+
*
|
|
37
|
+
* @param input - The Request object, Accept header string, or null/undefined
|
|
38
|
+
* @param supported - Array of supported media types (MIME strings like 'application/json')
|
|
39
|
+
* @returns Best matching media type or null if none acceptable
|
|
40
|
+
*
|
|
41
|
+
* Matching rules:
|
|
42
|
+
* - Exact match: "application/json" matches 'application/json'
|
|
43
|
+
* - Type wildcard: "text/\*" matches 'text/html', 'text/csv'
|
|
44
|
+
* - Full wildcard: "\*\/\*" matches anything
|
|
45
|
+
* - q=0 means explicitly not acceptable
|
|
46
|
+
*/
|
|
47
|
+
export declare function negotiate(input: Request | string | undefined | null, supported: string[]): string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Get response format from request or Accept header string.
|
|
50
|
+
* Only distinguishes between 'json' and 'csv'.
|
|
51
|
+
* Defaults to 'json' if no preference or Accept missing.
|
|
52
|
+
*
|
|
53
|
+
* @param input - The Request object or Accept header string
|
|
54
|
+
* @returns 'json', 'csv', or null when neither is acceptable
|
|
55
|
+
*/
|
|
56
|
+
export declare function getResponseFormat(input: Request | string | undefined | null): 'json' | 'csv' | null;
|
|
57
|
+
/**
|
|
58
|
+
* Convert an array of objects to CSV format.
|
|
59
|
+
*
|
|
60
|
+
* @param data - Array of objects (all should have same keys)
|
|
61
|
+
* @returns CSV string with headers
|
|
62
|
+
*
|
|
63
|
+
* Rules:
|
|
64
|
+
* - First row is headers (keys from first object)
|
|
65
|
+
* - Values containing commas, quotes, or newlines are quoted
|
|
66
|
+
* - Quotes in values are escaped as ""
|
|
67
|
+
* - null/undefined become empty string
|
|
68
|
+
* - Objects/arrays are JSON stringified
|
|
69
|
+
*/
|
|
70
|
+
export declare function toCSV<T extends Record<string, unknown>>(data: T[]): string;
|
|
71
|
+
//# sourceMappingURL=negotiate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiate.d.ts","sourceRoot":"","sources":["../src/negotiate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGzD;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMjD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAEvB,CAAC;AAE/B;;;;;;;;;;;;;;;;;;GAkBG;AAEH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CAqCzD;AA4KD;;;;;;;;;;;;GAYG;AAEH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAkDxG;AAED;;;;;;;GAOG;AAEH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI,CA4BnG;AA0BD;;;;;;;;;;;;GAYG;AAEH,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAkB1E"}
|