nuxt-ai-ready 2.0.1 → 2.1.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 +1 -1
- package/dist/chunks/prerender.mjs +1 -1
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- 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 +1 -1
- package/dist/runtime/server/db/drizzle/index.js +1 -1
- 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 -1
- package/dist/runtime/server/db/drizzle/queries.js +57 -22
- package/dist/runtime/server/db/drizzle/raw.d.ts +2 -2
- package/dist/runtime/server/db/drizzle/raw.js +32 -10
- package/dist/runtime/server/db/queries.d.ts +8 -6
- package/dist/runtime/server/db/queries.js +62 -34
- 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/plugins/db-lifecycle.js +5 -4
- package/dist/runtime/server/plugins/sitemap-seeder.js +4 -2
- package/dist/runtime/server/utils/indexPage.d.ts +0 -1
- package/dist/runtime/server/utils/indexPage.js +3 -3
- package/dist/runtime/server/utils/negotiation-decision.d.ts +1 -1
- package/dist/runtime/types.d.ts +5 -4
- package/dist/shared/{nuxt-ai-ready.D8qbnNrv.mjs → nuxt-ai-ready.BTQAkSYt.mjs} +4 -3
- package/package.json +23 -18
|
@@ -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.
|
|
5
|
+
import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.BTQAkSYt.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, joinURL, withBase } from 'ufo';
|
|
8
|
-
import { l as logger, s as supportsNativeNodeSqlite, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.
|
|
8
|
+
import { l as logger, s as supportsNativeNodeSqlite, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.BTQAkSYt.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.d.mts
CHANGED
|
@@ -31,7 +31,7 @@ interface ResolvedApiCatalogConfig {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
type DatabaseType = 'sqlite' | 'bun' | 'd1' | 'libsql' | 'neon';
|
|
34
|
+
type DatabaseType = 'sqlite' | 'bun' | 'd1' | 'libsql' | 'neon' | 'postgres';
|
|
35
35
|
/**
|
|
36
36
|
* Database state after parsing. Every consumer reads `_tag` first, so a
|
|
37
37
|
* disabled database can never carry a driver, a path, or credentials.
|
package/dist/module.json
CHANGED
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.
|
|
10
|
+
export { m as default } from './shared/nuxt-ai-ready.BTQAkSYt.mjs';
|
|
11
11
|
import '../dist/runtime/server/utils/discovery-response.js';
|
|
12
12
|
import 'node:url';
|
|
13
13
|
import 'pathe';
|
|
@@ -4,14 +4,19 @@ import type { DrizzleD1Database } from 'drizzle-orm/d1';
|
|
|
4
4
|
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
|
|
5
5
|
import type { NeonHttpDatabase } from 'drizzle-orm/neon-http';
|
|
6
6
|
import type { NodeSQLiteDatabase } from 'drizzle-orm/node-sqlite';
|
|
7
|
+
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
7
8
|
import type { H3Event } from '#nuxtseo/h3';
|
|
8
9
|
export type DatabaseDialect = 'sqlite' | 'postgres';
|
|
9
10
|
type SQLiteDB = BetterSQLite3Database | SQLiteBunDatabase | LibSQLDatabase | DrizzleD1Database | NodeSQLiteDatabase;
|
|
10
|
-
type PostgresDB = NeonHttpDatabase;
|
|
11
|
+
type PostgresDB = NeonHttpDatabase | PostgresJsDatabase;
|
|
11
12
|
export interface DrizzleDatabase {
|
|
12
13
|
dialect: DatabaseDialect;
|
|
13
14
|
db: SQLiteDB | PostgresDB;
|
|
14
15
|
}
|
|
16
|
+
/** Keep request-scoped clients alive until deferred database work finishes. */
|
|
17
|
+
export declare function trackDrizzleWork<T>(event: H3Event, work: Promise<T>): Promise<T>;
|
|
18
|
+
/** Transfer client cleanup to deferred work after the response ends. */
|
|
19
|
+
export declare function finishDrizzleResponse(event: H3Event): Promise<void>;
|
|
15
20
|
/**
|
|
16
21
|
* Get Drizzle database instance
|
|
17
22
|
*/
|
|
@@ -1,29 +1,82 @@
|
|
|
1
|
-
import { DB_CONTEXT_KEY } from "../context.js";
|
|
1
|
+
import { DB_CONTEXT_KEY, DB_PROMISE_CONTEXT_KEY, DB_WORK_CONTEXT_KEY } from "../context.js";
|
|
2
2
|
import { closeDriver } from "./raw.js";
|
|
3
3
|
let fallbackClient;
|
|
4
|
+
let fallbackClientPromise;
|
|
5
|
+
function createDrizzleClient(event) {
|
|
6
|
+
return import("#ai-ready-virtual/db-provider.mjs").then(({ createClient }) => createClient(event));
|
|
7
|
+
}
|
|
8
|
+
function getDrizzleWorkState(event) {
|
|
9
|
+
const context = event.context;
|
|
10
|
+
return context[DB_WORK_CONTEXT_KEY] ??= {
|
|
11
|
+
_tag: "ResponseOpen",
|
|
12
|
+
pending: /* @__PURE__ */ new Set()
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function trackDrizzleWork(event, work) {
|
|
16
|
+
const state = getDrizzleWorkState(event);
|
|
17
|
+
const tracked = work.finally(async () => {
|
|
18
|
+
state.pending.delete(tracked);
|
|
19
|
+
if (state._tag === "ResponseEnded" && state.pending.size === 0)
|
|
20
|
+
await closeDrizzle(event);
|
|
21
|
+
});
|
|
22
|
+
state.pending.add(tracked);
|
|
23
|
+
return tracked;
|
|
24
|
+
}
|
|
25
|
+
export async function finishDrizzleResponse(event) {
|
|
26
|
+
const state = getDrizzleWorkState(event);
|
|
27
|
+
state._tag = "ResponseEnded";
|
|
28
|
+
if (state.pending.size === 0)
|
|
29
|
+
await closeDrizzle(event);
|
|
30
|
+
}
|
|
4
31
|
export async function useDrizzle(event) {
|
|
5
32
|
if (event?.context?.[DB_CONTEXT_KEY]) {
|
|
6
33
|
return event.context[DB_CONTEXT_KEY];
|
|
7
34
|
}
|
|
35
|
+
if (event?.context?.[DB_PROMISE_CONTEXT_KEY]) {
|
|
36
|
+
return event.context[DB_PROMISE_CONTEXT_KEY];
|
|
37
|
+
}
|
|
8
38
|
if (!event && fallbackClient) {
|
|
9
39
|
return fallbackClient;
|
|
10
40
|
}
|
|
11
|
-
|
|
12
|
-
|
|
41
|
+
if (!event && fallbackClientPromise)
|
|
42
|
+
return fallbackClientPromise;
|
|
13
43
|
if (event?.context) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
44
|
+
const context = event.context;
|
|
45
|
+
const promise2 = createDrizzleClient(event).then((client) => {
|
|
46
|
+
context[DB_CONTEXT_KEY] = client;
|
|
47
|
+
return client;
|
|
48
|
+
}).finally(() => {
|
|
49
|
+
if (context[DB_PROMISE_CONTEXT_KEY] === promise2)
|
|
50
|
+
delete context[DB_PROMISE_CONTEXT_KEY];
|
|
51
|
+
});
|
|
52
|
+
context[DB_PROMISE_CONTEXT_KEY] = promise2;
|
|
53
|
+
return promise2;
|
|
17
54
|
}
|
|
18
|
-
|
|
55
|
+
const promise = createDrizzleClient().then((client) => {
|
|
56
|
+
fallbackClient = client;
|
|
57
|
+
return client;
|
|
58
|
+
}).finally(() => {
|
|
59
|
+
if (fallbackClientPromise === promise)
|
|
60
|
+
fallbackClientPromise = void 0;
|
|
61
|
+
});
|
|
62
|
+
fallbackClientPromise = promise;
|
|
63
|
+
return promise;
|
|
19
64
|
}
|
|
20
65
|
export async function closeDrizzle(event) {
|
|
21
|
-
if (event?.context
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
closeDriver(
|
|
66
|
+
if (event?.context) {
|
|
67
|
+
const context = event.context;
|
|
68
|
+
const client = context[DB_CONTEXT_KEY] ?? await context[DB_PROMISE_CONTEXT_KEY];
|
|
69
|
+
if (!client)
|
|
70
|
+
return;
|
|
71
|
+
await closeDriver(client.db);
|
|
72
|
+
delete context[DB_CONTEXT_KEY];
|
|
73
|
+
delete context[DB_PROMISE_CONTEXT_KEY];
|
|
74
|
+
} else if (!event) {
|
|
75
|
+
const client = fallbackClient ?? await fallbackClientPromise;
|
|
76
|
+
if (!client)
|
|
77
|
+
return;
|
|
78
|
+
await closeDriver(client.db);
|
|
27
79
|
fallbackClient = void 0;
|
|
80
|
+
fallbackClientPromise = void 0;
|
|
28
81
|
}
|
|
29
82
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Drizzle ORM database layer for nuxt-ai-ready
|
|
3
3
|
*/
|
|
4
4
|
export * from '#ai-ready-virtual/db-schema.mjs';
|
|
5
|
-
export { closeDrizzle, useDrizzle } from './client.js';
|
|
5
|
+
export { closeDrizzle, finishDrizzleResponse, trackDrizzleWork, useDrizzle } from './client.js';
|
|
6
6
|
export type { DatabaseDialect, DrizzleDatabase } from './client.js';
|
|
7
7
|
export { completeCronRun, countPages, deleteInfoValue, deletePage, getAllPages, getContentHashes, getInfoValue, getNextSitemapToCrawl, getPageByRoute, getPageLastmods, getPendingPages, getRecentCronRuns, getSitemapStatus, initSchema, markPageIndexed, markRoutesPending, markSitemapCrawled, markSitemapCrawlPartial, markSitemapError, markSitemapSeeded, resetSitemapErrors, searchPages, seedRoutes, setInfoValue, startCronRun, syncSitemaps, upsertPage, } from './queries.js';
|
|
8
8
|
export type { CronRunOutput, PageInput, PageMetaOutput, PageOutput, SitemapOutput, } from './queries.js';
|
|
@@ -36,7 +36,7 @@ export async function resolveWritableDbPath(dbPath) {
|
|
|
36
36
|
const fallbackErr = await ensureWritableDir(fallbackDir);
|
|
37
37
|
if (fallbackErr) {
|
|
38
38
|
throw new Error(
|
|
39
|
-
`[ai-ready] Database directory '${dir}' is not writable (${err.code}) and the temp dir fallback ('${fallbackDir}') also failed: ${fallbackErr.message}. Set database.filename to a writable path, or use a
|
|
39
|
+
`[ai-ready] Database directory '${dir}' is not writable (${err.code}) and the temp dir fallback ('${fallbackDir}') also failed: ${fallbackErr.message}. Set database.filename to a writable path, or use a remote driver: database.type 'd1' (Cloudflare), 'neon' (Vercel), 'postgres' (PostgreSQL), or 'libsql' (Turso).`
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
const fallback = join(fallbackDir, "pages.db");
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { H3Event } from '#nuxtseo/h3';
|
|
2
|
+
import postgres from 'postgres';
|
|
3
|
+
export declare function createClient(event?: H3Event): Promise<{
|
|
4
|
+
dialect: "postgres";
|
|
5
|
+
db: import("drizzle-orm/postgres-js").PostgresJsDatabase<import("drizzle-orm").EmptyRelations> & {
|
|
6
|
+
$client: postgres.Sql<{}>;
|
|
7
|
+
};
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
2
|
+
import postgres from "postgres";
|
|
3
|
+
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
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 connectionString = config.database.url || process.env.POSTGRES_URL || process.env.DATABASE_URL;
|
|
9
|
+
if (!connectionString) {
|
|
10
|
+
throw new Error("[ai-ready] Missing database URL. Set DATABASE_URL or configure database.url");
|
|
11
|
+
}
|
|
12
|
+
logger.debug("[drizzle] Connecting to PostgreSQL");
|
|
13
|
+
const sqlClient = postgres(connectionString, { prepare: false });
|
|
14
|
+
const db = drizzle({ client: sqlClient });
|
|
15
|
+
registerDriver(db, "postgres", sqlClient);
|
|
16
|
+
return { dialect: "postgres", db };
|
|
17
|
+
}
|
|
@@ -100,7 +100,7 @@ export declare function setInfoValue(event: H3Event | undefined, key: string, va
|
|
|
100
100
|
*/
|
|
101
101
|
export declare function deleteInfoValue(event: H3Event | undefined, key: string): Promise<void>;
|
|
102
102
|
/**
|
|
103
|
-
* Initialize database schema. Rebuilds on
|
|
103
|
+
* Initialize database schema. Rebuilds on a schema version change or when the
|
|
104
104
|
* SQLite FTS5 tokenizer differs from the one baked into the existing virtual
|
|
105
105
|
* table (Postgres has no FTS5; tokenizer comparison is SQLite-only).
|
|
106
106
|
*/
|
|
@@ -4,7 +4,7 @@ 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
6
|
import { useDrizzle } from "./client.js";
|
|
7
|
-
import { useRawDb } from "./raw.js";
|
|
7
|
+
import { getRawExecutor, useRawDb } from "./raw.js";
|
|
8
8
|
function resolveFtsTokenizer(event) {
|
|
9
9
|
const cfg = useRuntimeConfig(event);
|
|
10
10
|
return validateFtsTokenizer(cfg["nuxt-ai-ready"]?.ftsTokenizer);
|
|
@@ -229,16 +229,23 @@ export async function deleteInfoValue(event, key) {
|
|
|
229
229
|
const client = await useDrizzle(event);
|
|
230
230
|
await client.db.delete(info).where(eq(info.id, key));
|
|
231
231
|
}
|
|
232
|
-
const
|
|
232
|
+
const SQLITE_SCHEMA_VERSION = "v2.3.0-drizzle";
|
|
233
|
+
const LEGACY_POSTGRES_INTEGER_SCHEMA_VERSION = "v2.3.0-drizzle";
|
|
234
|
+
const POSTGRES_SCHEMA_VERSION = "v2.3.0-drizzle-postgres-bigint";
|
|
233
235
|
export async function initSchema(event) {
|
|
234
236
|
const client = await useDrizzle(event);
|
|
235
237
|
const tokenizer = resolveFtsTokenizer(event);
|
|
238
|
+
const schemaVersion = client.dialect === "postgres" ? POSTGRES_SCHEMA_VERSION : SQLITE_SCHEMA_VERSION;
|
|
236
239
|
const currentVersion = await getSchemaVersion(client);
|
|
237
240
|
const currentTokenizer = client.dialect === "postgres" ? null : await getStoredTokenizer(client);
|
|
238
|
-
const versionMatches = currentVersion ===
|
|
241
|
+
const versionMatches = currentVersion === schemaVersion;
|
|
239
242
|
const tokenizerMatches = client.dialect === "postgres" || !currentTokenizer || currentTokenizer === tokenizer;
|
|
240
243
|
if (versionMatches && tokenizerMatches)
|
|
241
244
|
return;
|
|
245
|
+
if (client.dialect === "postgres" && currentVersion === LEGACY_POSTGRES_INTEGER_SCHEMA_VERSION) {
|
|
246
|
+
await migratePostgresIntegerTimestamps(client);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
242
249
|
if (currentVersion) {
|
|
243
250
|
if (client.dialect === "postgres") {
|
|
244
251
|
await dropPostgresTables(client);
|
|
@@ -251,9 +258,9 @@ export async function initSchema(event) {
|
|
|
251
258
|
} else {
|
|
252
259
|
await createSQLiteTables(client, tokenizer);
|
|
253
260
|
}
|
|
254
|
-
await client.db.insert(info).values({ id: "schema", version:
|
|
261
|
+
await client.db.insert(info).values({ id: "schema", version: schemaVersion }).onConflictDoUpdate({
|
|
255
262
|
target: info.id,
|
|
256
|
-
set: { version:
|
|
263
|
+
set: { version: schemaVersion }
|
|
257
264
|
});
|
|
258
265
|
if (client.dialect !== "postgres") {
|
|
259
266
|
await client.db.insert(info).values({ id: "fts_tokenizer", value: tokenizer }).onConflictDoUpdate({
|
|
@@ -264,23 +271,38 @@ export async function initSchema(event) {
|
|
|
264
271
|
}
|
|
265
272
|
async function getSchemaVersion(client) {
|
|
266
273
|
try {
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
274
|
+
const [row] = await client.db.select({ version: info.version }).from(info).where(eq(info.id, "schema")).limit(1);
|
|
275
|
+
return row?.version || null;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (isMissingInfoTableError(error))
|
|
278
|
+
return null;
|
|
279
|
+
throw error;
|
|
273
280
|
}
|
|
274
281
|
}
|
|
275
282
|
async function getStoredTokenizer(client) {
|
|
276
283
|
try {
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
284
|
+
const [row] = await client.db.select({ value: info.value }).from(info).where(eq(info.id, "fts_tokenizer")).limit(1);
|
|
285
|
+
return row?.value || null;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (isMissingInfoTableError(error))
|
|
288
|
+
return null;
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function isMissingInfoTableError(error) {
|
|
293
|
+
let current = error;
|
|
294
|
+
const seen = /* @__PURE__ */ new Set();
|
|
295
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
296
|
+
seen.add(current);
|
|
297
|
+
const candidate = current;
|
|
298
|
+
if (candidate.code === "42P01")
|
|
299
|
+
return true;
|
|
300
|
+
if (typeof candidate.message === "string" && candidate.message.includes("no such table") && candidate.message.includes("_ai_ready_info")) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
current = candidate.cause;
|
|
283
304
|
}
|
|
305
|
+
return false;
|
|
284
306
|
}
|
|
285
307
|
const SQLITE_DROP_STATEMENTS = [
|
|
286
308
|
sql`DROP TABLE IF EXISTS ai_ready_pages_fts`,
|
|
@@ -305,6 +327,19 @@ async function dropPostgresTables(client) {
|
|
|
305
327
|
for (const stmt of POSTGRES_DROP_STATEMENTS)
|
|
306
328
|
await client.db.execute(stmt);
|
|
307
329
|
}
|
|
330
|
+
async function migratePostgresIntegerTimestamps(client) {
|
|
331
|
+
await getRawExecutor(client).batch([
|
|
332
|
+
{ sql: "ALTER TABLE ai_ready_pages ALTER COLUMN indexed_at TYPE BIGINT USING indexed_at::BIGINT" },
|
|
333
|
+
{ sql: "ALTER TABLE ai_ready_pages ALTER COLUMN last_seen_at TYPE BIGINT USING last_seen_at::BIGINT" },
|
|
334
|
+
{ sql: "ALTER TABLE ai_ready_cron_runs ALTER COLUMN started_at TYPE BIGINT USING started_at::BIGINT" },
|
|
335
|
+
{ sql: "ALTER TABLE ai_ready_cron_runs ALTER COLUMN finished_at TYPE BIGINT USING finished_at::BIGINT" },
|
|
336
|
+
{ sql: "ALTER TABLE ai_ready_sitemaps ALTER COLUMN last_crawled_at TYPE BIGINT USING last_crawled_at::BIGINT" },
|
|
337
|
+
{
|
|
338
|
+
sql: "UPDATE _ai_ready_info SET version = ? WHERE id = 'schema'",
|
|
339
|
+
params: [POSTGRES_SCHEMA_VERSION]
|
|
340
|
+
}
|
|
341
|
+
]);
|
|
342
|
+
}
|
|
308
343
|
async function createSQLiteTables(client, ftsTokenizer) {
|
|
309
344
|
const statements = [
|
|
310
345
|
sql`CREATE TABLE IF NOT EXISTS ai_ready_pages (
|
|
@@ -404,11 +439,11 @@ async function createPostgresTables(client) {
|
|
|
404
439
|
keywords TEXT NOT NULL DEFAULT '[]',
|
|
405
440
|
content_hash TEXT,
|
|
406
441
|
updated_at TEXT NOT NULL,
|
|
407
|
-
indexed_at
|
|
442
|
+
indexed_at BIGINT NOT NULL,
|
|
408
443
|
is_error INTEGER NOT NULL DEFAULT 0,
|
|
409
444
|
indexed INTEGER NOT NULL DEFAULT 0,
|
|
410
445
|
source TEXT NOT NULL DEFAULT 'prerender',
|
|
411
|
-
last_seen_at
|
|
446
|
+
last_seen_at BIGINT,
|
|
412
447
|
locale TEXT NOT NULL DEFAULT ''
|
|
413
448
|
)`,
|
|
414
449
|
sql`CREATE TABLE IF NOT EXISTS _ai_ready_info (
|
|
@@ -420,8 +455,8 @@ async function createPostgresTables(client) {
|
|
|
420
455
|
)`,
|
|
421
456
|
sql`CREATE TABLE IF NOT EXISTS ai_ready_cron_runs (
|
|
422
457
|
id SERIAL PRIMARY KEY,
|
|
423
|
-
started_at
|
|
424
|
-
finished_at
|
|
458
|
+
started_at BIGINT NOT NULL,
|
|
459
|
+
finished_at BIGINT,
|
|
425
460
|
duration_ms INTEGER,
|
|
426
461
|
pages_indexed INTEGER DEFAULT 0,
|
|
427
462
|
pages_remaining INTEGER DEFAULT 0,
|
|
@@ -431,7 +466,7 @@ async function createPostgresTables(client) {
|
|
|
431
466
|
sql`CREATE TABLE IF NOT EXISTS ai_ready_sitemaps (
|
|
432
467
|
name TEXT PRIMARY KEY,
|
|
433
468
|
route TEXT NOT NULL,
|
|
434
|
-
last_crawled_at
|
|
469
|
+
last_crawled_at BIGINT,
|
|
435
470
|
url_count INTEGER DEFAULT 0,
|
|
436
471
|
error_count INTEGER DEFAULT 0,
|
|
437
472
|
last_error TEXT,
|
|
@@ -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
|
}
|
|
@@ -128,14 +137,22 @@ export function getRawExecutor(client) {
|
|
|
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
139
|
const pgQueries = chunk.map((q) => {
|
|
131
|
-
|
|
132
|
-
const pgQuery = q.sql.replace(RE_PARAM_PLACEHOLDER, () => `$${++idx}`);
|
|
133
|
-
return sqlFn.query(pgQuery, q.params || []);
|
|
140
|
+
return sqlFn.query(toPostgresQuery(q.sql), q.params || []);
|
|
134
141
|
});
|
|
135
142
|
await sqlFn.transaction(pgQueries);
|
|
136
143
|
}
|
|
137
144
|
break;
|
|
138
145
|
}
|
|
146
|
+
case "postgres": {
|
|
147
|
+
const sqlClient = driver;
|
|
148
|
+
for (let i = 0; i < queries.length; i += MAX_BATCH_STATEMENTS) {
|
|
149
|
+
const chunk = queries.slice(i, i + MAX_BATCH_STATEMENTS);
|
|
150
|
+
await sqlClient.begin((transaction) => Promise.all(chunk.map(
|
|
151
|
+
(q) => transaction.unsafe(toPostgresQuery(q.sql), q.params || [])
|
|
152
|
+
)));
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
139
156
|
}
|
|
140
157
|
}
|
|
141
158
|
};
|
|
@@ -144,7 +161,7 @@ export async function useRawDb(event) {
|
|
|
144
161
|
const client = await useDrizzle(event);
|
|
145
162
|
return getRawExecutor(client);
|
|
146
163
|
}
|
|
147
|
-
export function closeDriver(db) {
|
|
164
|
+
export async function closeDriver(db) {
|
|
148
165
|
const cached = driverCache.get(db);
|
|
149
166
|
if (!cached)
|
|
150
167
|
return;
|
|
@@ -165,6 +182,11 @@ export function closeDriver(db) {
|
|
|
165
182
|
client.close?.();
|
|
166
183
|
break;
|
|
167
184
|
}
|
|
185
|
+
case "postgres": {
|
|
186
|
+
const sqlClient = driver;
|
|
187
|
+
await sqlClient.end();
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
168
190
|
}
|
|
169
191
|
driverCache.delete(db);
|
|
170
192
|
}
|
|
@@ -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[]>;
|
|
@@ -159,11 +160,11 @@ export declare function pruneStaleRoutes(event: H3Event | undefined, staleThresh
|
|
|
159
160
|
export declare function getStaleRoutes(event: H3Event | undefined, staleThresholdSeconds: number): Promise<string[]>;
|
|
160
161
|
export interface CronRunRow {
|
|
161
162
|
id: number;
|
|
162
|
-
started_at:
|
|
163
|
-
finished_at:
|
|
164
|
-
duration_ms:
|
|
165
|
-
pages_indexed:
|
|
166
|
-
pages_remaining:
|
|
163
|
+
started_at: DatabaseNumber;
|
|
164
|
+
finished_at: DatabaseNumber | null;
|
|
165
|
+
duration_ms: DatabaseNumber | null;
|
|
166
|
+
pages_indexed: DatabaseNumber;
|
|
167
|
+
pages_remaining: DatabaseNumber;
|
|
167
168
|
errors: string;
|
|
168
169
|
status: 'running' | 'success' | 'partial' | 'error';
|
|
169
170
|
}
|
|
@@ -303,3 +304,4 @@ export declare function getRecentlyIndexedPages(event: H3Event | undefined, limi
|
|
|
303
304
|
* Count pages indexed in a time window
|
|
304
305
|
*/
|
|
305
306
|
export declare function countRecentlyIndexed(event: H3Event | undefined, sinceMs: number): Promise<number>;
|
|
307
|
+
export {};
|
|
@@ -24,7 +24,7 @@ function getEventFromContext(providedEvent) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
let devWarningShown = false;
|
|
27
|
-
let
|
|
27
|
+
let schemaInitializationState = { _tag: "Uninitialized" };
|
|
28
28
|
const RE_FTS_CHARS = /[*:^"()]/g;
|
|
29
29
|
const RE_WHITESPACE = /\s+/;
|
|
30
30
|
async function getDb(event) {
|
|
@@ -43,10 +43,17 @@ async function getDb(event) {
|
|
|
43
43
|
if (cfg["nuxt-ai-ready"]?.database?._tag === "Disabled")
|
|
44
44
|
return null;
|
|
45
45
|
const db = await useRawDb(resolvedEvent);
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
if (schemaInitializationState._tag === "Uninitialized") {
|
|
47
|
+
const promise = initSchema(resolvedEvent).then(() => {
|
|
48
|
+
schemaInitializationState = { _tag: "Initialized" };
|
|
49
|
+
}).catch((error) => {
|
|
50
|
+
schemaInitializationState = { _tag: "Uninitialized" };
|
|
51
|
+
throw error;
|
|
52
|
+
});
|
|
53
|
+
schemaInitializationState = { _tag: "Initializing", promise };
|
|
49
54
|
}
|
|
55
|
+
if (schemaInitializationState._tag === "Initializing")
|
|
56
|
+
await schemaInitializationState.promise;
|
|
50
57
|
return db;
|
|
51
58
|
}
|
|
52
59
|
async function getPrerenderDb() {
|
|
@@ -149,6 +156,12 @@ function safeJsonParse(json, fallback) {
|
|
|
149
156
|
return fallback;
|
|
150
157
|
}
|
|
151
158
|
}
|
|
159
|
+
function toNumber(value, fallback = 0) {
|
|
160
|
+
return value === null || value === void 0 ? fallback : Number(value);
|
|
161
|
+
}
|
|
162
|
+
function toNullableNumber(value) {
|
|
163
|
+
return value === null || value === void 0 ? null : Number(value);
|
|
164
|
+
}
|
|
152
165
|
function rowToEntry(row) {
|
|
153
166
|
return {
|
|
154
167
|
route: row.route,
|
|
@@ -238,7 +251,7 @@ export async function countPages(event, options = {}) {
|
|
|
238
251
|
`SELECT COUNT(*) as count FROM ai_ready_pages ${whereClause}`,
|
|
239
252
|
params
|
|
240
253
|
);
|
|
241
|
-
return row?.count
|
|
254
|
+
return toNumber(row?.count);
|
|
242
255
|
}
|
|
243
256
|
export async function searchPages(event, query, options = {}) {
|
|
244
257
|
if (import.meta.dev || import.meta.prerender)
|
|
@@ -250,6 +263,17 @@ export async function searchPages(event, query, options = {}) {
|
|
|
250
263
|
const sanitized = query.replace(RE_FTS_CHARS, " ").trim();
|
|
251
264
|
if (!sanitized)
|
|
252
265
|
return [];
|
|
266
|
+
if (db.dialect === "postgres") {
|
|
267
|
+
const searchTerm = `%${sanitized}%`;
|
|
268
|
+
return db.all(`
|
|
269
|
+
SELECT route, title, description, 0 AS score
|
|
270
|
+
FROM ai_ready_pages
|
|
271
|
+
WHERE is_error = 0
|
|
272
|
+
AND (title ILIKE ? OR description ILIKE ? OR markdown ILIKE ? OR headings ILIKE ?)
|
|
273
|
+
ORDER BY route
|
|
274
|
+
LIMIT ?
|
|
275
|
+
`, [searchTerm, searchTerm, searchTerm, searchTerm, limit]);
|
|
276
|
+
}
|
|
253
277
|
const terms = sanitized.split(RE_WHITESPACE).map((t) => `${t}*`).join(" ");
|
|
254
278
|
return db.all(`
|
|
255
279
|
SELECT p.route, p.title, p.description, bm25(ai_ready_pages_fts, 5.0, 3.0, 1.0, 0.5, 2.0, 2.0) as score
|
|
@@ -299,7 +323,7 @@ export async function isPageFresh(event, route, ttlSeconds) {
|
|
|
299
323
|
const row = await db.first("SELECT indexed_at FROM ai_ready_pages WHERE route = ?", [route]);
|
|
300
324
|
if (!row)
|
|
301
325
|
return false;
|
|
302
|
-
const age = (Date.now() - row.indexed_at) / 1e3;
|
|
326
|
+
const age = (Date.now() - toNumber(row.indexed_at)) / 1e3;
|
|
303
327
|
return age < ttlSeconds;
|
|
304
328
|
}
|
|
305
329
|
export async function getPageIndexState(event, route) {
|
|
@@ -310,7 +334,7 @@ export async function getPageIndexState(event, route) {
|
|
|
310
334
|
"SELECT indexed_at, content_hash FROM ai_ready_pages WHERE route = ?",
|
|
311
335
|
[route]
|
|
312
336
|
);
|
|
313
|
-
return row ? { indexedAt: row.indexed_at, contentHash: row.content_hash } : void 0;
|
|
337
|
+
return row ? { indexedAt: toNumber(row.indexed_at), contentHash: row.content_hash } : void 0;
|
|
314
338
|
}
|
|
315
339
|
export async function getPageHash(event, route) {
|
|
316
340
|
const db = await getDb(event);
|
|
@@ -369,7 +393,10 @@ export async function setSitemapSeededAt(event, timestamp) {
|
|
|
369
393
|
const db = await getDb(event);
|
|
370
394
|
if (!db)
|
|
371
395
|
return;
|
|
372
|
-
await db.exec(
|
|
396
|
+
await db.exec(`
|
|
397
|
+
INSERT INTO _ai_ready_info (id, value) VALUES (?, ?)
|
|
398
|
+
ON CONFLICT(id) DO UPDATE SET value = excluded.value
|
|
399
|
+
`, ["sitemap_seeded_at", String(timestamp)]);
|
|
373
400
|
}
|
|
374
401
|
export async function pruneStaleRoutes(event, staleThresholdSeconds, protectedSince) {
|
|
375
402
|
const db = await getDb(event);
|
|
@@ -381,7 +408,7 @@ export async function pruneStaleRoutes(event, staleThresholdSeconds, protectedSi
|
|
|
381
408
|
"SELECT COUNT(*) as count FROM ai_ready_pages WHERE source = ? AND last_seen_at < ?",
|
|
382
409
|
["runtime", threshold]
|
|
383
410
|
);
|
|
384
|
-
const count = countRow?.count
|
|
411
|
+
const count = toNumber(countRow?.count);
|
|
385
412
|
if (count > 0) {
|
|
386
413
|
await db.exec("DELETE FROM ai_ready_pages WHERE source = ? AND last_seen_at < ?", ["runtime", threshold]);
|
|
387
414
|
}
|
|
@@ -401,11 +428,11 @@ export async function getStaleRoutes(event, staleThresholdSeconds) {
|
|
|
401
428
|
function rowToCronRun(row) {
|
|
402
429
|
return {
|
|
403
430
|
id: row.id,
|
|
404
|
-
startedAt: row.started_at,
|
|
405
|
-
finishedAt: row.finished_at,
|
|
406
|
-
durationMs: row.duration_ms,
|
|
407
|
-
pagesIndexed: row.pages_indexed,
|
|
408
|
-
pagesRemaining: row.pages_remaining,
|
|
431
|
+
startedAt: toNumber(row.started_at),
|
|
432
|
+
finishedAt: toNullableNumber(row.finished_at),
|
|
433
|
+
durationMs: toNullableNumber(row.duration_ms),
|
|
434
|
+
pagesIndexed: toNumber(row.pages_indexed),
|
|
435
|
+
pagesRemaining: toNumber(row.pages_remaining),
|
|
409
436
|
errors: safeJsonParse(row.errors, []),
|
|
410
437
|
status: row.status
|
|
411
438
|
};
|
|
@@ -415,12 +442,11 @@ export async function startCronRun(event) {
|
|
|
415
442
|
if (!db)
|
|
416
443
|
return null;
|
|
417
444
|
const now = Date.now();
|
|
418
|
-
await db.
|
|
419
|
-
"INSERT INTO ai_ready_cron_runs (started_at, status) VALUES (?, ?)",
|
|
445
|
+
const row = await db.first(
|
|
446
|
+
"INSERT INTO ai_ready_cron_runs (started_at, status) VALUES (?, ?) RETURNING id",
|
|
420
447
|
[now, "running"]
|
|
421
448
|
);
|
|
422
|
-
|
|
423
|
-
return row?.id || null;
|
|
449
|
+
return row ? toNumber(row.id) : null;
|
|
424
450
|
}
|
|
425
451
|
export async function completeCronRun(event, runId, result) {
|
|
426
452
|
const db = await getDb(event);
|
|
@@ -428,7 +454,7 @@ export async function completeCronRun(event, runId, result) {
|
|
|
428
454
|
return;
|
|
429
455
|
const now = Date.now();
|
|
430
456
|
const row = await db.first("SELECT started_at FROM ai_ready_cron_runs WHERE id = ?", [runId]);
|
|
431
|
-
const durationMs = row ? now - row.started_at : null;
|
|
457
|
+
const durationMs = row ? now - toNumber(row.started_at) : null;
|
|
432
458
|
const status = result.errors.length > 0 ? result.pagesIndexed > 0 ? "partial" : "error" : "success";
|
|
433
459
|
await db.exec(`
|
|
434
460
|
UPDATE ai_ready_cron_runs SET
|
|
@@ -456,7 +482,7 @@ export async function cleanupOldCronRuns(event, keepCount = 50) {
|
|
|
456
482
|
if (!db)
|
|
457
483
|
return 0;
|
|
458
484
|
const countRow = await db.first("SELECT COUNT(*) as count FROM ai_ready_cron_runs");
|
|
459
|
-
const total = countRow?.count
|
|
485
|
+
const total = toNumber(countRow?.count);
|
|
460
486
|
if (total <= keepCount)
|
|
461
487
|
return 0;
|
|
462
488
|
const deleteCount = total - keepCount;
|
|
@@ -476,7 +502,7 @@ export async function pruneCronRunsByAge(event, maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
|
476
502
|
"SELECT COUNT(*) as count FROM ai_ready_cron_runs WHERE started_at < ?",
|
|
477
503
|
[threshold]
|
|
478
504
|
);
|
|
479
|
-
const count = countRow?.count
|
|
505
|
+
const count = toNumber(countRow?.count);
|
|
480
506
|
if (count > 0) {
|
|
481
507
|
await db.exec("DELETE FROM ai_ready_cron_runs WHERE started_at < ?", [threshold]);
|
|
482
508
|
}
|
|
@@ -498,11 +524,11 @@ export async function getCronFastPathStatus(event, sitemapIntervalMinutes = 5) {
|
|
|
498
524
|
if (!row)
|
|
499
525
|
return null;
|
|
500
526
|
return {
|
|
501
|
-
totalPages: row.total_pages,
|
|
502
|
-
pendingPages: row.pending_pages,
|
|
527
|
+
totalPages: toNumber(row.total_pages),
|
|
528
|
+
pendingPages: toNumber(row.pending_pages),
|
|
503
529
|
lastStaleCheck: row.last_stale_check ? Number.parseInt(row.last_stale_check, 10) : null,
|
|
504
530
|
buildId: row.build_id,
|
|
505
|
-
sitemapsNeedCrawl: row.sitemaps_need_crawl
|
|
531
|
+
sitemapsNeedCrawl: toNumber(row.sitemaps_need_crawl)
|
|
506
532
|
};
|
|
507
533
|
}
|
|
508
534
|
const CRON_LOCK_TTL_MS = 3e5;
|
|
@@ -515,7 +541,7 @@ export async function tryAcquireCronLock(event) {
|
|
|
515
541
|
await db.exec(`
|
|
516
542
|
INSERT INTO _ai_ready_info (id, value) VALUES ('cron_lock', ?)
|
|
517
543
|
ON CONFLICT(id) DO UPDATE SET value = ?
|
|
518
|
-
WHERE CAST(value AS
|
|
544
|
+
WHERE CAST(_ai_ready_info.value AS BIGINT) < ?
|
|
519
545
|
`, [String(now), String(now), staleThreshold]);
|
|
520
546
|
const row = await db.first("SELECT value FROM _ai_ready_info WHERE id = ?", ["cron_lock"]);
|
|
521
547
|
return row?.value === String(now);
|
|
@@ -554,9 +580,9 @@ function rowToSitemapEntry(row) {
|
|
|
554
580
|
return {
|
|
555
581
|
name: row.name,
|
|
556
582
|
route: row.route,
|
|
557
|
-
lastCrawledAt: row.last_crawled_at,
|
|
558
|
-
urlCount: row.url_count
|
|
559
|
-
errorCount: row.error_count
|
|
583
|
+
lastCrawledAt: toNullableNumber(row.last_crawled_at),
|
|
584
|
+
urlCount: toNumber(row.url_count),
|
|
585
|
+
errorCount: toNumber(row.error_count),
|
|
560
586
|
lastError: row.last_error,
|
|
561
587
|
crawlState: parsedState.state
|
|
562
588
|
};
|
|
@@ -643,7 +669,7 @@ export async function getSitemapLastCrawledAt(event, name) {
|
|
|
643
669
|
"SELECT last_crawled_at FROM ai_ready_sitemaps WHERE name = ?",
|
|
644
670
|
[name]
|
|
645
671
|
);
|
|
646
|
-
return row?.last_crawled_at
|
|
672
|
+
return toNullableNumber(row?.last_crawled_at);
|
|
647
673
|
}
|
|
648
674
|
export async function markSitemapCrawled(event, name, urlCount) {
|
|
649
675
|
const db = await getDb(event);
|
|
@@ -663,6 +689,8 @@ export async function markSitemapSeeded(event, name, urlCount, expectedLastCrawl
|
|
|
663
689
|
const db = await getDb(event);
|
|
664
690
|
if (!db)
|
|
665
691
|
return;
|
|
692
|
+
const expectedClause = expectedLastCrawledAt === null ? "last_crawled_at IS NULL" : "last_crawled_at = ?";
|
|
693
|
+
const expectedParams = expectedLastCrawledAt === null ? [] : [expectedLastCrawledAt];
|
|
666
694
|
await db.exec(`
|
|
667
695
|
UPDATE ai_ready_sitemaps SET
|
|
668
696
|
last_crawled_at = ?,
|
|
@@ -672,8 +700,8 @@ export async function markSitemapSeeded(event, name, urlCount, expectedLastCrawl
|
|
|
672
700
|
WHERE name = ?
|
|
673
701
|
AND crawl_state IS NULL
|
|
674
702
|
AND error_count = 0
|
|
675
|
-
AND
|
|
676
|
-
`, [Date.now(), urlCount, name,
|
|
703
|
+
AND ${expectedClause}
|
|
704
|
+
`, [Date.now(), urlCount, name, ...expectedParams]);
|
|
677
705
|
}
|
|
678
706
|
export async function markSitemapCrawlPartial(event, name, state) {
|
|
679
707
|
const db = await getDb(event);
|
|
@@ -709,7 +737,7 @@ export async function resetSitemapErrors(event) {
|
|
|
709
737
|
const countRow = await db.first(
|
|
710
738
|
"SELECT COUNT(*) as count FROM ai_ready_sitemaps WHERE error_count > 0 OR crawl_state IS NOT NULL"
|
|
711
739
|
);
|
|
712
|
-
const count = countRow?.count
|
|
740
|
+
const count = toNumber(countRow?.count);
|
|
713
741
|
if (count > 0) {
|
|
714
742
|
await db.exec("UPDATE ai_ready_sitemaps SET error_count = 0, last_error = NULL, last_crawled_at = NULL, crawl_state = NULL");
|
|
715
743
|
}
|
|
@@ -733,7 +761,7 @@ export async function getRecentlyIndexedPages(event, limit = 10) {
|
|
|
733
761
|
"SELECT route, title, indexed_at FROM ai_ready_pages WHERE indexed = 1 AND is_error = 0 ORDER BY indexed_at DESC LIMIT ?",
|
|
734
762
|
[limit]
|
|
735
763
|
);
|
|
736
|
-
return rows.map((r) => ({ route: r.route, title: r.title, indexedAt: r.indexed_at }));
|
|
764
|
+
return rows.map((r) => ({ route: r.route, title: r.title, indexedAt: toNumber(r.indexed_at) }));
|
|
737
765
|
}
|
|
738
766
|
export async function countRecentlyIndexed(event, sinceMs) {
|
|
739
767
|
const db = await getDb(event);
|
|
@@ -744,5 +772,5 @@ export async function countRecentlyIndexed(event, sinceMs) {
|
|
|
744
772
|
"SELECT COUNT(*) as count FROM ai_ready_pages WHERE indexed = 1 AND indexed_at > ?",
|
|
745
773
|
[threshold]
|
|
746
774
|
);
|
|
747
|
-
return row?.count
|
|
775
|
+
return toNumber(row?.count);
|
|
748
776
|
}
|
|
@@ -152,10 +152,10 @@ export declare const pages: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
152
152
|
identity: undefined;
|
|
153
153
|
generated: undefined;
|
|
154
154
|
}>;
|
|
155
|
-
indexedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_pages", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").
|
|
155
|
+
indexedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_pages", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBigInt53Builder>, {
|
|
156
156
|
name: string;
|
|
157
157
|
tableName: "ai_ready_pages";
|
|
158
|
-
dataType: "number
|
|
158
|
+
dataType: "number int53";
|
|
159
159
|
data: number;
|
|
160
160
|
driverParam: string | number;
|
|
161
161
|
notNull: true;
|
|
@@ -212,10 +212,10 @@ export declare const pages: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
212
212
|
identity: undefined;
|
|
213
213
|
generated: undefined;
|
|
214
214
|
}>;
|
|
215
|
-
lastSeenAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_pages", import("drizzle-orm/pg-core").
|
|
215
|
+
lastSeenAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_pages", import("drizzle-orm/pg-core").PgBigInt53Builder, {
|
|
216
216
|
name: string;
|
|
217
217
|
tableName: "ai_ready_pages";
|
|
218
|
-
dataType: "number
|
|
218
|
+
dataType: "number int53";
|
|
219
219
|
data: number;
|
|
220
220
|
driverParam: string | number;
|
|
221
221
|
notNull: false;
|
|
@@ -346,10 +346,10 @@ export declare const cronRuns: import("drizzle-orm/pg-core").PgTableWithColumns<
|
|
|
346
346
|
identity: undefined;
|
|
347
347
|
generated: undefined;
|
|
348
348
|
}>;
|
|
349
|
-
startedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_cron_runs", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").
|
|
349
|
+
startedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_cron_runs", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBigInt53Builder>, {
|
|
350
350
|
name: string;
|
|
351
351
|
tableName: "ai_ready_cron_runs";
|
|
352
|
-
dataType: "number
|
|
352
|
+
dataType: "number int53";
|
|
353
353
|
data: number;
|
|
354
354
|
driverParam: string | number;
|
|
355
355
|
notNull: true;
|
|
@@ -361,10 +361,10 @@ export declare const cronRuns: import("drizzle-orm/pg-core").PgTableWithColumns<
|
|
|
361
361
|
identity: undefined;
|
|
362
362
|
generated: undefined;
|
|
363
363
|
}>;
|
|
364
|
-
finishedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_cron_runs", import("drizzle-orm/pg-core").
|
|
364
|
+
finishedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_cron_runs", import("drizzle-orm/pg-core").PgBigInt53Builder, {
|
|
365
365
|
name: string;
|
|
366
366
|
tableName: "ai_ready_cron_runs";
|
|
367
|
-
dataType: "number
|
|
367
|
+
dataType: "number int53";
|
|
368
368
|
data: number;
|
|
369
369
|
driverParam: string | number;
|
|
370
370
|
notNull: false;
|
|
@@ -488,10 +488,10 @@ export declare const sitemaps: import("drizzle-orm/pg-core").PgTableWithColumns<
|
|
|
488
488
|
identity: undefined;
|
|
489
489
|
generated: undefined;
|
|
490
490
|
}>;
|
|
491
|
-
lastCrawledAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_sitemaps", import("drizzle-orm/pg-core").
|
|
491
|
+
lastCrawledAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_sitemaps", import("drizzle-orm/pg-core").PgBigInt53Builder, {
|
|
492
492
|
name: string;
|
|
493
493
|
tableName: "ai_ready_sitemaps";
|
|
494
|
-
dataType: "number
|
|
494
|
+
dataType: "number int53";
|
|
495
495
|
data: number;
|
|
496
496
|
driverParam: string | number;
|
|
497
497
|
notNull: false;
|
|
@@ -721,10 +721,10 @@ export declare const schema: {
|
|
|
721
721
|
identity: undefined;
|
|
722
722
|
generated: undefined;
|
|
723
723
|
}>;
|
|
724
|
-
indexedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_pages", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").
|
|
724
|
+
indexedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_pages", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBigInt53Builder>, {
|
|
725
725
|
name: string;
|
|
726
726
|
tableName: "ai_ready_pages";
|
|
727
|
-
dataType: "number
|
|
727
|
+
dataType: "number int53";
|
|
728
728
|
data: number;
|
|
729
729
|
driverParam: string | number;
|
|
730
730
|
notNull: true;
|
|
@@ -781,10 +781,10 @@ export declare const schema: {
|
|
|
781
781
|
identity: undefined;
|
|
782
782
|
generated: undefined;
|
|
783
783
|
}>;
|
|
784
|
-
lastSeenAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_pages", import("drizzle-orm/pg-core").
|
|
784
|
+
lastSeenAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_pages", import("drizzle-orm/pg-core").PgBigInt53Builder, {
|
|
785
785
|
name: string;
|
|
786
786
|
tableName: "ai_ready_pages";
|
|
787
|
-
dataType: "number
|
|
787
|
+
dataType: "number int53";
|
|
788
788
|
data: number;
|
|
789
789
|
driverParam: string | number;
|
|
790
790
|
notNull: false;
|
|
@@ -915,10 +915,10 @@ export declare const schema: {
|
|
|
915
915
|
identity: undefined;
|
|
916
916
|
generated: undefined;
|
|
917
917
|
}>;
|
|
918
|
-
startedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_cron_runs", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").
|
|
918
|
+
startedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_cron_runs", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBigInt53Builder>, {
|
|
919
919
|
name: string;
|
|
920
920
|
tableName: "ai_ready_cron_runs";
|
|
921
|
-
dataType: "number
|
|
921
|
+
dataType: "number int53";
|
|
922
922
|
data: number;
|
|
923
923
|
driverParam: string | number;
|
|
924
924
|
notNull: true;
|
|
@@ -930,10 +930,10 @@ export declare const schema: {
|
|
|
930
930
|
identity: undefined;
|
|
931
931
|
generated: undefined;
|
|
932
932
|
}>;
|
|
933
|
-
finishedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_cron_runs", import("drizzle-orm/pg-core").
|
|
933
|
+
finishedAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_cron_runs", import("drizzle-orm/pg-core").PgBigInt53Builder, {
|
|
934
934
|
name: string;
|
|
935
935
|
tableName: "ai_ready_cron_runs";
|
|
936
|
-
dataType: "number
|
|
936
|
+
dataType: "number int53";
|
|
937
937
|
data: number;
|
|
938
938
|
driverParam: string | number;
|
|
939
939
|
notNull: false;
|
|
@@ -1057,10 +1057,10 @@ export declare const schema: {
|
|
|
1057
1057
|
identity: undefined;
|
|
1058
1058
|
generated: undefined;
|
|
1059
1059
|
}>;
|
|
1060
|
-
lastCrawledAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_sitemaps", import("drizzle-orm/pg-core").
|
|
1060
|
+
lastCrawledAt: import("drizzle-orm/pg-core").PgBuildColumn<"ai_ready_sitemaps", import("drizzle-orm/pg-core").PgBigInt53Builder, {
|
|
1061
1061
|
name: string;
|
|
1062
1062
|
tableName: "ai_ready_sitemaps";
|
|
1063
|
-
dataType: "number
|
|
1063
|
+
dataType: "number int53";
|
|
1064
1064
|
data: number;
|
|
1065
1065
|
driverParam: string | number;
|
|
1066
1066
|
notNull: false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { index, integer, pgTable, serial, text } from "drizzle-orm/pg-core";
|
|
1
|
+
import { bigint, index, integer, pgTable, serial, text } from "drizzle-orm/pg-core";
|
|
2
2
|
export const pages = pgTable("ai_ready_pages", {
|
|
3
3
|
id: serial("id").primaryKey(),
|
|
4
4
|
route: text("route").unique().notNull(),
|
|
@@ -10,11 +10,11 @@ export const pages = pgTable("ai_ready_pages", {
|
|
|
10
10
|
keywords: text("keywords").notNull().default("[]"),
|
|
11
11
|
contentHash: text("content_hash"),
|
|
12
12
|
updatedAt: text("updated_at").notNull(),
|
|
13
|
-
indexedAt:
|
|
13
|
+
indexedAt: bigint("indexed_at", { mode: "number" }).notNull(),
|
|
14
14
|
isError: integer("is_error").notNull().default(0),
|
|
15
15
|
indexed: integer("indexed").notNull().default(0),
|
|
16
16
|
source: text("source").notNull().default("prerender"),
|
|
17
|
-
lastSeenAt:
|
|
17
|
+
lastSeenAt: bigint("last_seen_at", { mode: "number" }),
|
|
18
18
|
locale: text("locale").notNull().default("")
|
|
19
19
|
}, (table) => [
|
|
20
20
|
index("idx_ai_ready_pages_route").on(table.route),
|
|
@@ -33,8 +33,8 @@ export const info = pgTable("_ai_ready_info", {
|
|
|
33
33
|
});
|
|
34
34
|
export const cronRuns = pgTable("ai_ready_cron_runs", {
|
|
35
35
|
id: serial("id").primaryKey(),
|
|
36
|
-
startedAt:
|
|
37
|
-
finishedAt:
|
|
36
|
+
startedAt: bigint("started_at", { mode: "number" }).notNull(),
|
|
37
|
+
finishedAt: bigint("finished_at", { mode: "number" }),
|
|
38
38
|
durationMs: integer("duration_ms"),
|
|
39
39
|
pagesIndexed: integer("pages_indexed").default(0),
|
|
40
40
|
pagesRemaining: integer("pages_remaining").default(0),
|
|
@@ -46,7 +46,7 @@ export const cronRuns = pgTable("ai_ready_cron_runs", {
|
|
|
46
46
|
export const sitemaps = pgTable("ai_ready_sitemaps", {
|
|
47
47
|
name: text("name").primaryKey(),
|
|
48
48
|
route: text("route").notNull(),
|
|
49
|
-
lastCrawledAt:
|
|
49
|
+
lastCrawledAt: bigint("last_crawled_at", { mode: "number" }),
|
|
50
50
|
urlCount: integer("url_count").default(0),
|
|
51
51
|
errorCount: integer("error_count").default(0),
|
|
52
52
|
lastError: text("last_error"),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { DB_CONTEXT_KEY } from "../db/context.js";
|
|
1
|
+
import { DB_CONTEXT_KEY, DB_PROMISE_CONTEXT_KEY, DB_WORK_CONTEXT_KEY } from "../db/context.js";
|
|
2
2
|
export default function dbLifecyclePlugin(nitroApp) {
|
|
3
3
|
nitroApp.hooks.hook("afterResponse", async (event) => {
|
|
4
|
-
if (!event.context?.[DB_CONTEXT_KEY])
|
|
4
|
+
if (!event.context?.[DB_CONTEXT_KEY] && !event.context?.[DB_PROMISE_CONTEXT_KEY] && !event.context?.[DB_WORK_CONTEXT_KEY]) {
|
|
5
5
|
return;
|
|
6
|
-
|
|
7
|
-
await
|
|
6
|
+
}
|
|
7
|
+
const { finishDrizzleResponse } = await import("../db/index.js");
|
|
8
|
+
await finishDrizzleResponse(event);
|
|
8
9
|
});
|
|
9
10
|
nitroApp.hooks.hook("close", async () => {
|
|
10
11
|
const { closeDrizzle } = await import("../db/index.js");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
2
|
+
import { trackDrizzleWork } from "../db/drizzle/client.js";
|
|
2
3
|
import { getPageLastmods, getSitemapLastCrawledAt, markSitemapSeeded, seedRoutes } from "../db/queries.js";
|
|
3
4
|
import { logger } from "../logger.js";
|
|
4
5
|
import { mapSitemapRoutes } from "../utils/sitemap-routes.js";
|
|
@@ -106,9 +107,10 @@ export default function sitemapSeederPlugin(nitroApp) {
|
|
|
106
107
|
logger.debug(`[sitemap-seeder] Seeded ${seeded} routes from ${sitemapName} in ${seedMs}ms`);
|
|
107
108
|
};
|
|
108
109
|
if (event.waitUntil) {
|
|
109
|
-
event
|
|
110
|
+
const backgroundSeed = trackDrizzleWork(event, seed()).catch(
|
|
110
111
|
(err) => logger.error(`[sitemap-seeder] Background seed failed: ${err.message}`)
|
|
111
|
-
)
|
|
112
|
+
);
|
|
113
|
+
event.waitUntil(backgroundSeed);
|
|
112
114
|
} else {
|
|
113
115
|
await seed();
|
|
114
116
|
}
|
|
@@ -5,7 +5,7 @@ import { logger } from "../logger.js";
|
|
|
5
5
|
import { convertHtmlToMarkdown } from "../utils.js";
|
|
6
6
|
import { createUniversalContext } from "./context.js";
|
|
7
7
|
import { extractKeywords } from "./keywords.js";
|
|
8
|
-
|
|
8
|
+
import { INTERNAL_HEADER } from "./negotiation-decision.js";
|
|
9
9
|
export async function indexPage(route, html, options = {}, event) {
|
|
10
10
|
const config = useRuntimeConfig()["nuxt-ai-ready"];
|
|
11
11
|
const ttl = options.ttl ?? config.runtimeSync.ttl;
|
|
@@ -70,10 +70,10 @@ export async function indexPage(route, html, options = {}, event) {
|
|
|
70
70
|
export async function indexPageByRoute(route, event, options = {}) {
|
|
71
71
|
logger.debug(`[indexPageByRoute] Fetching HTML for ${route} (timeout: 10000ms)`);
|
|
72
72
|
const html = await (event ? fetchWithEvent(event, route, {
|
|
73
|
-
headers: { [
|
|
73
|
+
headers: { accept: "text/html", [INTERNAL_HEADER]: "1" },
|
|
74
74
|
timeout: 1e4
|
|
75
75
|
}) : globalThis.$fetch(route, {
|
|
76
|
-
headers: { [
|
|
76
|
+
headers: { accept: "text/html", [INTERNAL_HEADER]: "1" },
|
|
77
77
|
timeout: 1e4
|
|
78
78
|
// 10s timeout per page (must fit within CF worker limit)
|
|
79
79
|
})).catch((err) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ContentNegotiationPolicy } from '../../types.js';
|
|
2
2
|
import type { ContentNegotiationResolution, NegotiationRouteRule } from './content-negotiation.js';
|
|
3
3
|
import type { MarkdownRequest } from './markdown-request.js';
|
|
4
|
-
/** Marks
|
|
4
|
+
/** Marks an internal HTML fetch that must bypass content negotiation. */
|
|
5
5
|
export declare const INTERNAL_HEADER = "x-ai-ready-internal";
|
|
6
6
|
/**
|
|
7
7
|
* Where the decision runs. The early stage sits in front of the Nitro static
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -231,7 +231,7 @@ export interface ModuleOptions {
|
|
|
231
231
|
llmsTxtCacheSeconds?: number;
|
|
232
232
|
/**
|
|
233
233
|
* Database configuration for page storage
|
|
234
|
-
* Supports SQLite, LibSQL/Turso, Cloudflare D1, and
|
|
234
|
+
* Supports SQLite, LibSQL/Turso, Cloudflare D1, Neon, and PostgreSQL
|
|
235
235
|
*
|
|
236
236
|
* Storage stays off until a runtime feature or page tool needs it.
|
|
237
237
|
* Set to `false` (or `{ type: 'none' }`) to force it off.
|
|
@@ -247,9 +247,10 @@ export interface ModuleOptions {
|
|
|
247
247
|
* - 'bun': Bun SQLite via bun:sqlite (auto-detected on Bun) [experimental]
|
|
248
248
|
* - 'libsql': Turso/LibSQL [experimental]
|
|
249
249
|
* - 'neon': Vercel Postgres via Neon serverless (auto-detected on Vercel with POSTGRES_URL) [experimental]
|
|
250
|
+
* - 'postgres': PostgreSQL via Postgres.js [experimental]
|
|
250
251
|
* @default 'sqlite' when a requested feature needs storage
|
|
251
252
|
*/
|
|
252
|
-
type?: 'none' | 'sqlite' | 'bun' | 'd1' | 'libsql' | 'neon';
|
|
253
|
+
type?: 'none' | 'sqlite' | 'bun' | 'd1' | 'libsql' | 'neon' | 'postgres';
|
|
253
254
|
/**
|
|
254
255
|
* SQLite filename (relative to rootDir or absolute)
|
|
255
256
|
* @default '.data/ai-ready/pages.db'
|
|
@@ -261,8 +262,8 @@ export interface ModuleOptions {
|
|
|
261
262
|
*/
|
|
262
263
|
bindingName?: string;
|
|
263
264
|
/**
|
|
264
|
-
* Database URL for LibSQL/Turso or
|
|
265
|
-
*
|
|
265
|
+
* Database URL for LibSQL/Turso, Neon, or PostgreSQL
|
|
266
|
+
* PostgreSQL drivers also read POSTGRES_URL or DATABASE_URL.
|
|
266
267
|
*/
|
|
267
268
|
url?: string;
|
|
268
269
|
/**
|
|
@@ -598,7 +598,7 @@ function resolveDatabaseConfig(input) {
|
|
|
598
598
|
database: { _tag: "Enabled", type, bindingName: config.bindingName || "DB" }
|
|
599
599
|
};
|
|
600
600
|
}
|
|
601
|
-
if (type === "neon") {
|
|
601
|
+
if (type === "neon" || type === "postgres") {
|
|
602
602
|
return {
|
|
603
603
|
_tag: "Resolved",
|
|
604
604
|
logs,
|
|
@@ -1579,12 +1579,13 @@ export const logger = createModuleLogger('nuxt-ai-ready', ${!!config.debug})
|
|
|
1579
1579
|
bun: "#ai-ready/server/db/drizzle/providers/bun",
|
|
1580
1580
|
d1: "#ai-ready/server/db/drizzle/providers/d1",
|
|
1581
1581
|
libsql: "#ai-ready/server/db/drizzle/providers/libsql",
|
|
1582
|
-
neon: "#ai-ready/server/db/drizzle/providers/neon"
|
|
1582
|
+
neon: "#ai-ready/server/db/drizzle/providers/neon",
|
|
1583
|
+
postgres: "#ai-ready/server/db/drizzle/providers/postgres"
|
|
1583
1584
|
};
|
|
1584
1585
|
nitroConfig.virtual["#ai-ready-virtual/db-provider.mjs"] = database._tag === "Enabled" ? `export { createClient } from '${providerMap[database.type] || providerMap.sqlite}'` : `export function createClient() {
|
|
1585
1586
|
throw new Error('[nuxt-ai-ready] The database is disabled. Set \`aiReady.database\` to store pages at runtime.')
|
|
1586
1587
|
}`;
|
|
1587
|
-
const schemaPath = database._tag === "Enabled" && database.type === "neon" ? "#ai-ready/server/db/schema/postgres" : "#ai-ready/server/db/schema/sqlite";
|
|
1588
|
+
const schemaPath = database._tag === "Enabled" && (database.type === "neon" || database.type === "postgres") ? "#ai-ready/server/db/schema/postgres" : "#ai-ready/server/db/schema/sqlite";
|
|
1588
1589
|
nitroConfig.virtual["#ai-ready-virtual/db-schema.mjs"] = `export * from '${schemaPath}'`;
|
|
1589
1590
|
nitroConfig.virtual["#ai-ready-virtual/devtools-meta.mjs"] = `export const devtoolsMeta = ${JSON.stringify({
|
|
1590
1591
|
contentSignal: config.contentSignal || false,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ai-ready",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"description": "Best practice AI & LLM discoverability for Nuxt sites.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"@libsql/client": "^0.14.0",
|
|
49
49
|
"@neondatabase/serverless": "^1.0.0",
|
|
50
50
|
"@nuxtjs/sitemap": ">=8.3.1",
|
|
51
|
-
"better-sqlite3": "^11.0.0 || ^12.0.0 || ^13.0.0"
|
|
51
|
+
"better-sqlite3": "^11.0.0 || ^12.0.0 || ^13.0.0",
|
|
52
|
+
"postgres": "^3.4.9"
|
|
52
53
|
},
|
|
53
54
|
"peerDependenciesMeta": {
|
|
54
55
|
"@libsql/client": {
|
|
@@ -59,6 +60,9 @@
|
|
|
59
60
|
},
|
|
60
61
|
"better-sqlite3": {
|
|
61
62
|
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"postgres": {
|
|
65
|
+
"optional": true
|
|
62
66
|
}
|
|
63
67
|
},
|
|
64
68
|
"dependencies": {
|
|
@@ -67,7 +71,7 @@
|
|
|
67
71
|
"citty": "^0.2.2",
|
|
68
72
|
"consola": "^3.4.2",
|
|
69
73
|
"defu": "^6.1.7",
|
|
70
|
-
"drizzle-orm": "1.0.0-rc.4",
|
|
74
|
+
"drizzle-orm": "^1.0.0-rc.4",
|
|
71
75
|
"mdream": "^1.7.0",
|
|
72
76
|
"nuxt-site-config": "^4.2.3",
|
|
73
77
|
"nuxtseo-shared": "^5.3.14",
|
|
@@ -82,41 +86,42 @@
|
|
|
82
86
|
"devDependencies": {
|
|
83
87
|
"@antfu/eslint-config": "^9.3.0",
|
|
84
88
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
85
|
-
"@harlan-zw/comark-content": "0.1.
|
|
89
|
+
"@harlan-zw/comark-content": "^0.1.5",
|
|
86
90
|
"@libsql/client": "^0.17.4",
|
|
87
|
-
"@nuxt/content": "^3.
|
|
91
|
+
"@nuxt/content": "^3.16.0",
|
|
88
92
|
"@nuxt/module-builder": "^1.0.3",
|
|
89
|
-
"@nuxt/test-utils": "^4.
|
|
93
|
+
"@nuxt/test-utils": "^4.2.0",
|
|
90
94
|
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
|
91
95
|
"@nuxtjs/mcp-toolkit": "^0.19.0",
|
|
92
|
-
"@nuxtjs/robots": "^6.
|
|
93
|
-
"@nuxtjs/sitemap": "^8.
|
|
96
|
+
"@nuxtjs/robots": "^6.2.0",
|
|
97
|
+
"@nuxtjs/sitemap": "^8.5.0",
|
|
94
98
|
"@types/better-sqlite3": "^9.6.0",
|
|
95
99
|
"@vitest/coverage-v8": "^4.1.11",
|
|
96
|
-
"@vue/test-utils": "^2.
|
|
100
|
+
"@vue/test-utils": "^2.5.0",
|
|
97
101
|
"@vueuse/nuxt": "^14.4.0",
|
|
98
102
|
"better-sqlite3": "^13.0.3",
|
|
99
|
-
"bumpp": "^12.2.
|
|
100
|
-
"eslint": "^10.
|
|
101
|
-
"eslint-plugin-harlanzw": "^0.
|
|
103
|
+
"bumpp": "^12.2.2",
|
|
104
|
+
"eslint": "^10.9.1",
|
|
105
|
+
"eslint-plugin-harlanzw": "^0.21.0",
|
|
102
106
|
"execa": "^10.0.1",
|
|
103
107
|
"h3": "^1.15.11",
|
|
104
|
-
"happy-dom": "^20.
|
|
108
|
+
"happy-dom": "^20.12.0",
|
|
105
109
|
"nitropack": "^2.13.4",
|
|
106
110
|
"nuxt": "^4.5.2",
|
|
107
111
|
"nuxt-site-config": "^4.2.3",
|
|
108
112
|
"nuxtseo-layer-devtools": "^5.3.14",
|
|
109
113
|
"playwright": "^1.62.1",
|
|
110
114
|
"playwright-core": "^1.62.1",
|
|
115
|
+
"postgres": "^3.4.9",
|
|
111
116
|
"tinyglobby": "^0.2.17",
|
|
112
117
|
"typescript": "6.0.3",
|
|
113
118
|
"unbuild": "^3.6.1",
|
|
114
119
|
"vitest": "^4.1.11",
|
|
115
|
-
"vue": "^3.5.
|
|
116
|
-
"vue-router": "^5.
|
|
117
|
-
"vue-tsc": "^3.3.
|
|
118
|
-
"wrangler": "^4.
|
|
119
|
-
"zod": "^4.4
|
|
120
|
+
"vue": "^3.5.42",
|
|
121
|
+
"vue-router": "^5.3.0",
|
|
122
|
+
"vue-tsc": "^3.3.11",
|
|
123
|
+
"wrangler": "^4.127.1",
|
|
124
|
+
"zod": "^4.5.4"
|
|
120
125
|
},
|
|
121
126
|
"scripts": {
|
|
122
127
|
"lint": "eslint .",
|