@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
@@ -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"}