nuxt-ai-ready 0.7.13 → 0.7.14

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.13",
7
+ "version": "0.7.14",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -4,6 +4,7 @@ export interface IndexNowResult {
4
4
  submitted: number;
5
5
  remaining: number;
6
6
  error?: string;
7
+ backoff?: boolean;
7
8
  }
8
9
  /**
9
10
  * Submit URLs to IndexNow API
@@ -18,5 +19,6 @@ export declare function submitToIndexNow(routes: string[], config: {
18
19
  /**
19
20
  * Submit pending pages to IndexNow
20
21
  * Queries DB for pages needing sync, submits, marks as synced
22
+ * Implements exponential backoff on 429 rate limit errors
21
23
  */
22
24
  export declare function syncToIndexNow(event: H3Event | undefined, limit?: number): Promise<IndexNowResult>;
@@ -1,5 +1,6 @@
1
1
  import { getSiteConfig } from "#site-config/server/composables";
2
2
  import { useRuntimeConfig } from "nitropack/runtime";
3
+ import { useDatabase } from "../db/index.js";
3
4
  import {
4
5
  countPagesNeedingIndexNowSync,
5
6
  getPagesNeedingIndexNowSync,
@@ -7,6 +8,27 @@ import {
7
8
  updateIndexNowStats
8
9
  } from "../db/queries.js";
9
10
  import { logger } from "../logger.js";
11
+ const BACKOFF_MINUTES = [5, 10, 20, 40, 60];
12
+ async function getBackoffInfo(event) {
13
+ const db = await useDatabase(event).catch(() => null);
14
+ if (!db)
15
+ return null;
16
+ const row = await db.first("SELECT value FROM _ai_ready_info WHERE id = ?", ["indexnow_backoff"]);
17
+ if (!row)
18
+ return null;
19
+ const parsed = JSON.parse(row.value);
20
+ return parsed;
21
+ }
22
+ async function setBackoffInfo(event, info) {
23
+ const db = await useDatabase(event).catch(() => null);
24
+ if (!db)
25
+ return;
26
+ if (info) {
27
+ await db.exec("INSERT OR REPLACE INTO _ai_ready_info (id, value) VALUES (?, ?)", ["indexnow_backoff", JSON.stringify(info)]);
28
+ } else {
29
+ await db.exec("DELETE FROM _ai_ready_info WHERE id = ?", ["indexnow_backoff"]);
30
+ }
31
+ }
10
32
  export async function submitToIndexNow(routes, config, siteUrl) {
11
33
  if (!siteUrl) {
12
34
  return { success: false, error: "Site URL not configured" };
@@ -44,6 +66,19 @@ export async function syncToIndexNow(event, limit = 100) {
44
66
  if (!siteConfig.url) {
45
67
  return { success: false, submitted: 0, remaining: 0, error: "Site URL not configured" };
46
68
  }
69
+ const backoff = await getBackoffInfo(event);
70
+ if (backoff && Date.now() < backoff.until) {
71
+ const waitMinutes = Math.ceil((backoff.until - Date.now()) / 6e4);
72
+ logger.debug(`[indexnow] In backoff period, ${waitMinutes}m remaining`);
73
+ const remaining2 = await countPagesNeedingIndexNowSync(event);
74
+ return {
75
+ success: false,
76
+ submitted: 0,
77
+ remaining: remaining2,
78
+ error: `Rate limited, retry in ${waitMinutes}m`,
79
+ backoff: true
80
+ };
81
+ }
47
82
  const pages = await getPagesNeedingIndexNowSync(event, limit);
48
83
  if (pages.length === 0) {
49
84
  return { success: true, submitted: 0, remaining: 0 };
@@ -51,16 +86,25 @@ export async function syncToIndexNow(event, limit = 100) {
51
86
  const routes = pages.map((p) => p.route);
52
87
  const result = await submitToIndexNow(routes, { key: config.indexNowKey }, siteConfig.url);
53
88
  if (result.success) {
89
+ await setBackoffInfo(event, null);
54
90
  await markIndexNowSynced(event, routes);
55
91
  await updateIndexNowStats(event, routes.length);
56
92
  } else {
57
93
  await updateIndexNowStats(event, 0, result.error);
94
+ if (result.error?.includes("429")) {
95
+ const attempt = backoff ? Math.min(backoff.attempt + 1, BACKOFF_MINUTES.length - 1) : 0;
96
+ const backoffMinutes = BACKOFF_MINUTES[attempt] ?? 60;
97
+ const until = Date.now() + backoffMinutes * 60 * 1e3;
98
+ await setBackoffInfo(event, { until, attempt });
99
+ logger.warn(`[indexnow] Rate limited, backing off for ${backoffMinutes}m (attempt ${attempt + 1})`);
100
+ }
58
101
  }
59
102
  const remaining = await countPagesNeedingIndexNowSync(event);
60
103
  return {
61
104
  success: result.success,
62
105
  submitted: result.success ? routes.length : 0,
63
106
  remaining,
64
- error: result.error
107
+ error: result.error,
108
+ backoff: !result.success && result.error?.includes("429")
65
109
  };
66
110
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ai-ready",
3
3
  "type": "module",
4
- "version": "0.7.13",
4
+ "version": "0.7.14",
5
5
  "description": "Best practice AI & LLM discoverability for Nuxt sites.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",