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.
Files changed (69) hide show
  1. package/README.md +1 -0
  2. package/dist/module.d.mts +12 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +226 -352
  5. package/dist/runtime/index.d.ts +5 -7
  6. package/dist/runtime/index.js +14 -3
  7. package/dist/runtime/llms-txt-format.d.ts +8 -0
  8. package/dist/runtime/llms-txt-format.js +32 -0
  9. package/dist/runtime/llms-txt-utils.d.ts +2 -5
  10. package/dist/runtime/llms-txt-utils.js +31 -88
  11. package/dist/runtime/server/db/index.d.ts +3 -7
  12. package/dist/runtime/server/db/index.js +14 -31
  13. package/dist/runtime/server/db/queries.d.ts +106 -26
  14. package/dist/runtime/server/db/queries.js +330 -33
  15. package/dist/runtime/server/db/schema-sql.d.ts +3 -0
  16. package/dist/runtime/server/db/schema-sql.js +71 -0
  17. package/dist/runtime/server/db/shared.d.ts +88 -0
  18. package/dist/runtime/server/db/shared.js +117 -0
  19. package/dist/runtime/server/mcp/resources/pages.js +11 -3
  20. package/dist/runtime/server/mcp/tools/list-pages.js +33 -7
  21. package/dist/runtime/server/mcp/tools/search-pages.js +3 -3
  22. package/dist/runtime/server/middleware/markdown.js +2 -3
  23. package/dist/runtime/server/middleware/markdown.prerender.js +6 -5
  24. package/dist/runtime/server/plugins/db-restore.js +10 -8
  25. package/dist/runtime/server/plugins/sitemap-seeder.js +44 -0
  26. package/dist/runtime/server/routes/__ai-ready/indexnow.post.d.ts +2 -0
  27. package/dist/runtime/server/routes/__ai-ready/indexnow.post.js +18 -0
  28. package/dist/runtime/server/routes/__ai-ready/poll.post.d.ts +8 -0
  29. package/dist/runtime/server/routes/__ai-ready/poll.post.js +25 -0
  30. package/dist/runtime/server/routes/__ai-ready/prune.post.d.ts +14 -0
  31. package/dist/runtime/server/routes/__ai-ready/prune.post.js +21 -0
  32. package/dist/runtime/server/routes/__ai-ready/status.get.d.ts +2 -0
  33. package/dist/runtime/server/routes/__ai-ready/status.get.js +28 -0
  34. package/dist/runtime/server/routes/__ai-ready-debug.get.js +13 -14
  35. package/dist/runtime/server/routes/indexnow-key.get.d.ts +6 -0
  36. package/dist/runtime/server/routes/indexnow-key.get.js +10 -0
  37. package/dist/runtime/server/routes/llms-full.txt.get.d.ts +1 -1
  38. package/dist/runtime/server/routes/llms-full.txt.get.js +34 -3
  39. package/dist/runtime/server/tasks/ai-ready-index.d.ts +11 -0
  40. package/dist/runtime/server/tasks/ai-ready-index.js +39 -0
  41. package/dist/runtime/server/utils/batchIndex.d.ts +26 -0
  42. package/dist/runtime/server/utils/batchIndex.js +53 -0
  43. package/dist/runtime/server/utils/indexPage.d.ts +8 -2
  44. package/dist/runtime/server/utils/indexPage.js +38 -23
  45. package/dist/runtime/server/utils/indexnow.d.ts +27 -0
  46. package/dist/runtime/server/utils/indexnow.js +66 -0
  47. package/dist/runtime/server/utils/keywords.d.ts +0 -4
  48. package/dist/runtime/server/utils/keywords.js +0 -3
  49. package/dist/runtime/server/utils/llms-full.d.ts +11 -0
  50. package/dist/runtime/server/utils/llms-full.js +39 -0
  51. package/dist/runtime/server/utils/sitemap.js +3 -3
  52. package/dist/runtime/server/utils.d.ts +12 -10
  53. package/dist/runtime/server/utils.js +63 -94
  54. package/dist/runtime/types.d.ts +64 -40
  55. package/package.json +28 -29
  56. package/dist/runtime/mcp.d.ts +0 -32
  57. package/dist/runtime/mcp.js +0 -5
  58. package/dist/runtime/server/db/dump.d.ts +0 -29
  59. package/dist/runtime/server/db/dump.js +0 -29
  60. package/dist/runtime/server/db/schema.d.ts +0 -8
  61. package/dist/runtime/server/db/schema.js +0 -124
  62. package/dist/runtime/server/plugins/page-indexer.js +0 -68
  63. package/dist/runtime/server/tsconfig.json +0 -3
  64. package/dist/runtime/server/utils/pageData.d.ts +0 -28
  65. package/dist/runtime/server/utils/pageData.js +0 -43
  66. package/mcp.d.ts +0 -4
  67. /package/dist/runtime/{nuxt → app}/plugins/md-hints.prerender.d.ts +0 -0
  68. /package/dist/runtime/{nuxt → app}/plugins/md-hints.prerender.js +0 -0
  69. /package/dist/runtime/server/plugins/{page-indexer.d.ts → sitemap-seeder.d.ts} +0 -0
@@ -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 { getPages, getPagesList, getErrorRoutes } from './server/utils/pageData.js';
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';
@@ -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 { searchPages, getAllPages, getPage, getPageWithMarkdown, upsertPage, getPageCount } from "./server/db/queries.js";
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 type { LlmsTxtConfig } from './types.js';
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 { getErrorRoutes, getPages } from "./server/utils/pageData.js";
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 sortPagesByPath(pages) {
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 firstSegmentA = segmentsA[0] || "";
35
- const firstSegmentB = segmentsB[0] || "";
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 twoSegmentCount = /* @__PURE__ */ new Map();
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, twoSegmentCount, segmentHasNested);
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 getPages();
130
+ const pages = await queryPages(event);
189
131
  const urls = await fetchSitemapUrls(event);
190
- const errorRoutes = await getErrorRoutes();
191
- const devModeHint = import.meta.dev && pages.size === 0 ? " (dev mode - run `nuxi generate` for page titles)" : "";
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 [pathname, page] of pages) {
195
- prerendered.push({ pathname, title: page.title, description: page.description });
196
- seenPaths.add(pathname);
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) && !errorRoutes.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 './schema.js';
2
+ import type { DatabaseAdapter } from './shared.js';
3
3
  /**
4
4
  * Get the database adapter instance
5
- * Initializes the database on first call
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
- let _db = null;
7
- let _initPromise = null;
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
- if (!_initPromise) {
10
- _initPromise = initDatabase(event);
8
+ const nitro = useNitroApp();
9
+ if (!nitro[DB_KEY]) {
10
+ nitro[DB_KEY] = initDatabase(event);
11
11
  }
12
- return _initPromise;
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
- _db = adapter({ binding });
22
+ connector = adapter({ binding });
22
23
  } else if (config.database.type === "libsql") {
23
- _db = adapter({
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
- _db = adapter({ path: dbPath });
31
+ connector = adapter({ path: dbPath });
31
32
  }
32
- if (!_db) {
33
+ if (!connector) {
33
34
  throw new Error("Failed to initialize database connector");
34
35
  }
35
- const dbAdapter = createAdapter(_db);
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 { DatabaseAdapter } from './schema.js';
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
- * Get all non-error pages
34
- */
35
- export declare function getAllPages(db: DatabaseAdapter): Promise<PageEntry[]>;
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 getPage(db: DatabaseAdapter, route: string): Promise<PageEntry | undefined>;
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
- * Get a page with markdown content
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 getPageWithMarkdown(db: DatabaseAdapter, route: string): Promise<PageData | undefined>;
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
- * Full-text search using FTS5
46
- * @param query Search query string
47
- * @param opts Search options
80
+ * Count pages matching criteria
48
81
  */
49
- export declare function searchPages(db: DatabaseAdapter, query: string, opts?: {
82
+ export declare function countPages(event?: H3Event, options?: CountPagesOptions): Promise<number>;
83
+ export interface SearchPagesOptions {
50
84
  limit?: number;
51
- }): Promise<SearchResult[]>;
85
+ }
52
86
  /**
53
- * Insert or update a page
87
+ * Full-text search using FTS5
88
+ * Note: FTS is only available at runtime, not during prerender
54
89
  */
55
- export declare function upsertPage(db: DatabaseAdapter, page: {
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
- }): Promise<void>;
101
+ source?: 'prerender' | 'runtime';
102
+ }
65
103
  /**
66
- * Get all error routes
104
+ * Insert or update a page
67
105
  */
68
- export declare function getErrorRoutes(db: DatabaseAdapter): Promise<string[]>;
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(db: DatabaseAdapter, route: string, ttlSeconds: number): Promise<boolean>;
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
- * Delete a page by route
117
+ * Seed routes from sitemap (insert with indexed=0 if not exists)
75
118
  */
76
- export declare function deletePage(db: DatabaseAdapter, route: string): Promise<void>;
119
+ export declare function seedRoutes(event: H3Event | undefined, routes: string[]): Promise<number>;
77
120
  /**
78
- * Get page count
121
+ * Get sitemap seeded timestamp from _ai_ready_info
79
122
  */
80
- export declare function getPageCount(db: DatabaseAdapter): Promise<number>;
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
- * Check if database has any pages
162
+ * Get IndexNow stats
83
163
  */
84
- export declare function hasPages(db: DatabaseAdapter): Promise<boolean>;
164
+ export declare function getIndexNowStats(event: H3Event | undefined): Promise<IndexNowStats>;