lynkow 1.32.20 → 1.32.22

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.d.mts CHANGED
@@ -2385,6 +2385,18 @@ interface Category {
2385
2385
  * list projections (e.g. when categories are embedded in content responses).
2386
2386
  */
2387
2387
  contentMode?: 'standard' | 'structured';
2388
+ /**
2389
+ * Parent category in the hierarchy. Present when the API eagerly loads
2390
+ * the parent relation (e.g. `GET /public/contents/slug/:slug` returns
2391
+ * each category's parent so consumers can build a breadcrumb in a single
2392
+ * request). `null` for root-level categories. `undefined` when the
2393
+ * endpoint did not include parent data.
2394
+ *
2395
+ * The relation is depth-bound to one level: `parent.parent` is never
2396
+ * populated by the public API, even when the underlying hierarchy is
2397
+ * deeper.
2398
+ */
2399
+ parent?: Category | null;
2388
2400
  }
2389
2401
  /**
2390
2402
  * Category with URL path, description, image, and content count.
package/dist/index.d.ts CHANGED
@@ -2385,6 +2385,18 @@ interface Category {
2385
2385
  * list projections (e.g. when categories are embedded in content responses).
2386
2386
  */
2387
2387
  contentMode?: 'standard' | 'structured';
2388
+ /**
2389
+ * Parent category in the hierarchy. Present when the API eagerly loads
2390
+ * the parent relation (e.g. `GET /public/contents/slug/:slug` returns
2391
+ * each category's parent so consumers can build a breadcrumb in a single
2392
+ * request). `null` for root-level categories. `undefined` when the
2393
+ * endpoint did not include parent data.
2394
+ *
2395
+ * The relation is depth-bound to one level: `parent.parent` is never
2396
+ * populated by the public API, even when the underlying hierarchy is
2397
+ * deeper.
2398
+ */
2399
+ parent?: Category | null;
2388
2400
  }
2389
2401
  /**
2390
2402
  * Category with URL path, description, image, and content count.
@@ -1,4 +1,108 @@
1
- import { NextMiddleware } from 'next/server';
1
+ import { NextRequest, NextResponse, NextMiddleware } from 'next/server';
2
+
3
+ /**
4
+ * Options for the {@link markdownProxy} helper.
5
+ */
6
+ interface MarkdownProxyOptions {
7
+ /**
8
+ * URL prefix where the Markdown route handler is mounted. The helper
9
+ * rewrites `Accept: text/markdown` requests from `<path>` to
10
+ * `<mdPathPrefix><path>`. Defaults to `'/md'`.
11
+ *
12
+ * If your route handler lives at `app/markdown/[...path]/route.ts`,
13
+ * set this to `'/markdown'`.
14
+ */
15
+ mdPathPrefix?: string;
16
+ /**
17
+ * Whether to set `Vary: Accept` on the rewritten response. Defaults
18
+ * to `true`. Set to `false` only if your edge or CDN already sets the
19
+ * header and you want to avoid duplication.
20
+ */
21
+ setVary?: boolean;
22
+ /**
23
+ * Whether to set `X-Forwarded-Accept: text/markdown` on the rewritten
24
+ * request so the route handler can confirm the request originated
25
+ * from a Markdown negotiation. Defaults to `true`.
26
+ */
27
+ setForwardedAccept?: boolean;
28
+ }
29
+ /**
30
+ * Parses an `Accept` header per RFC 9110 §12.5.1 and returns the
31
+ * highest q-value for a target media type. Supports exact match
32
+ * (`text/markdown`), type wildcard (`text/*`), full wildcard (`*\/*`),
33
+ * and q-values.
34
+ *
35
+ * Returns `0` if the type is not present or has `q=0`. Returns `1` if
36
+ * no q value is given (per RFC, missing q means q=1). Out-of-range or
37
+ * non-numeric q values fall back to the default `1`.
38
+ *
39
+ * @param acceptHeader - The raw `Accept` header value, or `null` if absent
40
+ * @param targetType - The target media type, e.g. `'text/markdown'`
41
+ * @returns The highest q-value for the target type (0 to 1)
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * acceptQuality('text/html;q=0.9, text/markdown;q=0.8', 'text/markdown') // 0.8
46
+ * acceptQuality('text/markdown', 'text/markdown') // 1
47
+ * acceptQuality(null, 'text/markdown') // 0
48
+ * ```
49
+ */
50
+ declare function acceptQuality(acceptHeader: string | null, targetType: string): number;
51
+ /**
52
+ * Decides whether the request prefers Markdown over HTML based on its
53
+ * `Accept` header.
54
+ *
55
+ * Returns `true` when `Accept` contains `text/markdown` with q strictly
56
+ * higher than `text/html`. Ties break in favor of HTML (the default
57
+ * browsers expect).
58
+ *
59
+ * @param acceptHeader - The raw `Accept` header value, or `null` if absent
60
+ * @returns `true` when Markdown is the preferred representation
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * prefersMarkdown('text/markdown;q=0.9, text/html;q=0.8') // true
65
+ * prefersMarkdown('text/markdown, text/html') // false (tie -> HTML)
66
+ * prefersMarkdown('text/html') // false
67
+ * ```
68
+ */
69
+ declare function prefersMarkdown(acceptHeader: string | null): boolean;
70
+ /**
71
+ * Next.js proxy/middleware helper that translates
72
+ * `Accept: text/markdown` requests into rewrites to a dedicated
73
+ * Markdown route handler.
74
+ *
75
+ * Returns the rewritten `NextResponse` when the request preferred
76
+ * Markdown, or `null` when the request did not (so the caller can pass
77
+ * through to the next middleware step).
78
+ *
79
+ * Sets `Vary: Accept` on the rewritten response so intermediate caches
80
+ * keep separate variants for HTML and Markdown.
81
+ *
82
+ * The helper is Edge runtime compatible (no `node:` imports).
83
+ *
84
+ * @param request - The incoming `NextRequest`
85
+ * @param options - Optional {@link MarkdownProxyOptions} overrides
86
+ * @returns A `NextResponse.rewrite(...)` when Markdown was preferred, or `null` otherwise
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * // Next 16, proxy.ts
91
+ * import { markdownProxy } from 'lynkow/middleware/next'
92
+ * import { NextResponse, type NextRequest } from 'next/server'
93
+ *
94
+ * export function proxy(request: NextRequest) {
95
+ * const md = markdownProxy(request)
96
+ * if (md) return md
97
+ * return NextResponse.next()
98
+ * }
99
+ *
100
+ * export const config = {
101
+ * matcher: ['/((?!_next|favicon.ico|md|api).*)'],
102
+ * }
103
+ * ```
104
+ */
105
+ declare function markdownProxy(request: NextRequest, options?: MarkdownProxyOptions): NextResponse | null;
2
106
 
3
107
  interface LynkowPreviewOptions {
4
108
  cmsOrigin?: string;
@@ -7,4 +111,4 @@ interface LynkowPreviewOptions {
7
111
  declare function lynkowPreviewMiddleware(options?: LynkowPreviewOptions): NextMiddleware;
8
112
  declare function withLynkowPreview(existingMiddleware: NextMiddleware, options?: LynkowPreviewOptions): NextMiddleware;
9
113
 
10
- export { lynkowPreviewMiddleware, withLynkowPreview };
114
+ export { type MarkdownProxyOptions, acceptQuality, lynkowPreviewMiddleware, markdownProxy, prefersMarkdown, withLynkowPreview };
@@ -1,4 +1,108 @@
1
- import { NextMiddleware } from 'next/server';
1
+ import { NextRequest, NextResponse, NextMiddleware } from 'next/server';
2
+
3
+ /**
4
+ * Options for the {@link markdownProxy} helper.
5
+ */
6
+ interface MarkdownProxyOptions {
7
+ /**
8
+ * URL prefix where the Markdown route handler is mounted. The helper
9
+ * rewrites `Accept: text/markdown` requests from `<path>` to
10
+ * `<mdPathPrefix><path>`. Defaults to `'/md'`.
11
+ *
12
+ * If your route handler lives at `app/markdown/[...path]/route.ts`,
13
+ * set this to `'/markdown'`.
14
+ */
15
+ mdPathPrefix?: string;
16
+ /**
17
+ * Whether to set `Vary: Accept` on the rewritten response. Defaults
18
+ * to `true`. Set to `false` only if your edge or CDN already sets the
19
+ * header and you want to avoid duplication.
20
+ */
21
+ setVary?: boolean;
22
+ /**
23
+ * Whether to set `X-Forwarded-Accept: text/markdown` on the rewritten
24
+ * request so the route handler can confirm the request originated
25
+ * from a Markdown negotiation. Defaults to `true`.
26
+ */
27
+ setForwardedAccept?: boolean;
28
+ }
29
+ /**
30
+ * Parses an `Accept` header per RFC 9110 §12.5.1 and returns the
31
+ * highest q-value for a target media type. Supports exact match
32
+ * (`text/markdown`), type wildcard (`text/*`), full wildcard (`*\/*`),
33
+ * and q-values.
34
+ *
35
+ * Returns `0` if the type is not present or has `q=0`. Returns `1` if
36
+ * no q value is given (per RFC, missing q means q=1). Out-of-range or
37
+ * non-numeric q values fall back to the default `1`.
38
+ *
39
+ * @param acceptHeader - The raw `Accept` header value, or `null` if absent
40
+ * @param targetType - The target media type, e.g. `'text/markdown'`
41
+ * @returns The highest q-value for the target type (0 to 1)
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * acceptQuality('text/html;q=0.9, text/markdown;q=0.8', 'text/markdown') // 0.8
46
+ * acceptQuality('text/markdown', 'text/markdown') // 1
47
+ * acceptQuality(null, 'text/markdown') // 0
48
+ * ```
49
+ */
50
+ declare function acceptQuality(acceptHeader: string | null, targetType: string): number;
51
+ /**
52
+ * Decides whether the request prefers Markdown over HTML based on its
53
+ * `Accept` header.
54
+ *
55
+ * Returns `true` when `Accept` contains `text/markdown` with q strictly
56
+ * higher than `text/html`. Ties break in favor of HTML (the default
57
+ * browsers expect).
58
+ *
59
+ * @param acceptHeader - The raw `Accept` header value, or `null` if absent
60
+ * @returns `true` when Markdown is the preferred representation
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * prefersMarkdown('text/markdown;q=0.9, text/html;q=0.8') // true
65
+ * prefersMarkdown('text/markdown, text/html') // false (tie -> HTML)
66
+ * prefersMarkdown('text/html') // false
67
+ * ```
68
+ */
69
+ declare function prefersMarkdown(acceptHeader: string | null): boolean;
70
+ /**
71
+ * Next.js proxy/middleware helper that translates
72
+ * `Accept: text/markdown` requests into rewrites to a dedicated
73
+ * Markdown route handler.
74
+ *
75
+ * Returns the rewritten `NextResponse` when the request preferred
76
+ * Markdown, or `null` when the request did not (so the caller can pass
77
+ * through to the next middleware step).
78
+ *
79
+ * Sets `Vary: Accept` on the rewritten response so intermediate caches
80
+ * keep separate variants for HTML and Markdown.
81
+ *
82
+ * The helper is Edge runtime compatible (no `node:` imports).
83
+ *
84
+ * @param request - The incoming `NextRequest`
85
+ * @param options - Optional {@link MarkdownProxyOptions} overrides
86
+ * @returns A `NextResponse.rewrite(...)` when Markdown was preferred, or `null` otherwise
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * // Next 16, proxy.ts
91
+ * import { markdownProxy } from 'lynkow/middleware/next'
92
+ * import { NextResponse, type NextRequest } from 'next/server'
93
+ *
94
+ * export function proxy(request: NextRequest) {
95
+ * const md = markdownProxy(request)
96
+ * if (md) return md
97
+ * return NextResponse.next()
98
+ * }
99
+ *
100
+ * export const config = {
101
+ * matcher: ['/((?!_next|favicon.ico|md|api).*)'],
102
+ * }
103
+ * ```
104
+ */
105
+ declare function markdownProxy(request: NextRequest, options?: MarkdownProxyOptions): NextResponse | null;
2
106
 
3
107
  interface LynkowPreviewOptions {
4
108
  cmsOrigin?: string;
@@ -7,4 +111,4 @@ interface LynkowPreviewOptions {
7
111
  declare function lynkowPreviewMiddleware(options?: LynkowPreviewOptions): NextMiddleware;
8
112
  declare function withLynkowPreview(existingMiddleware: NextMiddleware, options?: LynkowPreviewOptions): NextMiddleware;
9
113
 
10
- export { lynkowPreviewMiddleware, withLynkowPreview };
114
+ export { type MarkdownProxyOptions, acceptQuality, lynkowPreviewMiddleware, markdownProxy, prefersMarkdown, withLynkowPreview };
@@ -1,3 +1,3 @@
1
- 'use strict';var server=require('next/server');function p(e){let n=e?.cmsOrigin||"https://manage.lynkow.com",r=e?.previewParam||"lynkow-preview";return function(i){let t=server.NextResponse.next();return i.nextUrl.searchParams.has(r)?(t.headers.set("Content-Security-Policy",`frame-ancestors 'self' ${n}`),t.headers.delete("X-Frame-Options")):t.headers.set("Content-Security-Policy","frame-ancestors 'self'"),t}}function w(e,n,r,s){return n.nextUrl.searchParams.has(s)?(e.headers.set("Content-Security-Policy",`frame-ancestors 'self' ${r}`),e.headers.delete("X-Frame-Options")):e.headers.set("Content-Security-Policy","frame-ancestors 'self'"),e}function P(e,n){let r=n?.cmsOrigin||"https://manage.lynkow.com",s=n?.previewParam||"lynkow-preview";return function(t,c){let a=e(t,c);return a instanceof Promise?a.then(l=>w(l||server.NextResponse.next(),t,r,s)):w(a||server.NextResponse.next(),t,r,s)}}
2
- exports.lynkowPreviewMiddleware=p;exports.withLynkowPreview=P;//# sourceMappingURL=next.js.map
1
+ 'use strict';var server=require('next/server');var P="/md";function w(e,t){if(!e)return 0;let[r,s]=t.toLowerCase().split("/"),i=0;for(let n of e.split(",")){let o=n.trim().split(";").map(p=>p.trim()).filter(Boolean),a=o[0]?.toLowerCase();if(!a||!a.includes("/"))continue;let[c,l]=a.split("/");if(!(c===r&&l===s||c===r&&l==="*"||c==="*"&&l==="*"))continue;let d=1;for(let p of o.slice(1))if(p.startsWith("q=")){let m=Number.parseFloat(p.slice(2));!Number.isNaN(m)&&m>=0&&m<=1&&(d=m);}d>i&&(i=d);}return i}function u(e){let t=w(e,"text/markdown");if(t===0)return false;let r=w(e,"text/html");return t>r}function h(e,t){let r=e.headers.get("accept");if(!u(r))return null;let s=t?.mdPathPrefix??P,i=t?.setVary??true,n=t?.setForwardedAccept??true,o=e.nextUrl.clone();if(o.pathname.startsWith(`${s}/`)||o.pathname===s)return null;o.pathname=`${s}${o.pathname}`;let a=new Headers(e.headers);n&&a.set("x-forwarded-accept","text/markdown");let c=server.NextResponse.rewrite(o,{request:{headers:a}});return i&&c.headers.set("vary","accept"),c}function R(e){let t=e?.cmsOrigin||"https://manage.lynkow.com",r=e?.previewParam||"lynkow-preview";return function(i){let n=server.NextResponse.next();return i.nextUrl.searchParams.has(r)?(n.headers.set("Content-Security-Policy",`frame-ancestors 'self' ${t}`),n.headers.delete("X-Frame-Options")):n.headers.set("Content-Security-Policy","frame-ancestors 'self'"),n}}function x(e,t,r,s){return t.nextUrl.searchParams.has(s)?(e.headers.set("Content-Security-Policy",`frame-ancestors 'self' ${r}`),e.headers.delete("X-Frame-Options")):e.headers.set("Content-Security-Policy","frame-ancestors 'self'"),e}function O(e,t){let r=t?.cmsOrigin||"https://manage.lynkow.com",s=t?.previewParam||"lynkow-preview";return function(n,o){let a=e(n,o);return a instanceof Promise?a.then(l=>x(l||server.NextResponse.next(),n,r,s)):x(a||server.NextResponse.next(),n,r,s)}}
2
+ exports.acceptQuality=w;exports.lynkowPreviewMiddleware=R;exports.markdownProxy=h;exports.prefersMarkdown=u;exports.withLynkowPreview=O;//# sourceMappingURL=next.js.map
3
3
  //# sourceMappingURL=next.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middleware/next.ts"],"names":["lynkowPreviewMiddleware","options","cmsOrigin","previewParam","request","response","NextResponse","addCspHeaders","withLynkowPreview","existingMiddleware","event","res"],"mappings":"+CAOO,SAASA,CAAAA,CAAwBC,CAAAA,CAAgD,CACtF,IAAMC,CAAAA,CAAYD,CAAAA,EAAS,SAAA,EAAa,2BAAA,CAClCE,EAAeF,CAAAA,EAAS,YAAA,EAAgB,gBAAA,CAE9C,OAAO,SAAoBG,CAAAA,CAAsB,CAC/C,IAAMC,EAAWC,mBAAAA,CAAa,IAAA,EAAK,CAInC,OAFkBF,CAAAA,CAAQ,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAID,CAAY,CAAA,EAG7DE,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,yBAAA,CAA2B,CAAA,uBAAA,EAA0BH,CAAS,CAAA,CAAE,EACrFG,CAAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,iBAAiB,CAAA,EAEzCA,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,0BAA2B,wBAAwB,CAAA,CAGnEA,CACT,CACF,CAEA,SAASE,CAAAA,CACPF,CAAAA,CACAD,EACAF,CAAAA,CACAC,CAAAA,CACyB,CAGzB,OAFkBC,CAAAA,CAAQ,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAID,CAAY,CAAA,EAG7DE,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,yBAAA,CAA2B,CAAA,uBAAA,EAA0BH,CAAS,CAAA,CAAE,EACrFG,CAAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,iBAAiB,CAAA,EAEzCA,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,0BAA2B,wBAAwB,CAAA,CAGnEA,CACT,CAEO,SAASG,CAAAA,CACdC,CAAAA,CACAR,CAAAA,CACgB,CAChB,IAAMC,CAAAA,CAAYD,CAAAA,EAAS,SAAA,EAAa,2BAAA,CAClCE,CAAAA,CAAeF,CAAAA,EAAS,YAAA,EAAgB,iBAE9C,OAAO,SAAoBG,CAAAA,CAAsBM,CAAAA,CAAY,CAC3D,IAAML,CAAAA,CAAWI,CAAAA,CAAmBL,EAASM,CAAK,CAAA,CASlD,OANEL,CAAAA,YAAoB,OAAA,CAChBA,CAAAA,CAAS,IAAA,CAAMM,CAAAA,EACbJ,EAAcI,CAAAA,EAAOL,mBAAAA,CAAa,IAAA,EAAK,CAAGF,CAAAA,CAASF,CAAAA,CAAWC,CAAY,CAC5E,EACAI,CAAAA,CAAcF,CAAAA,EAAYC,mBAAAA,CAAa,IAAA,EAAK,CAAGF,CAAAA,CAASF,CAAAA,CAAWC,CAAY,CAGvF,CACF","file":"next.js","sourcesContent":["import { NextResponse, type NextRequest, type NextMiddleware } from 'next/server'\n\ninterface LynkowPreviewOptions {\n cmsOrigin?: string\n previewParam?: string\n}\n\nexport function lynkowPreviewMiddleware(options?: LynkowPreviewOptions): NextMiddleware {\n const cmsOrigin = options?.cmsOrigin || 'https://manage.lynkow.com'\n const previewParam = options?.previewParam || 'lynkow-preview'\n\n return function middleware(request: NextRequest) {\n const response = NextResponse.next()\n\n const isPreview = request.nextUrl.searchParams.has(previewParam)\n\n if (isPreview) {\n response.headers.set('Content-Security-Policy', `frame-ancestors 'self' ${cmsOrigin}`)\n response.headers.delete('X-Frame-Options')\n } else {\n response.headers.set('Content-Security-Policy', \"frame-ancestors 'self'\")\n }\n\n return response\n }\n}\n\nfunction addCspHeaders(\n response: NextResponse | Response,\n request: NextRequest,\n cmsOrigin: string,\n previewParam: string\n): NextResponse | Response {\n const isPreview = request.nextUrl.searchParams.has(previewParam)\n\n if (isPreview) {\n response.headers.set('Content-Security-Policy', `frame-ancestors 'self' ${cmsOrigin}`)\n response.headers.delete('X-Frame-Options')\n } else {\n response.headers.set('Content-Security-Policy', \"frame-ancestors 'self'\")\n }\n\n return response\n}\n\nexport function withLynkowPreview(\n existingMiddleware: NextMiddleware,\n options?: LynkowPreviewOptions\n): NextMiddleware {\n const cmsOrigin = options?.cmsOrigin || 'https://manage.lynkow.com'\n const previewParam = options?.previewParam || 'lynkow-preview'\n\n return function middleware(request: NextRequest, event: any) {\n const response = existingMiddleware(request, event)\n\n const finalResponse =\n response instanceof Promise\n ? response.then((res) =>\n addCspHeaders(res || NextResponse.next(), request, cmsOrigin, previewParam)\n )\n : addCspHeaders(response || NextResponse.next(), request, cmsOrigin, previewParam)\n\n return finalResponse\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/middleware/next-markdown.ts","../../src/middleware/next.ts"],"names":["DEFAULT_MD_PREFIX","acceptQuality","acceptHeader","targetType","targetMain","targetSub","best","raw","parts","mediaType","main","sub","q","param","parsed","prefersMarkdown","md","html","markdownProxy","request","options","accept","prefix","setVary","setForwardedAccept","url","headers","response","NextResponse","lynkowPreviewMiddleware","cmsOrigin","previewParam","addCspHeaders","withLynkowPreview","existingMiddleware","event","res"],"mappings":"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","file":"next.js","sourcesContent":["import { NextResponse, type NextRequest } from 'next/server'\n\nconst DEFAULT_MD_PREFIX = '/md'\n\n/**\n * Options for the {@link markdownProxy} helper.\n */\nexport interface MarkdownProxyOptions {\n /**\n * URL prefix where the Markdown route handler is mounted. The helper\n * rewrites `Accept: text/markdown` requests from `<path>` to\n * `<mdPathPrefix><path>`. Defaults to `'/md'`.\n *\n * If your route handler lives at `app/markdown/[...path]/route.ts`,\n * set this to `'/markdown'`.\n */\n mdPathPrefix?: string\n\n /**\n * Whether to set `Vary: Accept` on the rewritten response. Defaults\n * to `true`. Set to `false` only if your edge or CDN already sets the\n * header and you want to avoid duplication.\n */\n setVary?: boolean\n\n /**\n * Whether to set `X-Forwarded-Accept: text/markdown` on the rewritten\n * request so the route handler can confirm the request originated\n * from a Markdown negotiation. Defaults to `true`.\n */\n setForwardedAccept?: boolean\n}\n\n/**\n * Parses an `Accept` header per RFC 9110 §12.5.1 and returns the\n * highest q-value for a target media type. Supports exact match\n * (`text/markdown`), type wildcard (`text/*`), full wildcard (`*\\/*`),\n * and q-values.\n *\n * Returns `0` if the type is not present or has `q=0`. Returns `1` if\n * no q value is given (per RFC, missing q means q=1). Out-of-range or\n * non-numeric q values fall back to the default `1`.\n *\n * @param acceptHeader - The raw `Accept` header value, or `null` if absent\n * @param targetType - The target media type, e.g. `'text/markdown'`\n * @returns The highest q-value for the target type (0 to 1)\n *\n * @example\n * ```typescript\n * acceptQuality('text/html;q=0.9, text/markdown;q=0.8', 'text/markdown') // 0.8\n * acceptQuality('text/markdown', 'text/markdown') // 1\n * acceptQuality(null, 'text/markdown') // 0\n * ```\n */\nexport function acceptQuality(acceptHeader: string | null, targetType: string): number {\n if (!acceptHeader) return 0\n const [targetMain, targetSub] = targetType.toLowerCase().split('/')\n let best = 0\n\n for (const raw of acceptHeader.split(',')) {\n const parts = raw\n .trim()\n .split(';')\n .map((p) => p.trim())\n .filter(Boolean)\n const mediaType = parts[0]?.toLowerCase()\n if (!mediaType || !mediaType.includes('/')) continue\n\n const [main, sub] = mediaType.split('/')\n const matches =\n (main === targetMain && sub === targetSub) ||\n (main === targetMain && sub === '*') ||\n (main === '*' && sub === '*')\n\n if (!matches) continue\n\n let q = 1\n for (const param of parts.slice(1)) {\n if (param.startsWith('q=')) {\n const parsed = Number.parseFloat(param.slice(2))\n if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 1) q = parsed\n }\n }\n if (q > best) best = q\n }\n\n return best\n}\n\n/**\n * Decides whether the request prefers Markdown over HTML based on its\n * `Accept` header.\n *\n * Returns `true` when `Accept` contains `text/markdown` with q strictly\n * higher than `text/html`. Ties break in favor of HTML (the default\n * browsers expect).\n *\n * @param acceptHeader - The raw `Accept` header value, or `null` if absent\n * @returns `true` when Markdown is the preferred representation\n *\n * @example\n * ```typescript\n * prefersMarkdown('text/markdown;q=0.9, text/html;q=0.8') // true\n * prefersMarkdown('text/markdown, text/html') // false (tie -> HTML)\n * prefersMarkdown('text/html') // false\n * ```\n */\nexport function prefersMarkdown(acceptHeader: string | null): boolean {\n const md = acceptQuality(acceptHeader, 'text/markdown')\n if (md === 0) return false\n const html = acceptQuality(acceptHeader, 'text/html')\n return md > html\n}\n\n/**\n * Next.js proxy/middleware helper that translates\n * `Accept: text/markdown` requests into rewrites to a dedicated\n * Markdown route handler.\n *\n * Returns the rewritten `NextResponse` when the request preferred\n * Markdown, or `null` when the request did not (so the caller can pass\n * through to the next middleware step).\n *\n * Sets `Vary: Accept` on the rewritten response so intermediate caches\n * keep separate variants for HTML and Markdown.\n *\n * The helper is Edge runtime compatible (no `node:` imports).\n *\n * @param request - The incoming `NextRequest`\n * @param options - Optional {@link MarkdownProxyOptions} overrides\n * @returns A `NextResponse.rewrite(...)` when Markdown was preferred, or `null` otherwise\n *\n * @example\n * ```typescript\n * // Next 16, proxy.ts\n * import { markdownProxy } from 'lynkow/middleware/next'\n * import { NextResponse, type NextRequest } from 'next/server'\n *\n * export function proxy(request: NextRequest) {\n * const md = markdownProxy(request)\n * if (md) return md\n * return NextResponse.next()\n * }\n *\n * export const config = {\n * matcher: ['/((?!_next|favicon.ico|md|api).*)'],\n * }\n * ```\n */\nexport function markdownProxy(\n request: NextRequest,\n options?: MarkdownProxyOptions\n): NextResponse | null {\n const accept = request.headers.get('accept')\n if (!prefersMarkdown(accept)) return null\n\n const prefix = options?.mdPathPrefix ?? DEFAULT_MD_PREFIX\n const setVary = options?.setVary ?? true\n const setForwardedAccept = options?.setForwardedAccept ?? true\n\n const url = request.nextUrl.clone()\n if (url.pathname.startsWith(`${prefix}/`) || url.pathname === prefix) {\n return null\n }\n url.pathname = `${prefix}${url.pathname}`\n\n const headers = new Headers(request.headers)\n if (setForwardedAccept) headers.set('x-forwarded-accept', 'text/markdown')\n\n const response = NextResponse.rewrite(url, { request: { headers } })\n if (setVary) response.headers.set('vary', 'accept')\n\n return response\n}\n","import { NextResponse, type NextRequest, type NextMiddleware } from 'next/server'\n\ninterface LynkowPreviewOptions {\n cmsOrigin?: string\n previewParam?: string\n}\n\nexport function lynkowPreviewMiddleware(options?: LynkowPreviewOptions): NextMiddleware {\n const cmsOrigin = options?.cmsOrigin || 'https://manage.lynkow.com'\n const previewParam = options?.previewParam || 'lynkow-preview'\n\n return function middleware(request: NextRequest) {\n const response = NextResponse.next()\n\n const isPreview = request.nextUrl.searchParams.has(previewParam)\n\n if (isPreview) {\n response.headers.set('Content-Security-Policy', `frame-ancestors 'self' ${cmsOrigin}`)\n response.headers.delete('X-Frame-Options')\n } else {\n response.headers.set('Content-Security-Policy', \"frame-ancestors 'self'\")\n }\n\n return response\n }\n}\n\nfunction addCspHeaders(\n response: NextResponse | Response,\n request: NextRequest,\n cmsOrigin: string,\n previewParam: string\n): NextResponse | Response {\n const isPreview = request.nextUrl.searchParams.has(previewParam)\n\n if (isPreview) {\n response.headers.set('Content-Security-Policy', `frame-ancestors 'self' ${cmsOrigin}`)\n response.headers.delete('X-Frame-Options')\n } else {\n response.headers.set('Content-Security-Policy', \"frame-ancestors 'self'\")\n }\n\n return response\n}\n\nexport function withLynkowPreview(\n existingMiddleware: NextMiddleware,\n options?: LynkowPreviewOptions\n): NextMiddleware {\n const cmsOrigin = options?.cmsOrigin || 'https://manage.lynkow.com'\n const previewParam = options?.previewParam || 'lynkow-preview'\n\n return function middleware(request: NextRequest, event: any) {\n const response = existingMiddleware(request, event)\n\n const finalResponse =\n response instanceof Promise\n ? response.then((res) =>\n addCspHeaders(res || NextResponse.next(), request, cmsOrigin, previewParam)\n )\n : addCspHeaders(response || NextResponse.next(), request, cmsOrigin, previewParam)\n\n return finalResponse\n }\n}\n\nexport { markdownProxy, prefersMarkdown, acceptQuality } from './next-markdown'\nexport type { MarkdownProxyOptions } from './next-markdown'\n"]}
@@ -1,3 +1,3 @@
1
- import {NextResponse}from'next/server';function p(e){let n=e?.cmsOrigin||"https://manage.lynkow.com",r=e?.previewParam||"lynkow-preview";return function(i){let t=NextResponse.next();return i.nextUrl.searchParams.has(r)?(t.headers.set("Content-Security-Policy",`frame-ancestors 'self' ${n}`),t.headers.delete("X-Frame-Options")):t.headers.set("Content-Security-Policy","frame-ancestors 'self'"),t}}function w(e,n,r,s){return n.nextUrl.searchParams.has(s)?(e.headers.set("Content-Security-Policy",`frame-ancestors 'self' ${r}`),e.headers.delete("X-Frame-Options")):e.headers.set("Content-Security-Policy","frame-ancestors 'self'"),e}function P(e,n){let r=n?.cmsOrigin||"https://manage.lynkow.com",s=n?.previewParam||"lynkow-preview";return function(t,c){let a=e(t,c);return a instanceof Promise?a.then(l=>w(l||NextResponse.next(),t,r,s)):w(a||NextResponse.next(),t,r,s)}}
2
- export{p as lynkowPreviewMiddleware,P as withLynkowPreview};//# sourceMappingURL=next.mjs.map
1
+ import {NextResponse}from'next/server';var P="/md";function w(e,t){if(!e)return 0;let[r,s]=t.toLowerCase().split("/"),i=0;for(let n of e.split(",")){let o=n.trim().split(";").map(p=>p.trim()).filter(Boolean),a=o[0]?.toLowerCase();if(!a||!a.includes("/"))continue;let[c,l]=a.split("/");if(!(c===r&&l===s||c===r&&l==="*"||c==="*"&&l==="*"))continue;let d=1;for(let p of o.slice(1))if(p.startsWith("q=")){let m=Number.parseFloat(p.slice(2));!Number.isNaN(m)&&m>=0&&m<=1&&(d=m);}d>i&&(i=d);}return i}function u(e){let t=w(e,"text/markdown");if(t===0)return false;let r=w(e,"text/html");return t>r}function h(e,t){let r=e.headers.get("accept");if(!u(r))return null;let s=t?.mdPathPrefix??P,i=t?.setVary??true,n=t?.setForwardedAccept??true,o=e.nextUrl.clone();if(o.pathname.startsWith(`${s}/`)||o.pathname===s)return null;o.pathname=`${s}${o.pathname}`;let a=new Headers(e.headers);n&&a.set("x-forwarded-accept","text/markdown");let c=NextResponse.rewrite(o,{request:{headers:a}});return i&&c.headers.set("vary","accept"),c}function R(e){let t=e?.cmsOrigin||"https://manage.lynkow.com",r=e?.previewParam||"lynkow-preview";return function(i){let n=NextResponse.next();return i.nextUrl.searchParams.has(r)?(n.headers.set("Content-Security-Policy",`frame-ancestors 'self' ${t}`),n.headers.delete("X-Frame-Options")):n.headers.set("Content-Security-Policy","frame-ancestors 'self'"),n}}function x(e,t,r,s){return t.nextUrl.searchParams.has(s)?(e.headers.set("Content-Security-Policy",`frame-ancestors 'self' ${r}`),e.headers.delete("X-Frame-Options")):e.headers.set("Content-Security-Policy","frame-ancestors 'self'"),e}function O(e,t){let r=t?.cmsOrigin||"https://manage.lynkow.com",s=t?.previewParam||"lynkow-preview";return function(n,o){let a=e(n,o);return a instanceof Promise?a.then(l=>x(l||NextResponse.next(),n,r,s)):x(a||NextResponse.next(),n,r,s)}}
2
+ export{w as acceptQuality,R as lynkowPreviewMiddleware,h as markdownProxy,u as prefersMarkdown,O as withLynkowPreview};//# sourceMappingURL=next.mjs.map
3
3
  //# sourceMappingURL=next.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middleware/next.ts"],"names":["lynkowPreviewMiddleware","options","cmsOrigin","previewParam","request","response","NextResponse","addCspHeaders","withLynkowPreview","existingMiddleware","event","res"],"mappings":"uCAOO,SAASA,CAAAA,CAAwBC,CAAAA,CAAgD,CACtF,IAAMC,CAAAA,CAAYD,CAAAA,EAAS,SAAA,EAAa,2BAAA,CAClCE,EAAeF,CAAAA,EAAS,YAAA,EAAgB,gBAAA,CAE9C,OAAO,SAAoBG,CAAAA,CAAsB,CAC/C,IAAMC,EAAWC,YAAAA,CAAa,IAAA,EAAK,CAInC,OAFkBF,CAAAA,CAAQ,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAID,CAAY,CAAA,EAG7DE,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,yBAAA,CAA2B,CAAA,uBAAA,EAA0BH,CAAS,CAAA,CAAE,EACrFG,CAAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,iBAAiB,CAAA,EAEzCA,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,0BAA2B,wBAAwB,CAAA,CAGnEA,CACT,CACF,CAEA,SAASE,CAAAA,CACPF,CAAAA,CACAD,EACAF,CAAAA,CACAC,CAAAA,CACyB,CAGzB,OAFkBC,CAAAA,CAAQ,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAID,CAAY,CAAA,EAG7DE,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,yBAAA,CAA2B,CAAA,uBAAA,EAA0BH,CAAS,CAAA,CAAE,EACrFG,CAAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,iBAAiB,CAAA,EAEzCA,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,0BAA2B,wBAAwB,CAAA,CAGnEA,CACT,CAEO,SAASG,CAAAA,CACdC,CAAAA,CACAR,CAAAA,CACgB,CAChB,IAAMC,CAAAA,CAAYD,CAAAA,EAAS,SAAA,EAAa,2BAAA,CAClCE,CAAAA,CAAeF,CAAAA,EAAS,YAAA,EAAgB,iBAE9C,OAAO,SAAoBG,CAAAA,CAAsBM,CAAAA,CAAY,CAC3D,IAAML,CAAAA,CAAWI,CAAAA,CAAmBL,EAASM,CAAK,CAAA,CASlD,OANEL,CAAAA,YAAoB,OAAA,CAChBA,CAAAA,CAAS,IAAA,CAAMM,CAAAA,EACbJ,EAAcI,CAAAA,EAAOL,YAAAA,CAAa,IAAA,EAAK,CAAGF,CAAAA,CAASF,CAAAA,CAAWC,CAAY,CAC5E,EACAI,CAAAA,CAAcF,CAAAA,EAAYC,YAAAA,CAAa,IAAA,EAAK,CAAGF,CAAAA,CAASF,CAAAA,CAAWC,CAAY,CAGvF,CACF","file":"next.mjs","sourcesContent":["import { NextResponse, type NextRequest, type NextMiddleware } from 'next/server'\n\ninterface LynkowPreviewOptions {\n cmsOrigin?: string\n previewParam?: string\n}\n\nexport function lynkowPreviewMiddleware(options?: LynkowPreviewOptions): NextMiddleware {\n const cmsOrigin = options?.cmsOrigin || 'https://manage.lynkow.com'\n const previewParam = options?.previewParam || 'lynkow-preview'\n\n return function middleware(request: NextRequest) {\n const response = NextResponse.next()\n\n const isPreview = request.nextUrl.searchParams.has(previewParam)\n\n if (isPreview) {\n response.headers.set('Content-Security-Policy', `frame-ancestors 'self' ${cmsOrigin}`)\n response.headers.delete('X-Frame-Options')\n } else {\n response.headers.set('Content-Security-Policy', \"frame-ancestors 'self'\")\n }\n\n return response\n }\n}\n\nfunction addCspHeaders(\n response: NextResponse | Response,\n request: NextRequest,\n cmsOrigin: string,\n previewParam: string\n): NextResponse | Response {\n const isPreview = request.nextUrl.searchParams.has(previewParam)\n\n if (isPreview) {\n response.headers.set('Content-Security-Policy', `frame-ancestors 'self' ${cmsOrigin}`)\n response.headers.delete('X-Frame-Options')\n } else {\n response.headers.set('Content-Security-Policy', \"frame-ancestors 'self'\")\n }\n\n return response\n}\n\nexport function withLynkowPreview(\n existingMiddleware: NextMiddleware,\n options?: LynkowPreviewOptions\n): NextMiddleware {\n const cmsOrigin = options?.cmsOrigin || 'https://manage.lynkow.com'\n const previewParam = options?.previewParam || 'lynkow-preview'\n\n return function middleware(request: NextRequest, event: any) {\n const response = existingMiddleware(request, event)\n\n const finalResponse =\n response instanceof Promise\n ? response.then((res) =>\n addCspHeaders(res || NextResponse.next(), request, cmsOrigin, previewParam)\n )\n : addCspHeaders(response || NextResponse.next(), request, cmsOrigin, previewParam)\n\n return finalResponse\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/middleware/next-markdown.ts","../../src/middleware/next.ts"],"names":["DEFAULT_MD_PREFIX","acceptQuality","acceptHeader","targetType","targetMain","targetSub","best","raw","parts","mediaType","main","sub","q","param","parsed","prefersMarkdown","md","html","markdownProxy","request","options","accept","prefix","setVary","setForwardedAccept","url","headers","response","NextResponse","lynkowPreviewMiddleware","cmsOrigin","previewParam","addCspHeaders","withLynkowPreview","existingMiddleware","event","res"],"mappings":"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","file":"next.mjs","sourcesContent":["import { NextResponse, type NextRequest } from 'next/server'\n\nconst DEFAULT_MD_PREFIX = '/md'\n\n/**\n * Options for the {@link markdownProxy} helper.\n */\nexport interface MarkdownProxyOptions {\n /**\n * URL prefix where the Markdown route handler is mounted. The helper\n * rewrites `Accept: text/markdown` requests from `<path>` to\n * `<mdPathPrefix><path>`. Defaults to `'/md'`.\n *\n * If your route handler lives at `app/markdown/[...path]/route.ts`,\n * set this to `'/markdown'`.\n */\n mdPathPrefix?: string\n\n /**\n * Whether to set `Vary: Accept` on the rewritten response. Defaults\n * to `true`. Set to `false` only if your edge or CDN already sets the\n * header and you want to avoid duplication.\n */\n setVary?: boolean\n\n /**\n * Whether to set `X-Forwarded-Accept: text/markdown` on the rewritten\n * request so the route handler can confirm the request originated\n * from a Markdown negotiation. Defaults to `true`.\n */\n setForwardedAccept?: boolean\n}\n\n/**\n * Parses an `Accept` header per RFC 9110 §12.5.1 and returns the\n * highest q-value for a target media type. Supports exact match\n * (`text/markdown`), type wildcard (`text/*`), full wildcard (`*\\/*`),\n * and q-values.\n *\n * Returns `0` if the type is not present or has `q=0`. Returns `1` if\n * no q value is given (per RFC, missing q means q=1). Out-of-range or\n * non-numeric q values fall back to the default `1`.\n *\n * @param acceptHeader - The raw `Accept` header value, or `null` if absent\n * @param targetType - The target media type, e.g. `'text/markdown'`\n * @returns The highest q-value for the target type (0 to 1)\n *\n * @example\n * ```typescript\n * acceptQuality('text/html;q=0.9, text/markdown;q=0.8', 'text/markdown') // 0.8\n * acceptQuality('text/markdown', 'text/markdown') // 1\n * acceptQuality(null, 'text/markdown') // 0\n * ```\n */\nexport function acceptQuality(acceptHeader: string | null, targetType: string): number {\n if (!acceptHeader) return 0\n const [targetMain, targetSub] = targetType.toLowerCase().split('/')\n let best = 0\n\n for (const raw of acceptHeader.split(',')) {\n const parts = raw\n .trim()\n .split(';')\n .map((p) => p.trim())\n .filter(Boolean)\n const mediaType = parts[0]?.toLowerCase()\n if (!mediaType || !mediaType.includes('/')) continue\n\n const [main, sub] = mediaType.split('/')\n const matches =\n (main === targetMain && sub === targetSub) ||\n (main === targetMain && sub === '*') ||\n (main === '*' && sub === '*')\n\n if (!matches) continue\n\n let q = 1\n for (const param of parts.slice(1)) {\n if (param.startsWith('q=')) {\n const parsed = Number.parseFloat(param.slice(2))\n if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 1) q = parsed\n }\n }\n if (q > best) best = q\n }\n\n return best\n}\n\n/**\n * Decides whether the request prefers Markdown over HTML based on its\n * `Accept` header.\n *\n * Returns `true` when `Accept` contains `text/markdown` with q strictly\n * higher than `text/html`. Ties break in favor of HTML (the default\n * browsers expect).\n *\n * @param acceptHeader - The raw `Accept` header value, or `null` if absent\n * @returns `true` when Markdown is the preferred representation\n *\n * @example\n * ```typescript\n * prefersMarkdown('text/markdown;q=0.9, text/html;q=0.8') // true\n * prefersMarkdown('text/markdown, text/html') // false (tie -> HTML)\n * prefersMarkdown('text/html') // false\n * ```\n */\nexport function prefersMarkdown(acceptHeader: string | null): boolean {\n const md = acceptQuality(acceptHeader, 'text/markdown')\n if (md === 0) return false\n const html = acceptQuality(acceptHeader, 'text/html')\n return md > html\n}\n\n/**\n * Next.js proxy/middleware helper that translates\n * `Accept: text/markdown` requests into rewrites to a dedicated\n * Markdown route handler.\n *\n * Returns the rewritten `NextResponse` when the request preferred\n * Markdown, or `null` when the request did not (so the caller can pass\n * through to the next middleware step).\n *\n * Sets `Vary: Accept` on the rewritten response so intermediate caches\n * keep separate variants for HTML and Markdown.\n *\n * The helper is Edge runtime compatible (no `node:` imports).\n *\n * @param request - The incoming `NextRequest`\n * @param options - Optional {@link MarkdownProxyOptions} overrides\n * @returns A `NextResponse.rewrite(...)` when Markdown was preferred, or `null` otherwise\n *\n * @example\n * ```typescript\n * // Next 16, proxy.ts\n * import { markdownProxy } from 'lynkow/middleware/next'\n * import { NextResponse, type NextRequest } from 'next/server'\n *\n * export function proxy(request: NextRequest) {\n * const md = markdownProxy(request)\n * if (md) return md\n * return NextResponse.next()\n * }\n *\n * export const config = {\n * matcher: ['/((?!_next|favicon.ico|md|api).*)'],\n * }\n * ```\n */\nexport function markdownProxy(\n request: NextRequest,\n options?: MarkdownProxyOptions\n): NextResponse | null {\n const accept = request.headers.get('accept')\n if (!prefersMarkdown(accept)) return null\n\n const prefix = options?.mdPathPrefix ?? DEFAULT_MD_PREFIX\n const setVary = options?.setVary ?? true\n const setForwardedAccept = options?.setForwardedAccept ?? true\n\n const url = request.nextUrl.clone()\n if (url.pathname.startsWith(`${prefix}/`) || url.pathname === prefix) {\n return null\n }\n url.pathname = `${prefix}${url.pathname}`\n\n const headers = new Headers(request.headers)\n if (setForwardedAccept) headers.set('x-forwarded-accept', 'text/markdown')\n\n const response = NextResponse.rewrite(url, { request: { headers } })\n if (setVary) response.headers.set('vary', 'accept')\n\n return response\n}\n","import { NextResponse, type NextRequest, type NextMiddleware } from 'next/server'\n\ninterface LynkowPreviewOptions {\n cmsOrigin?: string\n previewParam?: string\n}\n\nexport function lynkowPreviewMiddleware(options?: LynkowPreviewOptions): NextMiddleware {\n const cmsOrigin = options?.cmsOrigin || 'https://manage.lynkow.com'\n const previewParam = options?.previewParam || 'lynkow-preview'\n\n return function middleware(request: NextRequest) {\n const response = NextResponse.next()\n\n const isPreview = request.nextUrl.searchParams.has(previewParam)\n\n if (isPreview) {\n response.headers.set('Content-Security-Policy', `frame-ancestors 'self' ${cmsOrigin}`)\n response.headers.delete('X-Frame-Options')\n } else {\n response.headers.set('Content-Security-Policy', \"frame-ancestors 'self'\")\n }\n\n return response\n }\n}\n\nfunction addCspHeaders(\n response: NextResponse | Response,\n request: NextRequest,\n cmsOrigin: string,\n previewParam: string\n): NextResponse | Response {\n const isPreview = request.nextUrl.searchParams.has(previewParam)\n\n if (isPreview) {\n response.headers.set('Content-Security-Policy', `frame-ancestors 'self' ${cmsOrigin}`)\n response.headers.delete('X-Frame-Options')\n } else {\n response.headers.set('Content-Security-Policy', \"frame-ancestors 'self'\")\n }\n\n return response\n}\n\nexport function withLynkowPreview(\n existingMiddleware: NextMiddleware,\n options?: LynkowPreviewOptions\n): NextMiddleware {\n const cmsOrigin = options?.cmsOrigin || 'https://manage.lynkow.com'\n const previewParam = options?.previewParam || 'lynkow-preview'\n\n return function middleware(request: NextRequest, event: any) {\n const response = existingMiddleware(request, event)\n\n const finalResponse =\n response instanceof Promise\n ? response.then((res) =>\n addCspHeaders(res || NextResponse.next(), request, cmsOrigin, previewParam)\n )\n : addCspHeaders(response || NextResponse.next(), request, cmsOrigin, previewParam)\n\n return finalResponse\n }\n}\n\nexport { markdownProxy, prefersMarkdown, acceptQuality } from './next-markdown'\nexport type { MarkdownProxyOptions } from './next-markdown'\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkow",
3
- "version": "1.32.20",
3
+ "version": "1.32.22",
4
4
  "description": "Official SDK for Lynkow Headless",
5
5
  "author": "Lynkow",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -96,6 +96,7 @@
96
96
  "prepublishOnly": "npm run build"
97
97
  },
98
98
  "devDependencies": {
99
+ "@edge-runtime/vm": "^5.0.0",
99
100
  "@eslint/js": "^9.39.2",
100
101
  "@testing-library/jest-dom": "^6.9.1",
101
102
  "@testing-library/react": "^16.3.2",