nuxt-ai-ready 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/module.d.mts +12 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +226 -352
- package/dist/runtime/index.d.ts +5 -7
- package/dist/runtime/index.js +14 -3
- package/dist/runtime/llms-txt-format.d.ts +8 -0
- package/dist/runtime/llms-txt-format.js +32 -0
- package/dist/runtime/llms-txt-utils.d.ts +2 -5
- package/dist/runtime/llms-txt-utils.js +31 -88
- package/dist/runtime/server/db/index.d.ts +3 -7
- package/dist/runtime/server/db/index.js +14 -31
- package/dist/runtime/server/db/queries.d.ts +106 -26
- package/dist/runtime/server/db/queries.js +330 -33
- package/dist/runtime/server/db/schema-sql.d.ts +3 -0
- package/dist/runtime/server/db/schema-sql.js +71 -0
- package/dist/runtime/server/db/shared.d.ts +88 -0
- package/dist/runtime/server/db/shared.js +117 -0
- package/dist/runtime/server/mcp/resources/pages.js +11 -3
- package/dist/runtime/server/mcp/tools/list-pages.js +33 -7
- package/dist/runtime/server/mcp/tools/search-pages.js +3 -3
- package/dist/runtime/server/middleware/markdown.js +2 -3
- package/dist/runtime/server/middleware/markdown.prerender.js +6 -5
- package/dist/runtime/server/plugins/db-restore.js +10 -8
- package/dist/runtime/server/plugins/sitemap-seeder.js +44 -0
- package/dist/runtime/server/routes/__ai-ready/indexnow.post.d.ts +2 -0
- package/dist/runtime/server/routes/__ai-ready/indexnow.post.js +18 -0
- package/dist/runtime/server/routes/__ai-ready/poll.post.d.ts +8 -0
- package/dist/runtime/server/routes/__ai-ready/poll.post.js +25 -0
- package/dist/runtime/server/routes/__ai-ready/prune.post.d.ts +14 -0
- package/dist/runtime/server/routes/__ai-ready/prune.post.js +21 -0
- package/dist/runtime/server/routes/__ai-ready/status.get.d.ts +2 -0
- package/dist/runtime/server/routes/__ai-ready/status.get.js +28 -0
- package/dist/runtime/server/routes/__ai-ready-debug.get.js +13 -14
- package/dist/runtime/server/routes/indexnow-key.get.d.ts +6 -0
- package/dist/runtime/server/routes/indexnow-key.get.js +10 -0
- package/dist/runtime/server/routes/llms-full.txt.get.d.ts +1 -1
- package/dist/runtime/server/routes/llms-full.txt.get.js +34 -3
- package/dist/runtime/server/tasks/ai-ready-index.d.ts +11 -0
- package/dist/runtime/server/tasks/ai-ready-index.js +39 -0
- package/dist/runtime/server/utils/batchIndex.d.ts +26 -0
- package/dist/runtime/server/utils/batchIndex.js +53 -0
- package/dist/runtime/server/utils/indexPage.d.ts +8 -2
- package/dist/runtime/server/utils/indexPage.js +38 -23
- package/dist/runtime/server/utils/indexnow.d.ts +27 -0
- package/dist/runtime/server/utils/indexnow.js +66 -0
- package/dist/runtime/server/utils/keywords.d.ts +0 -4
- package/dist/runtime/server/utils/keywords.js +0 -3
- package/dist/runtime/server/utils/llms-full.d.ts +11 -0
- package/dist/runtime/server/utils/llms-full.js +39 -0
- package/dist/runtime/server/utils/sitemap.js +3 -3
- package/dist/runtime/server/utils.d.ts +12 -10
- package/dist/runtime/server/utils.js +63 -94
- package/dist/runtime/types.d.ts +64 -40
- package/package.json +28 -29
- package/dist/runtime/mcp.d.ts +0 -32
- package/dist/runtime/mcp.js +0 -5
- package/dist/runtime/server/db/dump.d.ts +0 -29
- package/dist/runtime/server/db/dump.js +0 -29
- package/dist/runtime/server/db/schema.d.ts +0 -8
- package/dist/runtime/server/db/schema.js +0 -124
- package/dist/runtime/server/plugins/page-indexer.js +0 -68
- package/dist/runtime/server/tsconfig.json +0 -3
- package/dist/runtime/server/utils/pageData.d.ts +0 -28
- package/dist/runtime/server/utils/pageData.js +0 -43
- package/mcp.d.ts +0 -4
- /package/dist/runtime/{nuxt → app}/plugins/md-hints.prerender.d.ts +0 -0
- /package/dist/runtime/{nuxt → app}/plugins/md-hints.prerender.js +0 -0
- /package/dist/runtime/server/plugins/{page-indexer.d.ts → sitemap-seeder.d.ts} +0 -0
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
+
export { useDatabase } from './server/db/index.js';
|
|
2
|
+
export type { DatabaseAdapter } from './server/db/index.js';
|
|
3
|
+
export { countPages, getSitemapSeededAt, getStaleRoutes, isPageFresh, pruneStaleRoutes, queryPages, searchPages, seedRoutes, setSitemapSeededAt, streamPages, upsertPage, } from './server/db/queries.js';
|
|
4
|
+
export type { CountPagesOptions, PageData, PageEntry, PageRow, QueryPagesOptions, SearchPagesOptions, SearchResult, StreamPagesOptions, UpsertPageInput, } from './server/db/queries.js';
|
|
1
5
|
export { indexPage, indexPageByRoute } from './server/utils/indexPage.js';
|
|
2
6
|
export type { IndexPageOptions, IndexPageResult } from './server/utils/indexPage.js';
|
|
3
|
-
export {
|
|
4
|
-
export type { PageEntry, PageData, PageListItem } from './server/utils/pageData.js';
|
|
5
|
-
export { useDatabase } from './server/db/index.js';
|
|
6
|
-
export type { DatabaseAdapter } from './server/db/schema.js';
|
|
7
|
-
export { searchPages, getAllPages, getPage, getPageWithMarkdown, upsertPage, getPageCount } from './server/db/queries.js';
|
|
8
|
-
export type { SearchResult, PageRow } from './server/db/queries.js';
|
|
9
|
-
export type { MarkdownContext, PageIndexedContext, PageMarkdownContext, } from './types.js';
|
|
7
|
+
export type { MarkdownContext, PageIndexedContext, } from './types.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
export { indexPage, indexPageByRoute } from "./server/utils/indexPage.js";
|
|
2
|
-
export { getPages, getPagesList, getErrorRoutes } from "./server/utils/pageData.js";
|
|
3
1
|
export { useDatabase } from "./server/db/index.js";
|
|
4
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
countPages,
|
|
4
|
+
getSitemapSeededAt,
|
|
5
|
+
getStaleRoutes,
|
|
6
|
+
isPageFresh,
|
|
7
|
+
pruneStaleRoutes,
|
|
8
|
+
queryPages,
|
|
9
|
+
searchPages,
|
|
10
|
+
seedRoutes,
|
|
11
|
+
setSitemapSeededAt,
|
|
12
|
+
streamPages,
|
|
13
|
+
upsertPage
|
|
14
|
+
} from "./server/db/queries.js";
|
|
15
|
+
export { indexPage, indexPageByRoute } from "./server/utils/indexPage.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure formatting functions for llms.txt - no runtime dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { LlmsTxtConfig } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Normalize llms.txt structured configuration to markdown string
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeLlmsTxtConfig(config: LlmsTxtConfig): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function normalizeLink(link) {
|
|
2
|
+
const parts = [];
|
|
3
|
+
parts.push(`- [${link.title}](${link.href})`);
|
|
4
|
+
if (link.description)
|
|
5
|
+
parts.push(` ${link.description}`);
|
|
6
|
+
return parts.join("\n");
|
|
7
|
+
}
|
|
8
|
+
function normalizeSection(section) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
parts.push(`## ${section.title}`);
|
|
11
|
+
parts.push("");
|
|
12
|
+
if (section.description) {
|
|
13
|
+
const descriptions = Array.isArray(section.description) ? section.description : [section.description];
|
|
14
|
+
parts.push(...descriptions);
|
|
15
|
+
parts.push("");
|
|
16
|
+
}
|
|
17
|
+
if (section.links?.length)
|
|
18
|
+
parts.push(...section.links.map(normalizeLink));
|
|
19
|
+
return parts.join("\n");
|
|
20
|
+
}
|
|
21
|
+
export function normalizeLlmsTxtConfig(config) {
|
|
22
|
+
const parts = [];
|
|
23
|
+
if (config.sections?.length)
|
|
24
|
+
parts.push(...config.sections.map(normalizeSection));
|
|
25
|
+
if (config.notes) {
|
|
26
|
+
parts.push("## Notes");
|
|
27
|
+
parts.push("");
|
|
28
|
+
const notes = Array.isArray(config.notes) ? config.notes : [config.notes];
|
|
29
|
+
parts.push(...notes);
|
|
30
|
+
}
|
|
31
|
+
return parts.join("\n\n");
|
|
32
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
* Normalize llms.txt structured configuration to markdown string
|
|
5
|
-
*/
|
|
6
|
-
export declare function normalizeLlmsTxtConfig(config: LlmsTxtConfig): string;
|
|
2
|
+
import { normalizeLlmsTxtConfig } from './llms-txt-format.js';
|
|
3
|
+
export { normalizeLlmsTxtConfig };
|
|
7
4
|
export declare function buildLlmsTxt(event: H3Event): Promise<string>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeLlmsTxtConfig } from "./llms-txt-format.js";
|
|
4
|
+
import { queryPages } from "./server/db/queries.js";
|
|
4
5
|
import { fetchSitemapUrls } from "./server/utils/sitemap.js";
|
|
6
|
+
export { normalizeLlmsTxtConfig };
|
|
5
7
|
function getGroupPrefix(url, depth) {
|
|
6
8
|
const segments = url.split("/").filter(Boolean);
|
|
7
9
|
if (segments.length === 0)
|
|
@@ -13,14 +15,12 @@ function getGroupPrefix(url, depth) {
|
|
|
13
15
|
function getPathSegments(pathname) {
|
|
14
16
|
return pathname.split("/").filter((s) => Boolean(s));
|
|
15
17
|
}
|
|
16
|
-
function
|
|
18
|
+
function analyzePageGroups(pages) {
|
|
17
19
|
const twoSegmentCount = /* @__PURE__ */ new Map();
|
|
20
|
+
const segmentHasNested = /* @__PURE__ */ new Map();
|
|
18
21
|
for (const page of pages) {
|
|
19
22
|
const prefix = getGroupPrefix(page.pathname, 2);
|
|
20
23
|
twoSegmentCount.set(prefix, (twoSegmentCount.get(prefix) || 0) + 1);
|
|
21
|
-
}
|
|
22
|
-
const segmentHasNested = /* @__PURE__ */ new Map();
|
|
23
|
-
for (const page of pages) {
|
|
24
24
|
const segments = getPathSegments(page.pathname);
|
|
25
25
|
const firstSegment = segments[0] || "";
|
|
26
26
|
if (!segmentHasNested.has(firstSegment))
|
|
@@ -28,25 +28,25 @@ function sortPagesByPath(pages) {
|
|
|
28
28
|
if (segments.length > 1)
|
|
29
29
|
segmentHasNested.set(firstSegment, true);
|
|
30
30
|
}
|
|
31
|
+
return { twoSegmentCount, segmentHasNested };
|
|
32
|
+
}
|
|
33
|
+
function getPageGroupKey(pathname, { twoSegmentCount, segmentHasNested }) {
|
|
34
|
+
const segments = getPathSegments(pathname);
|
|
35
|
+
const firstSegment = segments[0] || "";
|
|
36
|
+
const twoSegPrefix = getGroupPrefix(pathname, 2);
|
|
37
|
+
const twoSegCount = twoSegmentCount.get(twoSegPrefix) || 0;
|
|
38
|
+
let groupKey = twoSegCount > 1 ? twoSegPrefix : `/${firstSegment}`;
|
|
39
|
+
if (segments.length <= 1 && !segmentHasNested.get(firstSegment))
|
|
40
|
+
groupKey = "";
|
|
41
|
+
return groupKey;
|
|
42
|
+
}
|
|
43
|
+
function sortPagesByPath(pages) {
|
|
44
|
+
const analysis = analyzePageGroups(pages);
|
|
31
45
|
return pages.sort((a, b) => {
|
|
32
46
|
const segmentsA = getPathSegments(a.pathname);
|
|
33
47
|
const segmentsB = getPathSegments(b.pathname);
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const twoSegPrefixA = getGroupPrefix(a.pathname, 2);
|
|
37
|
-
const twoSegPrefixB = getGroupPrefix(b.pathname, 2);
|
|
38
|
-
const twoSegCountA = twoSegmentCount.get(twoSegPrefixA) || 0;
|
|
39
|
-
const twoSegCountB = twoSegmentCount.get(twoSegPrefixB) || 0;
|
|
40
|
-
let groupKeyA = twoSegCountA > 1 ? twoSegPrefixA : `/${firstSegmentA}`;
|
|
41
|
-
let groupKeyB = twoSegCountB > 1 ? twoSegPrefixB : `/${firstSegmentB}`;
|
|
42
|
-
const isRootLevelA = segmentsA.length <= 1;
|
|
43
|
-
const isRootLevelB = segmentsB.length <= 1;
|
|
44
|
-
const hasNestedA = segmentHasNested.get(firstSegmentA);
|
|
45
|
-
const hasNestedB = segmentHasNested.get(firstSegmentB);
|
|
46
|
-
if (isRootLevelA && !hasNestedA)
|
|
47
|
-
groupKeyA = "";
|
|
48
|
-
if (isRootLevelB && !hasNestedB)
|
|
49
|
-
groupKeyB = "";
|
|
48
|
+
const groupKeyA = getPageGroupKey(a.pathname, analysis);
|
|
49
|
+
const groupKeyB = getPageGroupKey(b.pathname, analysis);
|
|
50
50
|
if (groupKeyA === "" && groupKeyB !== "")
|
|
51
51
|
return -1;
|
|
52
52
|
if (groupKeyA !== "" && groupKeyB === "")
|
|
@@ -66,39 +66,16 @@ function sortPagesByPath(pages) {
|
|
|
66
66
|
return segmentsA.length - segmentsB.length;
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
-
function getPageGroupKey(pathname, twoSegmentCount, segmentHasNested) {
|
|
70
|
-
const segments = getPathSegments(pathname);
|
|
71
|
-
const firstSegment = segments[0] || "";
|
|
72
|
-
const twoSegPrefix = getGroupPrefix(pathname, 2);
|
|
73
|
-
const twoSegCount = twoSegmentCount.get(twoSegPrefix) || 0;
|
|
74
|
-
let groupKey = twoSegCount > 1 ? twoSegPrefix : `/${firstSegment}`;
|
|
75
|
-
const isRootLevel = segments.length <= 1;
|
|
76
|
-
const hasNested = segmentHasNested.get(firstSegment);
|
|
77
|
-
if (isRootLevel && !hasNested)
|
|
78
|
-
groupKey = "";
|
|
79
|
-
return groupKey;
|
|
80
|
-
}
|
|
81
69
|
function formatPagesWithGroups(pages) {
|
|
82
70
|
if (pages.length === 0)
|
|
83
71
|
return [];
|
|
84
|
-
const
|
|
85
|
-
const segmentHasNested = /* @__PURE__ */ new Map();
|
|
86
|
-
for (const page of pages) {
|
|
87
|
-
const prefix = getGroupPrefix(page.pathname, 2);
|
|
88
|
-
twoSegmentCount.set(prefix, (twoSegmentCount.get(prefix) || 0) + 1);
|
|
89
|
-
const segments = getPathSegments(page.pathname);
|
|
90
|
-
const firstSegment = segments[0] || "";
|
|
91
|
-
if (!segmentHasNested.has(firstSegment))
|
|
92
|
-
segmentHasNested.set(firstSegment, false);
|
|
93
|
-
if (segments.length > 1)
|
|
94
|
-
segmentHasNested.set(firstSegment, true);
|
|
95
|
-
}
|
|
72
|
+
const analysis = analyzePageGroups(pages);
|
|
96
73
|
const lines = [];
|
|
97
74
|
let currentGroup = "";
|
|
98
75
|
let segmentGroupIndex = 0;
|
|
99
76
|
let urlsInCurrentGroup = 0;
|
|
100
77
|
for (const page of pages) {
|
|
101
|
-
const groupKey = getPageGroupKey(page.pathname,
|
|
78
|
+
const groupKey = getPageGroupKey(page.pathname, analysis);
|
|
102
79
|
if (groupKey !== currentGroup) {
|
|
103
80
|
if (urlsInCurrentGroup > 0) {
|
|
104
81
|
const shouldAddBlankLine = segmentGroupIndex === 0 || segmentGroupIndex >= 1 && segmentGroupIndex <= 2 && urlsInCurrentGroup > 1;
|
|
@@ -118,41 +95,6 @@ function formatPagesWithGroups(pages) {
|
|
|
118
95
|
}
|
|
119
96
|
return lines;
|
|
120
97
|
}
|
|
121
|
-
function normalizeLink(link) {
|
|
122
|
-
const parts = [];
|
|
123
|
-
parts.push(`- [${link.title}](${link.href})`);
|
|
124
|
-
if (link.description) {
|
|
125
|
-
parts.push(` ${link.description}`);
|
|
126
|
-
}
|
|
127
|
-
return parts.join("\n");
|
|
128
|
-
}
|
|
129
|
-
function normalizeSection(section) {
|
|
130
|
-
const parts = [];
|
|
131
|
-
parts.push(`## ${section.title}`);
|
|
132
|
-
parts.push("");
|
|
133
|
-
if (section.description) {
|
|
134
|
-
const descriptions = Array.isArray(section.description) ? section.description : [section.description];
|
|
135
|
-
parts.push(...descriptions);
|
|
136
|
-
parts.push("");
|
|
137
|
-
}
|
|
138
|
-
if (section.links?.length) {
|
|
139
|
-
parts.push(...section.links.map(normalizeLink));
|
|
140
|
-
}
|
|
141
|
-
return parts.join("\n");
|
|
142
|
-
}
|
|
143
|
-
export function normalizeLlmsTxtConfig(config) {
|
|
144
|
-
const parts = [];
|
|
145
|
-
if (config.sections?.length) {
|
|
146
|
-
parts.push(...config.sections.map(normalizeSection));
|
|
147
|
-
}
|
|
148
|
-
if (config.notes) {
|
|
149
|
-
parts.push("## Notes");
|
|
150
|
-
parts.push("");
|
|
151
|
-
const notes = Array.isArray(config.notes) ? config.notes : [config.notes];
|
|
152
|
-
parts.push(...notes);
|
|
153
|
-
}
|
|
154
|
-
return parts.join("\n\n");
|
|
155
|
-
}
|
|
156
98
|
export async function buildLlmsTxt(event) {
|
|
157
99
|
const runtimeConfig = useRuntimeConfig(event);
|
|
158
100
|
const aiReadyConfig = runtimeConfig["nuxt-ai-ready"];
|
|
@@ -185,20 +127,21 @@ Canonical Origin: ${siteConfig.url}`);
|
|
|
185
127
|
parts.push(normalizedContent);
|
|
186
128
|
parts.push("");
|
|
187
129
|
}
|
|
188
|
-
const pages = await
|
|
130
|
+
const pages = await queryPages(event);
|
|
189
131
|
const urls = await fetchSitemapUrls(event);
|
|
190
|
-
const errorRoutes = await
|
|
191
|
-
const
|
|
132
|
+
const errorRoutes = await queryPages(event, { where: { hasError: true } });
|
|
133
|
+
const errorSet = new Set(errorRoutes.map((e) => e.route));
|
|
134
|
+
const devModeHint = import.meta.dev && pages.length === 0 ? " (dev mode - run `nuxi generate` for page titles)" : "";
|
|
192
135
|
const prerendered = [];
|
|
193
136
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
194
|
-
for (const
|
|
195
|
-
prerendered.push({ pathname, title: page.title, description: page.description });
|
|
196
|
-
seenPaths.add(
|
|
137
|
+
for (const page of pages) {
|
|
138
|
+
prerendered.push({ pathname: page.route, title: page.title, description: page.description });
|
|
139
|
+
seenPaths.add(page.route);
|
|
197
140
|
}
|
|
198
141
|
const other = [];
|
|
199
142
|
for (const url of urls) {
|
|
200
143
|
const pathname = url.loc.startsWith("http") ? new URL(url.loc).pathname : url.loc;
|
|
201
|
-
if (!seenPaths.has(pathname) && !
|
|
144
|
+
if (!seenPaths.has(pathname) && !errorSet.has(pathname)) {
|
|
202
145
|
other.push({ pathname });
|
|
203
146
|
seenPaths.add(pathname);
|
|
204
147
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
|
-
import type { DatabaseAdapter } from './
|
|
2
|
+
import type { DatabaseAdapter } from './shared.js';
|
|
3
3
|
/**
|
|
4
4
|
* Get the database adapter instance
|
|
5
|
-
*
|
|
5
|
+
* Cached on nitro context for request lifecycle
|
|
6
6
|
*/
|
|
7
7
|
export declare function useDatabase(event?: H3Event): Promise<DatabaseAdapter>;
|
|
8
|
-
|
|
9
|
-
* Reset database connection (for testing)
|
|
10
|
-
*/
|
|
11
|
-
export declare function _resetDatabase(): void;
|
|
12
|
-
export type { DatabaseAdapter } from './schema.js';
|
|
8
|
+
export type { DatabaseAdapter } from './shared.js';
|
|
@@ -1,56 +1,39 @@
|
|
|
1
|
-
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
1
|
import { mkdir } from "node:fs/promises";
|
|
3
|
-
import { dirname } from "pathe";
|
|
4
|
-
import { initSchema } from "./schema.js";
|
|
5
2
|
import adapter from "#ai-ready/adapter";
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
|
|
4
|
+
import { dirname } from "pathe";
|
|
5
|
+
import { createAdapter, initSchema } from "./shared.js";
|
|
6
|
+
const DB_KEY = "_aiReadyDb";
|
|
8
7
|
export function useDatabase(event) {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const nitro = useNitroApp();
|
|
9
|
+
if (!nitro[DB_KEY]) {
|
|
10
|
+
nitro[DB_KEY] = initDatabase(event);
|
|
11
11
|
}
|
|
12
|
-
return
|
|
12
|
+
return nitro[DB_KEY];
|
|
13
13
|
}
|
|
14
14
|
async function initDatabase(event) {
|
|
15
15
|
const config = useRuntimeConfig()["nuxt-ai-ready"];
|
|
16
|
+
let connector;
|
|
16
17
|
if (config.database.type === "d1") {
|
|
17
18
|
const binding = event?.context?.cloudflare?.env?.[config.database.bindingName || "AI_READY_DB"];
|
|
18
19
|
if (!binding) {
|
|
19
20
|
throw new Error(`D1 binding '${config.database.bindingName || "AI_READY_DB"}' not found in event context`);
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
connector = adapter({ binding });
|
|
22
23
|
} else if (config.database.type === "libsql") {
|
|
23
|
-
|
|
24
|
+
connector = adapter({
|
|
24
25
|
url: config.database.url,
|
|
25
26
|
authToken: config.database.authToken
|
|
26
27
|
});
|
|
27
28
|
} else {
|
|
28
29
|
const dbPath = config.database.filename || ".data/ai-ready/pages.db";
|
|
29
30
|
await mkdir(dirname(dbPath), { recursive: true });
|
|
30
|
-
|
|
31
|
+
connector = adapter({ path: dbPath });
|
|
31
32
|
}
|
|
32
|
-
if (!
|
|
33
|
+
if (!connector) {
|
|
33
34
|
throw new Error("Failed to initialize database connector");
|
|
34
35
|
}
|
|
35
|
-
const dbAdapter = createAdapter(
|
|
36
|
+
const dbAdapter = createAdapter(connector);
|
|
36
37
|
await initSchema(dbAdapter);
|
|
37
38
|
return dbAdapter;
|
|
38
39
|
}
|
|
39
|
-
function createAdapter(db) {
|
|
40
|
-
return {
|
|
41
|
-
all: async (sql, params = []) => {
|
|
42
|
-
const result = await db.prepare(sql).all(...params);
|
|
43
|
-
return result || [];
|
|
44
|
-
},
|
|
45
|
-
first: async (sql, params = []) => {
|
|
46
|
-
return db.prepare(sql).get(...params);
|
|
47
|
-
},
|
|
48
|
-
exec: async (sql, params = []) => {
|
|
49
|
-
await db.prepare(sql).run(...params);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
export function _resetDatabase() {
|
|
54
|
-
_db = null;
|
|
55
|
-
_initPromise = null;
|
|
56
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
2
|
export interface PageRow {
|
|
3
3
|
id: number;
|
|
4
4
|
route: string;
|
|
@@ -8,9 +8,14 @@ export interface PageRow {
|
|
|
8
8
|
markdown: string;
|
|
9
9
|
headings: string;
|
|
10
10
|
keywords: string;
|
|
11
|
+
content_hash: string | null;
|
|
11
12
|
updated_at: string;
|
|
12
13
|
indexed_at: number;
|
|
13
14
|
is_error: number;
|
|
15
|
+
indexed: number;
|
|
16
|
+
source: 'prerender' | 'runtime';
|
|
17
|
+
last_seen_at: number | null;
|
|
18
|
+
indexnow_synced_at: number | null;
|
|
14
19
|
}
|
|
15
20
|
export interface PageEntry {
|
|
16
21
|
route: string;
|
|
@@ -29,56 +34,131 @@ export interface SearchResult {
|
|
|
29
34
|
description: string;
|
|
30
35
|
score: number;
|
|
31
36
|
}
|
|
37
|
+
export interface QueryPagesOptions {
|
|
38
|
+
route?: string;
|
|
39
|
+
includeMarkdown?: boolean;
|
|
40
|
+
where?: {
|
|
41
|
+
pending?: boolean;
|
|
42
|
+
hasError?: boolean;
|
|
43
|
+
source?: 'prerender' | 'runtime';
|
|
44
|
+
};
|
|
45
|
+
limit?: number;
|
|
46
|
+
offset?: number;
|
|
47
|
+
}
|
|
32
48
|
/**
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Get a single page by route
|
|
49
|
+
* Unified page query function
|
|
50
|
+
* @param event - H3Event (optional, used for db context)
|
|
51
|
+
* @param options - Query options
|
|
38
52
|
*/
|
|
39
|
-
export declare function
|
|
53
|
+
export declare function queryPages(event?: H3Event, options?: QueryPagesOptions): Promise<PageEntry[] | PageData[]>;
|
|
54
|
+
export declare function queryPages(event: H3Event | undefined, options: QueryPagesOptions & {
|
|
55
|
+
route: string;
|
|
56
|
+
}): Promise<PageEntry | PageData | undefined>;
|
|
57
|
+
export declare function queryPages(event: H3Event | undefined, options: QueryPagesOptions & {
|
|
58
|
+
route: string;
|
|
59
|
+
includeMarkdown: true;
|
|
60
|
+
}): Promise<PageData | undefined>;
|
|
61
|
+
export declare function queryPages(event: H3Event | undefined, options: QueryPagesOptions & {
|
|
62
|
+
includeMarkdown: true;
|
|
63
|
+
}): Promise<PageData[]>;
|
|
64
|
+
export interface StreamPagesOptions {
|
|
65
|
+
batchSize?: number;
|
|
66
|
+
}
|
|
40
67
|
/**
|
|
41
|
-
*
|
|
68
|
+
* Stream pages using cursor-based pagination
|
|
69
|
+
* Yields pages one batch at a time to avoid loading all into memory
|
|
42
70
|
*/
|
|
43
|
-
export declare function
|
|
71
|
+
export declare function streamPages(event?: H3Event, options?: StreamPagesOptions): AsyncGenerator<PageData, void, unknown>;
|
|
72
|
+
export interface CountPagesOptions {
|
|
73
|
+
where?: {
|
|
74
|
+
pending?: boolean;
|
|
75
|
+
hasError?: boolean;
|
|
76
|
+
source?: 'prerender' | 'runtime';
|
|
77
|
+
};
|
|
78
|
+
}
|
|
44
79
|
/**
|
|
45
|
-
*
|
|
46
|
-
* @param query Search query string
|
|
47
|
-
* @param opts Search options
|
|
80
|
+
* Count pages matching criteria
|
|
48
81
|
*/
|
|
49
|
-
export declare function
|
|
82
|
+
export declare function countPages(event?: H3Event, options?: CountPagesOptions): Promise<number>;
|
|
83
|
+
export interface SearchPagesOptions {
|
|
50
84
|
limit?: number;
|
|
51
|
-
}
|
|
85
|
+
}
|
|
52
86
|
/**
|
|
53
|
-
*
|
|
87
|
+
* Full-text search using FTS5
|
|
88
|
+
* Note: FTS is only available at runtime, not during prerender
|
|
54
89
|
*/
|
|
55
|
-
export declare function
|
|
90
|
+
export declare function searchPages(event: H3Event | undefined, query: string, options?: SearchPagesOptions): Promise<SearchResult[]>;
|
|
91
|
+
export interface UpsertPageInput {
|
|
56
92
|
route: string;
|
|
57
93
|
title: string;
|
|
58
94
|
description: string;
|
|
59
95
|
markdown: string;
|
|
60
96
|
headings: string;
|
|
61
97
|
keywords: string[];
|
|
98
|
+
contentHash?: string;
|
|
62
99
|
updatedAt: string;
|
|
63
100
|
isError?: boolean;
|
|
64
|
-
|
|
101
|
+
source?: 'prerender' | 'runtime';
|
|
102
|
+
}
|
|
65
103
|
/**
|
|
66
|
-
*
|
|
104
|
+
* Insert or update a page
|
|
67
105
|
*/
|
|
68
|
-
export declare function
|
|
106
|
+
export declare function upsertPage(event: H3Event | undefined, page: UpsertPageInput): Promise<void>;
|
|
69
107
|
/**
|
|
70
108
|
* Check if a page is fresh (within TTL)
|
|
71
109
|
*/
|
|
72
|
-
export declare function isPageFresh(
|
|
110
|
+
export declare function isPageFresh(event: H3Event | undefined, route: string, ttlSeconds: number): Promise<boolean>;
|
|
111
|
+
/**
|
|
112
|
+
* Get existing content hash for a page (for change detection)
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
export declare function getPageHash(event: H3Event | undefined, route: string): Promise<string | null>;
|
|
73
116
|
/**
|
|
74
|
-
*
|
|
117
|
+
* Seed routes from sitemap (insert with indexed=0 if not exists)
|
|
75
118
|
*/
|
|
76
|
-
export declare function
|
|
119
|
+
export declare function seedRoutes(event: H3Event | undefined, routes: string[]): Promise<number>;
|
|
77
120
|
/**
|
|
78
|
-
* Get
|
|
121
|
+
* Get sitemap seeded timestamp from _ai_ready_info
|
|
79
122
|
*/
|
|
80
|
-
export declare function
|
|
123
|
+
export declare function getSitemapSeededAt(event: H3Event | undefined): Promise<number | undefined>;
|
|
124
|
+
/**
|
|
125
|
+
* Set sitemap seeded timestamp
|
|
126
|
+
*/
|
|
127
|
+
export declare function setSitemapSeededAt(event: H3Event | undefined, timestamp: number): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Prune routes not seen in sitemap for longer than threshold
|
|
130
|
+
* Only prunes routes with source='runtime' (never prerendered pages)
|
|
131
|
+
*/
|
|
132
|
+
export declare function pruneStaleRoutes(event: H3Event | undefined, staleThresholdSeconds: number): Promise<number>;
|
|
133
|
+
/**
|
|
134
|
+
* Get stale routes that would be pruned (for preview)
|
|
135
|
+
*/
|
|
136
|
+
export declare function getStaleRoutes(event: H3Event | undefined, staleThresholdSeconds: number): Promise<string[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Get pages needing IndexNow sync (content changed since last sync)
|
|
139
|
+
*/
|
|
140
|
+
export declare function getPagesNeedingIndexNowSync(event: H3Event | undefined, limit?: number): Promise<{
|
|
141
|
+
route: string;
|
|
142
|
+
}[]>;
|
|
143
|
+
/**
|
|
144
|
+
* Count pages needing IndexNow sync
|
|
145
|
+
*/
|
|
146
|
+
export declare function countPagesNeedingIndexNowSync(event: H3Event | undefined): Promise<number>;
|
|
147
|
+
/**
|
|
148
|
+
* Mark pages as synced to IndexNow
|
|
149
|
+
*/
|
|
150
|
+
export declare function markIndexNowSynced(event: H3Event | undefined, routes: string[]): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Update IndexNow stats after submission
|
|
153
|
+
* Uses atomic SQL to handle concurrent updates safely
|
|
154
|
+
*/
|
|
155
|
+
export declare function updateIndexNowStats(event: H3Event | undefined, submitted: number, error?: string): Promise<void>;
|
|
156
|
+
export interface IndexNowStats {
|
|
157
|
+
totalSubmitted: number;
|
|
158
|
+
lastSubmittedAt: number | null;
|
|
159
|
+
lastError: string | null;
|
|
160
|
+
}
|
|
81
161
|
/**
|
|
82
|
-
*
|
|
162
|
+
* Get IndexNow stats
|
|
83
163
|
*/
|
|
84
|
-
export declare function
|
|
164
|
+
export declare function getIndexNowStats(event: H3Event | undefined): Promise<IndexNowStats>;
|