nuxt-ai-ready 0.11.0 → 0.12.1

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 (58) hide show
  1. package/README.md +8 -2
  2. package/dist/cli.mjs +11 -8
  3. package/dist/module.d.mts +7 -1
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +86 -77
  6. package/dist/runtime/index.d.ts +3 -3
  7. package/dist/runtime/index.js +2 -1
  8. package/dist/runtime/server/db/drizzle/client.d.ts +20 -0
  9. package/dist/runtime/server/db/drizzle/client.js +29 -0
  10. package/dist/runtime/server/db/drizzle/index.d.ts +10 -0
  11. package/dist/runtime/server/db/drizzle/index.js +33 -0
  12. package/dist/runtime/server/db/drizzle/providers/bun.d.ts +7 -0
  13. package/dist/runtime/server/db/drizzle/providers/bun.js +25 -0
  14. package/dist/runtime/server/db/drizzle/providers/d1.d.ts +7 -0
  15. package/dist/runtime/server/db/drizzle/providers/d1.js +19 -0
  16. package/dist/runtime/server/db/drizzle/providers/libsql.d.ts +7 -0
  17. package/dist/runtime/server/db/drizzle/providers/libsql.js +15 -0
  18. package/dist/runtime/server/db/drizzle/providers/neon.d.ts +7 -0
  19. package/dist/runtime/server/db/drizzle/providers/neon.js +18 -0
  20. package/dist/runtime/server/db/drizzle/providers/sqlite.d.ts +8 -0
  21. package/dist/runtime/server/db/drizzle/providers/sqlite.js +25 -0
  22. package/dist/runtime/server/db/drizzle/queries.d.ts +190 -0
  23. package/dist/runtime/server/db/drizzle/queries.js +575 -0
  24. package/dist/runtime/server/db/drizzle/raw.d.ts +28 -0
  25. package/dist/runtime/server/db/drizzle/raw.js +94 -0
  26. package/dist/runtime/server/db/index.d.ts +2 -12
  27. package/dist/runtime/server/db/index.js +1 -36
  28. package/dist/runtime/server/db/queries.d.ts +5 -0
  29. package/dist/runtime/server/db/queries.js +31 -5
  30. package/dist/runtime/server/db/schema/postgres.d.ts +1500 -0
  31. package/dist/runtime/server/db/schema/postgres.js +66 -0
  32. package/dist/runtime/server/db/schema/sqlite.d.ts +1580 -0
  33. package/dist/runtime/server/db/schema/sqlite.js +66 -0
  34. package/dist/runtime/server/db/schema-sql.d.ts +6 -2
  35. package/dist/runtime/server/db/schema-sql.js +73 -92
  36. package/dist/runtime/server/db/shared.d.ts +1 -6
  37. package/dist/runtime/server/db/shared.js +4 -21
  38. package/dist/runtime/server/plugins/db-lifecycle.js +3 -3
  39. package/dist/runtime/server/plugins/sitemap-seeder.js +19 -2
  40. package/dist/runtime/server/routes/__ai-ready/restore.post.js +2 -2
  41. package/dist/runtime/server/routes/__ai-ready-debug.get.js +4 -4
  42. package/dist/runtime/server/utils/checkStale.js +19 -31
  43. package/dist/runtime/server/utils/indexnow-shared.js +1 -1
  44. package/dist/runtime/server/utils/indexnow.js +12 -13
  45. package/dist/runtime/server/utils/keywords.js +6 -2
  46. package/dist/runtime/server/utils/llms-full.js +5 -2
  47. package/dist/runtime/server/utils.d.ts +1 -0
  48. package/dist/runtime/server/utils.js +8 -12
  49. package/dist/runtime/types.d.ts +10 -4
  50. package/package.json +42 -32
  51. package/dist/runtime/server/db/provider/d1.d.ts +0 -3
  52. package/dist/runtime/server/db/provider/d1.js +0 -7
  53. package/dist/runtime/server/db/provider/libsql.d.ts +0 -3
  54. package/dist/runtime/server/db/provider/libsql.js +0 -9
  55. package/dist/runtime/server/db/provider/sqlite-bun.d.ts +0 -3
  56. package/dist/runtime/server/db/provider/sqlite-bun.js +0 -13
  57. package/dist/runtime/server/db/provider/sqlite-node.d.ts +0 -3
  58. package/dist/runtime/server/db/provider/sqlite-node.js +0 -13
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## Why Nuxt AI Ready?
10
10
 
11
- ChatGPT search interest doubled in the past year. Users now ask AI assistants questions your site could answerbut LLMs only cite sources they can parse.
11
+ [ChatGPT](https://chatgpt.com) search interest doubled in the past year. Users now ask AI assistants questions your site could answer - but LLMs only cite sources they can parse.
12
12
 
13
13
  Two standards are emerging: [llms.txt](https://llmstxt.org/) (4,400 searches/mo, +26,900% YoY) for AI-readable site summaries, and [MCP](https://modelcontextprotocol.io/) (22,200 searches/mo) for letting agents query your content directly.
14
14
 
@@ -33,6 +33,12 @@ Install `nuxt-ai-ready` dependency to your project:
33
33
  npx nuxi@latest module add nuxt-ai-ready
34
34
  ```
35
35
 
36
+ > [!TIP]
37
+ > Generate an Agent Skill for this package using [skilld](https://github.com/harlan-zw/skilld):
38
+ > ```bash
39
+ > npx skilld add nuxt-ai-ready
40
+ > ```
41
+
36
42
  ## Documentation
37
43
 
38
44
  [📖 Read the full documentation](https://nuxtseo.com/ai-ready) for more information.
@@ -41,7 +47,7 @@ npx nuxi@latest module add nuxt-ai-ready
41
47
 
42
48
  <p align="center">
43
49
  <a href="https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg">
44
- <img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg'/>
50
+ <img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg' alt="Sponsors"/>
45
51
  </a>
46
52
  </p>
47
53
 
package/dist/cli.mjs CHANGED
@@ -5,6 +5,7 @@ import { defineCommand, runMain } from 'citty';
5
5
  import { consola } from 'consola';
6
6
  import { colors } from 'consola/utils';
7
7
  import { resolve, join } from 'pathe';
8
+ import { readPackageJSON } from 'pkg-types';
8
9
 
9
10
  async function getSecret(cwd) {
10
11
  const secretPath = join(cwd, "node_modules/.cache/nuxt/ai-ready/secret");
@@ -16,10 +17,11 @@ async function getSecret(cwd) {
16
17
  const main = defineCommand({
17
18
  meta: {
18
19
  name: "nuxt-ai-ready",
19
- description: "Nuxt AI Ready CLI"
20
+ description: "Nuxt AI Ready CLI",
21
+ version: await readPackageJSON(import.meta.url).then((p) => p.version || "0.0.0")
20
22
  },
21
23
  subCommands: {
22
- status: defineCommand({
24
+ status: () => defineCommand({
23
25
  meta: {
24
26
  name: "status",
25
27
  description: "Show indexing status and IndexNow sync progress"
@@ -71,7 +73,7 @@ const main = defineCommand({
71
73
  }
72
74
  }
73
75
  }),
74
- poll: defineCommand({
76
+ poll: () => defineCommand({
75
77
  meta: {
76
78
  name: "poll",
77
79
  description: "Trigger page indexing"
@@ -131,7 +133,7 @@ const main = defineCommand({
131
133
  }
132
134
  }
133
135
  }),
134
- restore: defineCommand({
136
+ restore: () => defineCommand({
135
137
  meta: {
136
138
  name: "restore",
137
139
  description: "Restore database from prerendered dump"
@@ -145,8 +147,9 @@ const main = defineCommand({
145
147
  },
146
148
  clear: {
147
149
  type: "boolean",
148
- description: "Clear existing pages first (default: true)",
149
- default: true
150
+ description: "Clear existing pages first",
151
+ default: true,
152
+ negativeDescription: "Restore without clearing existing pages"
150
153
  },
151
154
  cwd: {
152
155
  type: "string",
@@ -179,7 +182,7 @@ const main = defineCommand({
179
182
  }
180
183
  }
181
184
  }),
182
- prune: defineCommand({
185
+ prune: () => defineCommand({
183
186
  meta: {
184
187
  name: "prune",
185
188
  description: "Remove stale routes from database"
@@ -243,7 +246,7 @@ const main = defineCommand({
243
246
  }
244
247
  }
245
248
  }),
246
- indexnow: defineCommand({
249
+ indexnow: () => defineCommand({
247
250
  meta: {
248
251
  name: "indexnow",
249
252
  description: "Trigger IndexNow sync"
package/dist/module.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { ModuleOptions, LlmsTxtConfig } from '../dist/runtime/types.js';
2
+ import { LlmsTxtConfig, ModuleOptions } from '../dist/runtime/types.js';
3
3
  export { ModuleOptions } from '../dist/runtime/types.js';
4
4
 
5
5
  interface ParsedMarkdownResult {
@@ -27,6 +27,12 @@ interface ModuleHooks {
27
27
  notes: string[];
28
28
  }) => void | Promise<void>;
29
29
  }
30
+ declare module '@nuxt/schema' {
31
+ interface NuxtHooks {
32
+ 'ai-ready:page:markdown': ModuleHooks['ai-ready:page:markdown'];
33
+ 'ai-ready:llms-txt': ModuleHooks['ai-ready:llms-txt'];
34
+ }
35
+ }
30
36
  interface ModulePublicRuntimeConfig {
31
37
  debug: boolean;
32
38
  debugCron: boolean;
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "0.11.0",
7
+ "version": "0.12.1",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
2
  import { mkdir, writeFile, appendFile, stat, access } from 'node:fs/promises';
3
3
  import { join, dirname } from 'node:path';
4
- import { useLogger, useNuxt, addTemplate, addTypeTemplate, defineNuxtModule, createResolver, hasNuxtModule, addServerHandler, addPlugin, addServerPlugin } from '@nuxt/kit';
4
+ import { useLogger, useNuxt, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, hasNuxtModule, addServerHandler, addPlugin, addServerPlugin } from '@nuxt/kit';
5
5
  import defu from 'defu';
6
6
  import { useSiteConfig, installNuxtSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
7
7
  import { readPackageJSON } from 'pkg-types';
@@ -11,7 +11,7 @@ import { isTest, isCI } from 'std-env';
11
11
  import { parseSitemapXml } from '@nuxtjs/sitemap/utils';
12
12
  import { colorize } from 'consola/utils';
13
13
  import { withBase } from 'ufo';
14
- import { createAdapter, initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../dist/runtime/server/db/shared.js';
14
+ import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../dist/runtime/server/db/shared.js';
15
15
  import { comparePageHashes, submitToIndexNowShared } from '../dist/runtime/server/utils/indexnow-shared.js';
16
16
  import { buildLlmsFullTxtHeader, formatPageForLlmsFullTxt } from '../dist/runtime/server/utils/llms-full.js';
17
17
  import { join as join$1, isAbsolute } from 'pathe';
@@ -59,12 +59,12 @@ function hookNuxtSeoProLicense() {
59
59
  }
60
60
  }).catch((err) => {
61
61
  if (err?.response?.status === 401) {
62
- spinner.stop("\u274C Invalid API key");
62
+ spinner.error("Invalid API key");
63
63
  p.note("Your API key is invalid.\n\nhttps://nuxtseo.com/pro/dashboard", "License Issue");
64
64
  throw new Error("Invalid Nuxt SEO Pro API key.");
65
65
  }
66
66
  if (err?.response?.status === 403) {
67
- spinner.stop("\u274C No active subscription");
67
+ spinner.error("No active subscription");
68
68
  p.note("Your subscription has expired or is inactive.\n\nhttps://nuxtseo.com/pro/dashboard", "License Issue");
69
69
  throw new Error("No active Nuxt SEO Pro subscription.");
70
70
  }
@@ -72,7 +72,7 @@ function hookNuxtSeoProLicense() {
72
72
  return null;
73
73
  });
74
74
  if (!res) {
75
- spinner.stop("\u26A0\uFE0F License verification skipped (network issue)");
75
+ spinner.cancel("License verification skipped (network issue)");
76
76
  return;
77
77
  }
78
78
  spinner.stop("License verified \u2713");
@@ -81,6 +81,9 @@ function hookNuxtSeoProLicense() {
81
81
  }
82
82
 
83
83
  const BUILD_FETCH_TIMEOUT = 15e3;
84
+ const RE_HTML_MD_EXT = /\.(html|md)$/;
85
+ const RE_INDEX_SUFFIX = /\/index$/;
86
+ const RE_MD_EXT = /\.md$/;
84
87
  async function fetchPreviousMeta(siteUrl, indexNow) {
85
88
  const metaUrl = `${siteUrl}/__ai-ready/pages.meta.json`;
86
89
  logger.info(`Fetching previous build meta from ${metaUrl}`);
@@ -140,13 +143,21 @@ async function initCrawler(state) {
140
143
  if (state.dbPath) {
141
144
  logger.debug(`Creating directory for SQLite: ${dirname(state.dbPath)}`);
142
145
  await mkdir(dirname(state.dbPath), { recursive: true });
143
- const nodeVersion = Number.parseInt(process.versions.node?.split(".")[0] || "0");
144
- const connectorPath = nodeVersion >= 22 ? "db0/connectors/node-sqlite" : "db0/connectors/better-sqlite3";
145
- const { default: connectorFn } = await import(connectorPath);
146
- const connector = connectorFn({ path: state.dbPath });
147
- state.db = createAdapter(connector);
148
- await initSchema(state.db);
149
- logger.debug(`Crawler initialized with SQLite at ${state.dbPath} using ${connectorPath}`);
146
+ const { default: Database } = await import('better-sqlite3');
147
+ const sqlite = new Database(state.dbPath);
148
+ const db = {
149
+ all: async (sql, params = []) => sqlite.prepare(sql).all(...params),
150
+ first: async (sql, params = []) => sqlite.prepare(sql).get(...params),
151
+ exec: async (sql, params = []) => {
152
+ sqlite.prepare(sql).run(...params);
153
+ },
154
+ close: async () => {
155
+ sqlite.close();
156
+ }
157
+ };
158
+ state.db = db;
159
+ await initSchema(db);
160
+ logger.debug(`Crawler initialized with SQLite at ${state.dbPath}`);
150
161
  }
151
162
  if (state.llmsFullTxtPath) {
152
163
  logger.debug(`Creating directory for llms-full.txt: ${dirname(state.llmsFullTxtPath)}`);
@@ -200,7 +211,7 @@ async function processSitemapEntry(state, nuxt, nitro, entry) {
200
211
  if (state.prerenderedRoutes.has(route)) {
201
212
  return { crawled: false, skipped: true };
202
213
  }
203
- const mdRoute = route === "/" ? "/index.md" : `${route}.md`;
214
+ const mdRoute = route.endsWith("/") ? `${route}index.md` : `${route}.md`;
204
215
  const mdUrl = withBase(mdRoute, nitro.options.baseURL);
205
216
  logger.debug(`Fetching markdown for ${route} \u2192 ${mdUrl}`);
206
217
  const res = await globalThis.$fetch(mdUrl, {
@@ -305,14 +316,14 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, indexNo
305
316
  let initPromise = null;
306
317
  nitro.hooks.hook("prerender:generate", async (route) => {
307
318
  if (route.error) {
308
- const pageRoute2 = route.route.replace(/\.(html|md)$/, "").replace(/\/index$/, "") || "/";
319
+ const pageRoute2 = route.route.replace(RE_HTML_MD_EXT, "").replace(RE_INDEX_SUFFIX, "") || "/";
309
320
  state.errorRoutes.add(pageRoute2);
310
321
  logger.debug(`Detected error page: ${pageRoute2}`);
311
322
  return;
312
323
  }
313
324
  if (!route.fileName?.endsWith(".md"))
314
325
  return;
315
- let pageRoute = route.route.replace(/\.md$/, "");
326
+ let pageRoute = route.route.replace(RE_MD_EXT, "");
316
327
  if (pageRoute === "/index")
317
328
  pageRoute = "/";
318
329
  const pageStartTime = Date.now();
@@ -430,7 +441,7 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, indexNo
430
441
  await writeLlmsFiles();
431
442
  state.prerenderedRoutes.clear();
432
443
  if (state.db)
433
- await state.db.close();
444
+ await state.db.close?.();
434
445
  });
435
446
  } else if (usePrerenderHook) {
436
447
  nitro.hooks.hook("prerender:done", async () => {
@@ -444,28 +455,17 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, indexNo
444
455
  await writeLlmsFiles();
445
456
  state.prerenderedRoutes.clear();
446
457
  if (state.db)
447
- await state.db.close();
458
+ await state.db.close?.();
448
459
  });
449
460
  }
450
461
  });
451
462
  }
452
463
 
453
464
  function registerTypeTemplates(_ctx) {
454
- addTemplate({
455
- filename: "types/ai-ready-adapter.d.ts",
456
- getContents: () => `declare module '#ai-ready/adapter' {
457
- import type { Connector } from 'db0'
458
- const connector: (config: unknown) => Connector
459
- export default connector
460
- }
461
- `
462
- });
463
465
  addTypeTemplate({
464
466
  filename: "types/nuxt-ai-ready-augments.d.ts",
465
467
  getContents: () => `// Generated by nuxt-ai-ready
466
- /// <reference path="./ai-ready-adapter.d.ts" />
467
468
  /// <reference path="./ai-ready-virtual.d.ts" />
468
- /// <reference path="./ai-ready-db-provider.d.ts" />
469
469
  import type { MarkdownContext, PageIndexedContext } from 'nuxt-ai-ready'
470
470
  import type { HTMLToMarkdownOptions } from 'mdream'
471
471
 
@@ -505,42 +505,26 @@ declare module '#ai-ready-virtual/logger.mjs' {
505
505
  import type { ConsolaInstance } from 'consola'
506
506
  export const logger: ConsolaInstance
507
507
  }
508
- `
509
- });
510
- addTemplate({
511
- filename: "types/ai-ready-db-provider.d.ts",
512
- getContents: () => `declare module '#ai-ready/db-provider' {
508
+
509
+ declare module '#ai-ready-virtual/db-provider.mjs' {
513
510
  import type { H3Event } from 'h3'
514
- import type { Connector } from 'db0'
515
- export function createConnector(event?: H3Event): Promise<Connector>
511
+ import type { DrizzleDatabase } from '#ai-ready/server/db/drizzle/client'
512
+ export function createClient(event?: H3Event): Promise<DrizzleDatabase>
513
+ }
514
+
515
+ declare module '#ai-ready-virtual/db-schema.mjs' {
516
+ export * from '#ai-ready/server/db/schema/sqlite'
516
517
  }
517
518
  `
518
519
  });
519
520
  }
520
521
 
521
- async function resolveDatabaseAdapter(type) {
522
- const connectors = {
523
- d1: "db0/connectors/cloudflare-d1",
524
- libsql: "db0/connectors/libsql/node"
525
- };
526
- if (type !== "sqlite" && connectors[type]) {
527
- return connectors[type];
528
- }
529
- if (process.versions.bun) {
530
- return "db0/connectors/bun-sqlite";
531
- }
532
- const nodeVersion = Number.parseInt(process.versions.node?.split(".")[0] || "0");
533
- if (nodeVersion >= 22) {
534
- return "db0/connectors/node-sqlite";
535
- }
536
- return "db0/connectors/better-sqlite3";
537
- }
538
522
  function refineDatabaseConfig(config, rootDir) {
539
523
  const type = config.type || "sqlite";
540
- if (type === "sqlite") {
524
+ if (type === "sqlite" || type === "bun") {
541
525
  const filename = config.filename || ".data/ai-ready/pages.db";
542
526
  return {
543
- type: "sqlite",
527
+ type,
544
528
  filename: isAbsolute(filename) ? filename : join$1(rootDir, filename)
545
529
  };
546
530
  }
@@ -550,6 +534,13 @@ function refineDatabaseConfig(config, rootDir) {
550
534
  bindingName: config.bindingName || "DB"
551
535
  };
552
536
  }
537
+ if (type === "neon") {
538
+ return {
539
+ type: "neon",
540
+ url: config.url
541
+ // Will fallback to POSTGRES_URL at runtime
542
+ };
543
+ }
553
544
  return {
554
545
  type: "libsql",
555
546
  url: config.url,
@@ -610,24 +601,29 @@ const module$1 = defineNuxtModule({
610
601
  nuxt.options.alias["#ai-ready"] = resolve("./runtime");
611
602
  const preset = String(nuxt.options.nitro.preset || "");
612
603
  const isCloudflare = preset.startsWith("cloudflare");
613
- const dbType = config.database?.type || (isCloudflare ? "d1" : "sqlite");
614
- if (isCloudflare && !config.database?.type) {
615
- logger.debug(`Auto-detected Cloudflare preset "${preset}", using D1 database`);
616
- }
617
- const adapterPath = await resolveDatabaseAdapter(dbType);
618
- nuxt.options.alias["#ai-ready/adapter"] = adapterPath;
619
- nuxt.options.nitro.alias["#ai-ready/adapter"] = adapterPath;
620
- let providerPath = resolve("./runtime/server/db/provider/sqlite-node");
621
- if (dbType === "d1") {
622
- providerPath = resolve("./runtime/server/db/provider/d1");
623
- } else if (dbType === "libsql") {
624
- providerPath = resolve("./runtime/server/db/provider/libsql");
625
- } else if (process.versions.bun) {
626
- providerPath = resolve("./runtime/server/db/provider/sqlite-bun");
604
+ const isVercel = preset === "vercel" || preset === "vercel-edge";
605
+ const isVercelEdge = preset === "vercel-edge";
606
+ const isBun = preset === "bun";
607
+ const hasPostgresUrl = !!(process.env.POSTGRES_URL || process.env.DATABASE_URL || config.database?.url);
608
+ let dbType = config.database?.type;
609
+ if (!dbType) {
610
+ if (isCloudflare) {
611
+ dbType = "d1";
612
+ logger.debug(`Auto-detected Cloudflare preset "${preset}", using D1 database`);
613
+ } else if (isVercel && hasPostgresUrl) {
614
+ dbType = "neon";
615
+ logger.debug(`Auto-detected Vercel preset with POSTGRES_URL, using Neon serverless driver`);
616
+ } else if (isVercelEdge) {
617
+ logger.warn(`Vercel Edge has no filesystem. Set POSTGRES_URL (Vercel Postgres) or configure database.type: 'libsql' for full functionality.`);
618
+ dbType = "neon";
619
+ } else if (isBun) {
620
+ dbType = "bun";
621
+ logger.debug(`Auto-detected Bun preset, using bun:sqlite driver`);
622
+ } else {
623
+ dbType = "sqlite";
624
+ }
627
625
  }
628
- nuxt.options.alias["#ai-ready/db-provider"] = providerPath;
629
- nuxt.options.nitro.alias["#ai-ready/db-provider"] = providerPath;
630
- if (!nuxt.options.mcp?.name) {
626
+ if (nuxt.options.mcp !== false && !nuxt.options.mcp?.name) {
631
627
  nuxt.options.mcp = nuxt.options.mcp || {};
632
628
  nuxt.options.mcp.name = useSiteConfig().name;
633
629
  }
@@ -636,9 +632,11 @@ const module$1 = defineNuxtModule({
636
632
  resolve("./runtime/server/utils")
637
633
  );
638
634
  if (typeof config.contentSignal === "object") {
639
- nuxt.options.robots = nuxt.options.robots || {};
640
- nuxt.options.robots.groups = nuxt.options.robots.groups || [];
641
- nuxt.options.robots.groups.push({
635
+ const robotsOpts = nuxt.options.robots !== false ? nuxt.options.robots : {};
636
+ nuxt.options.robots = robotsOpts;
637
+ const groups = robotsOpts.groups || [];
638
+ robotsOpts.groups = groups;
639
+ groups.push({
642
640
  userAgent: "*",
643
641
  contentUsage: [`train-ai=${config.contentSignal.aiTrain ? "y" : "n"}`],
644
642
  contentSignal: [`ai-train=${config.contentSignal.aiTrain ? "yes" : "no"}`, `search=${config.contentSignal.search ? "yes" : "no"}`, `ai-input=${config.contentSignal.aiInput ? "yes" : "no"}`]
@@ -669,7 +667,7 @@ const module$1 = defineNuxtModule({
669
667
  });
670
668
  const mcpLink = {
671
669
  title: "MCP",
672
- href: withSiteUrl(nuxt.options.mcp?.route || "/mcp"),
670
+ href: withSiteUrl(nuxt.options.mcp !== false && nuxt.options.mcp?.route || "/mcp"),
673
671
  description: "Model Context Protocol server endpoint for AI agent integration."
674
672
  };
675
673
  if (defaultLlmsTxtSections[0]) {
@@ -737,11 +735,11 @@ const module$1 = defineNuxtModule({
737
735
  if (config.cron && !nuxt.options.dev) {
738
736
  const cronSchedule = "*/5 * * * *";
739
737
  const preset2 = String(nitroConfig.preset || "");
740
- const isVercel = preset2 === "vercel" || preset2 === "vercel-edge";
738
+ const isVercel2 = preset2 === "vercel" || preset2 === "vercel-edge";
741
739
  const isCloudflarePages = preset2 === "cloudflare-pages" || preset2 === "cloudflare_pages";
742
740
  if (isCloudflarePages) {
743
741
  logger.warn("Cloudflare Pages does not support cron. Use external cron to call /__ai-ready/cron instead.");
744
- } else if (isVercel) {
742
+ } else if (isVercel2) {
745
743
  nitroConfig.vercel = nitroConfig.vercel || {};
746
744
  nitroConfig.vercel.config = nitroConfig.vercel.config || {};
747
745
  nitroConfig.vercel.config.crons = nitroConfig.vercel.config.crons || [];
@@ -824,6 +822,17 @@ export const logger = createConsola({
824
822
  level: ${config.debug ? 4 : 3},
825
823
  })
826
824
  `;
825
+ const providerMap = {
826
+ sqlite: "#ai-ready/server/db/drizzle/providers/sqlite",
827
+ bun: "#ai-ready/server/db/drizzle/providers/bun",
828
+ d1: "#ai-ready/server/db/drizzle/providers/d1",
829
+ libsql: "#ai-ready/server/db/drizzle/providers/libsql",
830
+ neon: "#ai-ready/server/db/drizzle/providers/neon"
831
+ };
832
+ const providerPath = providerMap[dbType] || providerMap.sqlite;
833
+ nitroConfig.virtual["#ai-ready-virtual/db-provider.mjs"] = `export { createClient } from '${providerPath}'`;
834
+ const schemaPath = dbType === "neon" ? "#ai-ready/server/db/schema/postgres" : "#ai-ready/server/db/schema/sqlite";
835
+ nitroConfig.virtual["#ai-ready-virtual/db-schema.mjs"] = `export * from '${schemaPath}'`;
827
836
  });
828
837
  const database = refineDatabaseConfig(config.database || {}, nuxt.options.rootDir);
829
838
  nuxt.options.runtimeConfig["nuxt-ai-ready"] = {
@@ -1,6 +1,6 @@
1
- export { useDatabase } from './server/db/index.js';
2
- export type { DatabaseAdapter } from './server/db/index.js';
3
- export { countPages, getStaleRoutes, isPageFresh, pruneStaleRoutes, queryPages, searchPages, seedRoutes, streamPages, upsertPage, } from './server/db/queries.js';
1
+ export { closeDrizzle, useDrizzle, useRawDb } from './server/db/index.js';
2
+ export type { DatabaseDialect, DrizzleDatabase, RawExecutor } from './server/db/index.js';
3
+ export { countPages, getPageLastmods, getStaleRoutes, isPageFresh, pruneStaleRoutes, queryPages, searchPages, seedRoutes, streamPages, upsertPage, } from './server/db/queries.js';
4
4
  export type { CountPagesOptions, PageData, PageEntry, PageRow, QueryPagesOptions, SearchPagesOptions, SearchResult, StreamPagesOptions, UpsertPageInput, } from './server/db/queries.js';
5
5
  export { indexPage, indexPageByRoute } from './server/utils/indexPage.js';
6
6
  export type { IndexPageOptions, IndexPageResult } from './server/utils/indexPage.js';
@@ -1,6 +1,7 @@
1
- export { useDatabase } from "./server/db/index.js";
1
+ export { closeDrizzle, useDrizzle, useRawDb } from "./server/db/index.js";
2
2
  export {
3
3
  countPages,
4
+ getPageLastmods,
4
5
  getStaleRoutes,
5
6
  isPageFresh,
6
7
  pruneStaleRoutes,
@@ -0,0 +1,20 @@
1
+ import type * as schema from '#ai-ready-virtual/db-schema.mjs';
2
+ import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
3
+ import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
4
+ import type { DrizzleD1Database } from 'drizzle-orm/d1';
5
+ import type { LibSQLDatabase } from 'drizzle-orm/libsql';
6
+ import type { NeonHttpDatabase } from 'drizzle-orm/neon-http';
7
+ import type { H3Event } from 'h3';
8
+ export type DatabaseDialect = 'sqlite' | 'postgres';
9
+ type SQLiteDB = BetterSQLite3Database<typeof schema> | BunSQLiteDatabase<typeof schema> | LibSQLDatabase<typeof schema> | DrizzleD1Database<typeof schema>;
10
+ type PostgresDB = NeonHttpDatabase<typeof schema>;
11
+ export interface DrizzleDatabase {
12
+ dialect: DatabaseDialect;
13
+ db: SQLiteDB | PostgresDB;
14
+ }
15
+ /**
16
+ * Get Drizzle database instance
17
+ */
18
+ export declare function useDrizzle(event?: H3Event): Promise<DrizzleDatabase>;
19
+ export declare function closeDrizzle(event?: H3Event): Promise<void>;
20
+ export {};
@@ -0,0 +1,29 @@
1
+ import { closeDriver } from "./raw.js";
2
+ const DB_CONTEXT_KEY = "_aiReadyDrizzle";
3
+ let fallbackClient;
4
+ export async function useDrizzle(event) {
5
+ if (event?.context?.[DB_CONTEXT_KEY]) {
6
+ return event.context[DB_CONTEXT_KEY];
7
+ }
8
+ if (!event && fallbackClient) {
9
+ return fallbackClient;
10
+ }
11
+ const { createClient } = await import("#ai-ready-virtual/db-provider.mjs");
12
+ const client = await createClient(event);
13
+ if (event?.context) {
14
+ event.context[DB_CONTEXT_KEY] = client;
15
+ } else {
16
+ fallbackClient = client;
17
+ }
18
+ return client;
19
+ }
20
+ export async function closeDrizzle(event) {
21
+ if (event?.context?.[DB_CONTEXT_KEY]) {
22
+ const client = event.context[DB_CONTEXT_KEY];
23
+ closeDriver(client.db);
24
+ delete event.context[DB_CONTEXT_KEY];
25
+ } else if (!event && fallbackClient) {
26
+ closeDriver(fallbackClient.db);
27
+ fallbackClient = void 0;
28
+ }
29
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Drizzle ORM database layer for nuxt-ai-ready
3
+ */
4
+ export * from '#ai-ready-virtual/db-schema.mjs';
5
+ export { closeDrizzle, useDrizzle } from './client.js';
6
+ export type { DatabaseDialect, DrizzleDatabase } from './client.js';
7
+ export { completeCronRun, countPages, countPagesNeedingIndexNowSync, deleteInfoValue, deletePage, getAllPages, getContentHashes, getInfoValue, getNextSitemapToCrawl, getPageByRoute, getPageLastmods, getPagesNeedingIndexNowSync, getPendingPages, getRecentCronRuns, getSitemapStatus, initSchema, markIndexNowSynced, markPageIndexed, markRoutesPending, markSitemapCrawled, markSitemapError, resetSitemapErrors, searchPages, seedRoutes, setInfoValue, startCronRun, syncSitemaps, upsertPage, } from './queries.js';
8
+ export type { CronRunOutput, PageInput, PageMetaOutput, PageOutput, SitemapOutput, } from './queries.js';
9
+ export { useRawDb } from './raw.js';
10
+ export type { RawExecutor } from './raw.js';
@@ -0,0 +1,33 @@
1
+ export * from "#ai-ready-virtual/db-schema.mjs";
2
+ export { closeDrizzle, useDrizzle } from "./client.js";
3
+ export {
4
+ completeCronRun,
5
+ countPages,
6
+ countPagesNeedingIndexNowSync,
7
+ deleteInfoValue,
8
+ deletePage,
9
+ getAllPages,
10
+ getContentHashes,
11
+ getInfoValue,
12
+ getNextSitemapToCrawl,
13
+ getPageByRoute,
14
+ getPageLastmods,
15
+ getPagesNeedingIndexNowSync,
16
+ getPendingPages,
17
+ getRecentCronRuns,
18
+ getSitemapStatus,
19
+ initSchema,
20
+ markIndexNowSynced,
21
+ markPageIndexed,
22
+ markRoutesPending,
23
+ markSitemapCrawled,
24
+ markSitemapError,
25
+ resetSitemapErrors,
26
+ searchPages,
27
+ seedRoutes,
28
+ setInfoValue,
29
+ startCronRun,
30
+ syncSitemaps,
31
+ upsertPage
32
+ } from "./queries.js";
33
+ export { useRawDb } from "./raw.js";
@@ -0,0 +1,7 @@
1
+ import type { H3Event } from 'h3';
2
+ export declare function createClient(event?: H3Event): Promise<{
3
+ dialect: "sqlite";
4
+ db: import("drizzle-orm/bun-sqlite").BunSQLiteDatabase<any> & {
5
+ $client: any;
6
+ };
7
+ }>;
@@ -0,0 +1,25 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import * as schema from "#ai-ready-virtual/db-schema.mjs";
3
+ import { Database } from "bun:sqlite";
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+ import { useRuntimeConfig } from "nitropack/runtime";
6
+ import { dirname } from "pathe";
7
+ import { logger } from "../../../logger.js";
8
+ import { registerDriver } from "../raw.js";
9
+ export async function createClient(event) {
10
+ const config = useRuntimeConfig(event)["nuxt-ai-ready"];
11
+ const dbPath = config.database.filename || ".data/ai-ready/pages.db";
12
+ logger.debug(`[drizzle] Opening Bun SQLite database: ${dbPath}`);
13
+ await mkdir(dirname(dbPath), { recursive: true }).catch((err) => {
14
+ if (err.code === "EROFS" || err.code === "EACCES") {
15
+ throw new Error(
16
+ `[ai-ready] Cannot create database directory (read-only filesystem). On Vercel, set database.type: 'neon'. On Cloudflare, use 'd1'. Or configure database.url for LibSQL/Turso.`
17
+ );
18
+ }
19
+ throw err;
20
+ });
21
+ const sqlite = new Database(dbPath);
22
+ const db = drizzle(sqlite, { schema });
23
+ registerDriver(db, "better-sqlite3", sqlite);
24
+ return { dialect: "sqlite", db };
25
+ }
@@ -0,0 +1,7 @@
1
+ import type { H3Event } from 'h3';
2
+ export declare function createClient(event?: H3Event): Promise<{
3
+ dialect: "sqlite";
4
+ db: import("drizzle-orm/d1").DrizzleD1Database<any> & {
5
+ $client: any;
6
+ };
7
+ }>;
@@ -0,0 +1,19 @@
1
+ import * as schema from "#ai-ready-virtual/db-schema.mjs";
2
+ import { drizzle } from "drizzle-orm/d1";
3
+ import { useRuntimeConfig } from "nitropack/runtime";
4
+ import { logger } from "../../../logger.js";
5
+ import { registerDriver } from "../raw.js";
6
+ export async function createClient(event) {
7
+ const config = useRuntimeConfig(event)["nuxt-ai-ready"];
8
+ const bindingName = config.database.bindingName || "DB";
9
+ logger.debug(`[drizzle] Using D1 binding: ${bindingName}`);
10
+ const cfEnv = event?.context?.cloudflare?.env;
11
+ const globalEnv = globalThis.__env__;
12
+ const d1 = cfEnv?.[bindingName] || globalEnv?.[bindingName];
13
+ if (!d1) {
14
+ throw new Error(`[ai-ready] D1 binding "${bindingName}" not found`);
15
+ }
16
+ const db = drizzle(d1, { schema });
17
+ registerDriver(db, "d1", d1);
18
+ return { dialect: "sqlite", db };
19
+ }
@@ -0,0 +1,7 @@
1
+ import type { H3Event } from 'h3';
2
+ export declare function createClient(event?: H3Event): Promise<{
3
+ dialect: "sqlite";
4
+ db: import("drizzle-orm/libsql").LibSQLDatabase<any> & {
5
+ $client: import("@libsql/client").Client;
6
+ };
7
+ }>;
@@ -0,0 +1,15 @@
1
+ import * as schema from "#ai-ready-virtual/db-schema.mjs";
2
+ import { createClient as createLibSQLClient } from "@libsql/client";
3
+ import { drizzle } from "drizzle-orm/libsql";
4
+ import { useRuntimeConfig } from "nitropack/runtime";
5
+ import { logger } from "../../../logger.js";
6
+ import { registerDriver } from "../raw.js";
7
+ export async function createClient(event) {
8
+ const config = useRuntimeConfig(event)["nuxt-ai-ready"];
9
+ const dbUrl = config.database.url || `file:${config.database.filename || ".data/ai-ready/pages.db"}`;
10
+ logger.debug(`[drizzle] Connecting to LibSQL: ${dbUrl}`);
11
+ const client = createLibSQLClient({ url: dbUrl, authToken: config.database.authToken });
12
+ const db = drizzle(client, { schema });
13
+ registerDriver(db, "libsql", client);
14
+ return { dialect: "sqlite", db };
15
+ }