@cvfile/server 0.1.0 → 0.3.1
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/conneg-DbPVX8oc.d.cts +27 -0
- package/dist/conneg-DbPVX8oc.d.ts +27 -0
- package/dist/express.cjs +173 -60
- package/dist/express.cjs.map +1 -1
- package/dist/express.d.cts +2 -1
- package/dist/express.d.ts +2 -1
- package/dist/express.js +173 -60
- package/dist/express.js.map +1 -1
- package/dist/fastify.cjs +176 -61
- package/dist/fastify.cjs.map +1 -1
- package/dist/fastify.d.cts +2 -1
- package/dist/fastify.d.ts +2 -1
- package/dist/fastify.js +176 -61
- package/dist/fastify.js.map +1 -1
- package/dist/{handler-DGnThUpM.d.cts → handler-CP5HUtCf.d.ts} +2 -1
- package/dist/{handler-DGnThUpM.d.ts → handler-DMqP6HcG.d.cts} +2 -1
- package/dist/hono.cjs +163 -44
- package/dist/hono.cjs.map +1 -1
- package/dist/hono.d.cts +2 -0
- package/dist/hono.d.ts +2 -0
- package/dist/hono.js +163 -44
- package/dist/hono.js.map +1 -1
- package/dist/index.cjs +173 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -27
- package/dist/index.d.ts +5 -27
- package/dist/index.js +173 -60
- package/dist/index.js.map +1 -1
- package/package.json +53 -16
package/dist/hono.cjs
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var sdk = require('@cvfile/sdk');
|
|
4
|
+
var crypto = require('crypto');
|
|
4
5
|
|
|
5
6
|
// src/hono.ts
|
|
6
7
|
|
|
7
8
|
// src/conneg.ts
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"text/html": "html",
|
|
12
|
-
"application/xhtml+xml": "html",
|
|
13
|
-
"application/pdf": "pdf",
|
|
14
|
-
"application/vnd.cv+pdf": "pdf"
|
|
15
|
-
};
|
|
9
|
+
var MARKDOWN_MIMES = /* @__PURE__ */ new Set(["text/markdown", "text/x-markdown", "application/vnd.cv+markdown"]);
|
|
10
|
+
var PDF_MIMES = /* @__PURE__ */ new Set(["application/pdf", "application/vnd.cv+pdf"]);
|
|
11
|
+
var HTML_MIMES = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml"]);
|
|
16
12
|
var FORMAT_BY_QUERY = {
|
|
17
13
|
md: "markdown",
|
|
18
14
|
markdown: "markdown",
|
|
@@ -24,13 +20,21 @@ function parseAccept(header) {
|
|
|
24
20
|
if (!header) return [];
|
|
25
21
|
return header.split(",").map((part) => {
|
|
26
22
|
const [type, ...params] = part.trim().split(";").map((s) => s.trim());
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const m = p.match(/^q\s*=\s*(\d*\.?\d+)/i);
|
|
30
|
-
if (m) q = Number(m[1]);
|
|
31
|
-
}
|
|
23
|
+
const q = parseQ(params);
|
|
24
|
+
if (q === null) return null;
|
|
32
25
|
return { type: (type ?? "").toLowerCase(), q };
|
|
33
|
-
}).filter((p) => p.type).sort((a, b) => b.q - a.q);
|
|
26
|
+
}).filter((p) => p !== null && p.type !== "" && p.q > 0).sort((a, b) => b.q - a.q);
|
|
27
|
+
}
|
|
28
|
+
function parseQ(params) {
|
|
29
|
+
for (const p of params) {
|
|
30
|
+
if (!/^q\s*=/i.test(p)) continue;
|
|
31
|
+
const m = p.match(/^q\s*=\s*(\d*\.?\d+)\s*$/i);
|
|
32
|
+
if (!m) return null;
|
|
33
|
+
const value = Number(m[1]);
|
|
34
|
+
if (Number.isNaN(value)) return null;
|
|
35
|
+
return Math.min(1, Math.max(0, value));
|
|
36
|
+
}
|
|
37
|
+
return 1;
|
|
34
38
|
}
|
|
35
39
|
function parseAcceptLanguage(header) {
|
|
36
40
|
if (!header) return [];
|
|
@@ -52,20 +56,32 @@ function negotiate(input) {
|
|
|
52
56
|
return { format: fromQuery, language };
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
const fromAccept = negotiateFromAccept(input.accept);
|
|
60
|
+
const format = fromAccept ?? input.defaultFormat ?? "pdf";
|
|
61
|
+
return { format, language };
|
|
62
|
+
}
|
|
63
|
+
function negotiateFromAccept(header) {
|
|
64
|
+
const accepts = parseAccept(header);
|
|
65
|
+
if (accepts.length === 0) return void 0;
|
|
66
|
+
const topQ = accepts[0].q;
|
|
67
|
+
const top = accepts.filter((a) => a.q === topQ);
|
|
68
|
+
const hasWildcard = accepts.some((a) => a.type === "*/*" || a.type === "application/*");
|
|
69
|
+
if (top.some((a) => MARKDOWN_MIMES.has(a.type))) {
|
|
70
|
+
return "markdown";
|
|
71
|
+
}
|
|
72
|
+
if (top.some((a) => PDF_MIMES.has(a.type))) {
|
|
73
|
+
return "pdf";
|
|
74
|
+
}
|
|
75
|
+
if (top.some((a) => HTML_MIMES.has(a.type)) && !hasWildcard) {
|
|
76
|
+
return "html";
|
|
77
|
+
}
|
|
78
|
+
if (hasWildcard || top.some((a) => HTML_MIMES.has(a.type))) {
|
|
79
|
+
return "pdf";
|
|
80
|
+
}
|
|
81
|
+
if (top.some((a) => a.type === "text/*")) {
|
|
82
|
+
return "html";
|
|
67
83
|
}
|
|
68
|
-
return
|
|
84
|
+
return void 0;
|
|
69
85
|
}
|
|
70
86
|
function buildLinkHeader({ selfUrl, cvMime = "application/vnd.cv+pdf" }) {
|
|
71
87
|
const sep = selfUrl.includes("?") ? "&" : "?";
|
|
@@ -81,7 +97,8 @@ async function serveCv(req) {
|
|
|
81
97
|
const decision = negotiate({
|
|
82
98
|
accept: req.accept,
|
|
83
99
|
acceptLanguage: req.acceptLanguage,
|
|
84
|
-
formatQuery: req.formatQuery
|
|
100
|
+
formatQuery: req.formatQuery,
|
|
101
|
+
defaultFormat: req.defaultFormat
|
|
85
102
|
});
|
|
86
103
|
if (decision.format === "pdf") {
|
|
87
104
|
return {
|
|
@@ -98,7 +115,7 @@ async function serveCv(req) {
|
|
|
98
115
|
if (md) {
|
|
99
116
|
return {
|
|
100
117
|
format: "markdown",
|
|
101
|
-
contentType:
|
|
118
|
+
contentType: "text/markdown; charset=utf-8",
|
|
102
119
|
...md.language !== void 0 ? { language: md.language } : {},
|
|
103
120
|
body: md.bytes
|
|
104
121
|
};
|
|
@@ -110,7 +127,7 @@ async function serveCv(req) {
|
|
|
110
127
|
if (html) {
|
|
111
128
|
return {
|
|
112
129
|
format: "html",
|
|
113
|
-
contentType:
|
|
130
|
+
contentType: "text/html; charset=utf-8",
|
|
114
131
|
...html.language !== void 0 ? { language: html.language } : {},
|
|
115
132
|
body: html.bytes
|
|
116
133
|
};
|
|
@@ -151,9 +168,111 @@ function renderMarkdownAsHtml(md, file) {
|
|
|
151
168
|
</html>`;
|
|
152
169
|
}
|
|
153
170
|
|
|
171
|
+
// src/response.ts
|
|
172
|
+
async function buildCvResponse(input) {
|
|
173
|
+
const result = await serveCv({
|
|
174
|
+
bytes: input.bytes,
|
|
175
|
+
accept: input.accept,
|
|
176
|
+
acceptLanguage: input.acceptLanguage,
|
|
177
|
+
formatQuery: input.formatQuery,
|
|
178
|
+
defaultFormat: input.defaultFormat
|
|
179
|
+
});
|
|
180
|
+
const etag = computeETag(result.body, result.format);
|
|
181
|
+
const lastModified = input.lastModified?.toUTCString();
|
|
182
|
+
const headers = {
|
|
183
|
+
"Content-Type": result.contentType,
|
|
184
|
+
Vary: "Accept, Accept-Language",
|
|
185
|
+
Link: buildLinkHeader({ selfUrl: input.selfUrl, cvMime: PDF_PRIMARY_MIME }),
|
|
186
|
+
"Cache-Control": input.cacheControl,
|
|
187
|
+
ETag: etag,
|
|
188
|
+
"Content-Disposition": contentDisposition(input.selfUrl, result.format)
|
|
189
|
+
};
|
|
190
|
+
if (result.language) {
|
|
191
|
+
headers["Content-Language"] = result.language;
|
|
192
|
+
}
|
|
193
|
+
if (lastModified) {
|
|
194
|
+
headers["Last-Modified"] = lastModified;
|
|
195
|
+
}
|
|
196
|
+
const notModified = isNotModified({
|
|
197
|
+
etag,
|
|
198
|
+
lastModified: input.lastModified,
|
|
199
|
+
ifNoneMatch: input.ifNoneMatch,
|
|
200
|
+
ifModifiedSince: input.ifModifiedSince
|
|
201
|
+
});
|
|
202
|
+
if (notModified) {
|
|
203
|
+
return { status: 304, headers, format: result.format, body: new Uint8Array(0) };
|
|
204
|
+
}
|
|
205
|
+
headers["Content-Length"] = String(result.body.length);
|
|
206
|
+
return { status: 200, headers, format: result.format, body: result.body };
|
|
207
|
+
}
|
|
208
|
+
function computeETag(body, format) {
|
|
209
|
+
const hash = crypto.createHash("sha1").update(body).digest("base64url");
|
|
210
|
+
return `W/"${format}-${body.length.toString(16)}-${hash}"`;
|
|
211
|
+
}
|
|
212
|
+
function isNotModified({ etag, lastModified, ifNoneMatch, ifModifiedSince }) {
|
|
213
|
+
if (ifNoneMatch) {
|
|
214
|
+
return etagMatches(ifNoneMatch, etag);
|
|
215
|
+
}
|
|
216
|
+
if (ifModifiedSince && lastModified) {
|
|
217
|
+
const since = Date.parse(ifModifiedSince);
|
|
218
|
+
if (!Number.isNaN(since)) {
|
|
219
|
+
return Math.floor(lastModified.getTime() / 1e3) <= Math.floor(since / 1e3);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
function etagMatches(ifNoneMatch, etag) {
|
|
225
|
+
if (ifNoneMatch.trim() === "*") return true;
|
|
226
|
+
const normalize = (tag) => tag.trim().replace(/^W\//, "");
|
|
227
|
+
const target = normalize(etag);
|
|
228
|
+
return ifNoneMatch.split(",").some((candidate) => normalize(candidate) === target);
|
|
229
|
+
}
|
|
230
|
+
function contentDisposition(selfUrl, format) {
|
|
231
|
+
const filename = filenameForFormat(selfUrl, format);
|
|
232
|
+
const asciiSafe = sanitizeAsciiFilename(filename);
|
|
233
|
+
const base = `inline; filename="${asciiSafe}"`;
|
|
234
|
+
if (!hasNonAscii(filename)) return base;
|
|
235
|
+
return `${base}; filename*=UTF-8''${encodeRFC5987(filename)}`;
|
|
236
|
+
}
|
|
237
|
+
function sanitizeAsciiFilename(filename) {
|
|
238
|
+
let out = "";
|
|
239
|
+
for (const ch of filename) {
|
|
240
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
241
|
+
if (code < 32 || code === 127) continue;
|
|
242
|
+
if (ch === '"' || ch === "\\") continue;
|
|
243
|
+
out += code > 126 ? "_" : ch;
|
|
244
|
+
}
|
|
245
|
+
return out || "document";
|
|
246
|
+
}
|
|
247
|
+
function hasNonAscii(value) {
|
|
248
|
+
for (const ch of value) {
|
|
249
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
250
|
+
if (code < 32 || code > 126) return true;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
function filenameForFormat(selfUrl, format) {
|
|
255
|
+
const pathname = selfUrl.split("?")[0] ?? selfUrl;
|
|
256
|
+
const base = decodeOrRaw(pathname.split("/").pop() ?? "document");
|
|
257
|
+
const stem = base.replace(/\.cv$/i, "").replace(/\.(pdf|md|html)$/i, "") || "document";
|
|
258
|
+
if (format === "markdown") return `${stem}.md`;
|
|
259
|
+
if (format === "html") return `${stem}.html`;
|
|
260
|
+
return `${stem}.cv`;
|
|
261
|
+
}
|
|
262
|
+
function decodeOrRaw(value) {
|
|
263
|
+
try {
|
|
264
|
+
return decodeURIComponent(value);
|
|
265
|
+
} catch {
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function encodeRFC5987(value) {
|
|
270
|
+
return encodeURIComponent(value).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
154
273
|
// src/hono.ts
|
|
155
274
|
function cvHono(options) {
|
|
156
|
-
const { loader, cacheControl = "public, max-age=300" } = options;
|
|
275
|
+
const { loader, cacheControl = "public, max-age=300", defaultFormat } = options;
|
|
157
276
|
return async (c) => {
|
|
158
277
|
const url = new URL(c.req.url);
|
|
159
278
|
const logical = decodeURIComponent(url.pathname);
|
|
@@ -165,23 +284,23 @@ function cvHono(options) {
|
|
|
165
284
|
if (!await sdk.isCvFile(bytes)) {
|
|
166
285
|
return c.text("Not a .cv file", 415);
|
|
167
286
|
}
|
|
168
|
-
const
|
|
287
|
+
const built = await buildCvResponse({
|
|
169
288
|
bytes,
|
|
289
|
+
selfUrl: url.pathname,
|
|
170
290
|
accept: c.req.header("accept"),
|
|
171
291
|
acceptLanguage: c.req.header("accept-language"),
|
|
172
|
-
formatQuery: c.req.query("format") ?? void 0
|
|
292
|
+
formatQuery: c.req.query("format") ?? void 0,
|
|
293
|
+
defaultFormat,
|
|
294
|
+
cacheControl,
|
|
295
|
+
ifNoneMatch: c.req.header("if-none-match"),
|
|
296
|
+
ifModifiedSince: c.req.header("if-modified-since")
|
|
173
297
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
};
|
|
181
|
-
if (result.language) headers["Content-Language"] = result.language;
|
|
182
|
-
const view = new Uint8Array(result.body.byteLength);
|
|
183
|
-
view.set(result.body);
|
|
184
|
-
return c.newResponse(view.buffer, 200, headers);
|
|
298
|
+
if (built.status === 304) {
|
|
299
|
+
return c.body(null, 304, built.headers);
|
|
300
|
+
}
|
|
301
|
+
const view = new Uint8Array(built.body.byteLength);
|
|
302
|
+
view.set(built.body);
|
|
303
|
+
return c.newResponse(view.buffer, 200, built.headers);
|
|
185
304
|
};
|
|
186
305
|
}
|
|
187
306
|
|
package/dist/hono.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/conneg.ts","../src/serve.ts","../src/hono.ts"],"names":["extract","isCvFile"],"mappings":"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","file":"hono.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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\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 { Context, MiddlewareHandler } from 'hono';\nimport { isCvFile } from '@cvfile/sdk';\nimport { buildLinkHeader, PDF_PRIMARY_MIME } from './conneg.js';\nimport { serveCv } from './serve.js';\n\nexport interface CvHonoOptions {\n loader: (logicalPath: string) => Promise<Uint8Array | null>;\n cacheControl?: string;\n}\n\nexport function cvHono(options: CvHonoOptions): MiddlewareHandler {\n const { loader, cacheControl = 'public, max-age=300' } = options;\n return async (c: Context) => {\n const url = new URL(c.req.url);\n const logical = decodeURIComponent(url.pathname);\n if (!logical.toLowerCase().endsWith('.cv')) {\n return c.notFound();\n }\n const bytes = await loader(logical);\n if (!bytes) return c.notFound();\n if (!(await isCvFile(bytes))) {\n return c.text('Not a .cv file', 415);\n }\n const result = await serveCv({\n bytes,\n accept: c.req.header('accept'),\n acceptLanguage: c.req.header('accept-language'),\n formatQuery: c.req.query('format') ?? undefined,\n });\n const link = buildLinkHeader({ selfUrl: url.pathname, cvMime: PDF_PRIMARY_MIME });\n const headers: Record<string, string> = {\n 'Content-Type': result.contentType,\n Vary: 'Accept, Accept-Language',\n Link: link,\n 'Cache-Control': cacheControl,\n };\n if (result.language) headers['Content-Language'] = result.language;\n const view = new Uint8Array(result.body.byteLength);\n view.set(result.body);\n return c.newResponse(view.buffer, 200, headers);\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/conneg.ts","../src/serve.ts","../src/response.ts","../src/hono.ts"],"names":["extract","createHash","isCvFile"],"mappings":"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","file":"hono.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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\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 { Context, MiddlewareHandler } from 'hono';\nimport { isCvFile } from '@cvfile/sdk';\nimport type { ServeFormat } from './conneg.js';\nimport { buildCvResponse } from './response.js';\n\nexport interface CvHonoOptions {\n loader: (logicalPath: string) => Promise<Uint8Array | null>;\n cacheControl?: string;\n defaultFormat?: ServeFormat;\n}\n\nexport function cvHono(options: CvHonoOptions): MiddlewareHandler {\n const { loader, cacheControl = 'public, max-age=300', defaultFormat } = options;\n return async (c: Context) => {\n const url = new URL(c.req.url);\n const logical = decodeURIComponent(url.pathname);\n if (!logical.toLowerCase().endsWith('.cv')) {\n return c.notFound();\n }\n const bytes = await loader(logical);\n if (!bytes) return c.notFound();\n if (!(await isCvFile(bytes))) {\n return c.text('Not a .cv file', 415);\n }\n\n const built = await buildCvResponse({\n bytes,\n selfUrl: url.pathname,\n accept: c.req.header('accept'),\n acceptLanguage: c.req.header('accept-language'),\n formatQuery: c.req.query('format') ?? undefined,\n defaultFormat,\n cacheControl,\n ifNoneMatch: c.req.header('if-none-match'),\n ifModifiedSince: c.req.header('if-modified-since'),\n });\n\n if (built.status === 304) {\n return c.body(null, 304, built.headers);\n }\n const view = new Uint8Array(built.body.byteLength);\n view.set(built.body);\n return c.newResponse(view.buffer, 200, built.headers);\n };\n}\n"]}
|
package/dist/hono.d.cts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { MiddlewareHandler } from 'hono';
|
|
2
|
+
import { S as ServeFormat } from './conneg-DbPVX8oc.cjs';
|
|
2
3
|
|
|
3
4
|
interface CvHonoOptions {
|
|
4
5
|
loader: (logicalPath: string) => Promise<Uint8Array | null>;
|
|
5
6
|
cacheControl?: string;
|
|
7
|
+
defaultFormat?: ServeFormat;
|
|
6
8
|
}
|
|
7
9
|
declare function cvHono(options: CvHonoOptions): MiddlewareHandler;
|
|
8
10
|
|
package/dist/hono.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { MiddlewareHandler } from 'hono';
|
|
2
|
+
import { S as ServeFormat } from './conneg-DbPVX8oc.js';
|
|
2
3
|
|
|
3
4
|
interface CvHonoOptions {
|
|
4
5
|
loader: (logicalPath: string) => Promise<Uint8Array | null>;
|
|
5
6
|
cacheControl?: string;
|
|
7
|
+
defaultFormat?: ServeFormat;
|
|
6
8
|
}
|
|
7
9
|
declare function cvHono(options: CvHonoOptions): MiddlewareHandler;
|
|
8
10
|
|