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 CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "0.7.2",
7
+ "version": "0.7.4",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
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 pages = await queryAllPages(state.db);
275
- const errorRoutesList = (await queryAllPages(state.db, { includeErrors: true })).filter((p) => p.isError).map((p) => p.route);
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 queryPages(event);
131
- const urls = await fetchSitemapUrls(event);
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)" : "";
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("http") ? new URL(url.loc).pathname : url.loc;
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);
@@ -24,6 +24,7 @@ export interface PageEntry {
24
24
  headings: string;
25
25
  keywords: string[];
26
26
  updatedAt: string;
27
+ isError: boolean;
27
28
  }
28
29
  export interface PageData extends PageEntry {
29
30
  markdown: string;
@@ -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
- ...p,
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
- includeErrors?: boolean;
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 rows = await db.all(`SELECT route, title, description, markdown, headings, keywords, content_hash, updated_at, is_error FROM ai_ready_pages ${where}`);
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 rows = await db.all(`
104
- SELECT route, route_key, title, description, markdown, headings, keywords, content_hash, updated_at, indexed_at, is_error, indexed, source, last_seen_at
105
- FROM ai_ready_pages
106
- `);
107
- return compressToBase64(rows);
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 queryPages(event);
23
- const errorRoutes = await queryPages(event, { where: { hasError: true } });
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.2",
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.16",
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.27",
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.16",
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",