nuxt-ai-ready 1.7.9 → 1.7.11

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.
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { realpath, readFile } from 'node:fs/promises';
3
3
  import { resolve, isAbsolute, relative, sep } from 'node:path';
4
4
  import { parseDocument } from 'yaml';
5
- import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.DF6WiyQh.mjs';
5
+ import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.jeGqwuW1.mjs';
6
6
  import 'node:module';
7
7
  import '@nuxt/kit';
8
8
  import 'defu';
@@ -5,7 +5,7 @@ import { colorize } from 'consola/utils';
5
5
  import { resolveLocaleFromRoute } from 'nuxtseo-shared/i18n-runtime';
6
6
  import { collectSitemap } from 'sitemapd/parse';
7
7
  import { withLeadingSlash, withBase, joinURL } from 'ufo';
8
- import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.DF6WiyQh.mjs';
8
+ import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.jeGqwuW1.mjs';
9
9
  import { normalizePagePath, toMarkdownPath } from '../../dist/runtime/markdown-path.js';
10
10
  import { toLogicalRoute, toDeployedRoute } from '../../dist/runtime/route-path.js';
11
11
  import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../../dist/runtime/server/db/shared.js';
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "1.7.9",
7
+ "version": "1.7.11",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -7,7 +7,7 @@ import 'defu';
7
7
  import 'nuxt-site-config/kit';
8
8
  import 'nuxtseo-shared/kit';
9
9
  import 'pkg-types';
10
- export { m as default } from './shared/nuxt-ai-ready.DF6WiyQh.mjs';
10
+ export { m as default } from './shared/nuxt-ai-ready.jeGqwuW1.mjs';
11
11
  import '../dist/runtime/server/utils/discovery-response.js';
12
12
  import 'node:url';
13
13
  import 'pathe';
@@ -11,8 +11,13 @@ function resolveFtsTokenizer(event) {
11
11
  }
12
12
  const RE_LEADING_SLASH = /^\//;
13
13
  const RE_SLASH = /\//g;
14
+ function normalizeRoute(route) {
15
+ if (!route || route === "/")
16
+ return "/";
17
+ return route.startsWith("/") ? route : `/${route}`;
18
+ }
14
19
  function normalizeRouteKey(route) {
15
- return route.replace(RE_LEADING_SLASH, "").replace(RE_SLASH, ":") || "index";
20
+ return normalizeRoute(route).replace(RE_LEADING_SLASH, "").replace(RE_SLASH, ":") || "index";
16
21
  }
17
22
  function rowToPage(row) {
18
23
  return {
@@ -42,7 +47,7 @@ export async function upsertPage(event, page) {
42
47
  const client = await useDrizzle(event);
43
48
  const now = Date.now();
44
49
  const values = {
45
- route: page.route,
50
+ route: normalizeRoute(page.route),
46
51
  routeKey: normalizeRouteKey(page.route),
47
52
  title: page.title,
48
53
  description: page.description,
@@ -700,8 +705,10 @@ export async function seedRoutes(event, routes) {
700
705
  if (routes.length === 0)
701
706
  return 0;
702
707
  const byRoute = /* @__PURE__ */ new Map();
703
- for (const route of routes)
708
+ for (const raw of routes) {
709
+ const route = normalizeRoute(raw);
704
710
  byRoute.set(route, normalizeRouteKey(route));
711
+ }
705
712
  const ROWS_PER_INSERT = 20;
706
713
  const db = await useRawDb(event);
707
714
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4,7 +4,7 @@ import { resolveLocaleFromRoute } from "../utils/i18n.js";
4
4
  import { parseSitemapCrawlState, serializeSitemapCrawlState } from "../utils/sitemap-crawl-state.js";
5
5
  import { initSchema } from "./drizzle/queries.js";
6
6
  import { useRawDb } from "./drizzle/raw.js";
7
- import { normalizeRouteKey } from "./shared.js";
7
+ import { normalizeRoute, normalizeRouteKey } from "./shared.js";
8
8
  function deriveLocale(event, route, explicit) {
9
9
  if (explicit !== void 0)
10
10
  return explicit;
@@ -261,12 +261,13 @@ export async function upsertPage(event, page) {
261
261
  const db = await getDb(event);
262
262
  if (!db)
263
263
  return;
264
- const routeKey = normalizeRouteKey(page.route);
264
+ const route = normalizeRoute(page.route);
265
+ const routeKey = normalizeRouteKey(route);
265
266
  const keywordsJson = JSON.stringify(page.keywords);
266
267
  const indexedAt = Date.now();
267
268
  const source = page.source || "runtime";
268
269
  const lastSeenAt = source === "runtime" ? indexedAt : null;
269
- const locale = deriveLocale(event, page.route, page.locale);
270
+ const locale = deriveLocale(event, route, page.locale);
270
271
  await db.exec(`
271
272
  INSERT INTO ai_ready_pages (route, route_key, title, description, markdown, headings, keywords, content_hash, updated_at, indexed_at, is_error, indexed, source, last_seen_at, locale)
272
273
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)
@@ -284,7 +285,7 @@ export async function upsertPage(event, page) {
284
285
  source = excluded.source,
285
286
  last_seen_at = excluded.last_seen_at,
286
287
  locale = excluded.locale
287
- `, [page.route, routeKey, page.title, page.description, page.markdown, page.headings, keywordsJson, page.contentHash || null, page.updatedAt, indexedAt, page.isError ? 1 : 0, source, lastSeenAt, locale]);
288
+ `, [route, routeKey, page.title, page.description, page.markdown, page.headings, keywordsJson, page.contentHash || null, page.updatedAt, indexedAt, page.isError ? 1 : 0, source, lastSeenAt, locale]);
288
289
  }
289
290
  export async function isPageFresh(event, route, ttlSeconds) {
290
291
  if (ttlSeconds <= 0)
@@ -323,7 +324,7 @@ export async function seedRoutes(event, routes) {
323
324
  const nowMs = Date.now();
324
325
  const byRoute = /* @__PURE__ */ new Map();
325
326
  for (const entry of routes) {
326
- const route = typeof entry === "string" ? entry : entry.route;
327
+ const route = normalizeRoute(typeof entry === "string" ? entry : entry.route);
327
328
  const explicitLocale = typeof entry === "string" ? void 0 : entry.locale;
328
329
  byRoute.set(route, {
329
330
  route,
@@ -27,9 +27,29 @@ export interface InitSchemaOptions {
27
27
  * a CJK locale on a later deploy).
28
28
  */
29
29
  export declare function initSchema(db: DatabaseAdapter, options?: InitSchemaOptions): Promise<void>;
30
+ /**
31
+ * Canonical storage form of a route.
32
+ *
33
+ * `ai_ready_pages` puts a UNIQUE index on `route` and another on `route_key`,
34
+ * while every upsert targets `ON CONFLICT(route)`. Two spellings of one page
35
+ * therefore have to agree on `route`, or the conflict target misses, the insert
36
+ * reaches the `route_key` index instead, and the whole statement fails with
37
+ * `UNIQUE constraint failed: ai_ready_pages.route_key`.
38
+ *
39
+ * `''` and `'/'` are the pair that occurs in practice, since both key to
40
+ * `index`: one spelling takes the row, the other retries and fails on every
41
+ * subsequent write. A bare `'about'` against `'/about'` is the same defect.
42
+ *
43
+ * Normalising here rather than widening the conflict target keeps one page to
44
+ * one row. Widening it would let both spellings persist and race for the key.
45
+ */
46
+ export declare function normalizeRoute(route: string): string;
30
47
  /**
31
48
  * Normalize route to storage key format
32
49
  * e.g., '/about/team' -> 'about:team', '/' -> 'index'
50
+ *
51
+ * Derived from the canonical route so the two can never disagree: equal keys
52
+ * imply equal routes, which is what the pair of unique indexes requires.
33
53
  */
34
54
  export declare function normalizeRouteKey(route: string): string;
35
55
  /**
@@ -52,8 +52,13 @@ async function getStoredTokenizer(db) {
52
52
  }
53
53
  const RE_LEADING_SLASH = /^\//;
54
54
  const RE_SLASH = /\//g;
55
+ export function normalizeRoute(route) {
56
+ if (!route || route === "/")
57
+ return "/";
58
+ return route.startsWith("/") ? route : `/${route}`;
59
+ }
55
60
  export function normalizeRouteKey(route) {
56
- return route.replace(RE_LEADING_SLASH, "").replace(RE_SLASH, ":") || "index";
61
+ return normalizeRoute(route).replace(RE_LEADING_SLASH, "").replace(RE_SLASH, ":") || "index";
57
62
  }
58
63
  export async function compressToBase64(data) {
59
64
  const json = JSON.stringify(data);
@@ -172,9 +177,15 @@ const IMPORT_ROWS_PER_STATEMENT = 5;
172
177
  export async function importDbDump(db, rows) {
173
178
  if (rows.length === 0)
174
179
  return;
180
+ const byRoute = /* @__PURE__ */ new Map();
181
+ for (const row of rows) {
182
+ const route = normalizeRoute(row.route);
183
+ byRoute.set(route, { ...row, route, route_key: normalizeRouteKey(route) });
184
+ }
185
+ const canonical = [...byRoute.values()];
175
186
  const stmts = [];
176
- for (let i = 0; i < rows.length; i += IMPORT_ROWS_PER_STATEMENT) {
177
- const batch = rows.slice(i, i + IMPORT_ROWS_PER_STATEMENT);
187
+ for (let i = 0; i < canonical.length; i += IMPORT_ROWS_PER_STATEMENT) {
188
+ const batch = canonical.slice(i, i + IMPORT_ROWS_PER_STATEMENT);
178
189
  const valuesSql = batch.map(() => `(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)`).join(", ");
179
190
  const params = batch.flatMap((row) => {
180
191
  const source = row.source || "prerender";
@@ -16,7 +16,8 @@ export function negotiateRepresentation(event) {
16
16
  return negotiateContent(accept, secFetchDest);
17
17
  }
18
18
  export function getMarkdownRenderInfo(event, mode = { _tag: "runtime", contentNegotiation: true }) {
19
- const originalPath = event.path;
19
+ const queryIndex = event.path.indexOf("?");
20
+ const originalPath = queryIndex === -1 ? event.path : event.path.slice(0, queryIndex);
20
21
  const isPrerender = mode._tag === "prerender";
21
22
  if (originalPath.startsWith("/api") || originalPath.startsWith("/_") || originalPath.startsWith("/@"))
22
23
  return null;
@@ -1081,7 +1081,7 @@ ${details}`);
1081
1081
  const webmcpConfig = webmcpResult._tag === "Enabled" ? webmcpResult.config : null;
1082
1082
  const indexNow = config.indexNow === true ? createHash("sha256").update(useSiteConfig().url || "nuxt-ai-ready").digest("hex").slice(0, 32) : config.indexNow || process.env.NUXT_AI_READY_INDEX_NOW_KEY;
1083
1083
  const isStatic = nuxt.options.nitro.static || nuxt.options._generate || false;
1084
- const hasPrerenderedRoutes = !!nuxt.options.nitro.prerender?.routes?.length;
1084
+ const hasPrerenderedRoutes = !!(nuxt.options.nitro.prerender?.routes?.length || nuxt.options.nitro.prerender?.crawlLinks);
1085
1085
  const prerenderEnabled = !!(isStatic || hasPrerenderedRoutes);
1086
1086
  const isSPA = nuxt.options.ssr === false;
1087
1087
  let apiCatalogRegistered = false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ai-ready",
3
3
  "type": "module",
4
- "version": "1.7.9",
4
+ "version": "1.7.11",
5
5
  "description": "Best practice AI & LLM discoverability for Nuxt sites.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",