nuxt-ai-ready 0.7.4 → 0.7.6
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 +35 -9
- package/dist/runtime/server/db/index.js +3 -22
- package/dist/runtime/server/db/provider/d1.d.ts +3 -0
- package/dist/runtime/server/db/provider/d1.js +10 -0
- package/dist/runtime/server/db/provider/libsql.d.ts +3 -0
- package/dist/runtime/server/db/provider/libsql.js +9 -0
- package/dist/runtime/server/db/provider/sqlite-bun.d.ts +3 -0
- package/dist/runtime/server/db/provider/sqlite-bun.js +10 -0
- package/dist/runtime/server/db/provider/sqlite-node.d.ts +3 -0
- package/dist/runtime/server/db/provider/sqlite-node.js +10 -0
- package/dist/runtime/server/routes/__ai-ready-debug.get.js +55 -40
- package/dist/runtime/server/routes/llms-full.txt.get.js +11 -3
- package/dist/runtime/server/tasks/ai-ready-cron.js +2 -0
- package/dist/runtime/server/utils/indexnow.js +2 -2
- package/dist/runtime/server/utils/runCron.js +2 -0
- package/package.json +6 -6
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -84,11 +84,13 @@ async function initCrawler(state) {
|
|
|
84
84
|
return;
|
|
85
85
|
if (state.dbPath) {
|
|
86
86
|
await mkdir(dirname(state.dbPath), { recursive: true });
|
|
87
|
-
const
|
|
88
|
-
const
|
|
87
|
+
const nodeVersion = Number.parseInt(process.versions.node?.split(".")[0] || "0");
|
|
88
|
+
const connectorPath = nodeVersion >= 22 ? "db0/connectors/node-sqlite" : "db0/connectors/better-sqlite3";
|
|
89
|
+
const { default: connectorFn } = await import(connectorPath);
|
|
90
|
+
const connector = connectorFn({ path: state.dbPath });
|
|
89
91
|
state.db = createAdapter(connector);
|
|
90
92
|
await initSchema(state.db);
|
|
91
|
-
logger.debug(`Crawler initialized with SQLite at ${state.dbPath}`);
|
|
93
|
+
logger.debug(`Crawler initialized with SQLite at ${state.dbPath} using ${connectorPath}`);
|
|
92
94
|
}
|
|
93
95
|
if (state.llmsFullTxtPath) {
|
|
94
96
|
await mkdir(dirname(state.llmsFullTxtPath), { recursive: true });
|
|
@@ -370,6 +372,12 @@ declare module '#ai-ready/adapter' {
|
|
|
370
372
|
export default connector
|
|
371
373
|
}
|
|
372
374
|
|
|
375
|
+
declare module '#ai-ready/db-provider' {
|
|
376
|
+
import type { H3Event } from 'h3'
|
|
377
|
+
import type { Connector } from 'db0'
|
|
378
|
+
export function createConnector(event?: H3Event): Promise<Connector>
|
|
379
|
+
}
|
|
380
|
+
|
|
373
381
|
export {}
|
|
374
382
|
`
|
|
375
383
|
});
|
|
@@ -469,6 +477,16 @@ const module$1 = defineNuxtModule({
|
|
|
469
477
|
const adapterPath = await resolveDatabaseAdapter(dbType);
|
|
470
478
|
nuxt.options.alias["#ai-ready/adapter"] = adapterPath;
|
|
471
479
|
nuxt.options.nitro.alias["#ai-ready/adapter"] = adapterPath;
|
|
480
|
+
let providerPath = resolve("./runtime/server/db/provider/sqlite-node");
|
|
481
|
+
if (dbType === "d1") {
|
|
482
|
+
providerPath = resolve("./runtime/server/db/provider/d1");
|
|
483
|
+
} else if (dbType === "libsql") {
|
|
484
|
+
providerPath = resolve("./runtime/server/db/provider/libsql");
|
|
485
|
+
} else if (process.versions.bun) {
|
|
486
|
+
providerPath = resolve("./runtime/server/db/provider/sqlite-bun");
|
|
487
|
+
}
|
|
488
|
+
nuxt.options.alias["#ai-ready/db-provider"] = providerPath;
|
|
489
|
+
nuxt.options.nitro.alias["#ai-ready/db-provider"] = providerPath;
|
|
472
490
|
if (!nuxt.options.mcp?.name) {
|
|
473
491
|
nuxt.options.mcp = nuxt.options.mcp || {};
|
|
474
492
|
nuxt.options.mcp.name = useSiteConfig().name;
|
|
@@ -579,12 +597,20 @@ export async function readPageDataFromFilesystem() {
|
|
|
579
597
|
return { pages: [], errorRoutes: [] }
|
|
580
598
|
}
|
|
581
599
|
|
|
582
|
-
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
600
|
+
let rows = []
|
|
601
|
+
const nodeVersion = Number.parseInt(process.versions.node?.split('.')[0] || '0')
|
|
602
|
+
if (nodeVersion >= 22) {
|
|
603
|
+
const { DatabaseSync } = await import('node:sqlite')
|
|
604
|
+
const db = new DatabaseSync(dbPath, { open: true })
|
|
605
|
+
rows = db.prepare('SELECT route, title, description, markdown, headings, keywords, updated_at, is_error FROM ai_ready_pages').all()
|
|
606
|
+
db.close()
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
const Database = (await import('better-sqlite3')).default
|
|
610
|
+
const db = new Database(dbPath, { readonly: true })
|
|
611
|
+
rows = db.prepare('SELECT route, title, description, markdown, headings, keywords, updated_at, is_error FROM ai_ready_pages').all()
|
|
612
|
+
db.close()
|
|
613
|
+
}
|
|
588
614
|
|
|
589
615
|
const pages = rows.filter(r => !r.is_error).map(r => ({
|
|
590
616
|
route: r.route,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
|
|
4
|
-
import { dirname } from "pathe";
|
|
1
|
+
import { createConnector } from "#ai-ready/db-provider";
|
|
2
|
+
import { useNitroApp } from "nitropack/runtime";
|
|
5
3
|
import { createAdapter, initSchema } from "./shared.js";
|
|
6
4
|
const DB_KEY = "_aiReadyDb";
|
|
7
5
|
export function useDatabase(event) {
|
|
@@ -12,24 +10,7 @@ export function useDatabase(event) {
|
|
|
12
10
|
return nitro[DB_KEY];
|
|
13
11
|
}
|
|
14
12
|
async function initDatabase(event) {
|
|
15
|
-
const
|
|
16
|
-
let connector;
|
|
17
|
-
if (config.database.type === "d1") {
|
|
18
|
-
const binding = event?.context?.cloudflare?.env?.[config.database.bindingName || "AI_READY_DB"];
|
|
19
|
-
if (!binding) {
|
|
20
|
-
throw new Error(`D1 binding '${config.database.bindingName || "AI_READY_DB"}' not found in event context`);
|
|
21
|
-
}
|
|
22
|
-
connector = adapter({ binding });
|
|
23
|
-
} else if (config.database.type === "libsql") {
|
|
24
|
-
connector = adapter({
|
|
25
|
-
url: config.database.url,
|
|
26
|
-
authToken: config.database.authToken
|
|
27
|
-
});
|
|
28
|
-
} else {
|
|
29
|
-
const dbPath = config.database.filename || ".data/ai-ready/pages.db";
|
|
30
|
-
await mkdir(dirname(dbPath), { recursive: true });
|
|
31
|
-
connector = adapter({ path: dbPath });
|
|
32
|
-
}
|
|
13
|
+
const connector = await createConnector(event);
|
|
33
14
|
if (!connector) {
|
|
34
15
|
throw new Error("Failed to initialize database connector");
|
|
35
16
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import adapter from "#ai-ready/adapter";
|
|
2
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
export async function createConnector(event) {
|
|
4
|
+
const config = useRuntimeConfig(event)["nuxt-ai-ready"];
|
|
5
|
+
const binding = event?.context?.cloudflare?.env?.[config.database.bindingName || "AI_READY_DB"];
|
|
6
|
+
if (!binding) {
|
|
7
|
+
throw new Error(`D1 binding '${config.database.bindingName || "AI_READY_DB"}' not found in event context`);
|
|
8
|
+
}
|
|
9
|
+
return adapter({ binding });
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import adapter from "#ai-ready/adapter";
|
|
2
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
export async function createConnector(event) {
|
|
4
|
+
const config = useRuntimeConfig(event)["nuxt-ai-ready"];
|
|
5
|
+
return adapter({
|
|
6
|
+
url: config.database.url,
|
|
7
|
+
authToken: config.database.authToken
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import adapter from "#ai-ready/adapter";
|
|
3
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
4
|
+
import { dirname } from "pathe";
|
|
5
|
+
export async function createConnector(event) {
|
|
6
|
+
const config = useRuntimeConfig(event)["nuxt-ai-ready"];
|
|
7
|
+
const dbPath = config.database.filename || ".data/ai-ready/pages.db";
|
|
8
|
+
await mkdir(dirname(dbPath), { recursive: true });
|
|
9
|
+
return adapter({ path: dbPath });
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import adapter from "#ai-ready/adapter";
|
|
3
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
4
|
+
import { dirname } from "pathe";
|
|
5
|
+
export async function createConnector(event) {
|
|
6
|
+
const config = useRuntimeConfig(event)["nuxt-ai-ready"];
|
|
7
|
+
const dbPath = config.database.filename || ".data/ai-ready/pages.db";
|
|
8
|
+
await mkdir(dirname(dbPath), { recursive: true });
|
|
9
|
+
return adapter({ path: dbPath });
|
|
10
|
+
}
|
|
@@ -19,10 +19,23 @@ export default eventHandler(async (event) => {
|
|
|
19
19
|
} else {
|
|
20
20
|
mode = "production";
|
|
21
21
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
]
|
|
22
|
+
const issues = [];
|
|
23
|
+
const suggestions = [];
|
|
24
|
+
let pages = [];
|
|
25
|
+
let errorRoutes = [];
|
|
26
|
+
let dbError = null;
|
|
27
|
+
try {
|
|
28
|
+
const [p, e] = await Promise.all([
|
|
29
|
+
queryPages(event),
|
|
30
|
+
queryPages(event, { where: { hasError: true } })
|
|
31
|
+
]);
|
|
32
|
+
pages = p;
|
|
33
|
+
errorRoutes = e;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
dbError = err.message || String(err);
|
|
36
|
+
issues.push(`Database error: ${dbError}`);
|
|
37
|
+
suggestions.push("Verify your database configuration (D1 binding, SQLite path, etc.)");
|
|
38
|
+
}
|
|
26
39
|
let source;
|
|
27
40
|
if (isDev) {
|
|
28
41
|
source = "empty (dev mode returns empty array)";
|
|
@@ -75,8 +88,6 @@ export default eventHandler(async (event) => {
|
|
|
75
88
|
pageCount: publicData?.pages?.length ?? 0,
|
|
76
89
|
source: jsonFileSource
|
|
77
90
|
};
|
|
78
|
-
const issues = [];
|
|
79
|
-
const suggestions = [];
|
|
80
91
|
if (mode === "development") {
|
|
81
92
|
issues.push("Development mode: page data is intentionally empty");
|
|
82
93
|
suggestions.push("Run `nuxi generate` or `nuxi build --prerender` to generate page data");
|
|
@@ -95,42 +106,46 @@ export default eventHandler(async (event) => {
|
|
|
95
106
|
let runtimeSyncInfo;
|
|
96
107
|
let indexNowInfo;
|
|
97
108
|
let cronRunsInfo;
|
|
98
|
-
if (!isDev && !isPrerender) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
runtimeSyncInfo = {
|
|
106
|
-
total,
|
|
107
|
-
indexed: total - pending,
|
|
108
|
-
pending,
|
|
109
|
-
sitemapSeededAt: sitemapSeededAt ? new Date(sitemapSeededAt).toISOString() : null
|
|
110
|
-
};
|
|
111
|
-
cronRunsInfo = cronRuns.map((run) => ({
|
|
112
|
-
id: run.id,
|
|
113
|
-
startedAt: new Date(run.startedAt).toISOString(),
|
|
114
|
-
finishedAt: run.finishedAt ? new Date(run.finishedAt).toISOString() : null,
|
|
115
|
-
durationMs: run.durationMs,
|
|
116
|
-
status: run.status,
|
|
117
|
-
pagesIndexed: run.pagesIndexed,
|
|
118
|
-
pagesRemaining: run.pagesRemaining,
|
|
119
|
-
indexNowSubmitted: run.indexNowSubmitted,
|
|
120
|
-
indexNowRemaining: run.indexNowRemaining,
|
|
121
|
-
errors: run.errors
|
|
122
|
-
}));
|
|
123
|
-
if (runtimeConfig.indexNowKey) {
|
|
124
|
-
const [indexNowPending, indexNowStats] = await Promise.all([
|
|
125
|
-
countPagesNeedingIndexNowSync(event),
|
|
126
|
-
getIndexNowStats(event)
|
|
109
|
+
if (!isDev && !isPrerender && !dbError) {
|
|
110
|
+
try {
|
|
111
|
+
const [total, pending, sitemapSeededAt, cronRuns] = await Promise.all([
|
|
112
|
+
countPages(event),
|
|
113
|
+
countPages(event, { where: { pending: true } }),
|
|
114
|
+
getSitemapSeededAt(event),
|
|
115
|
+
getRecentCronRuns(event, 20)
|
|
127
116
|
]);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
117
|
+
runtimeSyncInfo = {
|
|
118
|
+
total,
|
|
119
|
+
indexed: total - pending,
|
|
120
|
+
pending,
|
|
121
|
+
sitemapSeededAt: sitemapSeededAt ? new Date(sitemapSeededAt).toISOString() : null
|
|
133
122
|
};
|
|
123
|
+
cronRunsInfo = cronRuns.map((run) => ({
|
|
124
|
+
id: run.id,
|
|
125
|
+
startedAt: new Date(run.startedAt).toISOString(),
|
|
126
|
+
finishedAt: run.finishedAt ? new Date(run.finishedAt).toISOString() : null,
|
|
127
|
+
durationMs: run.durationMs,
|
|
128
|
+
status: run.status,
|
|
129
|
+
pagesIndexed: run.pagesIndexed,
|
|
130
|
+
pagesRemaining: run.pagesRemaining,
|
|
131
|
+
indexNowSubmitted: run.indexNowSubmitted,
|
|
132
|
+
indexNowRemaining: run.indexNowRemaining,
|
|
133
|
+
errors: run.errors
|
|
134
|
+
}));
|
|
135
|
+
if (runtimeConfig.indexNowKey) {
|
|
136
|
+
const [indexNowPending, indexNowStats] = await Promise.all([
|
|
137
|
+
countPagesNeedingIndexNowSync(event),
|
|
138
|
+
getIndexNowStats(event)
|
|
139
|
+
]);
|
|
140
|
+
indexNowInfo = {
|
|
141
|
+
pending: indexNowPending,
|
|
142
|
+
totalSubmitted: indexNowStats.totalSubmitted,
|
|
143
|
+
lastSubmittedAt: indexNowStats.lastSubmittedAt ? new Date(indexNowStats.lastSubmittedAt).toISOString() : null,
|
|
144
|
+
lastError: indexNowStats.lastError
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
} catch (err) {
|
|
148
|
+
issues.push(`Runtime stats error: ${err.message || String(err)}`);
|
|
134
149
|
}
|
|
135
150
|
}
|
|
136
151
|
const debugInfo = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getSiteConfig } from "#site-config/server/composables";
|
|
2
2
|
import { eventHandler, sendIterable, setHeader, setResponseHeader } from "h3";
|
|
3
3
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
4
4
|
import { countPages, streamPages } from "../db/queries.js";
|
|
@@ -9,7 +9,7 @@ export default eventHandler(async (event) => {
|
|
|
9
9
|
return "# llms-full.txt\n\nThis file is generated during prerender.";
|
|
10
10
|
}
|
|
11
11
|
const config = useRuntimeConfig()["nuxt-ai-ready"];
|
|
12
|
-
const siteConfig =
|
|
12
|
+
const siteConfig = getSiteConfig(event);
|
|
13
13
|
const header = buildLlmsFullTxtHeader(
|
|
14
14
|
{
|
|
15
15
|
name: siteConfig.name,
|
|
@@ -18,7 +18,15 @@ export default eventHandler(async (event) => {
|
|
|
18
18
|
},
|
|
19
19
|
config.llmsTxt
|
|
20
20
|
);
|
|
21
|
-
|
|
21
|
+
let total = 0;
|
|
22
|
+
try {
|
|
23
|
+
total = await countPages(event);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
setHeader(event, "Content-Type", "text/plain; charset=utf-8");
|
|
26
|
+
return `${header}Database error: ${err.message || String(err)}
|
|
27
|
+
|
|
28
|
+
Runtime indexing may not be configured correctly for this environment.`;
|
|
29
|
+
}
|
|
22
30
|
if (total === 0) {
|
|
23
31
|
setHeader(event, "Content-Type", "text/plain; charset=utf-8");
|
|
24
32
|
return `${header}No pages indexed. Run \`nuxi generate\` or enable runtime indexing.`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getSiteConfig } from "#site-config/server/composables";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
3
|
import {
|
|
4
4
|
countPagesNeedingIndexNowSync,
|
|
@@ -37,7 +37,7 @@ export async function submitToIndexNow(routes, config, siteUrl) {
|
|
|
37
37
|
}
|
|
38
38
|
export async function syncToIndexNow(event, limit = 100) {
|
|
39
39
|
const config = useRuntimeConfig(event)["nuxt-ai-ready"];
|
|
40
|
-
const siteConfig =
|
|
40
|
+
const siteConfig = getSiteConfig(event);
|
|
41
41
|
if (!config.indexNowKey) {
|
|
42
42
|
return { success: false, submitted: 0, remaining: 0, error: "IndexNow not configured" };
|
|
43
43
|
}
|
|
@@ -3,6 +3,8 @@ import { cleanupOldCronRuns, completeCronRun, startCronRun } from "../db/queries
|
|
|
3
3
|
import { batchIndexPages } from "./batchIndex.js";
|
|
4
4
|
import { syncToIndexNow } from "./indexnow.js";
|
|
5
5
|
export async function runCron(event, options) {
|
|
6
|
+
if (import.meta.dev)
|
|
7
|
+
return {};
|
|
6
8
|
const config = useRuntimeConfig()["nuxt-ai-ready"];
|
|
7
9
|
const results = {};
|
|
8
10
|
const allErrors = [];
|
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.6",
|
|
5
5
|
"description": "Best practice AI & LLM discoverability for Nuxt sites.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"ufo": "^1.6.2"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@antfu/eslint-config": "^
|
|
63
|
+
"@antfu/eslint-config": "^7.0.0",
|
|
64
64
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
65
65
|
"@headlessui/vue": "^1.7.23",
|
|
66
66
|
"@nuxt/content": "^3.10.0",
|
|
@@ -77,10 +77,10 @@
|
|
|
77
77
|
"@vitest/coverage-v8": "^4.0.17",
|
|
78
78
|
"@vue/test-utils": "^2.4.6",
|
|
79
79
|
"@vueuse/nuxt": "^14.1.0",
|
|
80
|
-
"agents": "^0.3.
|
|
81
|
-
"ai": "^6.0.
|
|
80
|
+
"agents": "^0.3.5",
|
|
81
|
+
"ai": "^6.0.33",
|
|
82
82
|
"better-sqlite3": "^12.6.0",
|
|
83
|
-
"bumpp": "^10.
|
|
83
|
+
"bumpp": "^10.4.0",
|
|
84
84
|
"eslint": "^9.39.2",
|
|
85
85
|
"execa": "^9.6.1",
|
|
86
86
|
"happy-dom": "^20.1.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"vue": "^3.5.26",
|
|
95
95
|
"vue-router": "^4.6.4",
|
|
96
96
|
"vue-tsc": "^3.2.2",
|
|
97
|
-
"wrangler": "^4.
|
|
97
|
+
"wrangler": "^4.59.1",
|
|
98
98
|
"zod": "^4.3.5"
|
|
99
99
|
},
|
|
100
100
|
"resolutions": {
|