next-ai-discovery 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +267 -0
  3. package/dist/index.cjs +21 -0
  4. package/dist/index.d.cts +11 -0
  5. package/dist/index.d.cts.map +1 -0
  6. package/dist/index.d.ts +11 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +7 -0
  9. package/dist/llms-route.cjs +37 -0
  10. package/dist/llms-route.d.cts +13 -0
  11. package/dist/llms-route.d.cts.map +1 -0
  12. package/dist/llms-route.d.ts +13 -0
  13. package/dist/llms-route.d.ts.map +1 -0
  14. package/dist/llms-route.js +33 -0
  15. package/dist/llms.cjs +40 -0
  16. package/dist/llms.d.cts +20 -0
  17. package/dist/llms.d.cts.map +1 -0
  18. package/dist/llms.d.ts +20 -0
  19. package/dist/llms.d.ts.map +1 -0
  20. package/dist/llms.js +36 -0
  21. package/dist/markdown-route.cjs +59 -0
  22. package/dist/markdown-route.d.cts +16 -0
  23. package/dist/markdown-route.d.cts.map +1 -0
  24. package/dist/markdown-route.d.ts +16 -0
  25. package/dist/markdown-route.d.ts.map +1 -0
  26. package/dist/markdown-route.js +55 -0
  27. package/dist/metadata.cjs +26 -0
  28. package/dist/metadata.d.cts +57 -0
  29. package/dist/metadata.d.cts.map +1 -0
  30. package/dist/metadata.d.ts +57 -0
  31. package/dist/metadata.d.ts.map +1 -0
  32. package/dist/metadata.js +22 -0
  33. package/dist/pathname.cjs +37 -0
  34. package/dist/pathname.d.cts +4 -0
  35. package/dist/pathname.d.cts.map +1 -0
  36. package/dist/pathname.d.ts +4 -0
  37. package/dist/pathname.d.ts.map +1 -0
  38. package/dist/pathname.js +32 -0
  39. package/dist/proxy.cjs +79 -0
  40. package/dist/proxy.d.cts +22 -0
  41. package/dist/proxy.d.cts.map +1 -0
  42. package/dist/proxy.d.ts +22 -0
  43. package/dist/proxy.d.ts.map +1 -0
  44. package/dist/proxy.js +75 -0
  45. package/package.json +66 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-route.d.ts","sourceRoot":"","sources":["../src/markdown-route.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,eAAe,GAAG,IAAI,CAAC;AAE9D,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClE,CAAC;AAmCF,eAAO,MAAM,mBAAmB,GAAI,SAAS,oBAAoB,MAGjD,SAAS,WAAW,sBA6BnC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { NextRequest } from 'next/server.js';
2
+ export type MarkdownContent = {
3
+ body: string;
4
+ frontmatter?: Record<string, unknown>;
5
+ };
6
+ export type GetMarkdown = (pathname: string, request: NextRequest) => Promise<MarkdownContent | null> | MarkdownContent | null;
7
+ export type MarkdownRouteOptions = {
8
+ getMarkdown: GetMarkdown;
9
+ includeFrontmatter?: boolean;
10
+ onServed?: (event: {
11
+ pathname: string;
12
+ status: number;
13
+ }) => void;
14
+ };
15
+ export declare const createMarkdownRoute: (options: MarkdownRouteOptions) => (request: NextRequest) => Promise<Response>;
16
+ //# sourceMappingURL=markdown-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-route.d.ts","sourceRoot":"","sources":["../src/markdown-route.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,eAAe,GAAG,IAAI,CAAC;AAE9D,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClE,CAAC;AAmCF,eAAO,MAAM,mBAAmB,GAAI,SAAS,oBAAoB,MAGjD,SAAS,WAAW,sBA6BnC,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { normalizePathname } from "./pathname.js";
2
+ const DEFAULT_HEADERS = {
3
+ 'Content-Type': 'text/markdown; charset=utf-8',
4
+ Vary: 'Accept',
5
+ };
6
+ const toFrontmatter = (frontmatter) => {
7
+ const lines = ['---'];
8
+ for (const [key, value] of Object.entries(frontmatter)) {
9
+ lines.push(`${key}: ${JSON.stringify(value)}`);
10
+ }
11
+ lines.push('---');
12
+ return lines.join('\n');
13
+ };
14
+ const buildBody = (content, includeFrontmatter) => {
15
+ if (!(includeFrontmatter && content.frontmatter)) {
16
+ return content.body;
17
+ }
18
+ return `${toFrontmatter(content.frontmatter)}\n\n${content.body}`;
19
+ };
20
+ const handleError = (error) => {
21
+ console.error('[next-ai-discovery] markdown route error', error);
22
+ return new Response('Internal Server Error', {
23
+ status: 500,
24
+ headers: DEFAULT_HEADERS,
25
+ });
26
+ };
27
+ export const createMarkdownRoute = (options) => {
28
+ const includeFrontmatter = options.includeFrontmatter ?? true;
29
+ return async (request) => {
30
+ const url = new URL(request.url);
31
+ const rawPath = url.searchParams.get('path') ?? '/';
32
+ const pathname = normalizePathname(rawPath);
33
+ try {
34
+ const content = await options.getMarkdown(pathname, request);
35
+ const isHead = request.method === 'HEAD';
36
+ if (!content) {
37
+ options.onServed?.({ pathname, status: 404 });
38
+ return new Response(isHead ? null : 'Not Found', {
39
+ status: 404,
40
+ headers: DEFAULT_HEADERS,
41
+ });
42
+ }
43
+ const body = buildBody(content, includeFrontmatter);
44
+ options.onServed?.({ pathname, status: 200 });
45
+ return new Response(isHead ? null : body, {
46
+ status: 200,
47
+ headers: DEFAULT_HEADERS,
48
+ });
49
+ }
50
+ catch (error) {
51
+ options.onServed?.({ pathname, status: 500 });
52
+ return handleError(error);
53
+ }
54
+ };
55
+ };
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withMarkdownAlternate = void 0;
4
+ const pathname_js_1 = require("./pathname.cjs");
5
+ const mergeMarkdownAlternate = (metadata, mdPath) => {
6
+ const alternates = metadata.alternates ?? {};
7
+ const types = alternates.types ?? {};
8
+ return {
9
+ ...metadata,
10
+ alternates: {
11
+ ...alternates,
12
+ types: {
13
+ ...types,
14
+ 'text/markdown': mdPath,
15
+ },
16
+ },
17
+ };
18
+ };
19
+ const withMarkdownAlternate = (metadata, pathname, options = {}) => {
20
+ if (options.shouldAdd === false) {
21
+ return metadata;
22
+ }
23
+ const mdPath = (0, pathname_js_1.pathnameToMd)(pathname);
24
+ return mergeMarkdownAlternate(metadata, mdPath);
25
+ };
26
+ exports.withMarkdownAlternate = withMarkdownAlternate;
@@ -0,0 +1,57 @@
1
+ import type { Metadata } from 'next';
2
+ export type MarkdownAlternateOptions = {
3
+ shouldAdd?: boolean;
4
+ };
5
+ export declare const withMarkdownAlternate: (metadata: Metadata, pathname: string, options?: MarkdownAlternateOptions) => Metadata | {
6
+ alternates: {
7
+ types: {
8
+ 'text/markdown': string;
9
+ };
10
+ canonical?: null | string | URL | import("next/dist/lib/metadata/types/alternative-urls-types.js").AlternateLinkDescriptor | undefined;
11
+ languages?: import("next/dist/lib/metadata/types/alternative-urls-types.js").Languages<null | string | URL | import("next/dist/lib/metadata/types/alternative-urls-types.js").AlternateLinkDescriptor[]> | undefined;
12
+ media?: {
13
+ [media: string]: null | string | URL | import("next/dist/lib/metadata/types/alternative-urls-types.js").AlternateLinkDescriptor[];
14
+ } | undefined;
15
+ };
16
+ metadataBase?: null | string | URL | undefined;
17
+ title?: null | string | import("next/dist/lib/metadata/types/metadata-types.js").TemplateString | undefined;
18
+ description?: null | string | undefined;
19
+ applicationName?: null | string | undefined;
20
+ authors?: null | import("next/dist/lib/metadata/types/metadata-types.js").Author | Array<import("next/dist/lib/metadata/types/metadata-types.js").Author> | undefined;
21
+ generator?: null | string | undefined;
22
+ keywords?: null | string | Array<string> | undefined;
23
+ referrer?: null | import("next/dist/lib/metadata/types/metadata-types.js").ReferrerEnum | undefined;
24
+ themeColor?: null | string | import("next/dist/lib/metadata/types/metadata-types.js").ThemeColorDescriptor | import("next/dist/lib/metadata/types/metadata-types.js").ThemeColorDescriptor[] | undefined;
25
+ colorScheme?: null | import("next/dist/lib/metadata/types/metadata-types.js").ColorSchemeEnum | undefined;
26
+ viewport?: null | string | import("next/dist/lib/metadata/types/extra-types.js").ViewportLayout | undefined;
27
+ creator?: null | string | undefined;
28
+ publisher?: null | string | undefined;
29
+ robots?: null | string | import("next/dist/lib/metadata/types/metadata-types.js").Robots | undefined;
30
+ icons?: null | import("next/dist/lib/metadata/types/metadata-types.js").IconURL | Array<import("next/dist/lib/metadata/types/metadata-types.js").Icon> | import("next/dist/lib/metadata/types/metadata-types.js").Icons | undefined;
31
+ manifest?: null | string | URL | undefined;
32
+ openGraph?: null | import("next/dist/lib/metadata/types/opengraph-types.js").OpenGraph | undefined;
33
+ twitter?: null | import("next/dist/lib/metadata/types/twitter-types.js").Twitter | undefined;
34
+ facebook?: null | import("next/dist/lib/metadata/types/extra-types.js").Facebook | undefined;
35
+ pinterest?: null | import("next/dist/lib/metadata/types/extra-types.js").Pinterest;
36
+ verification?: import("next/dist/lib/metadata/types/metadata-types.js").Verification | undefined;
37
+ appleWebApp?: null | boolean | import("next/dist/lib/metadata/types/extra-types.js").AppleWebApp | undefined;
38
+ formatDetection?: null | import("next/dist/lib/metadata/types/extra-types.js").FormatDetection | undefined;
39
+ itunes?: null | import("next/dist/lib/metadata/types/extra-types.js").ItunesApp | undefined;
40
+ abstract?: null | string | undefined;
41
+ appLinks?: null | import("next/dist/lib/metadata/types/extra-types.js").AppLinks | undefined;
42
+ archives?: null | string | Array<string> | undefined;
43
+ assets?: null | string | Array<string> | undefined;
44
+ bookmarks?: null | string | Array<string> | undefined;
45
+ pagination?: {
46
+ previous?: null | string | URL | undefined;
47
+ next?: null | string | URL | undefined;
48
+ };
49
+ category?: null | string | undefined;
50
+ classification?: null | string | undefined;
51
+ other?: ({
52
+ [name: string]: string | number | Array<string | number>;
53
+ } & import("next/dist/lib/metadata/types/metadata-types.js").DeprecatedMetadataFields) | undefined;
54
+ 'apple-touch-fullscreen'?: never | undefined;
55
+ 'apple-touch-icon-precomposed'?: never | undefined;
56
+ };
57
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAErC,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAkBF,eAAO,MAAM,qBAAqB,GAChC,UAAU,QAAQ,EAClB,UAAU,MAAM,EAChB,UAAS,wBAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBASwhb,CAAC;YAA+C,CAAC;;;;;;;;;CADhnb,CAAC"}
@@ -0,0 +1,57 @@
1
+ import type { Metadata } from 'next';
2
+ export type MarkdownAlternateOptions = {
3
+ shouldAdd?: boolean;
4
+ };
5
+ export declare const withMarkdownAlternate: (metadata: Metadata, pathname: string, options?: MarkdownAlternateOptions) => Metadata | {
6
+ alternates: {
7
+ types: {
8
+ 'text/markdown': string;
9
+ };
10
+ canonical?: null | string | URL | import("next/dist/lib/metadata/types/alternative-urls-types.js").AlternateLinkDescriptor | undefined;
11
+ languages?: import("next/dist/lib/metadata/types/alternative-urls-types.js").Languages<null | string | URL | import("next/dist/lib/metadata/types/alternative-urls-types.js").AlternateLinkDescriptor[]> | undefined;
12
+ media?: {
13
+ [media: string]: null | string | URL | import("next/dist/lib/metadata/types/alternative-urls-types.js").AlternateLinkDescriptor[];
14
+ } | undefined;
15
+ };
16
+ metadataBase?: null | string | URL | undefined;
17
+ title?: null | string | import("next/dist/lib/metadata/types/metadata-types.js").TemplateString | undefined;
18
+ description?: null | string | undefined;
19
+ applicationName?: null | string | undefined;
20
+ authors?: null | import("next/dist/lib/metadata/types/metadata-types.js").Author | Array<import("next/dist/lib/metadata/types/metadata-types.js").Author> | undefined;
21
+ generator?: null | string | undefined;
22
+ keywords?: null | string | Array<string> | undefined;
23
+ referrer?: null | import("next/dist/lib/metadata/types/metadata-types.js").ReferrerEnum | undefined;
24
+ themeColor?: null | string | import("next/dist/lib/metadata/types/metadata-types.js").ThemeColorDescriptor | import("next/dist/lib/metadata/types/metadata-types.js").ThemeColorDescriptor[] | undefined;
25
+ colorScheme?: null | import("next/dist/lib/metadata/types/metadata-types.js").ColorSchemeEnum | undefined;
26
+ viewport?: null | string | import("next/dist/lib/metadata/types/extra-types.js").ViewportLayout | undefined;
27
+ creator?: null | string | undefined;
28
+ publisher?: null | string | undefined;
29
+ robots?: null | string | import("next/dist/lib/metadata/types/metadata-types.js").Robots | undefined;
30
+ icons?: null | import("next/dist/lib/metadata/types/metadata-types.js").IconURL | Array<import("next/dist/lib/metadata/types/metadata-types.js").Icon> | import("next/dist/lib/metadata/types/metadata-types.js").Icons | undefined;
31
+ manifest?: null | string | URL | undefined;
32
+ openGraph?: null | import("next/dist/lib/metadata/types/opengraph-types.js").OpenGraph | undefined;
33
+ twitter?: null | import("next/dist/lib/metadata/types/twitter-types.js").Twitter | undefined;
34
+ facebook?: null | import("next/dist/lib/metadata/types/extra-types.js").Facebook | undefined;
35
+ pinterest?: null | import("next/dist/lib/metadata/types/extra-types.js").Pinterest;
36
+ verification?: import("next/dist/lib/metadata/types/metadata-types.js").Verification | undefined;
37
+ appleWebApp?: null | boolean | import("next/dist/lib/metadata/types/extra-types.js").AppleWebApp | undefined;
38
+ formatDetection?: null | import("next/dist/lib/metadata/types/extra-types.js").FormatDetection | undefined;
39
+ itunes?: null | import("next/dist/lib/metadata/types/extra-types.js").ItunesApp | undefined;
40
+ abstract?: null | string | undefined;
41
+ appLinks?: null | import("next/dist/lib/metadata/types/extra-types.js").AppLinks | undefined;
42
+ archives?: null | string | Array<string> | undefined;
43
+ assets?: null | string | Array<string> | undefined;
44
+ bookmarks?: null | string | Array<string> | undefined;
45
+ pagination?: {
46
+ previous?: null | string | URL | undefined;
47
+ next?: null | string | URL | undefined;
48
+ };
49
+ category?: null | string | undefined;
50
+ classification?: null | string | undefined;
51
+ other?: ({
52
+ [name: string]: string | number | Array<string | number>;
53
+ } & import("next/dist/lib/metadata/types/metadata-types.js").DeprecatedMetadataFields) | undefined;
54
+ 'apple-touch-fullscreen'?: never | undefined;
55
+ 'apple-touch-icon-precomposed'?: never | undefined;
56
+ };
57
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAErC,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAkBF,eAAO,MAAM,qBAAqB,GAChC,UAAU,QAAQ,EAClB,UAAU,MAAM,EAChB,UAAS,wBAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBASwhb,CAAC;YAA+C,CAAC;;;;;;;;;CADhnb,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { pathnameToMd } from "./pathname.js";
2
+ const mergeMarkdownAlternate = (metadata, mdPath) => {
3
+ const alternates = metadata.alternates ?? {};
4
+ const types = alternates.types ?? {};
5
+ return {
6
+ ...metadata,
7
+ alternates: {
8
+ ...alternates,
9
+ types: {
10
+ ...types,
11
+ 'text/markdown': mdPath,
12
+ },
13
+ },
14
+ };
15
+ };
16
+ export const withMarkdownAlternate = (metadata, pathname, options = {}) => {
17
+ if (options.shouldAdd === false) {
18
+ return metadata;
19
+ }
20
+ const mdPath = pathnameToMd(pathname);
21
+ return mergeMarkdownAlternate(metadata, mdPath);
22
+ };
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pathnameToMd = exports.normalizePathname = exports.DEFAULT_INDEX_PATH = void 0;
4
+ exports.DEFAULT_INDEX_PATH = '/index';
5
+ const stripHashAndQuery = (pathname) => {
6
+ const queryIndex = pathname.indexOf('?');
7
+ const hashIndex = pathname.indexOf('#');
8
+ let cutIndex = -1;
9
+ if (queryIndex === -1) {
10
+ cutIndex = hashIndex;
11
+ }
12
+ else if (hashIndex === -1) {
13
+ cutIndex = queryIndex;
14
+ }
15
+ else {
16
+ cutIndex = Math.min(queryIndex, hashIndex);
17
+ }
18
+ return cutIndex === -1 ? pathname : pathname.slice(0, cutIndex);
19
+ };
20
+ const normalizePathname = (pathname) => {
21
+ const cleaned = stripHashAndQuery(pathname.trim());
22
+ const withLeadingSlash = cleaned.startsWith('/') ? cleaned : `/${cleaned}`;
23
+ const mdSuffix = '.md';
24
+ const withoutMd = withLeadingSlash.endsWith(mdSuffix)
25
+ ? withLeadingSlash.slice(0, -mdSuffix.length)
26
+ : withLeadingSlash;
27
+ if (withoutMd === '/' || withoutMd === '') {
28
+ return exports.DEFAULT_INDEX_PATH;
29
+ }
30
+ return withoutMd.endsWith('/') ? withoutMd.slice(0, -1) : withoutMd;
31
+ };
32
+ exports.normalizePathname = normalizePathname;
33
+ const pathnameToMd = (pathname) => {
34
+ const normalized = (0, exports.normalizePathname)(pathname);
35
+ return `${normalized}.md`;
36
+ };
37
+ exports.pathnameToMd = pathnameToMd;
@@ -0,0 +1,4 @@
1
+ export declare const DEFAULT_INDEX_PATH = "/index";
2
+ export declare const normalizePathname: (pathname: string) => string;
3
+ export declare const pathnameToMd: (pathname: string) => string;
4
+ //# sourceMappingURL=pathname.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathname.d.ts","sourceRoot":"","sources":["../src/pathname.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAkB3C,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,WAajD,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,UAAU,MAAM,WAG5C,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare const DEFAULT_INDEX_PATH = "/index";
2
+ export declare const normalizePathname: (pathname: string) => string;
3
+ export declare const pathnameToMd: (pathname: string) => string;
4
+ //# sourceMappingURL=pathname.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathname.d.ts","sourceRoot":"","sources":["../src/pathname.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAkB3C,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,WAajD,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,UAAU,MAAM,WAG5C,CAAC"}
@@ -0,0 +1,32 @@
1
+ export const DEFAULT_INDEX_PATH = '/index';
2
+ const stripHashAndQuery = (pathname) => {
3
+ const queryIndex = pathname.indexOf('?');
4
+ const hashIndex = pathname.indexOf('#');
5
+ let cutIndex = -1;
6
+ if (queryIndex === -1) {
7
+ cutIndex = hashIndex;
8
+ }
9
+ else if (hashIndex === -1) {
10
+ cutIndex = queryIndex;
11
+ }
12
+ else {
13
+ cutIndex = Math.min(queryIndex, hashIndex);
14
+ }
15
+ return cutIndex === -1 ? pathname : pathname.slice(0, cutIndex);
16
+ };
17
+ export const normalizePathname = (pathname) => {
18
+ const cleaned = stripHashAndQuery(pathname.trim());
19
+ const withLeadingSlash = cleaned.startsWith('/') ? cleaned : `/${cleaned}`;
20
+ const mdSuffix = '.md';
21
+ const withoutMd = withLeadingSlash.endsWith(mdSuffix)
22
+ ? withLeadingSlash.slice(0, -mdSuffix.length)
23
+ : withLeadingSlash;
24
+ if (withoutMd === '/' || withoutMd === '') {
25
+ return DEFAULT_INDEX_PATH;
26
+ }
27
+ return withoutMd.endsWith('/') ? withoutMd.slice(0, -1) : withoutMd;
28
+ };
29
+ export const pathnameToMd = (pathname) => {
30
+ const normalized = normalizePathname(pathname);
31
+ return `${normalized}.md`;
32
+ };
package/dist/proxy.cjs ADDED
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMarkdownProxy = exports.DEFAULT_ENDPOINT_PATH = exports.DEFAULT_MARKDOWN_ACCEPT = exports.DEFAULT_PROXY_EXCLUDE_EXACT = exports.DEFAULT_PROXY_EXCLUDE_PREFIXES = void 0;
4
+ const server_js_1 = require("next/server.js");
5
+ const pathname_js_1 = require("./pathname.cjs");
6
+ exports.DEFAULT_PROXY_EXCLUDE_PREFIXES = ['/_next', '/api'];
7
+ exports.DEFAULT_PROXY_EXCLUDE_EXACT = ['/robots.txt', '/sitemap.xml'];
8
+ exports.DEFAULT_MARKDOWN_ACCEPT = 'text/markdown';
9
+ exports.DEFAULT_ENDPOINT_PATH = '/__aid/md';
10
+ const hasAssetExtension = (pathname) => {
11
+ const lastSlash = pathname.lastIndexOf('/');
12
+ const lastDot = pathname.lastIndexOf('.');
13
+ return lastDot > lastSlash;
14
+ };
15
+ const shouldExcludePath = (pathname, options) => {
16
+ if (options.exclude?.(pathname)) {
17
+ return true;
18
+ }
19
+ const excludePrefixes = options.excludePrefixes ?? exports.DEFAULT_PROXY_EXCLUDE_PREFIXES;
20
+ for (const prefix of excludePrefixes) {
21
+ if (pathname.startsWith(prefix)) {
22
+ return true;
23
+ }
24
+ }
25
+ const excludeExact = options.excludeExact ?? exports.DEFAULT_PROXY_EXCLUDE_EXACT;
26
+ return excludeExact.includes(pathname);
27
+ };
28
+ const isMarkdownAccept = (request, acceptHeader) => {
29
+ const header = request.headers.get('accept') ?? '';
30
+ return header
31
+ .split(',')
32
+ .some((value) => value.trim().startsWith(acceptHeader));
33
+ };
34
+ const buildRewriteUrl = (request, pathname, endpoint) => {
35
+ const url = new URL(endpoint, request.url);
36
+ url.searchParams.set('path', pathname);
37
+ return url;
38
+ };
39
+ const createMarkdownProxy = (options = {}) => {
40
+ const endpointPath = options.endpointPath ?? exports.DEFAULT_ENDPOINT_PATH;
41
+ const enableDotMd = options.enableDotMd ?? true;
42
+ const enableAcceptNegotiation = options.enableAcceptNegotiation ?? true;
43
+ const acceptHeader = options.acceptHeader ?? exports.DEFAULT_MARKDOWN_ACCEPT;
44
+ const endpointPrefix = endpointPath.startsWith('/')
45
+ ? endpointPath
46
+ : `/${endpointPath}`;
47
+ const shouldSkipRequest = (request) => request.method !== 'GET' && request.method !== 'HEAD';
48
+ const isInternalEndpoint = (pathname) => pathname === endpointPrefix || pathname.startsWith(`${endpointPrefix}/`);
49
+ return (request, _event) => {
50
+ if (shouldSkipRequest(request)) {
51
+ return server_js_1.NextResponse.next();
52
+ }
53
+ const pathname = request.nextUrl.pathname;
54
+ // Avoid rewrite loops: the internal endpoint must never be rewritten.
55
+ if (isInternalEndpoint(pathname) || shouldExcludePath(pathname, options)) {
56
+ return server_js_1.NextResponse.next();
57
+ }
58
+ const isDotMd = enableDotMd && pathname.endsWith('.md');
59
+ // Don't rewrite "asset" paths like /logo.png, but do allow the explicit
60
+ // `.md` endpoint to pass through this check.
61
+ if (!isDotMd && hasAssetExtension(pathname)) {
62
+ return server_js_1.NextResponse.next();
63
+ }
64
+ const isAcceptMd = enableAcceptNegotiation && isMarkdownAccept(request, acceptHeader);
65
+ if (!(isDotMd || isAcceptMd)) {
66
+ return server_js_1.NextResponse.next();
67
+ }
68
+ const normalized = (0, pathname_js_1.normalizePathname)(pathname);
69
+ const rewriteUrl = buildRewriteUrl(request, normalized, endpointPath);
70
+ options.onRewrite?.({
71
+ type: isDotMd ? 'dotmd' : 'accept',
72
+ pathname: normalized,
73
+ });
74
+ const response = server_js_1.NextResponse.rewrite(rewriteUrl);
75
+ response.headers.set('Vary', 'Accept');
76
+ return response;
77
+ };
78
+ };
79
+ exports.createMarkdownProxy = createMarkdownProxy;
@@ -0,0 +1,22 @@
1
+ import { NextResponse } from 'next/server.js';
2
+ import type { NextFetchEvent, NextRequest } from 'next/server.js';
3
+ export type MarkdownRewriteType = 'accept' | 'dotmd';
4
+ export type MarkdownProxyOptions = {
5
+ endpointPath?: string;
6
+ enableDotMd?: boolean;
7
+ enableAcceptNegotiation?: boolean;
8
+ acceptHeader?: string;
9
+ exclude?: (pathname: string) => boolean;
10
+ excludePrefixes?: Array<string>;
11
+ excludeExact?: Array<string>;
12
+ onRewrite?: (event: {
13
+ type: MarkdownRewriteType;
14
+ pathname: string;
15
+ }) => void;
16
+ };
17
+ export declare const DEFAULT_PROXY_EXCLUDE_PREFIXES: string[];
18
+ export declare const DEFAULT_PROXY_EXCLUDE_EXACT: string[];
19
+ export declare const DEFAULT_MARKDOWN_ACCEPT = "text/markdown";
20
+ export declare const DEFAULT_ENDPOINT_PATH = "/__aid/md";
21
+ export declare const createMarkdownProxy: (options?: MarkdownProxyOptions) => (request: NextRequest, _event: NextFetchEvent) => NextResponse<unknown>;
22
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElE,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErD,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,mBAAmB,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC9E,CAAC;AAEF,eAAO,MAAM,8BAA8B,UAAqB,CAAC;AACjE,eAAO,MAAM,2BAA2B,UAAkC,CAAC;AAC3E,eAAO,MAAM,uBAAuB,kBAAkB,CAAC;AACvD,eAAO,MAAM,qBAAqB,cAAc,CAAC;AA2CjD,eAAO,MAAM,mBAAmB,GAAI,UAAS,oBAAyB,MAgB5D,SAAS,WAAW,EAAE,QAAQ,cAAc,0BAqCrD,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { NextResponse } from 'next/server.js';
2
+ import type { NextFetchEvent, NextRequest } from 'next/server.js';
3
+ export type MarkdownRewriteType = 'accept' | 'dotmd';
4
+ export type MarkdownProxyOptions = {
5
+ endpointPath?: string;
6
+ enableDotMd?: boolean;
7
+ enableAcceptNegotiation?: boolean;
8
+ acceptHeader?: string;
9
+ exclude?: (pathname: string) => boolean;
10
+ excludePrefixes?: Array<string>;
11
+ excludeExact?: Array<string>;
12
+ onRewrite?: (event: {
13
+ type: MarkdownRewriteType;
14
+ pathname: string;
15
+ }) => void;
16
+ };
17
+ export declare const DEFAULT_PROXY_EXCLUDE_PREFIXES: string[];
18
+ export declare const DEFAULT_PROXY_EXCLUDE_EXACT: string[];
19
+ export declare const DEFAULT_MARKDOWN_ACCEPT = "text/markdown";
20
+ export declare const DEFAULT_ENDPOINT_PATH = "/__aid/md";
21
+ export declare const createMarkdownProxy: (options?: MarkdownProxyOptions) => (request: NextRequest, _event: NextFetchEvent) => NextResponse<unknown>;
22
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElE,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErD,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,mBAAmB,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC9E,CAAC;AAEF,eAAO,MAAM,8BAA8B,UAAqB,CAAC;AACjE,eAAO,MAAM,2BAA2B,UAAkC,CAAC;AAC3E,eAAO,MAAM,uBAAuB,kBAAkB,CAAC;AACvD,eAAO,MAAM,qBAAqB,cAAc,CAAC;AA2CjD,eAAO,MAAM,mBAAmB,GAAI,UAAS,oBAAyB,MAgB5D,SAAS,WAAW,EAAE,QAAQ,cAAc,0BAqCrD,CAAC"}
package/dist/proxy.js ADDED
@@ -0,0 +1,75 @@
1
+ import { NextResponse } from 'next/server.js';
2
+ import { normalizePathname } from "./pathname.js";
3
+ export const DEFAULT_PROXY_EXCLUDE_PREFIXES = ['/_next', '/api'];
4
+ export const DEFAULT_PROXY_EXCLUDE_EXACT = ['/robots.txt', '/sitemap.xml'];
5
+ export const DEFAULT_MARKDOWN_ACCEPT = 'text/markdown';
6
+ export const DEFAULT_ENDPOINT_PATH = '/__aid/md';
7
+ const hasAssetExtension = (pathname) => {
8
+ const lastSlash = pathname.lastIndexOf('/');
9
+ const lastDot = pathname.lastIndexOf('.');
10
+ return lastDot > lastSlash;
11
+ };
12
+ const shouldExcludePath = (pathname, options) => {
13
+ if (options.exclude?.(pathname)) {
14
+ return true;
15
+ }
16
+ const excludePrefixes = options.excludePrefixes ?? DEFAULT_PROXY_EXCLUDE_PREFIXES;
17
+ for (const prefix of excludePrefixes) {
18
+ if (pathname.startsWith(prefix)) {
19
+ return true;
20
+ }
21
+ }
22
+ const excludeExact = options.excludeExact ?? DEFAULT_PROXY_EXCLUDE_EXACT;
23
+ return excludeExact.includes(pathname);
24
+ };
25
+ const isMarkdownAccept = (request, acceptHeader) => {
26
+ const header = request.headers.get('accept') ?? '';
27
+ return header
28
+ .split(',')
29
+ .some((value) => value.trim().startsWith(acceptHeader));
30
+ };
31
+ const buildRewriteUrl = (request, pathname, endpoint) => {
32
+ const url = new URL(endpoint, request.url);
33
+ url.searchParams.set('path', pathname);
34
+ return url;
35
+ };
36
+ export const createMarkdownProxy = (options = {}) => {
37
+ const endpointPath = options.endpointPath ?? DEFAULT_ENDPOINT_PATH;
38
+ const enableDotMd = options.enableDotMd ?? true;
39
+ const enableAcceptNegotiation = options.enableAcceptNegotiation ?? true;
40
+ const acceptHeader = options.acceptHeader ?? DEFAULT_MARKDOWN_ACCEPT;
41
+ const endpointPrefix = endpointPath.startsWith('/')
42
+ ? endpointPath
43
+ : `/${endpointPath}`;
44
+ const shouldSkipRequest = (request) => request.method !== 'GET' && request.method !== 'HEAD';
45
+ const isInternalEndpoint = (pathname) => pathname === endpointPrefix || pathname.startsWith(`${endpointPrefix}/`);
46
+ return (request, _event) => {
47
+ if (shouldSkipRequest(request)) {
48
+ return NextResponse.next();
49
+ }
50
+ const pathname = request.nextUrl.pathname;
51
+ // Avoid rewrite loops: the internal endpoint must never be rewritten.
52
+ if (isInternalEndpoint(pathname) || shouldExcludePath(pathname, options)) {
53
+ return NextResponse.next();
54
+ }
55
+ const isDotMd = enableDotMd && pathname.endsWith('.md');
56
+ // Don't rewrite "asset" paths like /logo.png, but do allow the explicit
57
+ // `.md` endpoint to pass through this check.
58
+ if (!isDotMd && hasAssetExtension(pathname)) {
59
+ return NextResponse.next();
60
+ }
61
+ const isAcceptMd = enableAcceptNegotiation && isMarkdownAccept(request, acceptHeader);
62
+ if (!(isDotMd || isAcceptMd)) {
63
+ return NextResponse.next();
64
+ }
65
+ const normalized = normalizePathname(pathname);
66
+ const rewriteUrl = buildRewriteUrl(request, normalized, endpointPath);
67
+ options.onRewrite?.({
68
+ type: isDotMd ? 'dotmd' : 'accept',
69
+ pathname: normalized,
70
+ });
71
+ const response = NextResponse.rewrite(rewriteUrl);
72
+ response.headers.set('Vary', 'Accept');
73
+ return response;
74
+ };
75
+ };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "next-ai-discovery",
3
+ "description": "next.js 16 helpers for markdown variants, auto-discovery links, and llms.txt",
4
+ "type": "module",
5
+ "version": "0.1.0",
6
+ "license": "MIT",
7
+ "packageManager": "bun@1.3.5",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "keywords": [
12
+ "nextjs",
13
+ "next",
14
+ "markdown",
15
+ "llms.txt",
16
+ "ai",
17
+ "discoverability"
18
+ ],
19
+ "homepage": "https://github.com/nivalis-studio/next-ai-discovery#readme",
20
+ "repository": {
21
+ "url": "git+https://github.com/nivalis-studio/next-ai-discovery.git"
22
+ },
23
+ "scripts": {
24
+ "dev": "bun run --watch src/index.ts",
25
+ "lint": "biome check",
26
+ "lint:fix": "biome check --write",
27
+ "test": "bun test",
28
+ "build": "bunx zshy",
29
+ "prepack": "bunx zshy",
30
+ "prepare": "bunx lefthook install",
31
+ "ts": "tsc --noEmit --incremental"
32
+ },
33
+ "dependencies": {},
34
+ "peerDependencies": {
35
+ "next": "^16",
36
+ "typescript": "^5"
37
+ },
38
+ "devDependencies": {
39
+ "@biomejs/biome": "2.3.11",
40
+ "@commitlint/cli": "20.3.1",
41
+ "@commitlint/config-conventional": "20.3.1",
42
+ "@nivalis/biome-config": "2.3.11",
43
+ "@total-typescript/ts-reset": "0.6.1",
44
+ "@types/bun": "1.3.5",
45
+ "@types/node": "24.2.0",
46
+ "lefthook": "2.0.13",
47
+ "next": "^16.1.3",
48
+ "typescript": "5.9.3",
49
+ "zshy": "0.7.0"
50
+ },
51
+ "zshy": {
52
+ "exports": "./src/index.ts",
53
+ "noEdit": true
54
+ },
55
+ "files": ["dist"],
56
+ "main": "./dist/index.cjs",
57
+ "module": "./dist/index.js",
58
+ "types": "./dist/index.d.cts",
59
+ "exports": {
60
+ ".": {
61
+ "types": "./dist/index.d.cts",
62
+ "import": "./dist/index.js",
63
+ "require": "./dist/index.cjs"
64
+ }
65
+ }
66
+ }