nuxt-ai-ready 0.7.2 → 0.7.4
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/module.json +1 -1
- package/dist/module.mjs +5 -4
- package/dist/runtime/llms-txt-utils.js +10 -8
- package/dist/runtime/server/db/queries.d.ts +1 -0
- package/dist/runtime/server/db/queries.js +11 -4
- package/dist/runtime/server/db/shared.d.ts +25 -3
- package/dist/runtime/server/db/shared.js +49 -7
- package/dist/runtime/server/routes/__ai-ready-debug.get.js +4 -2
- package/package.json +4 -4
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -271,8 +271,9 @@ function setupPrerenderHandler(dbPath, siteInfo, llmsTxtConfig) {
|
|
|
271
271
|
const publicDataDir = join(nitro.options.output.publicDir, "__ai-ready");
|
|
272
272
|
await mkdir(publicDataDir, { recursive: true });
|
|
273
273
|
if (state.db) {
|
|
274
|
-
const
|
|
275
|
-
const
|
|
274
|
+
const allPages = await queryAllPages(state.db, { includeErrors: true, excludeMarkdown: true });
|
|
275
|
+
const pages = allPages.filter((p) => !p.isError);
|
|
276
|
+
const errorRoutesList = allPages.filter((p) => p.isError).map((p) => p.route);
|
|
276
277
|
const jsonContent = JSON.stringify({
|
|
277
278
|
pages: pages.map((p) => ({
|
|
278
279
|
route: p.route,
|
|
@@ -541,7 +542,7 @@ const module$1 = defineNuxtModule({
|
|
|
541
542
|
nuxt.hooks.hook("nitro:config", (nitroConfig) => {
|
|
542
543
|
nitroConfig.experimental = nitroConfig.experimental || {};
|
|
543
544
|
nitroConfig.experimental.asyncContext = true;
|
|
544
|
-
if (config.cron) {
|
|
545
|
+
if (config.cron && !nuxt.options.dev) {
|
|
545
546
|
const cronSchedule = "* * * * *";
|
|
546
547
|
const isVercel = nitroConfig.preset === "vercel" || nitroConfig.preset === "vercel-edge";
|
|
547
548
|
if (isVercel) {
|
|
@@ -662,7 +663,7 @@ export const errorRoutes = []`;
|
|
|
662
663
|
addServerHandler({ route: "/__ai-ready/status", handler: resolve("./runtime/server/routes/__ai-ready/status.get") });
|
|
663
664
|
}
|
|
664
665
|
}
|
|
665
|
-
if (config.cron) {
|
|
666
|
+
if (config.cron && !nuxt.options.dev) {
|
|
666
667
|
addServerHandler({ route: "/__ai-ready/cron", handler: resolve("./runtime/server/routes/__ai-ready/cron.get") });
|
|
667
668
|
}
|
|
668
669
|
const isStatic = nuxt.options.nitro.static || nuxt.options._generate || false;
|
|
@@ -127,20 +127,22 @@ Canonical Origin: ${siteConfig.url}`);
|
|
|
127
127
|
parts.push(normalizedContent);
|
|
128
128
|
parts.push("");
|
|
129
129
|
}
|
|
130
|
-
const pages = await
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const prerendered = [];
|
|
130
|
+
const [pages, errorPages, urls] = await Promise.all([
|
|
131
|
+
queryPages(event),
|
|
132
|
+
queryPages(event, { where: { hasError: true } }),
|
|
133
|
+
fetchSitemapUrls(event)
|
|
134
|
+
]);
|
|
136
135
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
136
|
+
const errorSet = new Set(errorPages.map((p) => p.route));
|
|
137
|
+
const prerendered = [];
|
|
137
138
|
for (const page of pages) {
|
|
138
|
-
prerendered.push({ pathname: page.route, title: page.title, description: page.description });
|
|
139
139
|
seenPaths.add(page.route);
|
|
140
|
+
prerendered.push({ pathname: page.route, title: page.title, description: page.description });
|
|
140
141
|
}
|
|
142
|
+
const devModeHint = import.meta.dev && prerendered.length === 0 ? " (dev mode - run `nuxi generate` for page titles)" : "";
|
|
141
143
|
const other = [];
|
|
142
144
|
for (const url of urls) {
|
|
143
|
-
const pathname = url.loc.startsWith("
|
|
145
|
+
const pathname = url.loc.startsWith("/") ? url.loc : new URL(url.loc).pathname;
|
|
144
146
|
if (!seenPaths.has(pathname) && !errorSet.has(pathname)) {
|
|
145
147
|
other.push({ pathname });
|
|
146
148
|
seenPaths.add(pathname);
|
|
@@ -29,13 +29,18 @@ async function getPrerenderDb() {
|
|
|
29
29
|
const data = await m.readPageDataFromFilesystem();
|
|
30
30
|
const pages = data.pages || [];
|
|
31
31
|
const errorRoutes = new Set(data.errorRoutes || []);
|
|
32
|
+
const wantsMarkdown = (sql) => sql.includes("SELECT *") || sql.toLowerCase().includes("markdown");
|
|
32
33
|
return {
|
|
33
34
|
all: async (sql, params = []) => {
|
|
34
35
|
const isErrorQuery = sql.includes("is_error = 1") || params.includes(1) && sql.includes("is_error");
|
|
35
36
|
const excludeErrors = sql.includes("is_error = 0");
|
|
37
|
+
const includeMarkdown = wantsMarkdown(sql);
|
|
36
38
|
if (isErrorQuery) {
|
|
37
39
|
return pages.filter((p) => errorRoutes.has(p.route)).map((p) => ({
|
|
38
|
-
|
|
40
|
+
route: p.route,
|
|
41
|
+
title: p.title,
|
|
42
|
+
description: p.description,
|
|
43
|
+
...includeMarkdown ? { markdown: p.markdown } : {},
|
|
39
44
|
headings: p.headings,
|
|
40
45
|
keywords: JSON.stringify(p.keywords),
|
|
41
46
|
updated_at: p.updatedAt,
|
|
@@ -48,7 +53,7 @@ async function getPrerenderDb() {
|
|
|
48
53
|
route: p.route,
|
|
49
54
|
title: p.title,
|
|
50
55
|
description: p.description,
|
|
51
|
-
markdown: p.markdown,
|
|
56
|
+
...includeMarkdown ? { markdown: p.markdown } : {},
|
|
52
57
|
headings: p.headings,
|
|
53
58
|
keywords: JSON.stringify(p.keywords),
|
|
54
59
|
updated_at: p.updatedAt,
|
|
@@ -62,11 +67,12 @@ async function getPrerenderDb() {
|
|
|
62
67
|
const page = pages.find((p) => p.route === route);
|
|
63
68
|
if (!page)
|
|
64
69
|
return void 0;
|
|
70
|
+
const includeMarkdown = wantsMarkdown(sql);
|
|
65
71
|
return {
|
|
66
72
|
route: page.route,
|
|
67
73
|
title: page.title,
|
|
68
74
|
description: page.description,
|
|
69
|
-
markdown: page.markdown,
|
|
75
|
+
...includeMarkdown ? { markdown: page.markdown } : {},
|
|
70
76
|
headings: page.headings,
|
|
71
77
|
keywords: JSON.stringify(page.keywords),
|
|
72
78
|
updated_at: page.updatedAt,
|
|
@@ -110,7 +116,8 @@ function rowToEntry(row) {
|
|
|
110
116
|
description: row.description,
|
|
111
117
|
headings: row.headings,
|
|
112
118
|
keywords: JSON.parse(row.keywords || "[]"),
|
|
113
|
-
updatedAt: row.updated_at
|
|
119
|
+
updatedAt: row.updated_at,
|
|
120
|
+
isError: row.is_error === 1
|
|
114
121
|
};
|
|
115
122
|
}
|
|
116
123
|
function rowToData(row) {
|
|
@@ -52,16 +52,37 @@ export interface PageOutput {
|
|
|
52
52
|
updatedAt: string;
|
|
53
53
|
isError: boolean;
|
|
54
54
|
}
|
|
55
|
+
export interface PageMetaOutput {
|
|
56
|
+
route: string;
|
|
57
|
+
title: string;
|
|
58
|
+
description: string;
|
|
59
|
+
headings: string;
|
|
60
|
+
keywords: string[];
|
|
61
|
+
contentHash?: string;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
isError: boolean;
|
|
64
|
+
}
|
|
55
65
|
/**
|
|
56
66
|
* Insert or update a page
|
|
57
67
|
*/
|
|
58
68
|
export declare function insertPage(db: DatabaseAdapter, page: PageInput): Promise<void>;
|
|
69
|
+
export interface QueryAllPagesOptions {
|
|
70
|
+
includeErrors?: boolean;
|
|
71
|
+
excludeMarkdown?: boolean;
|
|
72
|
+
}
|
|
59
73
|
/**
|
|
60
74
|
* Query all pages from database
|
|
75
|
+
* @param db - Database adapter
|
|
76
|
+
* @param options - Query options
|
|
77
|
+
* @param options.excludeMarkdown - If true, omit markdown field to reduce memory usage
|
|
61
78
|
*/
|
|
62
|
-
export declare function queryAllPages(db: DatabaseAdapter, options?: {
|
|
63
|
-
|
|
79
|
+
export declare function queryAllPages(db: DatabaseAdapter, options?: QueryAllPagesOptions & {
|
|
80
|
+
excludeMarkdown: true;
|
|
81
|
+
}): Promise<PageMetaOutput[]>;
|
|
82
|
+
export declare function queryAllPages(db: DatabaseAdapter, options?: QueryAllPagesOptions & {
|
|
83
|
+
excludeMarkdown?: false;
|
|
64
84
|
}): Promise<PageOutput[]>;
|
|
85
|
+
export declare function queryAllPages(db: DatabaseAdapter, options?: QueryAllPagesOptions): Promise<PageOutput[] | PageMetaOutput[]>;
|
|
65
86
|
export interface DumpRow {
|
|
66
87
|
route: string;
|
|
67
88
|
route_key: string;
|
|
@@ -79,7 +100,8 @@ export interface DumpRow {
|
|
|
79
100
|
last_seen_at: number | null;
|
|
80
101
|
}
|
|
81
102
|
/**
|
|
82
|
-
* Export database as compressed dump (base64 gzip)
|
|
103
|
+
* Export database as compressed dump (base64 gzip) using batched streaming
|
|
104
|
+
* Processes rows in batches to avoid loading entire database into memory
|
|
83
105
|
*/
|
|
84
106
|
export declare function exportDbDump(db: DatabaseAdapter): Promise<string>;
|
|
85
107
|
/**
|
|
@@ -86,12 +86,13 @@ export async function insertPage(db, page) {
|
|
|
86
86
|
}
|
|
87
87
|
export async function queryAllPages(db, options) {
|
|
88
88
|
const where = options?.includeErrors ? "" : "WHERE is_error = 0";
|
|
89
|
-
const
|
|
89
|
+
const fields = options?.excludeMarkdown ? "route, title, description, headings, keywords, content_hash, updated_at, is_error" : "route, title, description, markdown, headings, keywords, content_hash, updated_at, is_error";
|
|
90
|
+
const rows = await db.all(`SELECT ${fields} FROM ai_ready_pages ${where}`);
|
|
90
91
|
return rows.map((row) => ({
|
|
91
92
|
route: row.route,
|
|
92
93
|
title: row.title,
|
|
93
94
|
description: row.description,
|
|
94
|
-
markdown: row.markdown,
|
|
95
|
+
...options?.excludeMarkdown ? {} : { markdown: row.markdown },
|
|
95
96
|
headings: row.headings,
|
|
96
97
|
keywords: JSON.parse(row.keywords || "[]"),
|
|
97
98
|
contentHash: row.content_hash || void 0,
|
|
@@ -99,12 +100,53 @@ export async function queryAllPages(db, options) {
|
|
|
99
100
|
isError: row.is_error === 1
|
|
100
101
|
}));
|
|
101
102
|
}
|
|
103
|
+
const DUMP_BATCH_SIZE = 500;
|
|
102
104
|
export async function exportDbDump(db) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const encoder = new TextEncoder();
|
|
106
|
+
const chunks = [];
|
|
107
|
+
const compressionStream = new CompressionStream("gzip");
|
|
108
|
+
const writer = compressionStream.writable.getWriter();
|
|
109
|
+
const reader = compressionStream.readable.getReader();
|
|
110
|
+
const readPromise = (async () => {
|
|
111
|
+
while (true) {
|
|
112
|
+
const { done, value } = await reader.read();
|
|
113
|
+
if (done)
|
|
114
|
+
break;
|
|
115
|
+
chunks.push(value);
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
118
|
+
await writer.write(encoder.encode("["));
|
|
119
|
+
let offset = 0;
|
|
120
|
+
let first = true;
|
|
121
|
+
while (true) {
|
|
122
|
+
const rows = await db.all(`
|
|
123
|
+
SELECT route, route_key, title, description, markdown, headings, keywords, content_hash, updated_at, indexed_at, is_error, indexed, source, last_seen_at
|
|
124
|
+
FROM ai_ready_pages
|
|
125
|
+
ORDER BY route
|
|
126
|
+
LIMIT ${DUMP_BATCH_SIZE} OFFSET ${offset}
|
|
127
|
+
`);
|
|
128
|
+
if (rows.length === 0)
|
|
129
|
+
break;
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
const prefix = first ? "" : ",";
|
|
132
|
+
first = false;
|
|
133
|
+
await writer.write(encoder.encode(prefix + JSON.stringify(row)));
|
|
134
|
+
}
|
|
135
|
+
if (rows.length < DUMP_BATCH_SIZE)
|
|
136
|
+
break;
|
|
137
|
+
offset += DUMP_BATCH_SIZE;
|
|
138
|
+
}
|
|
139
|
+
await writer.write(encoder.encode("]"));
|
|
140
|
+
await writer.close();
|
|
141
|
+
await readPromise;
|
|
142
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
143
|
+
const result = new Uint8Array(totalLength);
|
|
144
|
+
let pos = 0;
|
|
145
|
+
for (const chunk of chunks) {
|
|
146
|
+
result.set(chunk, pos);
|
|
147
|
+
pos += chunk.length;
|
|
148
|
+
}
|
|
149
|
+
return Buffer.from(result).toString("base64");
|
|
108
150
|
}
|
|
109
151
|
export async function importDbDump(db, rows) {
|
|
110
152
|
for (const row of rows) {
|
|
@@ -19,8 +19,10 @@ export default eventHandler(async (event) => {
|
|
|
19
19
|
} else {
|
|
20
20
|
mode = "production";
|
|
21
21
|
}
|
|
22
|
-
const pages = await
|
|
23
|
-
|
|
22
|
+
const [pages, errorRoutes] = await Promise.all([
|
|
23
|
+
queryPages(event),
|
|
24
|
+
queryPages(event, { where: { hasError: true } })
|
|
25
|
+
]);
|
|
24
26
|
let source;
|
|
25
27
|
if (isDev) {
|
|
26
28
|
source = "empty (dev mode returns empty array)";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ai-ready",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.4",
|
|
5
5
|
"description": "Best practice AI & LLM discoverability for Nuxt sites.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -74,11 +74,11 @@
|
|
|
74
74
|
"@nuxtjs/robots": "^5.6.7",
|
|
75
75
|
"@nuxtjs/sitemap": "^7.5.2",
|
|
76
76
|
"@types/better-sqlite3": "^7.6.13",
|
|
77
|
-
"@vitest/coverage-v8": "^4.0.
|
|
77
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
78
78
|
"@vue/test-utils": "^2.4.6",
|
|
79
79
|
"@vueuse/nuxt": "^14.1.0",
|
|
80
80
|
"agents": "^0.3.4",
|
|
81
|
-
"ai": "^6.0.
|
|
81
|
+
"ai": "^6.0.30",
|
|
82
82
|
"better-sqlite3": "^12.6.0",
|
|
83
83
|
"bumpp": "^10.3.2",
|
|
84
84
|
"eslint": "^9.39.2",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"playwright-core": "^1.57.0",
|
|
91
91
|
"postgres": "^3.4.8",
|
|
92
92
|
"typescript": "^5.9.3",
|
|
93
|
-
"vitest": "^4.0.
|
|
93
|
+
"vitest": "^4.0.17",
|
|
94
94
|
"vue": "^3.5.26",
|
|
95
95
|
"vue-router": "^4.6.4",
|
|
96
96
|
"vue-tsc": "^3.2.2",
|