nuxt-ai-ready 2.0.1 → 2.2.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.
- package/dist/chunks/agent-skills.mjs +4 -1
- package/dist/chunks/prerender.mjs +10 -3
- package/dist/cli.mjs +67 -11
- package/dist/module.d.mts +3 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +4 -1
- package/dist/runtime/app/plugins/md-alternate.server.js +6 -2
- package/dist/runtime/llms-txt-utils.js +2 -2
- package/dist/runtime/markdown-path.d.ts +2 -0
- package/dist/runtime/markdown-path.js +4 -0
- package/dist/runtime/server/db/context.d.ts +2 -0
- package/dist/runtime/server/db/context.js +2 -0
- package/dist/runtime/server/db/drizzle/client.d.ts +6 -1
- package/dist/runtime/server/db/drizzle/client.js +66 -13
- package/dist/runtime/server/db/drizzle/index.d.ts +2 -2
- package/dist/runtime/server/db/drizzle/index.js +1 -2
- package/dist/runtime/server/db/drizzle/providers/dbPath.js +1 -1
- package/dist/runtime/server/db/drizzle/providers/postgres.d.ts +8 -0
- package/dist/runtime/server/db/drizzle/providers/postgres.js +17 -0
- package/dist/runtime/server/db/drizzle/queries.d.ts +1 -5
- package/dist/runtime/server/db/drizzle/queries.js +79 -38
- package/dist/runtime/server/db/drizzle/raw.d.ts +2 -2
- package/dist/runtime/server/db/drizzle/raw.js +32 -13
- package/dist/runtime/server/db/queries.d.ts +30 -27
- package/dist/runtime/server/db/queries.js +142 -91
- package/dist/runtime/server/db/schema/postgres.d.ts +20 -20
- package/dist/runtime/server/db/schema/postgres.js +6 -6
- package/dist/runtime/server/db/shared.d.ts +16 -0
- package/dist/runtime/server/db/shared.js +12 -0
- package/dist/runtime/server/middleware/markdown.js +10 -5
- package/dist/runtime/server/middleware/markdown.prerender.js +4 -1
- package/dist/runtime/server/plugins/db-lifecycle.js +5 -4
- package/dist/runtime/server/plugins/sitemap-seeder.js +9 -4
- package/dist/runtime/server/routes/__ai-ready/cron.get.js +10 -2
- package/dist/runtime/server/routes/__ai-ready/poll.post.js +30 -14
- package/dist/runtime/server/routes/__ai-ready/reindex.post.d.ts +2 -0
- package/dist/runtime/server/routes/__ai-ready/reindex.post.js +23 -0
- package/dist/runtime/server/routes/api-catalog.js +11 -2
- package/dist/runtime/server/routes/llms.txt.get.js +17 -12
- package/dist/runtime/server/routes/sitemap.md.get.d.ts +2 -0
- package/dist/runtime/server/routes/sitemap.md.get.js +51 -0
- package/dist/runtime/server/tasks/ai-ready-cron.js +3 -0
- package/dist/runtime/server/utils/i18n.d.ts +4 -0
- package/dist/runtime/server/utils/i18n.js +13 -0
- package/dist/runtime/server/utils/indexPage.d.ts +0 -1
- package/dist/runtime/server/utils/indexPage.js +10 -4
- package/dist/runtime/server/utils/link-header.d.ts +2 -0
- package/dist/runtime/server/utils/link-header.js +6 -0
- package/dist/runtime/server/utils/markdown-request.js +3 -1
- package/dist/runtime/server/utils/negotiation-decision.d.ts +1 -1
- package/dist/runtime/server/utils/runCron.d.ts +24 -0
- package/dist/runtime/server/utils/runCron.js +21 -4
- package/dist/runtime/server/utils/sitemap-md.d.ts +13 -0
- package/dist/runtime/server/utils/sitemap-md.js +65 -0
- package/dist/runtime/types.d.ts +21 -7
- package/dist/shared/{nuxt-ai-ready.D8qbnNrv.mjs → nuxt-ai-ready.D5URd0TD.mjs} +107 -39
- package/package.json +31 -26
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { and, count, desc, eq, gt, isNotNull, isNull,
|
|
1
|
+
import { and, count, desc, eq, gt, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
|
|
2
2
|
import { cronRuns, info, pages, sitemaps } from "#ai-ready-virtual/db-schema.mjs";
|
|
3
3
|
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
4
4
|
import { parseSitemapCrawlState, serializeSitemapCrawlState } from "../../utils/sitemap-crawl-state.js";
|
|
5
5
|
import { resolveFtsTokenizer as validateFtsTokenizer } from "../schema-sql.js";
|
|
6
|
+
import { LIKE_ESCAPE, likeSubstring, maxRowsPerInsert } from "../shared.js";
|
|
6
7
|
import { useDrizzle } from "./client.js";
|
|
7
|
-
import { useRawDb } from "./raw.js";
|
|
8
|
+
import { getRawExecutor, useRawDb } from "./raw.js";
|
|
8
9
|
function resolveFtsTokenizer(event) {
|
|
9
10
|
const cfg = useRuntimeConfig(event);
|
|
10
11
|
return validateFtsTokenizer(cfg["nuxt-ai-ready"]?.ftsTokenizer);
|
|
@@ -127,7 +128,8 @@ export async function getPageLastmods(event) {
|
|
|
127
128
|
export async function searchPages(event, query, options) {
|
|
128
129
|
const client = await useDrizzle(event);
|
|
129
130
|
const limitNum = options?.limit || 20;
|
|
130
|
-
const searchTerm =
|
|
131
|
+
const searchTerm = likeSubstring(query);
|
|
132
|
+
const matches = (col) => sql`${col} LIKE ${searchTerm} ESCAPE ${LIKE_ESCAPE}`;
|
|
131
133
|
if (client.dialect === "sqlite") {
|
|
132
134
|
try {
|
|
133
135
|
const ftsResult = await client.db.all(sql`
|
|
@@ -155,10 +157,10 @@ export async function searchPages(event, query, options) {
|
|
|
155
157
|
and(
|
|
156
158
|
eq(pages.isError, 0),
|
|
157
159
|
or(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
matches(pages.title),
|
|
161
|
+
matches(pages.description),
|
|
162
|
+
matches(pages.markdown),
|
|
163
|
+
matches(pages.headings)
|
|
162
164
|
)
|
|
163
165
|
)
|
|
164
166
|
).limit(limitNum);
|
|
@@ -172,6 +174,8 @@ export async function countPages(event, options) {
|
|
|
172
174
|
}
|
|
173
175
|
if (options?.errors !== void 0) {
|
|
174
176
|
conditions.push(eq(pages.isError, options.errors ? 1 : 0));
|
|
177
|
+
} else {
|
|
178
|
+
conditions.push(eq(pages.isError, 0));
|
|
175
179
|
}
|
|
176
180
|
let query = client.db.select({ count: count() }).from(pages);
|
|
177
181
|
if (conditions.length > 0) {
|
|
@@ -180,13 +184,12 @@ export async function countPages(event, options) {
|
|
|
180
184
|
const result = await query;
|
|
181
185
|
return Number(result[0]?.count || 0);
|
|
182
186
|
}
|
|
183
|
-
export async function deletePage(event, route) {
|
|
184
|
-
const client = await useDrizzle(event);
|
|
185
|
-
await client.db.delete(pages).where(eq(pages.route, route));
|
|
186
|
-
}
|
|
187
187
|
export async function getPendingPages(event, limit = 10) {
|
|
188
188
|
const client = await useDrizzle(event);
|
|
189
|
-
return client.db.select({ route: pages.route }).from(pages).where(
|
|
189
|
+
return client.db.select({ route: pages.route }).from(pages).where(and(
|
|
190
|
+
eq(pages.indexed, 0),
|
|
191
|
+
eq(pages.isError, 0)
|
|
192
|
+
)).orderBy(pages.route).limit(limit);
|
|
190
193
|
}
|
|
191
194
|
export async function markPageIndexed(event, route) {
|
|
192
195
|
const client = await useDrizzle(event);
|
|
@@ -202,7 +205,7 @@ export async function markRoutesPending(event, routes) {
|
|
|
202
205
|
const batch = routes.slice(i, i + D1_MAX_IN_ROUTES);
|
|
203
206
|
const placeholders = batch.map(() => "?").join(",");
|
|
204
207
|
stmts.push({
|
|
205
|
-
sql: `UPDATE ai_ready_pages SET indexed = 0 WHERE route IN (${placeholders})`,
|
|
208
|
+
sql: `UPDATE ai_ready_pages SET indexed = 0, is_error = 0 WHERE route IN (${placeholders})`,
|
|
206
209
|
params: batch
|
|
207
210
|
});
|
|
208
211
|
}
|
|
@@ -229,16 +232,23 @@ export async function deleteInfoValue(event, key) {
|
|
|
229
232
|
const client = await useDrizzle(event);
|
|
230
233
|
await client.db.delete(info).where(eq(info.id, key));
|
|
231
234
|
}
|
|
232
|
-
const
|
|
235
|
+
const SQLITE_SCHEMA_VERSION = "v2.3.0-drizzle";
|
|
236
|
+
const LEGACY_POSTGRES_INTEGER_SCHEMA_VERSION = "v2.3.0-drizzle";
|
|
237
|
+
const POSTGRES_SCHEMA_VERSION = "v2.3.0-drizzle-postgres-bigint";
|
|
233
238
|
export async function initSchema(event) {
|
|
234
239
|
const client = await useDrizzle(event);
|
|
235
240
|
const tokenizer = resolveFtsTokenizer(event);
|
|
241
|
+
const schemaVersion = client.dialect === "postgres" ? POSTGRES_SCHEMA_VERSION : SQLITE_SCHEMA_VERSION;
|
|
236
242
|
const currentVersion = await getSchemaVersion(client);
|
|
237
243
|
const currentTokenizer = client.dialect === "postgres" ? null : await getStoredTokenizer(client);
|
|
238
|
-
const versionMatches = currentVersion ===
|
|
244
|
+
const versionMatches = currentVersion === schemaVersion;
|
|
239
245
|
const tokenizerMatches = client.dialect === "postgres" || !currentTokenizer || currentTokenizer === tokenizer;
|
|
240
246
|
if (versionMatches && tokenizerMatches)
|
|
241
247
|
return;
|
|
248
|
+
if (client.dialect === "postgres" && currentVersion === LEGACY_POSTGRES_INTEGER_SCHEMA_VERSION) {
|
|
249
|
+
await migratePostgresIntegerTimestamps(client);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
242
252
|
if (currentVersion) {
|
|
243
253
|
if (client.dialect === "postgres") {
|
|
244
254
|
await dropPostgresTables(client);
|
|
@@ -251,9 +261,9 @@ export async function initSchema(event) {
|
|
|
251
261
|
} else {
|
|
252
262
|
await createSQLiteTables(client, tokenizer);
|
|
253
263
|
}
|
|
254
|
-
await client.db.insert(info).values({ id: "schema", version:
|
|
264
|
+
await client.db.insert(info).values({ id: "schema", version: schemaVersion }).onConflictDoUpdate({
|
|
255
265
|
target: info.id,
|
|
256
|
-
set: { version:
|
|
266
|
+
set: { version: schemaVersion }
|
|
257
267
|
});
|
|
258
268
|
if (client.dialect !== "postgres") {
|
|
259
269
|
await client.db.insert(info).values({ id: "fts_tokenizer", value: tokenizer }).onConflictDoUpdate({
|
|
@@ -264,23 +274,38 @@ export async function initSchema(event) {
|
|
|
264
274
|
}
|
|
265
275
|
async function getSchemaVersion(client) {
|
|
266
276
|
try {
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
277
|
+
const [row] = await client.db.select({ version: info.version }).from(info).where(eq(info.id, "schema")).limit(1);
|
|
278
|
+
return row?.version || null;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (isMissingInfoTableError(error))
|
|
281
|
+
return null;
|
|
282
|
+
throw error;
|
|
273
283
|
}
|
|
274
284
|
}
|
|
275
285
|
async function getStoredTokenizer(client) {
|
|
276
286
|
try {
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
287
|
+
const [row] = await client.db.select({ value: info.value }).from(info).where(eq(info.id, "fts_tokenizer")).limit(1);
|
|
288
|
+
return row?.value || null;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (isMissingInfoTableError(error))
|
|
291
|
+
return null;
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function isMissingInfoTableError(error) {
|
|
296
|
+
let current = error;
|
|
297
|
+
const seen = /* @__PURE__ */ new Set();
|
|
298
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
299
|
+
seen.add(current);
|
|
300
|
+
const candidate = current;
|
|
301
|
+
if (candidate.code === "42P01")
|
|
302
|
+
return true;
|
|
303
|
+
if (typeof candidate.message === "string" && candidate.message.includes("no such table") && candidate.message.includes("_ai_ready_info")) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
current = candidate.cause;
|
|
283
307
|
}
|
|
308
|
+
return false;
|
|
284
309
|
}
|
|
285
310
|
const SQLITE_DROP_STATEMENTS = [
|
|
286
311
|
sql`DROP TABLE IF EXISTS ai_ready_pages_fts`,
|
|
@@ -305,6 +330,19 @@ async function dropPostgresTables(client) {
|
|
|
305
330
|
for (const stmt of POSTGRES_DROP_STATEMENTS)
|
|
306
331
|
await client.db.execute(stmt);
|
|
307
332
|
}
|
|
333
|
+
async function migratePostgresIntegerTimestamps(client) {
|
|
334
|
+
await getRawExecutor(client).batch([
|
|
335
|
+
{ sql: "ALTER TABLE ai_ready_pages ALTER COLUMN indexed_at TYPE BIGINT USING indexed_at::BIGINT" },
|
|
336
|
+
{ sql: "ALTER TABLE ai_ready_pages ALTER COLUMN last_seen_at TYPE BIGINT USING last_seen_at::BIGINT" },
|
|
337
|
+
{ sql: "ALTER TABLE ai_ready_cron_runs ALTER COLUMN started_at TYPE BIGINT USING started_at::BIGINT" },
|
|
338
|
+
{ sql: "ALTER TABLE ai_ready_cron_runs ALTER COLUMN finished_at TYPE BIGINT USING finished_at::BIGINT" },
|
|
339
|
+
{ sql: "ALTER TABLE ai_ready_sitemaps ALTER COLUMN last_crawled_at TYPE BIGINT USING last_crawled_at::BIGINT" },
|
|
340
|
+
{
|
|
341
|
+
sql: "UPDATE _ai_ready_info SET version = ? WHERE id = 'schema'",
|
|
342
|
+
params: [POSTGRES_SCHEMA_VERSION]
|
|
343
|
+
}
|
|
344
|
+
]);
|
|
345
|
+
}
|
|
308
346
|
async function createSQLiteTables(client, ftsTokenizer) {
|
|
309
347
|
const statements = [
|
|
310
348
|
sql`CREATE TABLE IF NOT EXISTS ai_ready_pages (
|
|
@@ -404,11 +442,11 @@ async function createPostgresTables(client) {
|
|
|
404
442
|
keywords TEXT NOT NULL DEFAULT '[]',
|
|
405
443
|
content_hash TEXT,
|
|
406
444
|
updated_at TEXT NOT NULL,
|
|
407
|
-
indexed_at
|
|
445
|
+
indexed_at BIGINT NOT NULL,
|
|
408
446
|
is_error INTEGER NOT NULL DEFAULT 0,
|
|
409
447
|
indexed INTEGER NOT NULL DEFAULT 0,
|
|
410
448
|
source TEXT NOT NULL DEFAULT 'prerender',
|
|
411
|
-
last_seen_at
|
|
449
|
+
last_seen_at BIGINT,
|
|
412
450
|
locale TEXT NOT NULL DEFAULT ''
|
|
413
451
|
)`,
|
|
414
452
|
sql`CREATE TABLE IF NOT EXISTS _ai_ready_info (
|
|
@@ -420,8 +458,8 @@ async function createPostgresTables(client) {
|
|
|
420
458
|
)`,
|
|
421
459
|
sql`CREATE TABLE IF NOT EXISTS ai_ready_cron_runs (
|
|
422
460
|
id SERIAL PRIMARY KEY,
|
|
423
|
-
started_at
|
|
424
|
-
finished_at
|
|
461
|
+
started_at BIGINT NOT NULL,
|
|
462
|
+
finished_at BIGINT,
|
|
425
463
|
duration_ms INTEGER,
|
|
426
464
|
pages_indexed INTEGER DEFAULT 0,
|
|
427
465
|
pages_remaining INTEGER DEFAULT 0,
|
|
@@ -431,7 +469,7 @@ async function createPostgresTables(client) {
|
|
|
431
469
|
sql`CREATE TABLE IF NOT EXISTS ai_ready_sitemaps (
|
|
432
470
|
name TEXT PRIMARY KEY,
|
|
433
471
|
route TEXT NOT NULL,
|
|
434
|
-
last_crawled_at
|
|
472
|
+
last_crawled_at BIGINT,
|
|
435
473
|
url_count INTEGER DEFAULT 0,
|
|
436
474
|
error_count INTEGER DEFAULT 0,
|
|
437
475
|
last_error TEXT,
|
|
@@ -640,21 +678,24 @@ export async function seedRoutes(event, routes) {
|
|
|
640
678
|
const route = normalizeRoute(raw);
|
|
641
679
|
byRoute.set(route, normalizeRouteKey(route));
|
|
642
680
|
}
|
|
643
|
-
const
|
|
681
|
+
const rowsPerInsert = maxRowsPerInsert(4);
|
|
644
682
|
const db = await useRawDb(event);
|
|
645
683
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
646
684
|
const nowMs = Date.now();
|
|
647
685
|
const entries = [...byRoute.entries()];
|
|
648
686
|
const stmts = [];
|
|
649
|
-
for (let i = 0; i < entries.length; i +=
|
|
650
|
-
const batch = entries.slice(i, i +
|
|
687
|
+
for (let i = 0; i < entries.length; i += rowsPerInsert) {
|
|
688
|
+
const batch = entries.slice(i, i + rowsPerInsert);
|
|
651
689
|
const valuesSql = batch.map(() => `(?, ?, '', '', '', '[]', '[]', ?, 0, 0, 0, 'runtime', ?)`).join(", ");
|
|
652
690
|
const params = batch.flatMap(([route, routeKey]) => [route, routeKey, now, nowMs]);
|
|
653
691
|
stmts.push({
|
|
654
692
|
sql: `
|
|
655
693
|
INSERT INTO ai_ready_pages (route, route_key, title, description, markdown, headings, keywords, updated_at, indexed_at, is_error, indexed, source, last_seen_at)
|
|
656
694
|
VALUES ${valuesSql}
|
|
657
|
-
ON CONFLICT(route) DO UPDATE SET
|
|
695
|
+
ON CONFLICT(route) DO UPDATE SET
|
|
696
|
+
last_seen_at = excluded.last_seen_at,
|
|
697
|
+
is_error = 0,
|
|
698
|
+
indexed = CASE WHEN ai_ready_pages.is_error = 1 THEN 0 ELSE ai_ready_pages.indexed END
|
|
658
699
|
`,
|
|
659
700
|
params
|
|
660
701
|
});
|
|
@@ -7,7 +7,7 @@ import type { DrizzleDatabase } from './client.js';
|
|
|
7
7
|
/**
|
|
8
8
|
* Register the underlying driver for raw SQL access
|
|
9
9
|
*/
|
|
10
|
-
export declare function registerDriver(db: DrizzleDatabase['db'], type: 'better-sqlite3' | 'node-sqlite' | 'libsql' | 'neon' | 'd1', driver: unknown): void;
|
|
10
|
+
export declare function registerDriver(db: DrizzleDatabase['db'], type: 'better-sqlite3' | 'node-sqlite' | 'libsql' | 'neon' | 'postgres' | 'd1', driver: unknown): void;
|
|
11
11
|
/**
|
|
12
12
|
* Get raw SQL executor for a Drizzle client
|
|
13
13
|
*/
|
|
@@ -33,4 +33,4 @@ export declare function useRawDb(event?: H3Event): Promise<RawExecutor>;
|
|
|
33
33
|
/**
|
|
34
34
|
* Close underlying database driver connection
|
|
35
35
|
*/
|
|
36
|
-
export declare function closeDriver(db: DrizzleDatabase['db']): void
|
|
36
|
+
export declare function closeDriver(db: DrizzleDatabase['db']): Promise<void>;
|
|
@@ -5,6 +5,10 @@ export function registerDriver(db, type, driver) {
|
|
|
5
5
|
}
|
|
6
6
|
const RE_PARAM_PLACEHOLDER = /\?/g;
|
|
7
7
|
const MAX_BATCH_STATEMENTS = 100;
|
|
8
|
+
function toPostgresQuery(query) {
|
|
9
|
+
let index = 0;
|
|
10
|
+
return query.replace(RE_PARAM_PLACEHOLDER, () => `$${++index}`);
|
|
11
|
+
}
|
|
8
12
|
export function getRawExecutor(client) {
|
|
9
13
|
const cached = driverCache.get(client.db);
|
|
10
14
|
if (!cached) {
|
|
@@ -35,11 +39,13 @@ export function getRawExecutor(client) {
|
|
|
35
39
|
}
|
|
36
40
|
case "neon": {
|
|
37
41
|
const sqlFn = driver;
|
|
38
|
-
|
|
39
|
-
const pgQuery = query.replace(RE_PARAM_PLACEHOLDER, () => `$${++idx}`);
|
|
40
|
-
const result = await sqlFn.query(pgQuery, params);
|
|
42
|
+
const result = await sqlFn.query(toPostgresQuery(query), params);
|
|
41
43
|
return result.rows || result;
|
|
42
44
|
}
|
|
45
|
+
case "postgres": {
|
|
46
|
+
const sqlClient = driver;
|
|
47
|
+
return await sqlClient.unsafe(toPostgresQuery(query), params);
|
|
48
|
+
}
|
|
43
49
|
}
|
|
44
50
|
},
|
|
45
51
|
async first(query, params = []) {
|
|
@@ -70,9 +76,12 @@ export function getRawExecutor(client) {
|
|
|
70
76
|
}
|
|
71
77
|
case "neon": {
|
|
72
78
|
const sqlFn = driver;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
await sqlFn.query(toPostgresQuery(query), params);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "postgres": {
|
|
83
|
+
const sqlClient = driver;
|
|
84
|
+
await sqlClient.unsafe(toPostgresQuery(query), params);
|
|
76
85
|
break;
|
|
77
86
|
}
|
|
78
87
|
}
|
|
@@ -127,12 +136,17 @@ export function getRawExecutor(client) {
|
|
|
127
136
|
const sqlFn = driver;
|
|
128
137
|
for (let i = 0; i < queries.length; i += MAX_BATCH_STATEMENTS) {
|
|
129
138
|
const chunk = queries.slice(i, i + MAX_BATCH_STATEMENTS);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
await sqlFn.transaction((tx) => chunk.map((q) => tx.query(toPostgresQuery(q.sql), q.params || [])));
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "postgres": {
|
|
144
|
+
const sqlClient = driver;
|
|
145
|
+
for (let i = 0; i < queries.length; i += MAX_BATCH_STATEMENTS) {
|
|
146
|
+
const chunk = queries.slice(i, i + MAX_BATCH_STATEMENTS);
|
|
147
|
+
await sqlClient.begin((transaction) => Promise.all(chunk.map(
|
|
148
|
+
(q) => transaction.unsafe(toPostgresQuery(q.sql), q.params || [])
|
|
149
|
+
)));
|
|
136
150
|
}
|
|
137
151
|
break;
|
|
138
152
|
}
|
|
@@ -144,7 +158,7 @@ export async function useRawDb(event) {
|
|
|
144
158
|
const client = await useDrizzle(event);
|
|
145
159
|
return getRawExecutor(client);
|
|
146
160
|
}
|
|
147
|
-
export function closeDriver(db) {
|
|
161
|
+
export async function closeDriver(db) {
|
|
148
162
|
const cached = driverCache.get(db);
|
|
149
163
|
if (!cached)
|
|
150
164
|
return;
|
|
@@ -165,6 +179,11 @@ export function closeDriver(db) {
|
|
|
165
179
|
client.close?.();
|
|
166
180
|
break;
|
|
167
181
|
}
|
|
182
|
+
case "postgres": {
|
|
183
|
+
const sqlClient = driver;
|
|
184
|
+
await sqlClient.end();
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
168
187
|
}
|
|
169
188
|
driverCache.delete(db);
|
|
170
189
|
}
|
|
@@ -48,6 +48,7 @@ export interface QueryPagesOptions {
|
|
|
48
48
|
limit?: number;
|
|
49
49
|
offset?: number;
|
|
50
50
|
}
|
|
51
|
+
type DatabaseNumber = number | string | bigint;
|
|
51
52
|
/**
|
|
52
53
|
* Get lastmod (updatedAt) for all indexed pages
|
|
53
54
|
* Returns a Map for O(1) lookup when enriching sitemaps
|
|
@@ -92,7 +93,7 @@ export interface SearchPagesOptions {
|
|
|
92
93
|
limit?: number;
|
|
93
94
|
}
|
|
94
95
|
/**
|
|
95
|
-
* Full-text search using FTS5
|
|
96
|
+
* Full-text search using FTS5 or PostgreSQL ILIKE
|
|
96
97
|
* Note: FTS is only available at runtime, not during prerender
|
|
97
98
|
*/
|
|
98
99
|
export declare function searchPages(event: H3Event | undefined, query: string, options?: SearchPagesOptions): Promise<SearchResult[]>;
|
|
@@ -139,15 +140,8 @@ export declare function getPageHash(event: H3Event | undefined, route: string):
|
|
|
139
140
|
export declare function seedRoutes(event: H3Event | undefined, routes: Array<string | {
|
|
140
141
|
route: string;
|
|
141
142
|
locale?: string;
|
|
143
|
+
url?: string;
|
|
142
144
|
}>): Promise<number>;
|
|
143
|
-
/**
|
|
144
|
-
* Get sitemap seeded timestamp from _ai_ready_info
|
|
145
|
-
*/
|
|
146
|
-
export declare function getSitemapSeededAt(event: H3Event | undefined): Promise<number | undefined>;
|
|
147
|
-
/**
|
|
148
|
-
* Set sitemap seeded timestamp
|
|
149
|
-
*/
|
|
150
|
-
export declare function setSitemapSeededAt(event: H3Event | undefined, timestamp: number): Promise<void>;
|
|
151
145
|
/**
|
|
152
146
|
* Prune routes not seen in sitemap for longer than threshold
|
|
153
147
|
* Only prunes routes with source='runtime' (never prerendered pages)
|
|
@@ -159,11 +153,11 @@ export declare function pruneStaleRoutes(event: H3Event | undefined, staleThresh
|
|
|
159
153
|
export declare function getStaleRoutes(event: H3Event | undefined, staleThresholdSeconds: number): Promise<string[]>;
|
|
160
154
|
export interface CronRunRow {
|
|
161
155
|
id: number;
|
|
162
|
-
started_at:
|
|
163
|
-
finished_at:
|
|
164
|
-
duration_ms:
|
|
165
|
-
pages_indexed:
|
|
166
|
-
pages_remaining:
|
|
156
|
+
started_at: DatabaseNumber;
|
|
157
|
+
finished_at: DatabaseNumber | null;
|
|
158
|
+
duration_ms: DatabaseNumber | null;
|
|
159
|
+
pages_indexed: DatabaseNumber;
|
|
160
|
+
pages_remaining: DatabaseNumber;
|
|
167
161
|
errors: string;
|
|
168
162
|
status: 'running' | 'success' | 'partial' | 'error';
|
|
169
163
|
}
|
|
@@ -193,10 +187,6 @@ export declare function completeCronRun(event: H3Event | undefined, runId: numbe
|
|
|
193
187
|
* Get recent cron runs
|
|
194
188
|
*/
|
|
195
189
|
export declare function getRecentCronRuns(event: H3Event | undefined, limit?: number): Promise<CronRun[]>;
|
|
196
|
-
/**
|
|
197
|
-
* Clean up old cron runs (keep last N)
|
|
198
|
-
*/
|
|
199
|
-
export declare function cleanupOldCronRuns(event: H3Event | undefined, keepCount?: number): Promise<number>;
|
|
200
190
|
/**
|
|
201
191
|
* Clean up cron runs older than specified age
|
|
202
192
|
*/
|
|
@@ -213,15 +203,27 @@ export interface CronFastPathStatus {
|
|
|
213
203
|
* Reduces 6+ sequential DB calls to 1
|
|
214
204
|
*/
|
|
215
205
|
export declare function getCronFastPathStatus(event: H3Event | undefined, sitemapIntervalMinutes?: number): Promise<CronFastPathStatus | null>;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
206
|
+
export type CronLockAcquire = {
|
|
207
|
+
_tag: 'acquired';
|
|
208
|
+
token: string;
|
|
209
|
+
} | {
|
|
210
|
+
_tag: 'held';
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Try to acquire cron lock. Returns the ownership token on success, `held`
|
|
214
|
+
* when a live lock belongs to another run.
|
|
215
|
+
*
|
|
216
|
+
* The value carries a random token plus expiry in one row, so the
|
|
217
|
+
* compare-expired-then-set upsert is atomic and ownership is unique even for
|
|
218
|
+
* two isolates that start in the same millisecond. A legacy value that
|
|
219
|
+
* predates tokens parses as no expiry, so it is treated as expired.
|
|
220
|
+
*/
|
|
221
|
+
export declare function tryAcquireCronLock(event: H3Event | undefined): Promise<CronLockAcquire>;
|
|
222
|
+
/**
|
|
223
|
+
* Release cron lock. Only deletes the row when its token is ours, so a run
|
|
224
|
+
* that outlived the lock TTL cannot delete the new owner's lock.
|
|
225
|
+
*/
|
|
226
|
+
export declare function releaseCronLock(event: H3Event | undefined, token: string): Promise<void>;
|
|
225
227
|
export interface CronLockStatus {
|
|
226
228
|
held: boolean;
|
|
227
229
|
since: number | null;
|
|
@@ -303,3 +305,4 @@ export declare function getRecentlyIndexedPages(event: H3Event | undefined, limi
|
|
|
303
305
|
* Count pages indexed in a time window
|
|
304
306
|
*/
|
|
305
307
|
export declare function countRecentlyIndexed(event: H3Event | undefined, sinceMs: number): Promise<number>;
|
|
308
|
+
export {};
|