@cvfile/server 0.1.0 → 0.2.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/dist/index.cjs CHANGED
@@ -3,18 +3,14 @@
3
3
  var promises = require('fs/promises');
4
4
  var path = require('path');
5
5
  var sdk = require('@cvfile/sdk');
6
+ var crypto = require('crypto');
6
7
 
7
8
  // src/handler.ts
8
9
 
9
10
  // src/conneg.ts
10
- var FORMAT_BY_MIME = {
11
- "text/markdown": "markdown",
12
- "text/x-markdown": "markdown",
13
- "text/html": "html",
14
- "application/xhtml+xml": "html",
15
- "application/pdf": "pdf",
16
- "application/vnd.cv+pdf": "pdf"
17
- };
11
+ var MARKDOWN_MIMES = /* @__PURE__ */ new Set(["text/markdown", "text/x-markdown", "application/vnd.cv+markdown"]);
12
+ var PDF_MIMES = /* @__PURE__ */ new Set(["application/pdf", "application/vnd.cv+pdf"]);
13
+ var HTML_MIMES = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml"]);
18
14
  var FORMAT_BY_QUERY = {
19
15
  md: "markdown",
20
16
  markdown: "markdown",
@@ -26,13 +22,21 @@ function parseAccept(header) {
26
22
  if (!header) return [];
27
23
  return header.split(",").map((part) => {
28
24
  const [type, ...params] = part.trim().split(";").map((s) => s.trim());
29
- let q = 1;
30
- for (const p of params) {
31
- const m = p.match(/^q\s*=\s*(\d*\.?\d+)/i);
32
- if (m) q = Number(m[1]);
33
- }
25
+ const q = parseQ(params);
26
+ if (q === null) return null;
34
27
  return { type: (type ?? "").toLowerCase(), q };
35
- }).filter((p) => p.type).sort((a, b) => b.q - a.q);
28
+ }).filter((p) => p !== null && p.type !== "" && p.q > 0).sort((a, b) => b.q - a.q);
29
+ }
30
+ function parseQ(params) {
31
+ for (const p of params) {
32
+ if (!/^q\s*=/i.test(p)) continue;
33
+ const m = p.match(/^q\s*=\s*(\d*\.?\d+)\s*$/i);
34
+ if (!m) return null;
35
+ const value = Number(m[1]);
36
+ if (Number.isNaN(value)) return null;
37
+ return Math.min(1, Math.max(0, value));
38
+ }
39
+ return 1;
36
40
  }
37
41
  function parseAcceptLanguage(header) {
38
42
  if (!header) return [];
@@ -54,20 +58,32 @@ function negotiate(input) {
54
58
  return { format: fromQuery, language };
55
59
  }
56
60
  }
57
- const accepts = parseAccept(input.accept);
58
- for (const a of accepts) {
59
- const direct = FORMAT_BY_MIME[a.type];
60
- if (direct) {
61
- return { format: direct, language };
62
- }
63
- if (a.type === "*/*" || a.type === "application/*") {
64
- return { format: "pdf", language };
65
- }
66
- if (a.type === "text/*") {
67
- return { format: "html", language };
68
- }
61
+ const fromAccept = negotiateFromAccept(input.accept);
62
+ const format = fromAccept ?? input.defaultFormat ?? "pdf";
63
+ return { format, language };
64
+ }
65
+ function negotiateFromAccept(header) {
66
+ const accepts = parseAccept(header);
67
+ if (accepts.length === 0) return void 0;
68
+ const topQ = accepts[0].q;
69
+ const top = accepts.filter((a) => a.q === topQ);
70
+ const hasWildcard = accepts.some((a) => a.type === "*/*" || a.type === "application/*");
71
+ if (top.some((a) => MARKDOWN_MIMES.has(a.type))) {
72
+ return "markdown";
73
+ }
74
+ if (top.some((a) => PDF_MIMES.has(a.type))) {
75
+ return "pdf";
76
+ }
77
+ if (top.some((a) => HTML_MIMES.has(a.type)) && !hasWildcard) {
78
+ return "html";
69
79
  }
70
- return { format: "pdf", language };
80
+ if (hasWildcard || top.some((a) => HTML_MIMES.has(a.type))) {
81
+ return "pdf";
82
+ }
83
+ if (top.some((a) => a.type === "text/*")) {
84
+ return "html";
85
+ }
86
+ return void 0;
71
87
  }
72
88
  function buildLinkHeader({ selfUrl, cvMime = "application/vnd.cv+pdf" }) {
73
89
  const sep2 = selfUrl.includes("?") ? "&" : "?";
@@ -84,7 +100,8 @@ async function serveCv(req) {
84
100
  const decision = negotiate({
85
101
  accept: req.accept,
86
102
  acceptLanguage: req.acceptLanguage,
87
- formatQuery: req.formatQuery
103
+ formatQuery: req.formatQuery,
104
+ defaultFormat: req.defaultFormat
88
105
  });
89
106
  if (decision.format === "pdf") {
90
107
  return {
@@ -101,7 +118,7 @@ async function serveCv(req) {
101
118
  if (md) {
102
119
  return {
103
120
  format: "markdown",
104
- contentType: `text/markdown; charset=utf-8; cv-language=${md.language ?? preferLang}`,
121
+ contentType: "text/markdown; charset=utf-8",
105
122
  ...md.language !== void 0 ? { language: md.language } : {},
106
123
  body: md.bytes
107
124
  };
@@ -113,7 +130,7 @@ async function serveCv(req) {
113
130
  if (html) {
114
131
  return {
115
132
  format: "html",
116
- contentType: `text/html; charset=utf-8; cv-language=${html.language ?? preferLang}`,
133
+ contentType: "text/html; charset=utf-8",
117
134
  ...html.language !== void 0 ? { language: html.language } : {},
118
135
  body: html.bytes
119
136
  };
@@ -154,6 +171,108 @@ function renderMarkdownAsHtml(md, file) {
154
171
  </html>`;
155
172
  }
156
173
 
174
+ // src/response.ts
175
+ async function buildCvResponse(input) {
176
+ const result = await serveCv({
177
+ bytes: input.bytes,
178
+ accept: input.accept,
179
+ acceptLanguage: input.acceptLanguage,
180
+ formatQuery: input.formatQuery,
181
+ defaultFormat: input.defaultFormat
182
+ });
183
+ const etag = computeETag(result.body, result.format);
184
+ const lastModified = input.lastModified?.toUTCString();
185
+ const headers = {
186
+ "Content-Type": result.contentType,
187
+ Vary: "Accept, Accept-Language",
188
+ Link: buildLinkHeader({ selfUrl: input.selfUrl, cvMime: PDF_PRIMARY_MIME }),
189
+ "Cache-Control": input.cacheControl,
190
+ ETag: etag,
191
+ "Content-Disposition": contentDisposition(input.selfUrl, result.format)
192
+ };
193
+ if (result.language) {
194
+ headers["Content-Language"] = result.language;
195
+ }
196
+ if (lastModified) {
197
+ headers["Last-Modified"] = lastModified;
198
+ }
199
+ const notModified = isNotModified({
200
+ etag,
201
+ lastModified: input.lastModified,
202
+ ifNoneMatch: input.ifNoneMatch,
203
+ ifModifiedSince: input.ifModifiedSince
204
+ });
205
+ if (notModified) {
206
+ return { status: 304, headers, format: result.format, body: new Uint8Array(0) };
207
+ }
208
+ headers["Content-Length"] = String(result.body.length);
209
+ return { status: 200, headers, format: result.format, body: result.body };
210
+ }
211
+ function computeETag(body, format) {
212
+ const hash = crypto.createHash("sha1").update(body).digest("base64url");
213
+ return `W/"${format}-${body.length.toString(16)}-${hash}"`;
214
+ }
215
+ function isNotModified({ etag, lastModified, ifNoneMatch, ifModifiedSince }) {
216
+ if (ifNoneMatch) {
217
+ return etagMatches(ifNoneMatch, etag);
218
+ }
219
+ if (ifModifiedSince && lastModified) {
220
+ const since = Date.parse(ifModifiedSince);
221
+ if (!Number.isNaN(since)) {
222
+ return Math.floor(lastModified.getTime() / 1e3) <= Math.floor(since / 1e3);
223
+ }
224
+ }
225
+ return false;
226
+ }
227
+ function etagMatches(ifNoneMatch, etag) {
228
+ if (ifNoneMatch.trim() === "*") return true;
229
+ const normalize2 = (tag) => tag.trim().replace(/^W\//, "");
230
+ const target = normalize2(etag);
231
+ return ifNoneMatch.split(",").some((candidate) => normalize2(candidate) === target);
232
+ }
233
+ function contentDisposition(selfUrl, format) {
234
+ const filename = filenameForFormat(selfUrl, format);
235
+ const asciiSafe = sanitizeAsciiFilename(filename);
236
+ const base = `inline; filename="${asciiSafe}"`;
237
+ if (!hasNonAscii(filename)) return base;
238
+ return `${base}; filename*=UTF-8''${encodeRFC5987(filename)}`;
239
+ }
240
+ function sanitizeAsciiFilename(filename) {
241
+ let out = "";
242
+ for (const ch of filename) {
243
+ const code = ch.codePointAt(0) ?? 0;
244
+ if (code < 32 || code === 127) continue;
245
+ if (ch === '"' || ch === "\\") continue;
246
+ out += code > 126 ? "_" : ch;
247
+ }
248
+ return out || "document";
249
+ }
250
+ function hasNonAscii(value) {
251
+ for (const ch of value) {
252
+ const code = ch.codePointAt(0) ?? 0;
253
+ if (code < 32 || code > 126) return true;
254
+ }
255
+ return false;
256
+ }
257
+ function filenameForFormat(selfUrl, format) {
258
+ const pathname = selfUrl.split("?")[0] ?? selfUrl;
259
+ const base = decodeOrRaw(pathname.split("/").pop() ?? "document");
260
+ const stem = base.replace(/\.cv$/i, "").replace(/\.(pdf|md|html)$/i, "") || "document";
261
+ if (format === "markdown") return `${stem}.md`;
262
+ if (format === "html") return `${stem}.html`;
263
+ return `${stem}.cv`;
264
+ }
265
+ function decodeOrRaw(value) {
266
+ try {
267
+ return decodeURIComponent(value);
268
+ } catch {
269
+ return value;
270
+ }
271
+ }
272
+ function encodeRFC5987(value) {
273
+ return encodeURIComponent(value).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
274
+ }
275
+
157
276
  // src/handler.ts
158
277
  function cvHandler(options = {}) {
159
278
  const { root, loader, cacheControl = "public, max-age=300", defaultFormat } = options;
@@ -165,47 +284,48 @@ function cvHandler(options = {}) {
165
284
  try {
166
285
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
167
286
  const logical = decodeURIComponent(url.pathname);
168
- const formatQuery = url.searchParams.get("format") ?? defaultFormat;
169
- const bytes = await load(logical, { baseRoot, loader });
170
- if (!bytes) {
287
+ const loaded = await load(logical, { baseRoot, loader });
288
+ if (!loaded) {
171
289
  res.statusCode = 404;
172
290
  res.end("Not found");
173
291
  return;
174
292
  }
175
- if (!await sdk.isCvFile(bytes)) {
293
+ if (!await sdk.isCvFile(loaded.bytes)) {
176
294
  res.statusCode = 415;
177
295
  res.end("Not a .cv file");
178
296
  return;
179
297
  }
180
- const result = await serveCv({
181
- bytes,
298
+ const built = await buildCvResponse({
299
+ bytes: loaded.bytes,
300
+ selfUrl: url.pathname,
182
301
  accept: req.headers["accept"],
183
302
  acceptLanguage: req.headers["accept-language"],
184
- formatQuery: formatQuery ?? void 0
303
+ formatQuery: url.searchParams.get("format") ?? void 0,
304
+ defaultFormat,
305
+ cacheControl,
306
+ lastModified: loaded.lastModified,
307
+ ifNoneMatch: req.headers["if-none-match"],
308
+ ifModifiedSince: req.headers["if-modified-since"]
185
309
  });
186
- const link = buildLinkHeader({ selfUrl: url.pathname, cvMime: PDF_PRIMARY_MIME });
187
- res.setHeader("Content-Type", result.contentType);
188
- res.setHeader("Content-Length", String(result.body.length));
189
- res.setHeader("Vary", "Accept, Accept-Language");
190
- res.setHeader("Link", link);
191
- res.setHeader("Cache-Control", cacheControl);
192
- if (result.language) {
193
- res.setHeader("Content-Language", result.language);
310
+ for (const [name, value] of Object.entries(built.headers)) {
311
+ res.setHeader(name, value);
312
+ }
313
+ res.statusCode = built.status;
314
+ res.end(built.status === 304 ? void 0 : Buffer.from(built.body));
315
+ } catch {
316
+ if (res.headersSent) {
317
+ res.end();
318
+ return;
194
319
  }
195
- const filename = filenameForFormat(logical, result.format);
196
- res.setHeader("Content-Disposition", `inline; filename="${filename}"`);
197
- res.statusCode = 200;
198
- res.end(Buffer.from(result.body));
199
- } catch (err) {
200
320
  res.statusCode = 500;
201
- res.end(`cvHandler error: ${err.message}`);
321
+ res.end("Internal Server Error");
202
322
  }
203
323
  };
204
324
  }
205
325
  async function load(logicalPath, { baseRoot, loader }) {
206
326
  if (loader) {
207
327
  const bytes = await loader(logicalPath);
208
- return bytes ?? null;
328
+ return bytes ? { bytes } : null;
209
329
  }
210
330
  if (!baseRoot) return null;
211
331
  const safe = path.normalize(logicalPath).replace(/^[/\\]+/, "");
@@ -216,7 +336,7 @@ async function load(logicalPath, { baseRoot, loader }) {
216
336
  try {
217
337
  const s = await promises.stat(full);
218
338
  if (!s.isFile()) return null;
219
- return new Uint8Array(await promises.readFile(full));
339
+ return { bytes: new Uint8Array(await promises.readFile(full)), lastModified: s.mtime };
220
340
  } catch {
221
341
  return null;
222
342
  }
@@ -226,13 +346,6 @@ function isWithin(parent, child) {
226
346
  const base = path.resolve(parent);
227
347
  return rel === base || rel.startsWith(base + path.sep);
228
348
  }
229
- function filenameForFormat(logical, format) {
230
- const base = logical.split("/").pop() ?? "document";
231
- const stem = base.replace(/\.cv$/i, "").replace(/\.(pdf|md|html)$/i, "") || "document";
232
- if (format === "markdown") return `${stem}.md`;
233
- if (format === "html") return `${stem}.html`;
234
- return `${stem}.cv`;
235
- }
236
349
 
237
350
  exports.PDF_FALLBACK_MIME = PDF_FALLBACK_MIME;
238
351
  exports.PDF_PRIMARY_MIME = PDF_PRIMARY_MIME;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/conneg.ts","../src/serve.ts","../src/handler.ts"],"names":["sep","extract","resolve","isCvFile","normalize","stat","readFile"],"mappings":"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","file":"index.cjs","sourcesContent":["export type ServeFormat = 'pdf' | 'markdown' | 'html';\n\nexport interface NegotiationInput {\n accept?: string | undefined;\n acceptLanguage?: string | undefined;\n formatQuery?: string | undefined;\n}\n\nexport interface NegotiationResult {\n format: ServeFormat;\n language: string | undefined;\n}\n\nconst FORMAT_BY_MIME: Record<string, ServeFormat> = {\n 'text/markdown': 'markdown',\n 'text/x-markdown': 'markdown',\n 'text/html': 'html',\n 'application/xhtml+xml': 'html',\n 'application/pdf': 'pdf',\n 'application/vnd.cv+pdf': 'pdf',\n};\n\nconst FORMAT_BY_QUERY: Record<string, ServeFormat> = {\n md: 'markdown',\n markdown: 'markdown',\n html: 'html',\n pdf: 'pdf',\n cv: 'pdf',\n};\n\ninterface ParsedAccept {\n type: string;\n q: number;\n}\n\nexport function parseAccept(header: string | undefined | null): ParsedAccept[] {\n if (!header) return [];\n return header\n .split(',')\n .map((part) => {\n const [type, ...params] = part.trim().split(';').map((s) => s.trim());\n let q = 1;\n for (const p of params) {\n const m = p.match(/^q\\s*=\\s*(\\d*\\.?\\d+)/i);\n if (m) q = Number(m[1]);\n }\n return { type: (type ?? '').toLowerCase(), q };\n })\n .filter((p) => p.type)\n .sort((a, b) => b.q - a.q);\n}\n\nexport function parseAcceptLanguage(header: string | undefined | null): string[] {\n if (!header) return [];\n return header\n .split(',')\n .map((part) => {\n const [tag, ...params] = part.trim().split(';').map((s) => s.trim());\n let q = 1;\n for (const p of params) {\n const m = p.match(/^q\\s*=\\s*(\\d*\\.?\\d+)/i);\n if (m) q = Number(m[1]);\n }\n return { tag: (tag ?? '').toLowerCase(), q };\n })\n .filter((p) => p.tag && p.tag !== '*')\n .sort((a, b) => b.q - a.q)\n .map((p) => p.tag);\n}\n\nexport function negotiate(input: NegotiationInput): NegotiationResult {\n const language = parseAcceptLanguage(input.acceptLanguage)[0];\n\n if (input.formatQuery) {\n const fromQuery = FORMAT_BY_QUERY[input.formatQuery.toLowerCase()];\n if (fromQuery) {\n return { format: fromQuery, language };\n }\n }\n\n const accepts = parseAccept(input.accept);\n for (const a of accepts) {\n const direct = FORMAT_BY_MIME[a.type];\n if (direct) {\n return { format: direct, language };\n }\n if (a.type === '*/*' || a.type === 'application/*') {\n return { format: 'pdf', language };\n }\n if (a.type === 'text/*') {\n return { format: 'html', language };\n }\n }\n\n return { format: 'pdf', language };\n}\n\nexport interface BuildLinkHeaderInput {\n selfUrl: string;\n cvMime?: string;\n}\n\nexport function buildLinkHeader({ selfUrl, cvMime = 'application/vnd.cv+pdf' }: BuildLinkHeaderInput): string {\n const sep = selfUrl.includes('?') ? '&' : '?';\n return [\n `<${selfUrl}>; rel=\"alternate\"; type=\"${cvMime}\"`,\n `<${selfUrl}${sep}format=md>; rel=\"alternate\"; type=\"text/markdown\"`,\n `<${selfUrl}${sep}format=html>; rel=\"alternate\"; type=\"text/html\"`,\n ].join(', ');\n}\n\nexport const PDF_PRIMARY_MIME = 'application/vnd.cv+pdf';\nexport const PDF_FALLBACK_MIME = 'application/pdf';\n","import { extract } from '@cvfile/sdk';\nimport type { CvFile, ExtractedPayload } from '@cvfile/sdk';\nimport { negotiate, type ServeFormat } from './conneg.js';\n\nexport interface ServeRequest {\n bytes: Uint8Array;\n accept?: string | undefined;\n acceptLanguage?: string | undefined;\n formatQuery?: string | undefined;\n}\n\nexport interface ServeResponse {\n format: ServeFormat;\n contentType: string;\n language?: string | undefined;\n body: Uint8Array;\n}\n\nconst ENCODER = new TextEncoder();\n\nexport async function serveCv(req: ServeRequest): Promise<ServeResponse> {\n const decision = negotiate({\n accept: req.accept,\n acceptLanguage: req.acceptLanguage,\n formatQuery: req.formatQuery,\n });\n\n if (decision.format === 'pdf') {\n return {\n format: 'pdf',\n contentType: 'application/vnd.cv+pdf',\n ...(decision.language !== undefined ? { language: decision.language } : {}),\n body: req.bytes,\n };\n }\n\n const file = await extract(req.bytes);\n const preferLang = decision.language ?? file.metadata.primaryLanguage;\n\n if (decision.format === 'markdown') {\n const md = pickPayload(file, 'text/markdown', preferLang);\n if (md) {\n return {\n format: 'markdown',\n contentType: `text/markdown; charset=utf-8; cv-language=${md.language ?? preferLang}`,\n ...(md.language !== undefined ? { language: md.language } : {}),\n body: md.bytes,\n };\n }\n return fallbackToPdf(req.bytes);\n }\n\n if (decision.format === 'html') {\n const html = pickPayload(file, 'text/html', preferLang);\n if (html) {\n return {\n format: 'html',\n contentType: `text/html; charset=utf-8; cv-language=${html.language ?? preferLang}`,\n ...(html.language !== undefined ? { language: html.language } : {}),\n body: html.bytes,\n };\n }\n const md = pickPayload(file, 'text/markdown', preferLang);\n if (md) {\n const body = ENCODER.encode(renderMarkdownAsHtml(md.text(), file));\n return {\n format: 'html',\n contentType: 'text/html; charset=utf-8',\n ...(md.language !== undefined ? { language: md.language } : {}),\n body,\n };\n }\n return fallbackToPdf(req.bytes);\n }\n\n return fallbackToPdf(req.bytes);\n}\n\nfunction pickPayload(file: CvFile, mimeType: string, preferLang: string): ExtractedPayload | undefined {\n const matches = file.payloads.filter((p) => p.mimeType === mimeType);\n if (matches.length === 0) return undefined;\n return matches.find((p) => p.language === preferLang) ?? matches[0];\n}\n\nfunction fallbackToPdf(bytes: Uint8Array): ServeResponse {\n return { format: 'pdf', contentType: 'application/vnd.cv+pdf', body: bytes };\n}\n\nfunction renderMarkdownAsHtml(md: string, file: CvFile): string {\n const safe = md\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n return `<!doctype html>\n<html lang=\"${file.metadata.primaryLanguage}\">\n<head>\n<meta charset=\"utf-8\">\n<title>${file.metadata.primaryPayload}</title>\n</head>\n<body>\n<pre>${safe}</pre>\n</body>\n</html>`;\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { readFile, stat } from 'node:fs/promises';\nimport { normalize, resolve, sep } from 'node:path';\nimport { isCvFile } from '@cvfile/sdk';\nimport { buildLinkHeader, PDF_PRIMARY_MIME } from './conneg.js';\nimport { serveCv } from './serve.js';\n\nexport interface CvHandlerOptions {\n root?: string;\n loader?: (logicalPath: string) => Promise<Uint8Array | null>;\n cacheControl?: string;\n defaultFormat?: 'pdf' | 'markdown' | 'html';\n}\n\nexport type CvHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport function cvHandler(options: CvHandlerOptions = {}): CvHandler {\n const { root, loader, cacheControl = 'public, max-age=300', defaultFormat } = options;\n if (!root && !loader) {\n throw new Error('cvHandler requires either { root } or { loader }');\n }\n\n const baseRoot = root ? resolve(root) : null;\n\n return async (req, res) => {\n try {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const logical = decodeURIComponent(url.pathname);\n const formatQuery = url.searchParams.get('format') ?? defaultFormat;\n\n const bytes = await load(logical, { baseRoot, loader });\n if (!bytes) {\n res.statusCode = 404;\n res.end('Not found');\n return;\n }\n\n if (!(await isCvFile(bytes))) {\n res.statusCode = 415;\n res.end('Not a .cv file');\n return;\n }\n\n const result = await serveCv({\n bytes,\n accept: req.headers['accept'],\n acceptLanguage: req.headers['accept-language'],\n formatQuery: formatQuery ?? undefined,\n });\n\n const link = buildLinkHeader({ selfUrl: url.pathname, cvMime: PDF_PRIMARY_MIME });\n\n res.setHeader('Content-Type', result.contentType);\n res.setHeader('Content-Length', String(result.body.length));\n res.setHeader('Vary', 'Accept, Accept-Language');\n res.setHeader('Link', link);\n res.setHeader('Cache-Control', cacheControl);\n if (result.language) {\n res.setHeader('Content-Language', result.language);\n }\n const filename = filenameForFormat(logical, result.format);\n res.setHeader('Content-Disposition', `inline; filename=\"${filename}\"`);\n res.statusCode = 200;\n res.end(Buffer.from(result.body));\n } catch (err) {\n res.statusCode = 500;\n res.end(`cvHandler error: ${(err as Error).message}`);\n }\n };\n}\n\ninterface LoadOpts {\n baseRoot: string | null;\n loader?: ((logicalPath: string) => Promise<Uint8Array | null>) | undefined;\n}\n\nasync function load(logicalPath: string, { baseRoot, loader }: LoadOpts): Promise<Uint8Array | null> {\n if (loader) {\n const bytes = await loader(logicalPath);\n return bytes ?? null;\n }\n if (!baseRoot) return null;\n const safe = normalize(logicalPath).replace(/^[/\\\\]+/, '');\n const full = resolve(baseRoot, safe);\n if (!isWithin(baseRoot, full)) {\n return null;\n }\n try {\n const s = await stat(full);\n if (!s.isFile()) return null;\n return new Uint8Array(await readFile(full));\n } catch {\n return null;\n }\n}\n\nfunction isWithin(parent: string, child: string): boolean {\n const rel = resolve(child);\n const base = resolve(parent);\n return rel === base || rel.startsWith(base + sep);\n}\n\nfunction filenameForFormat(logical: string, format: 'pdf' | 'markdown' | 'html'): string {\n const base = logical.split('/').pop() ?? 'document';\n const stem = base.replace(/\\.cv$/i, '').replace(/\\.(pdf|md|html)$/i, '') || 'document';\n if (format === 'markdown') return `${stem}.md`;\n if (format === 'html') return `${stem}.html`;\n return `${stem}.cv`;\n}\n\n"]}
1
+ {"version":3,"sources":["../src/conneg.ts","../src/serve.ts","../src/response.ts","../src/handler.ts"],"names":["sep","extract","createHash","normalize","resolve","isCvFile","stat","readFile"],"mappings":"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","file":"index.cjs","sourcesContent":["export type ServeFormat = 'pdf' | 'markdown' | 'html';\n\nexport interface NegotiationInput {\n accept?: string | undefined;\n acceptLanguage?: string | undefined;\n formatQuery?: string | undefined;\n defaultFormat?: ServeFormat | undefined;\n}\n\nexport interface NegotiationResult {\n format: ServeFormat;\n language: string | undefined;\n}\n\nconst MARKDOWN_MIMES = new Set(['text/markdown', 'text/x-markdown', 'application/vnd.cv+markdown']);\nconst PDF_MIMES = new Set(['application/pdf', 'application/vnd.cv+pdf']);\nconst HTML_MIMES = new Set(['text/html', 'application/xhtml+xml']);\n\nconst FORMAT_BY_QUERY: Record<string, ServeFormat> = {\n md: 'markdown',\n markdown: 'markdown',\n html: 'html',\n pdf: 'pdf',\n cv: 'pdf',\n};\n\ninterface ParsedAccept {\n type: string;\n q: number;\n}\n\nexport function parseAccept(header: string | undefined | null): ParsedAccept[] {\n if (!header) return [];\n return header\n .split(',')\n .map((part): ParsedAccept | null => {\n const [type, ...params] = part.trim().split(';').map((s) => s.trim());\n const q = parseQ(params);\n // A malformed q (present but unparseable) marks the type as unusable per RFC 9110.\n if (q === null) return null;\n return { type: (type ?? '').toLowerCase(), q };\n })\n .filter((p): p is ParsedAccept => p !== null && p.type !== '' && p.q > 0)\n .sort((a, b) => b.q - a.q);\n}\n\n/**\n * Resolve the q-value of a media-range's parameters.\n * Returns 1 when absent, the clamped [0,1] value when valid, and null when a\n * q parameter is present but cannot be parsed (signalling a malformed type).\n */\nfunction parseQ(params: string[]): number | null {\n for (const p of params) {\n if (!/^q\\s*=/i.test(p)) continue;\n const m = p.match(/^q\\s*=\\s*(\\d*\\.?\\d+)\\s*$/i);\n if (!m) return null;\n const value = Number(m[1]);\n if (Number.isNaN(value)) return null;\n return Math.min(1, Math.max(0, value));\n }\n return 1;\n}\n\nexport function parseAcceptLanguage(header: string | undefined | null): string[] {\n if (!header) return [];\n return header\n .split(',')\n .map((part) => {\n const [tag, ...params] = part.trim().split(';').map((s) => s.trim());\n let q = 1;\n for (const p of params) {\n const m = p.match(/^q\\s*=\\s*(\\d*\\.?\\d+)/i);\n if (m) q = Number(m[1]);\n }\n return { tag: (tag ?? '').toLowerCase(), q };\n })\n .filter((p) => p.tag && p.tag !== '*')\n .sort((a, b) => b.q - a.q)\n .map((p) => p.tag);\n}\n\nexport function negotiate(input: NegotiationInput): NegotiationResult {\n const language = parseAcceptLanguage(input.acceptLanguage)[0];\n\n // An explicit ?format= query is the only override and wins over Accept.\n if (input.formatQuery) {\n const fromQuery = FORMAT_BY_QUERY[input.formatQuery.toLowerCase()];\n if (fromQuery) {\n return { format: fromQuery, language };\n }\n }\n\n const fromAccept = negotiateFromAccept(input.accept);\n const format = fromAccept ?? input.defaultFormat ?? 'pdf';\n return { format, language };\n}\n\n/**\n * Map an Accept header to a format following the .cv contract:\n * - markdown only when it is an explicit, top, non-wildcard preference;\n * - html only when text/html is requested without a wildcard (a deliberate fetch);\n * - pdf for the browser case (text/html alongside a wildcard, or any wildcard);\n * - undefined when the header expresses no usable preference (caller falls back).\n */\nfunction negotiateFromAccept(header: string | undefined): ServeFormat | undefined {\n const accepts = parseAccept(header);\n if (accepts.length === 0) return undefined;\n\n const topQ = accepts[0]!.q;\n const top = accepts.filter((a) => a.q === topQ);\n const hasWildcard = accepts.some((a) => a.type === '*/*' || a.type === 'application/*');\n\n // Markdown wins only as an explicit, top, non-wildcard preference.\n if (top.some((a) => MARKDOWN_MIMES.has(a.type))) {\n return 'markdown';\n }\n\n // An explicit, top preference for the PDF type also serves PDF.\n if (top.some((a) => PDF_MIMES.has(a.type))) {\n return 'pdf';\n }\n\n // A deliberate HTML fetch: text/html requested without a catch-all wildcard.\n if (top.some((a) => HTML_MIMES.has(a.type)) && !hasWildcard) {\n return 'html';\n }\n\n // Browser case (text/html + */*) or any wildcard: serve the visual PDF.\n if (hasWildcard || top.some((a) => HTML_MIMES.has(a.type))) {\n return 'pdf';\n }\n\n // text/* (without a more specific match) is a deliberate text fetch -> html.\n if (top.some((a) => a.type === 'text/*')) {\n return 'html';\n }\n\n return undefined;\n}\n\nexport interface BuildLinkHeaderInput {\n selfUrl: string;\n cvMime?: string;\n}\n\nexport function buildLinkHeader({ selfUrl, cvMime = 'application/vnd.cv+pdf' }: BuildLinkHeaderInput): string {\n const sep = selfUrl.includes('?') ? '&' : '?';\n return [\n `<${selfUrl}>; rel=\"alternate\"; type=\"${cvMime}\"`,\n `<${selfUrl}${sep}format=md>; rel=\"alternate\"; type=\"text/markdown\"`,\n `<${selfUrl}${sep}format=html>; rel=\"alternate\"; type=\"text/html\"`,\n ].join(', ');\n}\n\nexport const PDF_PRIMARY_MIME = 'application/vnd.cv+pdf';\nexport const PDF_FALLBACK_MIME = 'application/pdf';\n","import { extract } from '@cvfile/sdk';\nimport type { CvFile, ExtractedPayload } from '@cvfile/sdk';\nimport { negotiate, type ServeFormat } from './conneg.js';\n\nexport interface ServeRequest {\n bytes: Uint8Array;\n accept?: string | undefined;\n acceptLanguage?: string | undefined;\n formatQuery?: string | undefined;\n defaultFormat?: ServeFormat | undefined;\n}\n\nexport interface ServeResponse {\n format: ServeFormat;\n contentType: string;\n language?: string | undefined;\n body: Uint8Array;\n}\n\nconst ENCODER = new TextEncoder();\n\nexport async function serveCv(req: ServeRequest): Promise<ServeResponse> {\n const decision = negotiate({\n accept: req.accept,\n acceptLanguage: req.acceptLanguage,\n formatQuery: req.formatQuery,\n defaultFormat: req.defaultFormat,\n });\n\n if (decision.format === 'pdf') {\n return {\n format: 'pdf',\n contentType: 'application/vnd.cv+pdf',\n ...(decision.language !== undefined ? { language: decision.language } : {}),\n body: req.bytes,\n };\n }\n\n const file = await extract(req.bytes);\n const preferLang = decision.language ?? file.metadata.primaryLanguage;\n\n if (decision.format === 'markdown') {\n const md = pickPayload(file, 'text/markdown', preferLang);\n if (md) {\n return {\n format: 'markdown',\n contentType: 'text/markdown; charset=utf-8',\n ...(md.language !== undefined ? { language: md.language } : {}),\n body: md.bytes,\n };\n }\n return fallbackToPdf(req.bytes);\n }\n\n if (decision.format === 'html') {\n const html = pickPayload(file, 'text/html', preferLang);\n if (html) {\n return {\n format: 'html',\n contentType: 'text/html; charset=utf-8',\n ...(html.language !== undefined ? { language: html.language } : {}),\n body: html.bytes,\n };\n }\n const md = pickPayload(file, 'text/markdown', preferLang);\n if (md) {\n const body = ENCODER.encode(renderMarkdownAsHtml(md.text(), file));\n return {\n format: 'html',\n contentType: 'text/html; charset=utf-8',\n ...(md.language !== undefined ? { language: md.language } : {}),\n body,\n };\n }\n return fallbackToPdf(req.bytes);\n }\n\n return fallbackToPdf(req.bytes);\n}\n\nfunction pickPayload(file: CvFile, mimeType: string, preferLang: string): ExtractedPayload | undefined {\n const matches = file.payloads.filter((p) => p.mimeType === mimeType);\n if (matches.length === 0) return undefined;\n return matches.find((p) => p.language === preferLang) ?? matches[0];\n}\n\nfunction fallbackToPdf(bytes: Uint8Array): ServeResponse {\n return { format: 'pdf', contentType: 'application/vnd.cv+pdf', body: bytes };\n}\n\nfunction renderMarkdownAsHtml(md: string, file: CvFile): string {\n const safe = md\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n return `<!doctype html>\n<html lang=\"${file.metadata.primaryLanguage}\">\n<head>\n<meta charset=\"utf-8\">\n<title>${file.metadata.primaryPayload}</title>\n</head>\n<body>\n<pre>${safe}</pre>\n</body>\n</html>`;\n}\n","import { createHash } from 'node:crypto';\nimport { buildLinkHeader, PDF_PRIMARY_MIME, type ServeFormat } from './conneg.js';\nimport { serveCv } from './serve.js';\n\nexport interface BuildResponseInput {\n bytes: Uint8Array;\n selfUrl: string;\n accept?: string | undefined;\n acceptLanguage?: string | undefined;\n formatQuery?: string | undefined;\n defaultFormat?: ServeFormat | undefined;\n cacheControl: string;\n lastModified?: Date | undefined;\n ifNoneMatch?: string | undefined;\n ifModifiedSince?: string | undefined;\n}\n\nexport interface BuiltResponse {\n status: 200 | 304;\n headers: Record<string, string>;\n format: ServeFormat;\n body: Uint8Array;\n}\n\n/**\n * Negotiate the format and assemble the full set of response headers shared by\n * every adapter. The same URL yields a different body per negotiated format, so\n * the ETag is keyed on both the bytes and the format. Returns a 304 (empty body)\n * when the conditional request headers match.\n */\nexport async function buildCvResponse(input: BuildResponseInput): Promise<BuiltResponse> {\n const result = await serveCv({\n bytes: input.bytes,\n accept: input.accept,\n acceptLanguage: input.acceptLanguage,\n formatQuery: input.formatQuery,\n defaultFormat: input.defaultFormat,\n });\n\n const etag = computeETag(result.body, result.format);\n const lastModified = input.lastModified?.toUTCString();\n\n const headers: Record<string, string> = {\n 'Content-Type': result.contentType,\n Vary: 'Accept, Accept-Language',\n Link: buildLinkHeader({ selfUrl: input.selfUrl, cvMime: PDF_PRIMARY_MIME }),\n 'Cache-Control': input.cacheControl,\n ETag: etag,\n 'Content-Disposition': contentDisposition(input.selfUrl, result.format),\n };\n if (result.language) {\n headers['Content-Language'] = result.language;\n }\n if (lastModified) {\n headers['Last-Modified'] = lastModified;\n }\n\n const notModified = isNotModified({\n etag,\n lastModified: input.lastModified,\n ifNoneMatch: input.ifNoneMatch,\n ifModifiedSince: input.ifModifiedSince,\n });\n if (notModified) {\n return { status: 304, headers, format: result.format, body: new Uint8Array(0) };\n }\n\n headers['Content-Length'] = String(result.body.length);\n return { status: 200, headers, format: result.format, body: result.body };\n}\n\n/** Weak ETag keyed on the negotiated body so each format gets a distinct tag. */\nfunction computeETag(body: Uint8Array, format: ServeFormat): string {\n const hash = createHash('sha1').update(body).digest('base64url');\n return `W/\"${format}-${body.length.toString(16)}-${hash}\"`;\n}\n\ninterface NotModifiedInput {\n etag: string;\n lastModified?: Date | undefined;\n ifNoneMatch?: string | undefined;\n ifModifiedSince?: string | undefined;\n}\n\nfunction isNotModified({ etag, lastModified, ifNoneMatch, ifModifiedSince }: NotModifiedInput): boolean {\n if (ifNoneMatch) {\n return etagMatches(ifNoneMatch, etag);\n }\n if (ifModifiedSince && lastModified) {\n const since = Date.parse(ifModifiedSince);\n if (!Number.isNaN(since)) {\n // Compare at second resolution, matching HTTP-date granularity.\n return Math.floor(lastModified.getTime() / 1000) <= Math.floor(since / 1000);\n }\n }\n return false;\n}\n\nfunction etagMatches(ifNoneMatch: string, etag: string): boolean {\n if (ifNoneMatch.trim() === '*') return true;\n const normalize = (tag: string): string => tag.trim().replace(/^W\\//, '');\n const target = normalize(etag);\n return ifNoneMatch.split(',').some((candidate) => normalize(candidate) === target);\n}\n\n/**\n * Build a header-injection-safe Content-Disposition value. Control characters,\n * CR/LF, quotes and backslashes are stripped from the ASCII filename; non-ASCII\n * names also get an RFC 5987 filename* form.\n */\nexport function contentDisposition(selfUrl: string, format: ServeFormat): string {\n const filename = filenameForFormat(selfUrl, format);\n const asciiSafe = sanitizeAsciiFilename(filename);\n const base = `inline; filename=\"${asciiSafe}\"`;\n if (!hasNonAscii(filename)) return base;\n return `${base}; filename*=UTF-8''${encodeRFC5987(filename)}`;\n}\n\n/**\n * Produce a safe quoted-string filename: drop control chars (CR/LF/DEL),\n * double quotes and backslashes; replace any remaining non-ASCII with '_'.\n */\nfunction sanitizeAsciiFilename(filename: string): string {\n let out = '';\n for (const ch of filename) {\n const code = ch.codePointAt(0) ?? 0;\n if (code < 0x20 || code === 0x7f) continue; // control chars incl. CR/LF\n if (ch === '\"' || ch === '\\\\') continue; // quoted-string delimiters\n out += code > 0x7e ? '_' : ch; // collapse non-ASCII to underscore\n }\n return out || 'document';\n}\n\nfunction hasNonAscii(value: string): boolean {\n for (const ch of value) {\n const code = ch.codePointAt(0) ?? 0;\n if (code < 0x20 || code > 0x7e) return true;\n }\n return false;\n}\n\nfunction filenameForFormat(selfUrl: string, format: ServeFormat): string {\n const pathname = selfUrl.split('?')[0] ?? selfUrl;\n const base = decodeOrRaw(pathname.split('/').pop() ?? 'document');\n const stem = base.replace(/\\.cv$/i, '').replace(/\\.(pdf|md|html)$/i, '') || 'document';\n if (format === 'markdown') return `${stem}.md`;\n if (format === 'html') return `${stem}.html`;\n return `${stem}.cv`;\n}\n\nfunction decodeOrRaw(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\nfunction encodeRFC5987(value: string): string {\n return encodeURIComponent(value).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { readFile, stat } from 'node:fs/promises';\nimport { normalize, resolve, sep } from 'node:path';\nimport { isCvFile } from '@cvfile/sdk';\nimport type { ServeFormat } from './conneg.js';\nimport { buildCvResponse } from './response.js';\n\nexport interface CvHandlerOptions {\n root?: string;\n loader?: (logicalPath: string) => Promise<Uint8Array | null>;\n cacheControl?: string;\n defaultFormat?: ServeFormat;\n}\n\nexport type CvHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport function cvHandler(options: CvHandlerOptions = {}): CvHandler {\n const { root, loader, cacheControl = 'public, max-age=300', defaultFormat } = options;\n if (!root && !loader) {\n throw new Error('cvHandler requires either { root } or { loader }');\n }\n\n const baseRoot = root ? resolve(root) : null;\n\n return async (req, res) => {\n try {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const logical = decodeURIComponent(url.pathname);\n\n const loaded = await load(logical, { baseRoot, loader });\n if (!loaded) {\n res.statusCode = 404;\n res.end('Not found');\n return;\n }\n\n if (!(await isCvFile(loaded.bytes))) {\n res.statusCode = 415;\n res.end('Not a .cv file');\n return;\n }\n\n const built = await buildCvResponse({\n bytes: loaded.bytes,\n selfUrl: url.pathname,\n accept: req.headers['accept'],\n acceptLanguage: req.headers['accept-language'],\n formatQuery: url.searchParams.get('format') ?? undefined,\n defaultFormat,\n cacheControl,\n lastModified: loaded.lastModified,\n ifNoneMatch: req.headers['if-none-match'],\n ifModifiedSince: req.headers['if-modified-since'],\n });\n\n for (const [name, value] of Object.entries(built.headers)) {\n res.setHeader(name, value);\n }\n res.statusCode = built.status;\n res.end(built.status === 304 ? undefined : Buffer.from(built.body));\n } catch {\n if (res.headersSent) {\n res.end();\n return;\n }\n res.statusCode = 500;\n res.end('Internal Server Error');\n }\n };\n}\n\ninterface LoadOpts {\n baseRoot: string | null;\n loader?: ((logicalPath: string) => Promise<Uint8Array | null>) | undefined;\n}\n\ninterface LoadedFile {\n bytes: Uint8Array;\n lastModified?: Date | undefined;\n}\n\nasync function load(logicalPath: string, { baseRoot, loader }: LoadOpts): Promise<LoadedFile | null> {\n if (loader) {\n const bytes = await loader(logicalPath);\n return bytes ? { bytes } : null;\n }\n if (!baseRoot) return null;\n const safe = normalize(logicalPath).replace(/^[/\\\\]+/, '');\n const full = resolve(baseRoot, safe);\n if (!isWithin(baseRoot, full)) {\n return null;\n }\n try {\n const s = await stat(full);\n if (!s.isFile()) return null;\n return { bytes: new Uint8Array(await readFile(full)), lastModified: s.mtime };\n } catch {\n return null;\n }\n}\n\nfunction isWithin(parent: string, child: string): boolean {\n const rel = resolve(child);\n const base = resolve(parent);\n return rel === base || rel.startsWith(base + sep);\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,36 +1,14 @@
1
- export { C as CvHandler, a as CvHandlerOptions, c as cvHandler } from './handler-DGnThUpM.cjs';
1
+ export { C as CvHandler, a as CvHandlerOptions, c as cvHandler } from './handler-DMqP6HcG.cjs';
2
+ import { S as ServeFormat } from './conneg-DbPVX8oc.cjs';
3
+ export { B as BuildLinkHeaderInput, N as NegotiationInput, a as NegotiationResult, P as PDF_FALLBACK_MIME, b as PDF_PRIMARY_MIME, c as buildLinkHeader, n as negotiate, p as parseAccept, d as parseAcceptLanguage } from './conneg-DbPVX8oc.cjs';
2
4
  import 'node:http';
3
5
 
4
- type ServeFormat = 'pdf' | 'markdown' | 'html';
5
- interface NegotiationInput {
6
- accept?: string | undefined;
7
- acceptLanguage?: string | undefined;
8
- formatQuery?: string | undefined;
9
- }
10
- interface NegotiationResult {
11
- format: ServeFormat;
12
- language: string | undefined;
13
- }
14
- interface ParsedAccept {
15
- type: string;
16
- q: number;
17
- }
18
- declare function parseAccept(header: string | undefined | null): ParsedAccept[];
19
- declare function parseAcceptLanguage(header: string | undefined | null): string[];
20
- declare function negotiate(input: NegotiationInput): NegotiationResult;
21
- interface BuildLinkHeaderInput {
22
- selfUrl: string;
23
- cvMime?: string;
24
- }
25
- declare function buildLinkHeader({ selfUrl, cvMime }: BuildLinkHeaderInput): string;
26
- declare const PDF_PRIMARY_MIME = "application/vnd.cv+pdf";
27
- declare const PDF_FALLBACK_MIME = "application/pdf";
28
-
29
6
  interface ServeRequest {
30
7
  bytes: Uint8Array;
31
8
  accept?: string | undefined;
32
9
  acceptLanguage?: string | undefined;
33
10
  formatQuery?: string | undefined;
11
+ defaultFormat?: ServeFormat | undefined;
34
12
  }
35
13
  interface ServeResponse {
36
14
  format: ServeFormat;
@@ -40,4 +18,4 @@ interface ServeResponse {
40
18
  }
41
19
  declare function serveCv(req: ServeRequest): Promise<ServeResponse>;
42
20
 
43
- export { type BuildLinkHeaderInput, type NegotiationInput, type NegotiationResult, PDF_FALLBACK_MIME, PDF_PRIMARY_MIME, type ServeFormat, type ServeRequest, type ServeResponse, buildLinkHeader, negotiate, parseAccept, parseAcceptLanguage, serveCv };
21
+ export { ServeFormat, type ServeRequest, type ServeResponse, serveCv };
package/dist/index.d.ts CHANGED
@@ -1,36 +1,14 @@
1
- export { C as CvHandler, a as CvHandlerOptions, c as cvHandler } from './handler-DGnThUpM.js';
1
+ export { C as CvHandler, a as CvHandlerOptions, c as cvHandler } from './handler-CP5HUtCf.js';
2
+ import { S as ServeFormat } from './conneg-DbPVX8oc.js';
3
+ export { B as BuildLinkHeaderInput, N as NegotiationInput, a as NegotiationResult, P as PDF_FALLBACK_MIME, b as PDF_PRIMARY_MIME, c as buildLinkHeader, n as negotiate, p as parseAccept, d as parseAcceptLanguage } from './conneg-DbPVX8oc.js';
2
4
  import 'node:http';
3
5
 
4
- type ServeFormat = 'pdf' | 'markdown' | 'html';
5
- interface NegotiationInput {
6
- accept?: string | undefined;
7
- acceptLanguage?: string | undefined;
8
- formatQuery?: string | undefined;
9
- }
10
- interface NegotiationResult {
11
- format: ServeFormat;
12
- language: string | undefined;
13
- }
14
- interface ParsedAccept {
15
- type: string;
16
- q: number;
17
- }
18
- declare function parseAccept(header: string | undefined | null): ParsedAccept[];
19
- declare function parseAcceptLanguage(header: string | undefined | null): string[];
20
- declare function negotiate(input: NegotiationInput): NegotiationResult;
21
- interface BuildLinkHeaderInput {
22
- selfUrl: string;
23
- cvMime?: string;
24
- }
25
- declare function buildLinkHeader({ selfUrl, cvMime }: BuildLinkHeaderInput): string;
26
- declare const PDF_PRIMARY_MIME = "application/vnd.cv+pdf";
27
- declare const PDF_FALLBACK_MIME = "application/pdf";
28
-
29
6
  interface ServeRequest {
30
7
  bytes: Uint8Array;
31
8
  accept?: string | undefined;
32
9
  acceptLanguage?: string | undefined;
33
10
  formatQuery?: string | undefined;
11
+ defaultFormat?: ServeFormat | undefined;
34
12
  }
35
13
  interface ServeResponse {
36
14
  format: ServeFormat;
@@ -40,4 +18,4 @@ interface ServeResponse {
40
18
  }
41
19
  declare function serveCv(req: ServeRequest): Promise<ServeResponse>;
42
20
 
43
- export { type BuildLinkHeaderInput, type NegotiationInput, type NegotiationResult, PDF_FALLBACK_MIME, PDF_PRIMARY_MIME, type ServeFormat, type ServeRequest, type ServeResponse, buildLinkHeader, negotiate, parseAccept, parseAcceptLanguage, serveCv };
21
+ export { ServeFormat, type ServeRequest, type ServeResponse, serveCv };