@sonicjs-cms/core 2.5.0 → 2.7.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/{app-Db0AfT5F.d.cts → app-DV27cjPy.d.cts} +1 -1
- package/dist/{app-Db0AfT5F.d.ts → app-DV27cjPy.d.ts} +1 -1
- package/dist/{chunk-YIXSSJWD.cjs → chunk-AYPF6C4D.cjs} +5 -5
- package/dist/{chunk-YIXSSJWD.cjs.map → chunk-AYPF6C4D.cjs.map} +1 -1
- package/dist/chunk-CLIH2T74.js +403 -0
- package/dist/chunk-CLIH2T74.js.map +1 -0
- package/dist/{chunk-BHNDALCA.js → chunk-DNHJS6RN.js} +6 -4
- package/dist/chunk-DNHJS6RN.js.map +1 -0
- package/dist/{chunk-YYV3XQOQ.cjs → chunk-E2BXLXPW.cjs} +7 -7
- package/dist/{chunk-YYV3XQOQ.cjs.map → chunk-E2BXLXPW.cjs.map} +1 -1
- package/dist/{chunk-AZLU3ROK.cjs → chunk-EHSZ6TAN.cjs} +11 -4
- package/dist/chunk-EHSZ6TAN.cjs.map +1 -0
- package/dist/{chunk-3YUHXWSG.js → chunk-F332TENF.js} +3 -3
- package/dist/{chunk-3YUHXWSG.js.map → chunk-F332TENF.js.map} +1 -1
- package/dist/{chunk-V5LBQN3I.js → chunk-GRN3GHUG.js} +11 -4
- package/dist/chunk-GRN3GHUG.js.map +1 -0
- package/dist/{chunk-UAQL2VWX.cjs → chunk-J7F3NPAP.cjs} +2436 -707
- package/dist/chunk-J7F3NPAP.cjs.map +1 -0
- package/dist/{chunk-VEL7QRYI.js → chunk-L2IDZI7F.js} +9 -2
- package/dist/chunk-L2IDZI7F.js.map +1 -0
- package/dist/{chunk-ILZ3DP4I.cjs → chunk-MPT5PA6U.cjs} +24 -2
- package/dist/chunk-MPT5PA6U.cjs.map +1 -0
- package/dist/{chunk-ZWV3EBZ7.cjs → chunk-MYB5RY7H.cjs} +6 -4
- package/dist/chunk-MYB5RY7H.cjs.map +1 -0
- package/dist/{chunk-OJZ45OJD.js → chunk-UISZ2MBW.js} +2272 -544
- package/dist/chunk-UISZ2MBW.js.map +1 -0
- package/dist/{chunk-AVPUX57O.js → chunk-V3KVSEG6.js} +3 -3
- package/dist/{chunk-AVPUX57O.js.map → chunk-V3KVSEG6.js.map} +1 -1
- package/dist/{chunk-TJTWRO4G.js → chunk-Y3EWJQ4D.js} +4 -4
- package/dist/{chunk-TJTWRO4G.js.map → chunk-Y3EWJQ4D.js.map} +1 -1
- package/dist/{chunk-LWG2MWDA.cjs → chunk-Y72M3MVX.cjs} +4 -4
- package/dist/{chunk-LWG2MWDA.cjs.map → chunk-Y72M3MVX.cjs.map} +1 -1
- package/dist/{chunk-SGAG6FD3.js → chunk-YFJJU26H.js} +24 -2
- package/dist/chunk-YFJJU26H.js.map +1 -0
- package/dist/chunk-YHW27CBV.cjs +406 -0
- package/dist/chunk-YHW27CBV.cjs.map +1 -0
- package/dist/{chunk-I4V3VZWF.cjs → chunk-YRFAQ6MI.cjs} +9 -2
- package/dist/chunk-YRFAQ6MI.cjs.map +1 -0
- package/dist/{collection-config-B6gMPunn.d.cts → collection-config-BF95LgQb.d.cts} +1 -1
- package/dist/{collection-config-B6gMPunn.d.ts → collection-config-BF95LgQb.d.ts} +1 -1
- package/dist/index.cjs +4098 -424
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +503 -8
- package/dist/index.d.ts +503 -8
- package/dist/index.js +4008 -341
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +24 -24
- package/dist/middleware.d.cts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +3 -3
- package/dist/migrations-LEMFV2ND.cjs +13 -0
- package/dist/{migrations-NIEUFG44.cjs.map → migrations-LEMFV2ND.cjs.map} +1 -1
- package/dist/migrations-RKQES6XY.js +4 -0
- package/dist/{migrations-TGZKJKV4.js.map → migrations-RKQES6XY.js.map} +1 -1
- package/dist/{plugin-bootstrap-dYhD9fQR.d.ts → plugin-bootstrap-CB-xaBfK.d.ts} +2 -2
- package/dist/{plugin-bootstrap-SHsdjE6X.d.cts → plugin-bootstrap-U-cw9jn3.d.cts} +2 -2
- package/dist/plugins.cjs +11 -11
- package/dist/plugins.js +2 -2
- package/dist/routes.cjs +27 -27
- package/dist/routes.d.cts +1 -1
- package/dist/routes.d.ts +1 -1
- package/dist/routes.js +7 -7
- package/dist/services.cjs +16 -16
- package/dist/services.d.cts +2 -2
- package/dist/services.d.ts +2 -2
- package/dist/services.js +2 -2
- package/dist/templates.cjs +17 -17
- package/dist/templates.js +2 -2
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils.cjs +14 -14
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/migrations/029_ai_search_plugin.sql +45 -0
- package/package.json +4 -2
- package/dist/chunk-AI2JJIJX.cjs +0 -211
- package/dist/chunk-AI2JJIJX.cjs.map +0 -1
- package/dist/chunk-AZLU3ROK.cjs.map +0 -1
- package/dist/chunk-BHNDALCA.js.map +0 -1
- package/dist/chunk-I4V3VZWF.cjs.map +0 -1
- package/dist/chunk-ILZ3DP4I.cjs.map +0 -1
- package/dist/chunk-OJZ45OJD.js.map +0 -1
- package/dist/chunk-QDBNW7KQ.js +0 -209
- package/dist/chunk-QDBNW7KQ.js.map +0 -1
- package/dist/chunk-SGAG6FD3.js.map +0 -1
- package/dist/chunk-UAQL2VWX.cjs.map +0 -1
- package/dist/chunk-V5LBQN3I.js.map +0 -1
- package/dist/chunk-VEL7QRYI.js.map +0 -1
- package/dist/chunk-ZWV3EBZ7.cjs.map +0 -1
- package/dist/migrations-NIEUFG44.cjs +0 -13
- package/dist/migrations-TGZKJKV4.js +0 -4
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkJ7F3NPAP_cjs = require('./chunk-J7F3NPAP.cjs');
|
|
4
4
|
var chunk7FOAMNTI_cjs = require('./chunk-7FOAMNTI.cjs');
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var
|
|
5
|
+
var chunkE2BXLXPW_cjs = require('./chunk-E2BXLXPW.cjs');
|
|
6
|
+
var chunkMPT5PA6U_cjs = require('./chunk-MPT5PA6U.cjs');
|
|
7
|
+
var chunkYRFAQ6MI_cjs = require('./chunk-YRFAQ6MI.cjs');
|
|
8
|
+
var chunkAYPF6C4D_cjs = require('./chunk-AYPF6C4D.cjs');
|
|
9
|
+
var chunkEHSZ6TAN_cjs = require('./chunk-EHSZ6TAN.cjs');
|
|
10
|
+
var chunkY72M3MVX_cjs = require('./chunk-Y72M3MVX.cjs');
|
|
11
|
+
var chunkYHW27CBV_cjs = require('./chunk-YHW27CBV.cjs');
|
|
12
|
+
var chunkMYB5RY7H_cjs = require('./chunk-MYB5RY7H.cjs');
|
|
13
13
|
require('./chunk-P3XDZL6Q.cjs');
|
|
14
14
|
var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
|
|
15
15
|
var chunkKYGRJCZM_cjs = require('./chunk-KYGRJCZM.cjs');
|
|
@@ -20,33 +20,6 @@ var cookie = require('hono/cookie');
|
|
|
20
20
|
var zod = require('zod');
|
|
21
21
|
var d1 = require('drizzle-orm/d1');
|
|
22
22
|
|
|
23
|
-
// src/middleware/admin-setup.ts
|
|
24
|
-
function adminSetupMiddleware() {
|
|
25
|
-
return async (c, next) => {
|
|
26
|
-
const path = new URL(c.req.url).pathname;
|
|
27
|
-
if (path.startsWith("/auth/")) {
|
|
28
|
-
return next();
|
|
29
|
-
}
|
|
30
|
-
if (path.match(/\.(js|css|ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/)) {
|
|
31
|
-
return next();
|
|
32
|
-
}
|
|
33
|
-
if (path === "/health") {
|
|
34
|
-
return next();
|
|
35
|
-
}
|
|
36
|
-
if (path.startsWith("/api/")) {
|
|
37
|
-
return next();
|
|
38
|
-
}
|
|
39
|
-
const db = c.env.DB;
|
|
40
|
-
const adminExists = await chunkUAQL2VWX_cjs.checkAdminUserExists(db);
|
|
41
|
-
if (!adminExists) {
|
|
42
|
-
if (path.startsWith("/admin")) {
|
|
43
|
-
return c.redirect("/auth/register?setup=true");
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return next();
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
23
|
// src/plugins/core-plugins/database-tools-plugin/services/database-service.ts
|
|
51
24
|
var DatabaseToolsService = class {
|
|
52
25
|
constructor(db) {
|
|
@@ -262,7 +235,7 @@ var DatabaseToolsService = class {
|
|
|
262
235
|
};
|
|
263
236
|
|
|
264
237
|
// src/templates/pages/admin-database-table.template.ts
|
|
265
|
-
|
|
238
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
266
239
|
function renderDatabaseTablePage(data) {
|
|
267
240
|
const totalPages = Math.ceil(data.totalRows / data.pageSize);
|
|
268
241
|
const startRow = (data.currentPage - 1) * data.pageSize + 1;
|
|
@@ -511,7 +484,7 @@ function renderDatabaseTablePage(data) {
|
|
|
511
484
|
user: data.user,
|
|
512
485
|
content: pageContent
|
|
513
486
|
};
|
|
514
|
-
return
|
|
487
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
515
488
|
}
|
|
516
489
|
function generatePageNumbers(currentPage, totalPages) {
|
|
517
490
|
const pages = [];
|
|
@@ -586,7 +559,7 @@ function formatCellValue(value) {
|
|
|
586
559
|
// src/plugins/core-plugins/database-tools-plugin/admin-routes.ts
|
|
587
560
|
function createDatabaseToolsAdminRoutes() {
|
|
588
561
|
const router2 = new hono.Hono();
|
|
589
|
-
router2.use("*",
|
|
562
|
+
router2.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
590
563
|
router2.get("/api/stats", async (c) => {
|
|
591
564
|
try {
|
|
592
565
|
const user = c.get("user");
|
|
@@ -1334,7 +1307,7 @@ function createSeedDataAdminRoutes() {
|
|
|
1334
1307
|
return routes;
|
|
1335
1308
|
}
|
|
1336
1309
|
function createEmailPlugin() {
|
|
1337
|
-
const builder =
|
|
1310
|
+
const builder = chunkYHW27CBV_cjs.PluginBuilder.create({
|
|
1338
1311
|
name: "email",
|
|
1339
1312
|
version: "1.0.0-beta.1",
|
|
1340
1313
|
description: "Send transactional emails using Resend"
|
|
@@ -1351,10 +1324,10 @@ function createEmailPlugin() {
|
|
|
1351
1324
|
emailRoutes.get("/settings", async (c) => {
|
|
1352
1325
|
const user = c.get("user");
|
|
1353
1326
|
const db = c.env.DB;
|
|
1354
|
-
const
|
|
1327
|
+
const plugin2 = await db.prepare(`
|
|
1355
1328
|
SELECT settings FROM plugins WHERE id = 'email'
|
|
1356
1329
|
`).first();
|
|
1357
|
-
const settings =
|
|
1330
|
+
const settings = plugin2?.settings ? JSON.parse(plugin2.settings) : {};
|
|
1358
1331
|
const contentHTML = await html.html`
|
|
1359
1332
|
<div class="p-8">
|
|
1360
1333
|
<!-- Header -->
|
|
@@ -1587,7 +1560,7 @@ function createEmailPlugin() {
|
|
|
1587
1560
|
role: user.role ?? "admin"
|
|
1588
1561
|
} : void 0;
|
|
1589
1562
|
return c.html(
|
|
1590
|
-
|
|
1563
|
+
chunkEHSZ6TAN_cjs.renderAdminLayout({
|
|
1591
1564
|
title: "Email Settings",
|
|
1592
1565
|
content: contentHTML,
|
|
1593
1566
|
user: templateUser,
|
|
@@ -1615,16 +1588,16 @@ function createEmailPlugin() {
|
|
|
1615
1588
|
try {
|
|
1616
1589
|
const db = c.env.DB;
|
|
1617
1590
|
const body = await c.req.json();
|
|
1618
|
-
const
|
|
1591
|
+
const plugin2 = await db.prepare(`
|
|
1619
1592
|
SELECT settings FROM plugins WHERE id = 'email'
|
|
1620
1593
|
`).first();
|
|
1621
|
-
if (!
|
|
1594
|
+
if (!plugin2?.settings) {
|
|
1622
1595
|
return c.json({
|
|
1623
1596
|
success: false,
|
|
1624
1597
|
error: "Email settings not configured. Please save your settings first."
|
|
1625
1598
|
}, 400);
|
|
1626
1599
|
}
|
|
1627
|
-
const settings = JSON.parse(
|
|
1600
|
+
const settings = JSON.parse(plugin2.settings);
|
|
1628
1601
|
if (!settings.apiKey || !settings.fromEmail || !settings.fromName) {
|
|
1629
1602
|
return c.json({
|
|
1630
1603
|
success: false,
|
|
@@ -2011,7 +1984,7 @@ var DEFAULT_SETTINGS = {
|
|
|
2011
1984
|
appName: "SonicJS"
|
|
2012
1985
|
};
|
|
2013
1986
|
function createOTPLoginPlugin() {
|
|
2014
|
-
const builder =
|
|
1987
|
+
const builder = chunkYHW27CBV_cjs.PluginBuilder.create({
|
|
2015
1988
|
name: "otp-login",
|
|
2016
1989
|
version: "1.0.0-beta.1",
|
|
2017
1990
|
description: "Passwordless authentication via email one-time codes"
|
|
@@ -2175,7 +2148,7 @@ function createOTPLoginPlugin() {
|
|
|
2175
2148
|
error: "Account is deactivated"
|
|
2176
2149
|
}, 403);
|
|
2177
2150
|
}
|
|
2178
|
-
const token = await
|
|
2151
|
+
const token = await chunkE2BXLXPW_cjs.AuthManager.generateToken(user.id, user.email, user.role);
|
|
2179
2152
|
cookie.setCookie(c, "auth_token", token, {
|
|
2180
2153
|
httpOnly: true,
|
|
2181
2154
|
secure: true,
|
|
@@ -2230,8 +2203,8 @@ function createOTPLoginPlugin() {
|
|
|
2230
2203
|
requiresAuth: false,
|
|
2231
2204
|
priority: 100
|
|
2232
2205
|
});
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2206
|
+
const adminRoutes2 = new hono.Hono();
|
|
2207
|
+
adminRoutes2.get("/settings", async (c) => {
|
|
2235
2208
|
const user = c.get("user");
|
|
2236
2209
|
const contentHTML = await html.html`
|
|
2237
2210
|
<div class="p-8">
|
|
@@ -2403,7 +2376,7 @@ function createOTPLoginPlugin() {
|
|
|
2403
2376
|
role: user.role ?? "admin"
|
|
2404
2377
|
} : void 0;
|
|
2405
2378
|
return c.html(
|
|
2406
|
-
|
|
2379
|
+
chunkEHSZ6TAN_cjs.adminLayoutV2({
|
|
2407
2380
|
title: "OTP Login Settings",
|
|
2408
2381
|
content: contentHTML,
|
|
2409
2382
|
user: templateUser,
|
|
@@ -2411,7 +2384,7 @@ function createOTPLoginPlugin() {
|
|
|
2411
2384
|
})
|
|
2412
2385
|
);
|
|
2413
2386
|
});
|
|
2414
|
-
builder.addRoute("/admin/plugins/otp-login",
|
|
2387
|
+
builder.addRoute("/admin/plugins/otp-login", adminRoutes2, {
|
|
2415
2388
|
description: "OTP login admin interface",
|
|
2416
2389
|
requiresAuth: true,
|
|
2417
2390
|
priority: 85
|
|
@@ -2432,363 +2405,4056 @@ function createOTPLoginPlugin() {
|
|
|
2432
2405
|
return builder.build();
|
|
2433
2406
|
}
|
|
2434
2407
|
var otpLoginPlugin = createOTPLoginPlugin();
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2408
|
+
|
|
2409
|
+
// src/plugins/core-plugins/ai-search-plugin/services/embedding.service.ts
|
|
2410
|
+
var EmbeddingService = class {
|
|
2411
|
+
constructor(ai) {
|
|
2412
|
+
this.ai = ai;
|
|
2413
|
+
}
|
|
2414
|
+
/**
|
|
2415
|
+
* Generate embedding for a single text
|
|
2416
|
+
*/
|
|
2417
|
+
async generateEmbedding(text) {
|
|
2441
2418
|
try {
|
|
2442
|
-
const
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
details: validation.error.issues
|
|
2448
|
-
}, 400);
|
|
2419
|
+
const response = await this.ai.run("@cf/baai/bge-base-en-v1.5", {
|
|
2420
|
+
text: this.preprocessText(text)
|
|
2421
|
+
});
|
|
2422
|
+
if (response.data && response.data.length > 0) {
|
|
2423
|
+
return response.data[0];
|
|
2449
2424
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2425
|
+
throw new Error("No embedding data returned");
|
|
2426
|
+
} catch (error) {
|
|
2427
|
+
console.error("[EmbeddingService] Error generating embedding:", error);
|
|
2428
|
+
throw error;
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Generate embeddings for multiple texts (batch processing)
|
|
2433
|
+
*/
|
|
2434
|
+
async generateBatch(texts) {
|
|
2435
|
+
try {
|
|
2436
|
+
const batchSize = 10;
|
|
2437
|
+
const batches = [];
|
|
2438
|
+
for (let i = 0; i < texts.length; i += batchSize) {
|
|
2439
|
+
batches.push(texts.slice(i, i + batchSize));
|
|
2464
2440
|
}
|
|
2465
|
-
const
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2441
|
+
const allEmbeddings = [];
|
|
2442
|
+
for (const batch of batches) {
|
|
2443
|
+
const batchEmbeddings = await Promise.all(
|
|
2444
|
+
batch.map((text) => this.generateEmbedding(text))
|
|
2445
|
+
);
|
|
2446
|
+
allEmbeddings.push(...batchEmbeddings);
|
|
2447
|
+
}
|
|
2448
|
+
return allEmbeddings;
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
console.error("[EmbeddingService] Error generating batch embeddings:", error);
|
|
2451
|
+
throw error;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Preprocess text before generating embedding
|
|
2456
|
+
* - Trim whitespace
|
|
2457
|
+
* - Limit length to avoid token limits
|
|
2458
|
+
* - Remove special characters that might cause issues
|
|
2459
|
+
*/
|
|
2460
|
+
preprocessText(text) {
|
|
2461
|
+
if (!text) return "";
|
|
2462
|
+
let processed = text.trim().replace(/\s+/g, " ");
|
|
2463
|
+
if (processed.length > 8e3) {
|
|
2464
|
+
processed = processed.substring(0, 8e3);
|
|
2465
|
+
}
|
|
2466
|
+
return processed;
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Calculate cosine similarity between two embeddings
|
|
2470
|
+
*/
|
|
2471
|
+
cosineSimilarity(a, b) {
|
|
2472
|
+
if (a.length !== b.length) {
|
|
2473
|
+
throw new Error("Embeddings must have same dimensions");
|
|
2474
|
+
}
|
|
2475
|
+
let dotProduct = 0;
|
|
2476
|
+
let normA = 0;
|
|
2477
|
+
let normB = 0;
|
|
2478
|
+
for (let i = 0; i < a.length; i++) {
|
|
2479
|
+
const aVal = a[i] ?? 0;
|
|
2480
|
+
const bVal = b[i] ?? 0;
|
|
2481
|
+
dotProduct += aVal * bVal;
|
|
2482
|
+
normA += aVal * aVal;
|
|
2483
|
+
normB += bVal * bVal;
|
|
2484
|
+
}
|
|
2485
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
|
|
2489
|
+
// src/plugins/core-plugins/ai-search-plugin/services/chunking.service.ts
|
|
2490
|
+
var ChunkingService = class {
|
|
2491
|
+
// Default chunk size (in approximate tokens)
|
|
2492
|
+
CHUNK_SIZE = 500;
|
|
2493
|
+
CHUNK_OVERLAP = 50;
|
|
2494
|
+
/**
|
|
2495
|
+
* Chunk a single content item
|
|
2496
|
+
*/
|
|
2497
|
+
chunkContent(contentId, collectionId, title, data, metadata = {}) {
|
|
2498
|
+
const text = this.extractText(data);
|
|
2499
|
+
if (!text || text.trim().length === 0) {
|
|
2500
|
+
console.warn(`[ChunkingService] No text found for content ${contentId}`);
|
|
2501
|
+
return [];
|
|
2502
|
+
}
|
|
2503
|
+
const textChunks = this.splitIntoChunks(text);
|
|
2504
|
+
return textChunks.map((chunkText, index) => ({
|
|
2505
|
+
id: `${contentId}_chunk_${index}`,
|
|
2506
|
+
content_id: contentId,
|
|
2507
|
+
collection_id: collectionId,
|
|
2508
|
+
title,
|
|
2509
|
+
text: chunkText,
|
|
2510
|
+
chunk_index: index,
|
|
2511
|
+
metadata: {
|
|
2512
|
+
...metadata,
|
|
2513
|
+
total_chunks: textChunks.length
|
|
2514
|
+
}
|
|
2515
|
+
}));
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Chunk multiple content items
|
|
2519
|
+
*/
|
|
2520
|
+
chunkContentBatch(items) {
|
|
2521
|
+
const allChunks = [];
|
|
2522
|
+
for (const item of items) {
|
|
2523
|
+
const chunks = this.chunkContent(
|
|
2524
|
+
item.id,
|
|
2525
|
+
item.collection_id,
|
|
2526
|
+
item.title,
|
|
2527
|
+
item.data,
|
|
2528
|
+
item.metadata
|
|
2529
|
+
);
|
|
2530
|
+
allChunks.push(...chunks);
|
|
2531
|
+
}
|
|
2532
|
+
return allChunks;
|
|
2533
|
+
}
|
|
2534
|
+
/**
|
|
2535
|
+
* Extract all text from content data
|
|
2536
|
+
*/
|
|
2537
|
+
extractText(data) {
|
|
2538
|
+
const parts = [];
|
|
2539
|
+
if (data.title) parts.push(String(data.title));
|
|
2540
|
+
if (data.name) parts.push(String(data.name));
|
|
2541
|
+
if (data.description) parts.push(String(data.description));
|
|
2542
|
+
if (data.content) parts.push(String(data.content));
|
|
2543
|
+
if (data.body) parts.push(String(data.body));
|
|
2544
|
+
if (data.text) parts.push(String(data.text));
|
|
2545
|
+
if (data.summary) parts.push(String(data.summary));
|
|
2546
|
+
const extractRecursive = (obj) => {
|
|
2547
|
+
if (typeof obj === "string") {
|
|
2548
|
+
if (obj.length > 10 && !obj.startsWith("http")) {
|
|
2549
|
+
parts.push(obj);
|
|
2550
|
+
}
|
|
2551
|
+
} else if (Array.isArray(obj)) {
|
|
2552
|
+
obj.forEach(extractRecursive);
|
|
2553
|
+
} else if (obj && typeof obj === "object") {
|
|
2554
|
+
const skipKeys = ["id", "slug", "url", "image", "thumbnail", "metadata"];
|
|
2555
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
2556
|
+
if (!skipKeys.includes(key.toLowerCase())) {
|
|
2557
|
+
extractRecursive(value);
|
|
2558
|
+
}
|
|
2474
2559
|
});
|
|
2475
2560
|
}
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2561
|
+
};
|
|
2562
|
+
extractRecursive(data);
|
|
2563
|
+
return parts.join("\n\n").trim();
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Split text into overlapping chunks
|
|
2567
|
+
*/
|
|
2568
|
+
splitIntoChunks(text) {
|
|
2569
|
+
const words = text.split(/\s+/);
|
|
2570
|
+
if (words.length <= this.CHUNK_SIZE) {
|
|
2571
|
+
return [text];
|
|
2572
|
+
}
|
|
2573
|
+
const chunks = [];
|
|
2574
|
+
let startIndex = 0;
|
|
2575
|
+
while (startIndex < words.length) {
|
|
2576
|
+
const endIndex = Math.min(startIndex + this.CHUNK_SIZE, words.length);
|
|
2577
|
+
const chunk = words.slice(startIndex, endIndex).join(" ");
|
|
2578
|
+
chunks.push(chunk);
|
|
2579
|
+
startIndex += this.CHUNK_SIZE - this.CHUNK_OVERLAP;
|
|
2580
|
+
if (startIndex >= words.length - this.CHUNK_OVERLAP) {
|
|
2581
|
+
break;
|
|
2480
2582
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2583
|
+
}
|
|
2584
|
+
return chunks;
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Get optimal chunk size based on content type
|
|
2588
|
+
*/
|
|
2589
|
+
getOptimalChunkSize(contentType) {
|
|
2590
|
+
switch (contentType) {
|
|
2591
|
+
case "blog_posts":
|
|
2592
|
+
case "articles":
|
|
2593
|
+
return 600;
|
|
2594
|
+
// Larger chunks for long-form content
|
|
2595
|
+
case "products":
|
|
2596
|
+
case "pages":
|
|
2597
|
+
return 400;
|
|
2598
|
+
// Medium chunks for structured content
|
|
2599
|
+
case "messages":
|
|
2600
|
+
case "comments":
|
|
2601
|
+
return 200;
|
|
2602
|
+
// Small chunks for short content
|
|
2603
|
+
default:
|
|
2604
|
+
return this.CHUNK_SIZE;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
};
|
|
2608
|
+
|
|
2609
|
+
// src/plugins/core-plugins/ai-search-plugin/services/custom-rag.service.ts
|
|
2610
|
+
var CustomRAGService = class {
|
|
2611
|
+
constructor(db, ai, vectorize) {
|
|
2612
|
+
this.db = db;
|
|
2613
|
+
this.ai = ai;
|
|
2614
|
+
this.vectorize = vectorize;
|
|
2615
|
+
this.embeddingService = new EmbeddingService(ai);
|
|
2616
|
+
this.chunkingService = new ChunkingService();
|
|
2617
|
+
}
|
|
2618
|
+
embeddingService;
|
|
2619
|
+
chunkingService;
|
|
2620
|
+
/**
|
|
2621
|
+
* Index all content from a collection
|
|
2622
|
+
*/
|
|
2623
|
+
async indexCollection(collectionId) {
|
|
2624
|
+
console.log(`[CustomRAG] Starting indexing for collection: ${collectionId}`);
|
|
2625
|
+
try {
|
|
2626
|
+
const { results: contentItems } = await this.db.prepare(`
|
|
2627
|
+
SELECT c.id, c.title, c.data, c.collection_id, c.status,
|
|
2628
|
+
c.created_at, c.updated_at, c.author_id,
|
|
2629
|
+
col.name as collection_name, col.display_name as collection_display_name
|
|
2630
|
+
FROM content c
|
|
2631
|
+
JOIN collections col ON c.collection_id = col.id
|
|
2632
|
+
WHERE c.collection_id = ? AND c.status = 'published'
|
|
2633
|
+
`).bind(collectionId).all();
|
|
2634
|
+
const totalItems = contentItems?.length || 0;
|
|
2635
|
+
if (totalItems === 0) {
|
|
2636
|
+
console.log(`[CustomRAG] No content found in collection ${collectionId}`);
|
|
2637
|
+
return { total_items: 0, total_chunks: 0, indexed_chunks: 0, errors: 0 };
|
|
2638
|
+
}
|
|
2639
|
+
const items = (contentItems || []).map((item) => ({
|
|
2640
|
+
id: item.id,
|
|
2641
|
+
collection_id: item.collection_id,
|
|
2642
|
+
title: item.title || "Untitled",
|
|
2643
|
+
data: typeof item.data === "string" ? JSON.parse(item.data) : item.data,
|
|
2644
|
+
metadata: {
|
|
2645
|
+
status: item.status,
|
|
2646
|
+
created_at: item.created_at,
|
|
2647
|
+
updated_at: item.updated_at,
|
|
2648
|
+
author_id: item.author_id,
|
|
2649
|
+
collection_name: item.collection_name,
|
|
2650
|
+
collection_display_name: item.collection_display_name
|
|
2651
|
+
}
|
|
2652
|
+
}));
|
|
2653
|
+
const chunks = this.chunkingService.chunkContentBatch(items);
|
|
2654
|
+
const totalChunks = chunks.length;
|
|
2655
|
+
console.log(`[CustomRAG] Generated ${totalChunks} chunks from ${totalItems} items`);
|
|
2656
|
+
const embeddings = await this.embeddingService.generateBatch(
|
|
2657
|
+
chunks.map((c) => `${c.title}
|
|
2658
|
+
|
|
2659
|
+
${c.text}`)
|
|
2660
|
+
);
|
|
2661
|
+
console.log(`[CustomRAG] Generated ${embeddings.length} embeddings`);
|
|
2662
|
+
let indexedChunks = 0;
|
|
2663
|
+
let errors = 0;
|
|
2664
|
+
const batchSize = 100;
|
|
2665
|
+
for (let i = 0; i < chunks.length; i += batchSize) {
|
|
2666
|
+
const chunkBatch = chunks.slice(i, i + batchSize);
|
|
2667
|
+
const embeddingBatch = embeddings.slice(i, i + batchSize);
|
|
2668
|
+
try {
|
|
2669
|
+
await this.vectorize.upsert(
|
|
2670
|
+
chunkBatch.map((chunk, idx) => ({
|
|
2671
|
+
id: chunk.id,
|
|
2672
|
+
values: embeddingBatch[idx],
|
|
2673
|
+
metadata: {
|
|
2674
|
+
content_id: chunk.content_id,
|
|
2675
|
+
collection_id: chunk.collection_id,
|
|
2676
|
+
title: chunk.title,
|
|
2677
|
+
text: chunk.text.substring(0, 500),
|
|
2678
|
+
// Store snippet for display
|
|
2679
|
+
chunk_index: chunk.chunk_index,
|
|
2680
|
+
...chunk.metadata
|
|
2681
|
+
}
|
|
2682
|
+
}))
|
|
2683
|
+
);
|
|
2684
|
+
indexedChunks += chunkBatch.length;
|
|
2685
|
+
console.log(`[CustomRAG] Indexed batch ${i / batchSize + 1}: ${chunkBatch.length} chunks`);
|
|
2686
|
+
} catch (error) {
|
|
2687
|
+
console.error(`[CustomRAG] Error indexing batch ${i / batchSize + 1}:`, error);
|
|
2688
|
+
errors += chunkBatch.length;
|
|
2511
2689
|
}
|
|
2512
|
-
} catch (error) {
|
|
2513
|
-
console.error("Failed to send magic link email:", error);
|
|
2514
|
-
return c.json({
|
|
2515
|
-
error: "Failed to send email. Please try again later."
|
|
2516
|
-
}, 500);
|
|
2517
2690
|
}
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2691
|
+
console.log(`[CustomRAG] Indexing complete: ${indexedChunks}/${totalChunks} chunks indexed`);
|
|
2692
|
+
return {
|
|
2693
|
+
total_items: totalItems,
|
|
2694
|
+
total_chunks: totalChunks,
|
|
2695
|
+
indexed_chunks: indexedChunks,
|
|
2696
|
+
errors
|
|
2697
|
+
};
|
|
2523
2698
|
} catch (error) {
|
|
2524
|
-
console.error(
|
|
2525
|
-
|
|
2699
|
+
console.error(`[CustomRAG] Error indexing collection ${collectionId}:`, error);
|
|
2700
|
+
throw error;
|
|
2526
2701
|
}
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2702
|
+
}
|
|
2703
|
+
/**
|
|
2704
|
+
* Search using RAG (semantic search with Vectorize)
|
|
2705
|
+
*/
|
|
2706
|
+
async search(query, settings) {
|
|
2707
|
+
const startTime = Date.now();
|
|
2529
2708
|
try {
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2709
|
+
console.log(`[CustomRAG] Searching for: "${query.query}"`);
|
|
2710
|
+
const queryEmbedding = await this.embeddingService.generateEmbedding(query.query);
|
|
2711
|
+
const filter = {};
|
|
2712
|
+
if (query.filters?.collections && query.filters.collections.length > 0) {
|
|
2713
|
+
filter.collection_id = { $in: query.filters.collections };
|
|
2714
|
+
} else if (settings.selected_collections.length > 0) {
|
|
2715
|
+
filter.collection_id = { $in: settings.selected_collections };
|
|
2533
2716
|
}
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
SELECT * FROM magic_links
|
|
2537
|
-
WHERE token = ? AND used = 0
|
|
2538
|
-
`).bind(token).first();
|
|
2539
|
-
if (!magicLink) {
|
|
2540
|
-
return c.redirect("/auth/login?error=Invalid or expired magic link");
|
|
2717
|
+
if (query.filters?.status && query.filters.status.length > 0) {
|
|
2718
|
+
filter.status = { $in: query.filters.status };
|
|
2541
2719
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2720
|
+
const vectorResults = await this.vectorize.query(queryEmbedding, {
|
|
2721
|
+
topK: 50,
|
|
2722
|
+
// Max allowed with returnMetadata: true
|
|
2723
|
+
returnMetadata: true
|
|
2724
|
+
});
|
|
2725
|
+
let filteredMatches = vectorResults.matches || [];
|
|
2726
|
+
if (filter.collection_id?.$in && Array.isArray(filter.collection_id.$in)) {
|
|
2727
|
+
const allowedCollections = filter.collection_id.$in;
|
|
2728
|
+
filteredMatches = filteredMatches.filter(
|
|
2729
|
+
(match) => allowedCollections.includes(match.metadata?.collection_id)
|
|
2730
|
+
);
|
|
2544
2731
|
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
magicLink.user_email,
|
|
2561
|
-
username,
|
|
2562
|
-
username,
|
|
2563
|
-
"",
|
|
2564
|
-
now,
|
|
2565
|
-
now
|
|
2566
|
-
).run();
|
|
2567
|
-
user = {
|
|
2568
|
-
id: userId,
|
|
2569
|
-
email: magicLink.user_email,
|
|
2570
|
-
username,
|
|
2571
|
-
role: "viewer"
|
|
2732
|
+
if (filter.status?.$in && Array.isArray(filter.status.$in)) {
|
|
2733
|
+
const allowedStatuses = filter.status.$in;
|
|
2734
|
+
filteredMatches = filteredMatches.filter(
|
|
2735
|
+
(match) => allowedStatuses.includes(match.metadata?.status)
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
const topK = query.limit || settings.results_limit || 20;
|
|
2739
|
+
filteredMatches = filteredMatches.slice(0, topK);
|
|
2740
|
+
vectorResults.matches = filteredMatches;
|
|
2741
|
+
if (!vectorResults.matches || vectorResults.matches.length === 0) {
|
|
2742
|
+
return {
|
|
2743
|
+
results: [],
|
|
2744
|
+
total: 0,
|
|
2745
|
+
query_time_ms: Date.now() - startTime,
|
|
2746
|
+
mode: "ai"
|
|
2572
2747
|
};
|
|
2573
|
-
} else if (!user) {
|
|
2574
|
-
return c.redirect("/auth/login?error=No account found for this email");
|
|
2575
2748
|
}
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2749
|
+
const contentIds = [...new Set(
|
|
2750
|
+
vectorResults.matches.map((m) => m.metadata.content_id)
|
|
2751
|
+
)];
|
|
2752
|
+
const placeholders = contentIds.map(() => "?").join(",");
|
|
2753
|
+
const { results: contentItems } = await this.db.prepare(`
|
|
2754
|
+
SELECT c.id, c.title, c.slug, c.collection_id, c.status,
|
|
2755
|
+
c.created_at, c.updated_at, c.author_id,
|
|
2756
|
+
col.display_name as collection_name
|
|
2757
|
+
FROM content c
|
|
2758
|
+
JOIN collections col ON c.collection_id = col.id
|
|
2759
|
+
WHERE c.id IN (${placeholders})
|
|
2760
|
+
`).bind(...contentIds).all();
|
|
2761
|
+
const searchResults = (contentItems || []).map((item) => {
|
|
2762
|
+
const matchingChunks = vectorResults.matches.filter(
|
|
2763
|
+
(m) => m.metadata.content_id === item.id
|
|
2764
|
+
);
|
|
2765
|
+
const bestMatch = matchingChunks.reduce(
|
|
2766
|
+
(best, current) => current.score > (best?.score || 0) ? current : best,
|
|
2767
|
+
null
|
|
2768
|
+
);
|
|
2769
|
+
return {
|
|
2770
|
+
id: item.id,
|
|
2771
|
+
title: item.title || "Untitled",
|
|
2772
|
+
slug: item.slug || "",
|
|
2773
|
+
collection_id: item.collection_id,
|
|
2774
|
+
collection_name: item.collection_name,
|
|
2775
|
+
snippet: bestMatch?.metadata?.text || "",
|
|
2776
|
+
relevance_score: bestMatch?.score || 0,
|
|
2777
|
+
status: item.status,
|
|
2778
|
+
created_at: item.created_at,
|
|
2779
|
+
updated_at: item.updated_at
|
|
2780
|
+
};
|
|
2781
|
+
});
|
|
2782
|
+
searchResults.sort((a, b) => (b.relevance_score || 0) - (a.relevance_score || 0));
|
|
2783
|
+
const queryTime = Date.now() - startTime;
|
|
2784
|
+
console.log(`[CustomRAG] Search completed in ${queryTime}ms, ${searchResults.length} results`);
|
|
2785
|
+
return {
|
|
2786
|
+
results: searchResults,
|
|
2787
|
+
total: searchResults.length,
|
|
2788
|
+
query_time_ms: queryTime,
|
|
2789
|
+
mode: "ai"
|
|
2790
|
+
};
|
|
2791
|
+
} catch (error) {
|
|
2792
|
+
console.error("[CustomRAG] Search error:", error);
|
|
2793
|
+
throw error;
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Update index for a single content item
|
|
2798
|
+
*/
|
|
2799
|
+
async updateContentIndex(contentId) {
|
|
2800
|
+
try {
|
|
2801
|
+
const content2 = await this.db.prepare(`
|
|
2802
|
+
SELECT c.id, c.title, c.data, c.collection_id, c.status,
|
|
2803
|
+
c.created_at, c.updated_at, c.author_id,
|
|
2804
|
+
col.name as collection_name, col.display_name as collection_display_name
|
|
2805
|
+
FROM content c
|
|
2806
|
+
JOIN collections col ON c.collection_id = col.id
|
|
2807
|
+
WHERE c.id = ?
|
|
2808
|
+
`).bind(contentId).first();
|
|
2809
|
+
if (!content2) {
|
|
2810
|
+
console.warn(`[CustomRAG] Content ${contentId} not found`);
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
if (content2.status !== "published") {
|
|
2814
|
+
await this.removeContentFromIndex(contentId);
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
const chunks = this.chunkingService.chunkContent(
|
|
2818
|
+
content2.id,
|
|
2819
|
+
content2.collection_id,
|
|
2820
|
+
content2.title || "Untitled",
|
|
2821
|
+
typeof content2.data === "string" ? JSON.parse(content2.data) : content2.data,
|
|
2822
|
+
{
|
|
2823
|
+
status: content2.status,
|
|
2824
|
+
created_at: content2.created_at,
|
|
2825
|
+
updated_at: content2.updated_at,
|
|
2826
|
+
author_id: content2.author_id,
|
|
2827
|
+
collection_name: content2.collection_name,
|
|
2828
|
+
collection_display_name: content2.collection_display_name
|
|
2829
|
+
}
|
|
2585
2830
|
);
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2831
|
+
const embeddings = await this.embeddingService.generateBatch(
|
|
2832
|
+
chunks.map((c) => `${c.title}
|
|
2833
|
+
|
|
2834
|
+
${c.text}`)
|
|
2835
|
+
);
|
|
2836
|
+
await this.vectorize.upsert(
|
|
2837
|
+
chunks.map((chunk, idx) => ({
|
|
2838
|
+
id: chunk.id,
|
|
2839
|
+
values: embeddings[idx],
|
|
2840
|
+
metadata: {
|
|
2841
|
+
content_id: chunk.content_id,
|
|
2842
|
+
collection_id: chunk.collection_id,
|
|
2843
|
+
title: chunk.title,
|
|
2844
|
+
text: chunk.text.substring(0, 500),
|
|
2845
|
+
chunk_index: chunk.chunk_index,
|
|
2846
|
+
...chunk.metadata
|
|
2847
|
+
}
|
|
2848
|
+
}))
|
|
2849
|
+
);
|
|
2850
|
+
console.log(`[CustomRAG] Updated index for content ${contentId}: ${chunks.length} chunks`);
|
|
2591
2851
|
} catch (error) {
|
|
2592
|
-
console.error(
|
|
2593
|
-
|
|
2852
|
+
console.error(`[CustomRAG] Error updating index for ${contentId}:`, error);
|
|
2853
|
+
throw error;
|
|
2594
2854
|
}
|
|
2595
|
-
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
routes: [{
|
|
2606
|
-
path: "/auth/magic-link",
|
|
2607
|
-
handler: magicLinkRoutes,
|
|
2608
|
-
description: "Magic link authentication endpoints",
|
|
2609
|
-
requiresAuth: false
|
|
2610
|
-
}],
|
|
2611
|
-
async install(context) {
|
|
2612
|
-
console.log("Installing magic-link-auth plugin...");
|
|
2613
|
-
},
|
|
2614
|
-
async activate(context) {
|
|
2615
|
-
console.log("Magic link authentication activated");
|
|
2616
|
-
console.log("Users can now sign in via /auth/magic-link/request");
|
|
2617
|
-
},
|
|
2618
|
-
async deactivate(context) {
|
|
2619
|
-
console.log("Magic link authentication deactivated");
|
|
2620
|
-
},
|
|
2621
|
-
async uninstall(context) {
|
|
2622
|
-
console.log("Uninstalling magic-link-auth plugin...");
|
|
2855
|
+
}
|
|
2856
|
+
/**
|
|
2857
|
+
* Remove content from index
|
|
2858
|
+
*/
|
|
2859
|
+
async removeContentFromIndex(contentId) {
|
|
2860
|
+
try {
|
|
2861
|
+
console.log(`[CustomRAG] Removing content ${contentId} from index`);
|
|
2862
|
+
} catch (error) {
|
|
2863
|
+
console.error(`[CustomRAG] Error removing content ${contentId}:`, error);
|
|
2864
|
+
throw error;
|
|
2623
2865
|
}
|
|
2624
|
-
}
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Get search suggestions based on query
|
|
2869
|
+
*/
|
|
2870
|
+
async getSuggestions(partialQuery, limit = 5) {
|
|
2871
|
+
try {
|
|
2872
|
+
const queryEmbedding = await this.embeddingService.generateEmbedding(partialQuery);
|
|
2873
|
+
const results = await this.vectorize.query(queryEmbedding, {
|
|
2874
|
+
topK: limit * 2,
|
|
2875
|
+
// Get more to filter
|
|
2876
|
+
returnMetadata: true
|
|
2877
|
+
});
|
|
2878
|
+
const suggestions = [...new Set(
|
|
2879
|
+
results.matches?.map((m) => m.metadata.title).filter(Boolean) || []
|
|
2880
|
+
)].slice(0, limit);
|
|
2881
|
+
return suggestions;
|
|
2882
|
+
} catch (error) {
|
|
2883
|
+
console.error("[CustomRAG] Error getting suggestions:", error);
|
|
2884
|
+
return [];
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
/**
|
|
2888
|
+
* Check if Vectorize is available and configured
|
|
2889
|
+
*/
|
|
2890
|
+
isAvailable() {
|
|
2891
|
+
return !!this.vectorize && !!this.ai;
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
|
|
2895
|
+
// src/plugins/core-plugins/ai-search-plugin/services/ai-search.ts
|
|
2896
|
+
var AISearchService = class {
|
|
2897
|
+
constructor(db, ai, vectorize) {
|
|
2898
|
+
this.db = db;
|
|
2899
|
+
this.ai = ai;
|
|
2900
|
+
this.vectorize = vectorize;
|
|
2901
|
+
if (this.ai && this.vectorize) {
|
|
2902
|
+
this.customRAG = new CustomRAGService(db, ai, vectorize);
|
|
2903
|
+
console.log("[AISearchService] Custom RAG initialized");
|
|
2904
|
+
} else {
|
|
2905
|
+
console.log("[AISearchService] Custom RAG not available, using keyword search only");
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
customRAG;
|
|
2909
|
+
/**
|
|
2910
|
+
* Get plugin settings
|
|
2911
|
+
*/
|
|
2912
|
+
async getSettings() {
|
|
2913
|
+
try {
|
|
2914
|
+
const plugin2 = await this.db.prepare(`SELECT settings FROM plugins WHERE id = ? LIMIT 1`).bind("ai-search").first();
|
|
2915
|
+
if (!plugin2 || !plugin2.settings) {
|
|
2916
|
+
return this.getDefaultSettings();
|
|
2917
|
+
}
|
|
2918
|
+
return JSON.parse(plugin2.settings);
|
|
2919
|
+
} catch (error) {
|
|
2920
|
+
console.error("Error fetching AI Search settings:", error);
|
|
2921
|
+
return this.getDefaultSettings();
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Get default settings
|
|
2926
|
+
*/
|
|
2927
|
+
getDefaultSettings() {
|
|
2928
|
+
return {
|
|
2929
|
+
enabled: true,
|
|
2930
|
+
ai_mode_enabled: true,
|
|
2931
|
+
selected_collections: [],
|
|
2932
|
+
dismissed_collections: [],
|
|
2933
|
+
autocomplete_enabled: true,
|
|
2934
|
+
cache_duration: 1,
|
|
2935
|
+
results_limit: 20,
|
|
2936
|
+
index_media: false
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Update plugin settings
|
|
2941
|
+
*/
|
|
2942
|
+
async updateSettings(settings) {
|
|
2943
|
+
const existing = await this.getSettings();
|
|
2944
|
+
const updated = {
|
|
2945
|
+
...existing,
|
|
2946
|
+
...settings
|
|
2947
|
+
};
|
|
2948
|
+
try {
|
|
2949
|
+
await this.db.prepare(`
|
|
2950
|
+
UPDATE plugins
|
|
2951
|
+
SET settings = ?,
|
|
2952
|
+
updated_at = unixepoch()
|
|
2953
|
+
WHERE id = 'ai-search'
|
|
2954
|
+
`).bind(JSON.stringify(updated)).run();
|
|
2955
|
+
return updated;
|
|
2956
|
+
} catch (error) {
|
|
2957
|
+
console.error("Error updating AI Search settings:", error);
|
|
2958
|
+
throw error;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Detect new collections that aren't indexed or dismissed
|
|
2963
|
+
*/
|
|
2964
|
+
async detectNewCollections() {
|
|
2965
|
+
try {
|
|
2966
|
+
const collectionsStmt = this.db.prepare(
|
|
2967
|
+
"SELECT id, name, display_name, description FROM collections WHERE is_active = 1"
|
|
2968
|
+
);
|
|
2969
|
+
const { results: allCollections } = await collectionsStmt.all();
|
|
2970
|
+
const collections2 = (allCollections || []).filter(
|
|
2971
|
+
(col) => {
|
|
2972
|
+
if (!col.name) return false;
|
|
2973
|
+
const name = col.name.toLowerCase();
|
|
2974
|
+
return !name.startsWith("test_") && !name.endsWith("_test") && name !== "test_collection" && !name.includes("_test_") && name !== "large_payload_test" && name !== "concurrent_test";
|
|
2657
2975
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2976
|
+
);
|
|
2977
|
+
const settings = await this.getSettings();
|
|
2978
|
+
const selected = settings?.selected_collections || [];
|
|
2979
|
+
const dismissed = settings?.dismissed_collections || [];
|
|
2980
|
+
const notifications = [];
|
|
2981
|
+
for (const collection of collections2 || []) {
|
|
2982
|
+
const collectionId = String(collection.id);
|
|
2983
|
+
if (selected.includes(collectionId) || dismissed.includes(collectionId)) {
|
|
2984
|
+
continue;
|
|
2660
2985
|
}
|
|
2661
|
-
.
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2986
|
+
const countStmt = this.db.prepare(
|
|
2987
|
+
"SELECT COUNT(*) as count FROM content WHERE collection_id = ?"
|
|
2988
|
+
);
|
|
2989
|
+
const countResult = await countStmt.bind(collectionId).first();
|
|
2990
|
+
const itemCount = countResult?.count || 0;
|
|
2991
|
+
notifications.push({
|
|
2992
|
+
collection: {
|
|
2993
|
+
id: collectionId,
|
|
2994
|
+
name: collection.name,
|
|
2995
|
+
display_name: collection.display_name,
|
|
2996
|
+
description: collection.description,
|
|
2997
|
+
item_count: itemCount,
|
|
2998
|
+
is_indexed: false,
|
|
2999
|
+
is_dismissed: false,
|
|
3000
|
+
is_new: true
|
|
3001
|
+
},
|
|
3002
|
+
message: `New collection "${collection.display_name}" with ${itemCount} items available for indexing`
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
return notifications;
|
|
3006
|
+
} catch (error) {
|
|
3007
|
+
console.error("Error detecting new collections:", error);
|
|
3008
|
+
return [];
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
/**
|
|
3012
|
+
* Get all collections with indexing status
|
|
3013
|
+
*/
|
|
3014
|
+
async getAllCollections() {
|
|
3015
|
+
try {
|
|
3016
|
+
const collectionsStmt = this.db.prepare(
|
|
3017
|
+
"SELECT id, name, display_name, description FROM collections WHERE is_active = 1 ORDER BY display_name"
|
|
3018
|
+
);
|
|
3019
|
+
const { results: allCollections } = await collectionsStmt.all();
|
|
3020
|
+
console.log("[AISearchService.getAllCollections] Raw collections from DB:", allCollections?.length || 0);
|
|
3021
|
+
const firstCollection = allCollections?.[0];
|
|
3022
|
+
if (firstCollection) {
|
|
3023
|
+
console.log("[AISearchService.getAllCollections] Sample collection:", {
|
|
3024
|
+
id: firstCollection.id,
|
|
3025
|
+
name: firstCollection.name,
|
|
3026
|
+
display_name: firstCollection.display_name
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
const collections2 = (allCollections || []).filter(
|
|
3030
|
+
(col) => col.id && col.name
|
|
3031
|
+
);
|
|
3032
|
+
console.log("[AISearchService.getAllCollections] After filtering test collections:", collections2.length);
|
|
3033
|
+
console.log("[AISearchService.getAllCollections] Remaining collections:", collections2.map((c) => c.name).join(", "));
|
|
3034
|
+
const settings = await this.getSettings();
|
|
3035
|
+
const selected = settings?.selected_collections || [];
|
|
3036
|
+
const dismissed = settings?.dismissed_collections || [];
|
|
3037
|
+
console.log("[AISearchService.getAllCollections] Settings:", {
|
|
3038
|
+
selected_count: selected.length,
|
|
3039
|
+
dismissed_count: dismissed.length,
|
|
3040
|
+
selected
|
|
3041
|
+
});
|
|
3042
|
+
const collectionInfos = [];
|
|
3043
|
+
for (const collection of collections2) {
|
|
3044
|
+
if (!collection.id || !collection.name) continue;
|
|
3045
|
+
const collectionId = String(collection.id);
|
|
3046
|
+
if (!collectionId) {
|
|
3047
|
+
console.warn("[AISearchService] Skipping invalid collection:", collection);
|
|
3048
|
+
continue;
|
|
2671
3049
|
}
|
|
2672
|
-
.
|
|
2673
|
-
|
|
3050
|
+
const countStmt = this.db.prepare(
|
|
3051
|
+
"SELECT COUNT(*) as count FROM content WHERE collection_id = ?"
|
|
3052
|
+
);
|
|
3053
|
+
const countResult = await countStmt.bind(collectionId).first();
|
|
3054
|
+
const itemCount = countResult?.count || 0;
|
|
3055
|
+
collectionInfos.push({
|
|
3056
|
+
id: collectionId,
|
|
3057
|
+
name: collection.name,
|
|
3058
|
+
display_name: collection.display_name || collection.name,
|
|
3059
|
+
description: collection.description,
|
|
3060
|
+
item_count: itemCount,
|
|
3061
|
+
is_indexed: selected.includes(collectionId),
|
|
3062
|
+
is_dismissed: dismissed.includes(collectionId),
|
|
3063
|
+
is_new: !selected.includes(collectionId) && !dismissed.includes(collectionId)
|
|
3064
|
+
});
|
|
3065
|
+
}
|
|
3066
|
+
console.log("[AISearchService.getAllCollections] Returning collectionInfos:", collectionInfos.length);
|
|
3067
|
+
const firstInfo = collectionInfos[0];
|
|
3068
|
+
if (collectionInfos.length > 0 && firstInfo) {
|
|
3069
|
+
console.log("[AISearchService.getAllCollections] First collectionInfo:", {
|
|
3070
|
+
id: firstInfo.id,
|
|
3071
|
+
name: firstInfo.name,
|
|
3072
|
+
display_name: firstInfo.display_name,
|
|
3073
|
+
item_count: firstInfo.item_count
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
return collectionInfos;
|
|
3077
|
+
} catch (error) {
|
|
3078
|
+
console.error("[AISearchService] Error fetching collections:", error);
|
|
3079
|
+
return [];
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
/**
|
|
3083
|
+
* Execute search query
|
|
3084
|
+
*/
|
|
3085
|
+
async search(query) {
|
|
3086
|
+
const settings = await this.getSettings();
|
|
3087
|
+
if (!settings?.enabled) {
|
|
3088
|
+
return {
|
|
3089
|
+
results: [],
|
|
3090
|
+
total: 0,
|
|
3091
|
+
query_time_ms: 0,
|
|
3092
|
+
mode: query.mode
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
if (query.mode === "ai" && settings.ai_mode_enabled && this.customRAG?.isAvailable()) {
|
|
3096
|
+
return this.searchAI(query, settings);
|
|
3097
|
+
}
|
|
3098
|
+
return this.searchKeyword(query, settings);
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* AI-powered semantic search using Custom RAG
|
|
3102
|
+
*/
|
|
3103
|
+
async searchAI(query, settings) {
|
|
3104
|
+
try {
|
|
3105
|
+
if (!this.customRAG) {
|
|
3106
|
+
console.warn("[AISearchService] CustomRAG not available, falling back to keyword search");
|
|
3107
|
+
return this.searchKeyword(query, settings);
|
|
3108
|
+
}
|
|
3109
|
+
const result = await this.customRAG.search(query, settings);
|
|
3110
|
+
return result;
|
|
3111
|
+
} catch (error) {
|
|
3112
|
+
console.error("[AISearchService] AI search error, falling back to keyword:", error);
|
|
3113
|
+
return this.searchKeyword(query, settings);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
/**
|
|
3117
|
+
* Traditional keyword search
|
|
3118
|
+
*/
|
|
3119
|
+
async searchKeyword(query, settings) {
|
|
3120
|
+
const startTime = Date.now();
|
|
3121
|
+
try {
|
|
3122
|
+
const conditions = [];
|
|
3123
|
+
const params = [];
|
|
3124
|
+
if (query.query) {
|
|
3125
|
+
conditions.push("(c.title LIKE ? OR c.slug LIKE ? OR c.data LIKE ?)");
|
|
3126
|
+
const searchTerm = `%${query.query}%`;
|
|
3127
|
+
params.push(searchTerm, searchTerm, searchTerm);
|
|
3128
|
+
}
|
|
3129
|
+
if (query.filters?.collections && query.filters.collections.length > 0) {
|
|
3130
|
+
const placeholders = query.filters.collections.map(() => "?").join(",");
|
|
3131
|
+
conditions.push(`c.collection_id IN (${placeholders})`);
|
|
3132
|
+
params.push(...query.filters.collections);
|
|
3133
|
+
} else if (settings.selected_collections.length > 0) {
|
|
3134
|
+
const placeholders = settings.selected_collections.map(() => "?").join(",");
|
|
3135
|
+
conditions.push(`c.collection_id IN (${placeholders})`);
|
|
3136
|
+
params.push(...settings.selected_collections);
|
|
3137
|
+
}
|
|
3138
|
+
if (query.filters?.status && query.filters.status.length > 0) {
|
|
3139
|
+
const placeholders = query.filters.status.map(() => "?").join(",");
|
|
3140
|
+
conditions.push(`c.status IN (${placeholders})`);
|
|
3141
|
+
params.push(...query.filters.status);
|
|
3142
|
+
} else {
|
|
3143
|
+
conditions.push("c.status != 'deleted'");
|
|
3144
|
+
}
|
|
3145
|
+
if (query.filters?.dateRange) {
|
|
3146
|
+
const field = query.filters.dateRange.field || "created_at";
|
|
3147
|
+
if (query.filters.dateRange.start) {
|
|
3148
|
+
conditions.push(`c.${field} >= ?`);
|
|
3149
|
+
params.push(query.filters.dateRange.start.getTime());
|
|
2674
3150
|
}
|
|
2675
|
-
.
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
margin-top: 20px;
|
|
3151
|
+
if (query.filters.dateRange.end) {
|
|
3152
|
+
conditions.push(`c.${field} <= ?`);
|
|
3153
|
+
params.push(query.filters.dateRange.end.getTime());
|
|
2679
3154
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
3155
|
+
}
|
|
3156
|
+
if (query.filters?.author) {
|
|
3157
|
+
conditions.push("c.author_id = ?");
|
|
3158
|
+
params.push(query.filters.author);
|
|
3159
|
+
}
|
|
3160
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3161
|
+
const countStmt = this.db.prepare(`
|
|
3162
|
+
SELECT COUNT(*) as count
|
|
3163
|
+
FROM content c
|
|
3164
|
+
${whereClause}
|
|
3165
|
+
`);
|
|
3166
|
+
const countResult = await countStmt.bind(...params).first();
|
|
3167
|
+
const total = countResult?.count || 0;
|
|
3168
|
+
const limit = query.limit || settings.results_limit;
|
|
3169
|
+
const offset = query.offset || 0;
|
|
3170
|
+
const resultsStmt = this.db.prepare(`
|
|
3171
|
+
SELECT
|
|
3172
|
+
c.id, c.title, c.slug, c.collection_id, c.status,
|
|
3173
|
+
c.created_at, c.updated_at, c.author_id, c.data,
|
|
3174
|
+
col.name as collection_name, col.display_name as collection_display_name,
|
|
3175
|
+
u.email as author_email
|
|
3176
|
+
FROM content c
|
|
3177
|
+
JOIN collections col ON c.collection_id = col.id
|
|
3178
|
+
LEFT JOIN users u ON c.author_id = u.id
|
|
3179
|
+
${whereClause}
|
|
3180
|
+
ORDER BY c.updated_at DESC
|
|
3181
|
+
LIMIT ? OFFSET ?
|
|
3182
|
+
`);
|
|
3183
|
+
const { results } = await resultsStmt.bind(...params, limit, offset).all();
|
|
3184
|
+
const searchResults = (results || []).map((row) => ({
|
|
3185
|
+
id: String(row.id),
|
|
3186
|
+
title: row.title || "Untitled",
|
|
3187
|
+
slug: row.slug || "",
|
|
3188
|
+
collection_id: String(row.collection_id),
|
|
3189
|
+
collection_name: row.collection_display_name || row.collection_name,
|
|
3190
|
+
snippet: this.extractSnippet(row.data, query.query),
|
|
3191
|
+
status: row.status,
|
|
3192
|
+
created_at: Number(row.created_at),
|
|
3193
|
+
updated_at: Number(row.updated_at),
|
|
3194
|
+
author_name: row.author_email
|
|
3195
|
+
}));
|
|
3196
|
+
const queryTime = Date.now() - startTime;
|
|
3197
|
+
await this.logSearch(query.query, query.mode, searchResults.length);
|
|
3198
|
+
return {
|
|
3199
|
+
results: searchResults,
|
|
3200
|
+
total,
|
|
3201
|
+
query_time_ms: queryTime,
|
|
3202
|
+
mode: query.mode
|
|
3203
|
+
};
|
|
3204
|
+
} catch (error) {
|
|
3205
|
+
console.error("Keyword search error:", error);
|
|
3206
|
+
return {
|
|
3207
|
+
results: [],
|
|
3208
|
+
total: 0,
|
|
3209
|
+
query_time_ms: Date.now() - startTime,
|
|
3210
|
+
mode: query.mode
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
/**
|
|
3215
|
+
* Extract snippet from content data
|
|
3216
|
+
*/
|
|
3217
|
+
extractSnippet(data, query) {
|
|
3218
|
+
try {
|
|
3219
|
+
const parsed = typeof data === "string" ? JSON.parse(data) : data;
|
|
3220
|
+
const text = JSON.stringify(parsed).toLowerCase();
|
|
3221
|
+
const queryLower = query.toLowerCase();
|
|
3222
|
+
const index = text.indexOf(queryLower);
|
|
3223
|
+
if (index === -1) {
|
|
3224
|
+
return JSON.stringify(parsed).substring(0, 200) + "...";
|
|
3225
|
+
}
|
|
3226
|
+
const start = Math.max(0, index - 50);
|
|
3227
|
+
const end = Math.min(text.length, index + query.length + 50);
|
|
3228
|
+
return text.substring(start, end) + "...";
|
|
3229
|
+
} catch {
|
|
3230
|
+
return data.substring(0, 200) + "...";
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* Get search suggestions (autocomplete)
|
|
3235
|
+
*/
|
|
3236
|
+
async getSearchSuggestions(partial) {
|
|
3237
|
+
try {
|
|
3238
|
+
const settings = await this.getSettings();
|
|
3239
|
+
if (!settings?.autocomplete_enabled) {
|
|
3240
|
+
return [];
|
|
3241
|
+
}
|
|
3242
|
+
if (this.customRAG?.isAvailable()) {
|
|
3243
|
+
try {
|
|
3244
|
+
const aiSuggestions = await this.customRAG.getSuggestions(partial, 5);
|
|
3245
|
+
if (aiSuggestions.length > 0) {
|
|
3246
|
+
return aiSuggestions;
|
|
3247
|
+
}
|
|
3248
|
+
} catch (error) {
|
|
3249
|
+
console.error("[AISearchService] Error getting AI suggestions:", error);
|
|
2687
3250
|
}
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
3251
|
+
}
|
|
3252
|
+
const stmt = this.db.prepare(`
|
|
3253
|
+
SELECT DISTINCT query
|
|
3254
|
+
FROM ai_search_history
|
|
3255
|
+
WHERE query LIKE ?
|
|
3256
|
+
ORDER BY created_at DESC
|
|
3257
|
+
LIMIT 10
|
|
3258
|
+
`);
|
|
3259
|
+
const { results } = await stmt.bind(`%${partial}%`).all();
|
|
3260
|
+
return (results || []).map((r) => r.query);
|
|
3261
|
+
} catch (error) {
|
|
3262
|
+
console.error("Error getting suggestions:", error);
|
|
3263
|
+
return [];
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
/**
|
|
3267
|
+
* Log search query to history
|
|
3268
|
+
*/
|
|
3269
|
+
async logSearch(query, mode, resultsCount) {
|
|
3270
|
+
try {
|
|
3271
|
+
const stmt = this.db.prepare(`
|
|
3272
|
+
INSERT INTO ai_search_history (query, mode, results_count, created_at)
|
|
3273
|
+
VALUES (?, ?, ?, ?)
|
|
3274
|
+
`);
|
|
3275
|
+
await stmt.bind(query, mode, resultsCount, Date.now()).run();
|
|
3276
|
+
} catch (error) {
|
|
3277
|
+
console.error("Error logging search:", error);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Get search analytics
|
|
3282
|
+
*/
|
|
3283
|
+
async getSearchAnalytics() {
|
|
3284
|
+
try {
|
|
3285
|
+
const totalStmt = this.db.prepare(`
|
|
3286
|
+
SELECT COUNT(*) as count
|
|
3287
|
+
FROM ai_search_history
|
|
3288
|
+
WHERE created_at >= ?
|
|
3289
|
+
`);
|
|
3290
|
+
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3;
|
|
3291
|
+
const totalResult = await totalStmt.bind(thirtyDaysAgo).first();
|
|
3292
|
+
const modeStmt = this.db.prepare(`
|
|
3293
|
+
SELECT mode, COUNT(*) as count
|
|
3294
|
+
FROM ai_search_history
|
|
3295
|
+
WHERE created_at >= ?
|
|
3296
|
+
GROUP BY mode
|
|
3297
|
+
`);
|
|
3298
|
+
const { results: modeResults } = await modeStmt.bind(thirtyDaysAgo).all();
|
|
3299
|
+
const aiCount = modeResults?.find((r) => r.mode === "ai")?.count || 0;
|
|
3300
|
+
const keywordCount = modeResults?.find((r) => r.mode === "keyword")?.count || 0;
|
|
3301
|
+
const popularStmt = this.db.prepare(`
|
|
3302
|
+
SELECT query, COUNT(*) as count
|
|
3303
|
+
FROM ai_search_history
|
|
3304
|
+
WHERE created_at >= ?
|
|
3305
|
+
GROUP BY query
|
|
3306
|
+
ORDER BY count DESC
|
|
3307
|
+
LIMIT 10
|
|
3308
|
+
`);
|
|
3309
|
+
const { results: popularResults } = await popularStmt.bind(thirtyDaysAgo).all();
|
|
3310
|
+
return {
|
|
3311
|
+
total_queries: totalResult?.count || 0,
|
|
3312
|
+
ai_queries: aiCount,
|
|
3313
|
+
keyword_queries: keywordCount,
|
|
3314
|
+
popular_queries: (popularResults || []).map((r) => ({
|
|
3315
|
+
query: r.query,
|
|
3316
|
+
count: r.count
|
|
3317
|
+
})),
|
|
3318
|
+
average_query_time: 0
|
|
3319
|
+
// TODO: Track query times
|
|
3320
|
+
};
|
|
3321
|
+
} catch (error) {
|
|
3322
|
+
console.error("Error getting analytics:", error);
|
|
3323
|
+
return {
|
|
3324
|
+
total_queries: 0,
|
|
3325
|
+
ai_queries: 0,
|
|
3326
|
+
keyword_queries: 0,
|
|
3327
|
+
popular_queries: [],
|
|
3328
|
+
average_query_time: 0
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
/**
|
|
3333
|
+
* Verify Custom RAG is available
|
|
3334
|
+
*/
|
|
3335
|
+
verifyBinding() {
|
|
3336
|
+
return this.customRAG?.isAvailable() ?? false;
|
|
3337
|
+
}
|
|
3338
|
+
/**
|
|
3339
|
+
* Get Custom RAG service instance (for indexer)
|
|
3340
|
+
*/
|
|
3341
|
+
getCustomRAG() {
|
|
3342
|
+
return this.customRAG;
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
3345
|
+
|
|
3346
|
+
// src/plugins/core-plugins/ai-search-plugin/services/indexer.ts
|
|
3347
|
+
var IndexManager = class {
|
|
3348
|
+
constructor(db, ai, vectorize) {
|
|
3349
|
+
this.db = db;
|
|
3350
|
+
this.ai = ai;
|
|
3351
|
+
this.vectorize = vectorize;
|
|
3352
|
+
if (this.ai && this.vectorize) {
|
|
3353
|
+
this.customRAG = new CustomRAGService(db, ai, vectorize);
|
|
3354
|
+
console.log("[IndexManager] Custom RAG initialized");
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
customRAG;
|
|
3358
|
+
/**
|
|
3359
|
+
* Index all content items within a collection using Custom RAG
|
|
3360
|
+
*/
|
|
3361
|
+
async indexCollection(collectionId) {
|
|
3362
|
+
try {
|
|
3363
|
+
const collectionStmt = this.db.prepare(
|
|
3364
|
+
"SELECT id, name, display_name FROM collections WHERE id = ?"
|
|
3365
|
+
);
|
|
3366
|
+
const collection = await collectionStmt.bind(collectionId).first();
|
|
3367
|
+
if (!collection) {
|
|
3368
|
+
throw new Error(`Collection ${collectionId} not found`);
|
|
3369
|
+
}
|
|
3370
|
+
await this.updateIndexStatus(collectionId, {
|
|
3371
|
+
collection_id: collectionId,
|
|
3372
|
+
collection_name: collection.display_name,
|
|
3373
|
+
total_items: 0,
|
|
3374
|
+
indexed_items: 0,
|
|
3375
|
+
status: "indexing"
|
|
3376
|
+
});
|
|
3377
|
+
if (this.customRAG?.isAvailable()) {
|
|
3378
|
+
console.log(`[IndexManager] Using Custom RAG to index collection ${collectionId}`);
|
|
3379
|
+
const result = await this.customRAG.indexCollection(collectionId);
|
|
3380
|
+
const finalStatus = {
|
|
3381
|
+
collection_id: collectionId,
|
|
3382
|
+
collection_name: collection.display_name,
|
|
3383
|
+
total_items: result.total_items,
|
|
3384
|
+
indexed_items: result.indexed_chunks,
|
|
3385
|
+
last_sync_at: Date.now(),
|
|
3386
|
+
status: result.errors > 0 ? "error" : "completed",
|
|
3387
|
+
error_message: result.errors > 0 ? `${result.errors} errors during indexing` : void 0
|
|
3388
|
+
};
|
|
3389
|
+
await this.updateIndexStatus(collectionId, finalStatus);
|
|
3390
|
+
return finalStatus;
|
|
3391
|
+
}
|
|
3392
|
+
console.warn(`[IndexManager] Custom RAG not available, skipping indexing for ${collectionId}`);
|
|
3393
|
+
const fallbackStatus = {
|
|
3394
|
+
collection_id: collectionId,
|
|
3395
|
+
collection_name: collection.display_name,
|
|
3396
|
+
total_items: 0,
|
|
3397
|
+
indexed_items: 0,
|
|
3398
|
+
last_sync_at: Date.now(),
|
|
3399
|
+
status: "completed",
|
|
3400
|
+
error_message: "Custom RAG not available - using keyword search only"
|
|
3401
|
+
};
|
|
3402
|
+
await this.updateIndexStatus(collectionId, fallbackStatus);
|
|
3403
|
+
return fallbackStatus;
|
|
3404
|
+
} catch (error) {
|
|
3405
|
+
console.error(`[IndexManager] Error indexing collection ${collectionId}:`, error);
|
|
3406
|
+
const errorStatus = {
|
|
3407
|
+
collection_id: collectionId,
|
|
3408
|
+
collection_name: "Unknown",
|
|
3409
|
+
total_items: 0,
|
|
3410
|
+
indexed_items: 0,
|
|
3411
|
+
status: "error",
|
|
3412
|
+
error_message: error instanceof Error ? error.message : String(error)
|
|
3413
|
+
};
|
|
3414
|
+
await this.updateIndexStatus(collectionId, errorStatus);
|
|
3415
|
+
return errorStatus;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
/**
|
|
3419
|
+
* Index a single content item
|
|
3420
|
+
*/
|
|
3421
|
+
async indexContentItem(item, collectionId) {
|
|
3422
|
+
try {
|
|
3423
|
+
let parsedData = {};
|
|
3424
|
+
try {
|
|
3425
|
+
parsedData = typeof item.data === "string" ? JSON.parse(item.data) : item.data;
|
|
3426
|
+
} catch {
|
|
3427
|
+
parsedData = {};
|
|
3428
|
+
}
|
|
3429
|
+
const document = {
|
|
3430
|
+
id: `content_${item.id}`,
|
|
3431
|
+
title: item.title || "Untitled",
|
|
3432
|
+
slug: item.slug || "",
|
|
3433
|
+
content: this.extractSearchableText(parsedData),
|
|
3434
|
+
metadata: {
|
|
3435
|
+
collection_id: collectionId,
|
|
3436
|
+
collection_name: item.collection_name,
|
|
3437
|
+
collection_display_name: item.collection_display_name,
|
|
3438
|
+
status: item.status,
|
|
3439
|
+
created_at: item.created_at,
|
|
3440
|
+
updated_at: item.updated_at,
|
|
3441
|
+
author_id: item.author_id
|
|
2695
3442
|
}
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
3443
|
+
};
|
|
3444
|
+
console.log(`Indexed content item: ${item.id}`);
|
|
3445
|
+
} catch (error) {
|
|
3446
|
+
console.error(`Error indexing content item ${item.id}:`, error);
|
|
3447
|
+
throw error;
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
/**
|
|
3451
|
+
* Extract searchable text from content data
|
|
3452
|
+
*/
|
|
3453
|
+
extractSearchableText(data) {
|
|
3454
|
+
const parts = [];
|
|
3455
|
+
if (data.title) parts.push(String(data.title));
|
|
3456
|
+
if (data.name) parts.push(String(data.name));
|
|
3457
|
+
if (data.description) parts.push(String(data.description));
|
|
3458
|
+
if (data.content) parts.push(String(data.content));
|
|
3459
|
+
if (data.body) parts.push(String(data.body));
|
|
3460
|
+
if (data.text) parts.push(String(data.text));
|
|
3461
|
+
const extractStrings = (obj) => {
|
|
3462
|
+
if (typeof obj === "string") {
|
|
3463
|
+
parts.push(obj);
|
|
3464
|
+
} else if (Array.isArray(obj)) {
|
|
3465
|
+
obj.forEach(extractStrings);
|
|
3466
|
+
} else if (obj && typeof obj === "object") {
|
|
3467
|
+
Object.values(obj).forEach(extractStrings);
|
|
3468
|
+
}
|
|
3469
|
+
};
|
|
3470
|
+
extractStrings(data);
|
|
3471
|
+
return parts.join(" ");
|
|
3472
|
+
}
|
|
3473
|
+
/**
|
|
3474
|
+
* Update a single content item in the index
|
|
3475
|
+
*/
|
|
3476
|
+
async updateIndex(collectionId, contentId) {
|
|
3477
|
+
try {
|
|
3478
|
+
const stmt = this.db.prepare(`
|
|
3479
|
+
SELECT
|
|
3480
|
+
c.id, c.title, c.slug, c.data, c.status,
|
|
3481
|
+
c.created_at, c.updated_at, c.author_id,
|
|
3482
|
+
col.name as collection_name, col.display_name as collection_display_name
|
|
3483
|
+
FROM content c
|
|
3484
|
+
JOIN collections col ON c.collection_id = col.id
|
|
3485
|
+
WHERE c.id = ? AND c.collection_id = ?
|
|
3486
|
+
`);
|
|
3487
|
+
const item = await stmt.bind(contentId, collectionId).first();
|
|
3488
|
+
if (!item) {
|
|
3489
|
+
throw new Error(`Content item ${contentId} not found`);
|
|
3490
|
+
}
|
|
3491
|
+
await this.indexContentItem(item, String(collectionId));
|
|
3492
|
+
const status = await this.getIndexStatus(String(collectionId));
|
|
3493
|
+
if (status) {
|
|
3494
|
+
await this.updateIndexStatus(String(collectionId), {
|
|
3495
|
+
...status,
|
|
3496
|
+
last_sync_at: Date.now()
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
} catch (error) {
|
|
3500
|
+
console.error(`Error updating index for content ${contentId}:`, error);
|
|
3501
|
+
throw error;
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
/**
|
|
3505
|
+
* Remove a content item from the index using Custom RAG
|
|
3506
|
+
*/
|
|
3507
|
+
async removeFromIndex(collectionId, contentId) {
|
|
3508
|
+
try {
|
|
3509
|
+
if (this.customRAG?.isAvailable()) {
|
|
3510
|
+
console.log(`[IndexManager] Removing content ${contentId} from index`);
|
|
3511
|
+
await this.customRAG.removeContentFromIndex(contentId);
|
|
3512
|
+
} else {
|
|
3513
|
+
console.warn(`[IndexManager] Custom RAG not available, skipping removal for ${contentId}`);
|
|
3514
|
+
}
|
|
3515
|
+
} catch (error) {
|
|
3516
|
+
console.error(`[IndexManager] Error removing content ${contentId} from index:`, error);
|
|
3517
|
+
throw error;
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
/**
|
|
3521
|
+
* Get indexing status for a collection
|
|
3522
|
+
*/
|
|
3523
|
+
async getIndexStatus(collectionId) {
|
|
3524
|
+
try {
|
|
3525
|
+
const stmt = this.db.prepare(
|
|
3526
|
+
"SELECT * FROM ai_search_index_meta WHERE collection_id = ?"
|
|
3527
|
+
);
|
|
3528
|
+
const result = await stmt.bind(collectionId).first();
|
|
3529
|
+
if (!result) {
|
|
3530
|
+
return null;
|
|
3531
|
+
}
|
|
3532
|
+
return {
|
|
3533
|
+
collection_id: String(result.collection_id),
|
|
3534
|
+
collection_name: result.collection_name,
|
|
3535
|
+
total_items: result.total_items,
|
|
3536
|
+
indexed_items: result.indexed_items,
|
|
3537
|
+
last_sync_at: result.last_sync_at,
|
|
3538
|
+
status: result.status,
|
|
3539
|
+
error_message: result.error_message
|
|
3540
|
+
};
|
|
3541
|
+
} catch (error) {
|
|
3542
|
+
console.error(`Error getting index status for collection ${collectionId}:`, error);
|
|
3543
|
+
return null;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
/**
|
|
3547
|
+
* Get indexing status for all collections
|
|
3548
|
+
*/
|
|
3549
|
+
async getAllIndexStatus() {
|
|
3550
|
+
try {
|
|
3551
|
+
const stmt = this.db.prepare("SELECT * FROM ai_search_index_meta");
|
|
3552
|
+
const { results } = await stmt.all();
|
|
3553
|
+
const statusMap = {};
|
|
3554
|
+
for (const row of results || []) {
|
|
3555
|
+
const collectionId = String(row.collection_id);
|
|
3556
|
+
statusMap[collectionId] = {
|
|
3557
|
+
collection_id: collectionId,
|
|
3558
|
+
collection_name: row.collection_name,
|
|
3559
|
+
total_items: row.total_items,
|
|
3560
|
+
indexed_items: row.indexed_items,
|
|
3561
|
+
last_sync_at: row.last_sync_at,
|
|
3562
|
+
status: row.status,
|
|
3563
|
+
error_message: row.error_message
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
return statusMap;
|
|
3567
|
+
} catch (error) {
|
|
3568
|
+
console.error("Error getting all index status:", error);
|
|
3569
|
+
return {};
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* Update index status in database
|
|
3574
|
+
*/
|
|
3575
|
+
async updateIndexStatus(collectionId, status) {
|
|
3576
|
+
try {
|
|
3577
|
+
const checkStmt = this.db.prepare(
|
|
3578
|
+
"SELECT id FROM ai_search_index_meta WHERE collection_id = ?"
|
|
3579
|
+
);
|
|
3580
|
+
const existing = await checkStmt.bind(collectionId).first();
|
|
3581
|
+
if (existing) {
|
|
3582
|
+
const stmt = this.db.prepare(`
|
|
3583
|
+
UPDATE ai_search_index_meta
|
|
3584
|
+
SET collection_name = ?,
|
|
3585
|
+
total_items = ?,
|
|
3586
|
+
indexed_items = ?,
|
|
3587
|
+
last_sync_at = ?,
|
|
3588
|
+
status = ?,
|
|
3589
|
+
error_message = ?
|
|
3590
|
+
WHERE collection_id = ?
|
|
3591
|
+
`);
|
|
3592
|
+
await stmt.bind(
|
|
3593
|
+
status.collection_name,
|
|
3594
|
+
status.total_items,
|
|
3595
|
+
status.indexed_items,
|
|
3596
|
+
status.last_sync_at || null,
|
|
3597
|
+
status.status,
|
|
3598
|
+
status.error_message || null,
|
|
3599
|
+
String(collectionId)
|
|
3600
|
+
).run();
|
|
3601
|
+
} else {
|
|
3602
|
+
const stmt = this.db.prepare(`
|
|
3603
|
+
INSERT INTO ai_search_index_meta (
|
|
3604
|
+
collection_id, collection_name, total_items, indexed_items,
|
|
3605
|
+
last_sync_at, status, error_message
|
|
3606
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3607
|
+
`);
|
|
3608
|
+
await stmt.bind(
|
|
3609
|
+
String(status.collection_id),
|
|
3610
|
+
status.collection_name,
|
|
3611
|
+
status.total_items,
|
|
3612
|
+
status.indexed_items,
|
|
3613
|
+
status.last_sync_at || null,
|
|
3614
|
+
status.status,
|
|
3615
|
+
status.error_message || null
|
|
3616
|
+
).run();
|
|
3617
|
+
}
|
|
3618
|
+
} catch (error) {
|
|
3619
|
+
console.error(`Error updating index status for collection ${collectionId}:`, error);
|
|
3620
|
+
throw error;
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Sync all selected collections
|
|
3625
|
+
*/
|
|
3626
|
+
async syncAll(selectedCollections) {
|
|
3627
|
+
for (const collectionId of selectedCollections) {
|
|
3628
|
+
try {
|
|
3629
|
+
await this.indexCollection(collectionId);
|
|
3630
|
+
} catch (error) {
|
|
3631
|
+
console.error(`Error syncing collection ${collectionId}:`, error);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
|
|
3637
|
+
// src/plugins/core-plugins/ai-search-plugin/components/settings-page.ts
|
|
3638
|
+
function renderSettingsPage(data) {
|
|
3639
|
+
const settings = data.settings || {
|
|
3640
|
+
enabled: false,
|
|
3641
|
+
ai_mode_enabled: true,
|
|
3642
|
+
selected_collections: [],
|
|
3643
|
+
dismissed_collections: [],
|
|
3644
|
+
autocomplete_enabled: true,
|
|
3645
|
+
cache_duration: 1,
|
|
3646
|
+
results_limit: 20,
|
|
3647
|
+
index_media: false
|
|
3648
|
+
};
|
|
3649
|
+
const selectedCollections = Array.isArray(settings.selected_collections) ? settings.selected_collections : [];
|
|
3650
|
+
const dismissedCollections = Array.isArray(settings.dismissed_collections) ? settings.dismissed_collections : [];
|
|
3651
|
+
const enabled = settings.enabled === true;
|
|
3652
|
+
const aiModeEnabled = settings.ai_mode_enabled !== false;
|
|
3653
|
+
const autocompleteEnabled = settings.autocomplete_enabled !== false;
|
|
3654
|
+
const indexMedia = settings.index_media === true;
|
|
3655
|
+
const selectedCollectionIds = new Set(selectedCollections.map((id) => String(id)));
|
|
3656
|
+
const dismissedCollectionIds = new Set(dismissedCollections.map((id) => String(id)));
|
|
3657
|
+
const collections2 = Array.isArray(data.collections) ? data.collections : [];
|
|
3658
|
+
console.log("[SettingsPage Template] Collections received:", collections2.length);
|
|
3659
|
+
if (collections2.length > 0) {
|
|
3660
|
+
console.log("[SettingsPage Template] First collection:", collections2[0]);
|
|
3661
|
+
}
|
|
3662
|
+
const content2 = `
|
|
3663
|
+
<div class="w-full px-4 sm:px-6 lg:px-8 py-6">
|
|
3664
|
+
<!-- Header with Back Button -->
|
|
3665
|
+
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
|
|
3666
|
+
<div>
|
|
3667
|
+
<h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">\u{1F50D} AI Search Settings</h1>
|
|
3668
|
+
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
|
|
3669
|
+
Configure advanced search with Cloudflare AI Search. Select collections to index and manage search preferences.
|
|
3670
|
+
</p>
|
|
3671
|
+
</div>
|
|
3672
|
+
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
|
3673
|
+
<a href="/admin/plugins" class="inline-flex items-center justify-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm">
|
|
3674
|
+
<svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3675
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
3676
|
+
</svg>
|
|
3677
|
+
Back to Plugins
|
|
3678
|
+
</a>
|
|
3679
|
+
</div>
|
|
3680
|
+
</div>
|
|
3681
|
+
|
|
3682
|
+
|
|
3683
|
+
<!-- Main Settings Card -->
|
|
3684
|
+
<div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 mb-6">
|
|
3685
|
+
<form id="settingsForm" class="space-y-6">
|
|
3686
|
+
<!-- Enable Search Section -->
|
|
3687
|
+
<div>
|
|
3688
|
+
<h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">\u{1F50D} Search Settings</h2>
|
|
3689
|
+
<div class="space-y-3">
|
|
3690
|
+
<div class="flex items-center gap-3 p-4 border border-indigo-200 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg">
|
|
3691
|
+
<input type="checkbox" id="enabled" name="enabled" ${enabled ? "checked" : ""} class="w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer">
|
|
3692
|
+
<div class="flex-1">
|
|
3693
|
+
<label for="enabled" class="text-base font-semibold text-zinc-900 dark:text-white select-none cursor-pointer block">Enable AI Search</label>
|
|
3694
|
+
<p class="text-xs text-zinc-600 dark:text-zinc-400 mt-0.5">Turn on advanced search capabilities across your content</p>
|
|
3695
|
+
</div>
|
|
3696
|
+
</div>
|
|
3697
|
+
|
|
3698
|
+
<div class="flex items-center gap-3 p-4 border border-blue-200 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
|
3699
|
+
<input type="checkbox" id="ai_mode_enabled" name="ai_mode_enabled" ${aiModeEnabled ? "checked" : ""} class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer">
|
|
3700
|
+
<div class="flex-1">
|
|
3701
|
+
<label for="ai_mode_enabled" class="text-base font-semibold text-zinc-900 dark:text-white select-none cursor-pointer block">\u{1F916} AI/Semantic Search</label>
|
|
3702
|
+
<p class="text-xs text-zinc-600 dark:text-zinc-400 mt-0.5">
|
|
3703
|
+
Enable natural language queries (requires Cloudflare Workers AI binding)
|
|
3704
|
+
<a href="https://developers.cloudflare.com/workers-ai/" target="_blank" class="text-blue-600 dark:text-blue-400 hover:underline ml-1">\u2192 Setup Guide</a>
|
|
3705
|
+
</p>
|
|
3706
|
+
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
|
|
3707
|
+
\u26A0\uFE0F If AI binding unavailable, will fallback to keyword search
|
|
3708
|
+
</p>
|
|
3709
|
+
</div>
|
|
3710
|
+
</div>
|
|
3711
|
+
</div>
|
|
3712
|
+
</div>
|
|
3713
|
+
|
|
3714
|
+
<hr class="border-zinc-200 dark:border-zinc-800">
|
|
3715
|
+
|
|
3716
|
+
<!-- Collections Section -->
|
|
3717
|
+
<div>
|
|
3718
|
+
<div class="flex items-start justify-between mb-4">
|
|
3719
|
+
<div>
|
|
3720
|
+
<h2 class="text-xl font-semibold text-zinc-950 dark:text-white">\u{1F4DA} Collections to Index</h2>
|
|
3721
|
+
<p class="text-sm text-zinc-600 dark:text-zinc-400 mt-1">
|
|
3722
|
+
Select which content collections should be indexed and searchable. Only checked collections will be included in search results.
|
|
3723
|
+
</p>
|
|
3724
|
+
</div>
|
|
3725
|
+
</div>
|
|
3726
|
+
<div class="space-y-3 max-h-96 overflow-y-auto border-2 border-zinc-300 dark:border-zinc-700 rounded-lg p-4 bg-white dark:bg-zinc-800" id="collections-list">
|
|
3727
|
+
${collections2.length === 0 ? '<p class="text-sm text-zinc-500 dark:text-zinc-400 p-4">No collections available. Create collections first.</p>' : collections2.map((collection) => {
|
|
3728
|
+
const collectionId = String(collection.id);
|
|
3729
|
+
const isChecked = selectedCollectionIds.has(collectionId);
|
|
3730
|
+
const isDismissed = dismissedCollectionIds.has(collectionId);
|
|
3731
|
+
const indexStatusMap = data.indexStatus || {};
|
|
3732
|
+
const status = indexStatusMap[collectionId];
|
|
3733
|
+
const isNew = collection.is_new === true && !isDismissed && !status;
|
|
3734
|
+
const statusBadge = status && isChecked ? `<span class="ml-2 px-2 py-1 text-xs rounded-full ${status.status === "completed" ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" : status.status === "indexing" ? "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300" : status.status === "error" ? "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300" : "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300"}">${status.status}</span>` : "";
|
|
3735
|
+
return `<div class="flex items-start gap-3 p-3 rounded-lg border border-zinc-200 dark:border-zinc-700 ${isNew ? "bg-blue-50 dark:bg-blue-900/10 border-blue-200 dark:border-blue-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800"}">
|
|
3736
|
+
<input
|
|
3737
|
+
type="checkbox"
|
|
3738
|
+
id="collection_${collectionId}"
|
|
3739
|
+
name="selected_collections"
|
|
3740
|
+
value="${collectionId}"
|
|
3741
|
+
${isChecked ? "checked" : ""}
|
|
3742
|
+
class="mt-1 w-5 h-5 text-indigo-600 bg-white border-gray-300 rounded focus:ring-indigo-500 focus:ring-2 cursor-pointer"
|
|
3743
|
+
style="cursor: pointer; flex-shrink: 0;"
|
|
3744
|
+
/>
|
|
3745
|
+
<div class="flex-1 min-w-0">
|
|
3746
|
+
<label for="collection_${collectionId}" class="text-sm font-medium text-zinc-950 dark:text-white select-none cursor-pointer flex items-center">
|
|
3747
|
+
${collection.display_name || collection.name || "Unnamed Collection"}
|
|
3748
|
+
${isNew ? '<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300">NEW</span>' : ""}
|
|
3749
|
+
${statusBadge}
|
|
3750
|
+
</label>
|
|
3751
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
|
|
3752
|
+
${collection.description || collection.name || "No description"} \u2022 ${collection.item_count || 0} items
|
|
3753
|
+
${status ? ` \u2022 ${status.indexed_items}/${status.total_items} indexed` : ""}
|
|
3754
|
+
</p>
|
|
3755
|
+
${status && status.status === "indexing" ? `<div class="mt-2 w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
|
|
3756
|
+
<div class="bg-blue-600 h-2 rounded-full" style="width: ${status.indexed_items / status.total_items * 100}%"></div>
|
|
3757
|
+
</div>` : ""}
|
|
3758
|
+
</div>
|
|
3759
|
+
${isChecked ? `
|
|
3760
|
+
<button
|
|
3761
|
+
type="button"
|
|
3762
|
+
onclick="reindexCollection('${collectionId}')"
|
|
3763
|
+
class="px-3 py-1.5 text-xs font-medium text-white bg-indigo-600 hover:bg-indigo-700 rounded-md transition-colors flex items-center gap-1.5 whitespace-nowrap"
|
|
3764
|
+
${status && status.status === "indexing" ? "disabled" : ""}
|
|
3765
|
+
>
|
|
3766
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3767
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
3768
|
+
</svg>
|
|
3769
|
+
Re-index
|
|
3770
|
+
</button>
|
|
3771
|
+
` : ""}
|
|
3772
|
+
</div>`;
|
|
3773
|
+
}).join("")}
|
|
3774
|
+
</div>
|
|
3775
|
+
</div>
|
|
3776
|
+
|
|
3777
|
+
<hr class="border-zinc-200 dark:border-zinc-800">
|
|
3778
|
+
|
|
3779
|
+
<!-- Advanced Options -->
|
|
3780
|
+
<div>
|
|
3781
|
+
<h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">\u2699\uFE0F Advanced Options</h2>
|
|
3782
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3783
|
+
<div class="flex items-start gap-3 p-3 border border-zinc-200 dark:border-zinc-700 rounded-lg">
|
|
3784
|
+
<input type="checkbox" id="autocomplete_enabled" name="autocomplete_enabled" ${autocompleteEnabled ? "checked" : ""} class="mt-0.5 w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer">
|
|
3785
|
+
<div>
|
|
3786
|
+
<label for="autocomplete_enabled" class="text-sm font-medium text-zinc-950 dark:text-white select-none cursor-pointer block">Autocomplete Suggestions</label>
|
|
3787
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">Show search suggestions as users type</p>
|
|
3788
|
+
</div>
|
|
3789
|
+
</div>
|
|
3790
|
+
|
|
3791
|
+
<div class="flex items-start gap-3 p-3 border border-zinc-200 dark:border-zinc-700 rounded-lg">
|
|
3792
|
+
<input type="checkbox" id="index_media" name="index_media" ${indexMedia ? "checked" : ""} class="mt-0.5 w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer">
|
|
3793
|
+
<div>
|
|
3794
|
+
<label for="index_media" class="text-sm font-medium text-zinc-950 dark:text-white select-none cursor-pointer block">Index Media Metadata</label>
|
|
3795
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">Include media files in search results</p>
|
|
3796
|
+
</div>
|
|
3797
|
+
</div>
|
|
3798
|
+
|
|
3799
|
+
<div>
|
|
3800
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Cache Duration (hours)</label>
|
|
3801
|
+
<input type="number" id="cache_duration" name="cache_duration" value="${settings.cache_duration || 1}" min="0" max="24" class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-indigo-500">
|
|
3802
|
+
</div>
|
|
3803
|
+
<div>
|
|
3804
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Results Per Page</label>
|
|
3805
|
+
<input type="number" id="results_limit" name="results_limit" value="${settings.results_limit || 20}" min="10" max="100" class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-indigo-500">
|
|
3806
|
+
</div>
|
|
3807
|
+
</div>
|
|
3808
|
+
</div>
|
|
3809
|
+
|
|
3810
|
+
<!-- Save Button -->
|
|
3811
|
+
<div class="flex items-center justify-between pt-4 border-t border-zinc-200 dark:border-zinc-800">
|
|
3812
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400">
|
|
3813
|
+
\u{1F4A1} Collections marked as <span class="px-1.5 py-0.5 text-xs font-medium rounded-full bg-blue-500 text-white">NEW</span> haven't been indexed yet
|
|
3814
|
+
</p>
|
|
3815
|
+
<button type="submit" class="inline-flex items-center justify-center rounded-lg bg-indigo-600 text-white px-6 py-2.5 text-sm font-semibold hover:bg-indigo-500 shadow-sm transition-colors">
|
|
3816
|
+
\u{1F4BE} Save Settings
|
|
3817
|
+
</button>
|
|
3818
|
+
</div>
|
|
3819
|
+
</form>
|
|
3820
|
+
</div>
|
|
3821
|
+
|
|
3822
|
+
|
|
3823
|
+
<!-- Search Analytics -->
|
|
3824
|
+
<div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6">
|
|
3825
|
+
<h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">\u{1F4CA} Search Analytics</h2>
|
|
3826
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
3827
|
+
<div class="p-4 rounded-lg bg-zinc-50 dark:bg-zinc-800">
|
|
3828
|
+
<div class="text-sm text-zinc-500 dark:text-zinc-400">Total Queries</div>
|
|
3829
|
+
<div class="text-2xl font-bold text-zinc-950 dark:text-white mt-1">${data.analytics.total_queries}</div>
|
|
3830
|
+
</div>
|
|
3831
|
+
<div class="p-4 rounded-lg bg-zinc-50 dark:bg-zinc-800">
|
|
3832
|
+
<div class="text-sm text-zinc-500 dark:text-zinc-400">AI Queries</div>
|
|
3833
|
+
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400 mt-1">${data.analytics.ai_queries}</div>
|
|
3834
|
+
</div>
|
|
3835
|
+
<div class="p-4 rounded-lg bg-zinc-50 dark:bg-zinc-800">
|
|
3836
|
+
<div class="text-sm text-zinc-500 dark:text-zinc-400">Keyword Queries</div>
|
|
3837
|
+
<div class="text-2xl font-bold text-indigo-600 dark:text-indigo-400 mt-1">${data.analytics.keyword_queries}</div>
|
|
3838
|
+
</div>
|
|
3839
|
+
</div>
|
|
3840
|
+
${data.analytics.popular_queries.length > 0 ? `
|
|
3841
|
+
<div>
|
|
3842
|
+
<h3 class="text-sm font-semibold text-zinc-950 dark:text-white mb-2">Popular Searches</h3>
|
|
3843
|
+
<div class="space-y-1">
|
|
3844
|
+
${data.analytics.popular_queries.map(
|
|
3845
|
+
(item) => `
|
|
3846
|
+
<div class="flex items-center justify-between text-sm">
|
|
3847
|
+
<span class="text-zinc-700 dark:text-zinc-300">"${item.query}"</span>
|
|
3848
|
+
<span class="text-zinc-500 dark:text-zinc-400">${item.count} times</span>
|
|
3849
|
+
</div>
|
|
3850
|
+
`
|
|
3851
|
+
).join("")}
|
|
3852
|
+
</div>
|
|
3853
|
+
</div>
|
|
3854
|
+
` : '<p class="text-sm text-zinc-500 dark:text-zinc-400">No search history yet.</p>'}
|
|
3855
|
+
</div>
|
|
3856
|
+
|
|
3857
|
+
<!-- Success Message -->
|
|
3858
|
+
<div id="msg" class="hidden fixed bottom-4 right-4 p-4 rounded-lg bg-green-50 text-green-900 border border-green-200 dark:bg-green-900/20 dark:text-green-100 dark:border-green-800 shadow-lg z-50">
|
|
3859
|
+
<div class="flex items-center gap-2">
|
|
3860
|
+
<span class="text-xl">\u2705</span>
|
|
3861
|
+
<span class="font-semibold">Settings Saved Successfully!</span>
|
|
3862
|
+
</div>
|
|
3863
|
+
</div>
|
|
3864
|
+
</div>
|
|
3865
|
+
<script>
|
|
3866
|
+
// Form submission with error handling
|
|
3867
|
+
document.getElementById('settingsForm').addEventListener('submit', async (e) => {
|
|
3868
|
+
e.preventDefault();
|
|
3869
|
+
console.log('[AI Search Client] Form submitted');
|
|
3870
|
+
|
|
3871
|
+
try {
|
|
3872
|
+
const btn = e.submitter;
|
|
3873
|
+
btn.innerText = 'Saving...';
|
|
3874
|
+
btn.disabled = true;
|
|
3875
|
+
|
|
3876
|
+
const formData = new FormData(e.target);
|
|
3877
|
+
const selectedCollections = Array.from(formData.getAll('selected_collections')).map(String);
|
|
3878
|
+
|
|
3879
|
+
const data = {
|
|
3880
|
+
enabled: document.getElementById('enabled').checked,
|
|
3881
|
+
ai_mode_enabled: document.getElementById('ai_mode_enabled').checked,
|
|
3882
|
+
selected_collections: selectedCollections,
|
|
3883
|
+
autocomplete_enabled: document.getElementById('autocomplete_enabled').checked,
|
|
3884
|
+
cache_duration: Number(formData.get('cache_duration')),
|
|
3885
|
+
results_limit: Number(formData.get('results_limit')),
|
|
3886
|
+
index_media: document.getElementById('index_media').checked,
|
|
3887
|
+
};
|
|
3888
|
+
|
|
3889
|
+
console.log('[AI Search Client] Sending data:', data);
|
|
3890
|
+
console.log('[AI Search Client] Selected collections:', selectedCollections);
|
|
3891
|
+
|
|
3892
|
+
const res = await fetch('/admin/plugins/ai-search', {
|
|
3893
|
+
method: 'POST',
|
|
3894
|
+
headers: {'Content-Type': 'application/json'},
|
|
3895
|
+
body: JSON.stringify(data)
|
|
3896
|
+
});
|
|
3897
|
+
|
|
3898
|
+
console.log('[AI Search Client] Response status:', res.status);
|
|
3899
|
+
|
|
3900
|
+
if (res.ok) {
|
|
3901
|
+
const result = await res.json();
|
|
3902
|
+
console.log('[AI Search Client] Save successful:', result);
|
|
3903
|
+
document.getElementById('msg').classList.remove('hidden');
|
|
3904
|
+
setTimeout(() => {
|
|
3905
|
+
document.getElementById('msg').classList.add('hidden');
|
|
3906
|
+
location.reload();
|
|
3907
|
+
}, 2000);
|
|
3908
|
+
} else {
|
|
3909
|
+
const error = await res.text();
|
|
3910
|
+
console.error('[AI Search Client] Save failed:', error);
|
|
3911
|
+
alert('Failed to save settings: ' + error);
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
btn.innerText = 'Save Settings';
|
|
3915
|
+
btn.disabled = false;
|
|
3916
|
+
} catch (error) {
|
|
3917
|
+
console.error('[AI Search Client] Error:', error);
|
|
3918
|
+
alert('Error saving settings: ' + error.message);
|
|
3919
|
+
}
|
|
3920
|
+
});
|
|
3921
|
+
|
|
3922
|
+
// Add collection to index
|
|
3923
|
+
async function addCollectionToIndex(collectionId) {
|
|
3924
|
+
const form = document.getElementById('settingsForm');
|
|
3925
|
+
const checkbox = document.getElementById('collection_' + collectionId);
|
|
3926
|
+
if (checkbox) {
|
|
3927
|
+
checkbox.checked = true;
|
|
3928
|
+
form.dispatchEvent(new Event('submit'));
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
// Dismiss collection
|
|
3933
|
+
async function dismissCollection(collectionId) {
|
|
3934
|
+
const res = await fetch('/admin/plugins/ai-search', {
|
|
3935
|
+
method: 'POST',
|
|
3936
|
+
headers: {'Content-Type': 'application/json'},
|
|
3937
|
+
body: JSON.stringify({
|
|
3938
|
+
dismissed_collections: [collectionId]
|
|
3939
|
+
})
|
|
3940
|
+
});
|
|
3941
|
+
if (res.ok) {
|
|
3942
|
+
location.reload();
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
// Re-index collection
|
|
3947
|
+
async function reindexCollection(collectionId) {
|
|
3948
|
+
const res = await fetch('/admin/plugins/ai-search/api/reindex', {
|
|
3949
|
+
method: 'POST',
|
|
3950
|
+
headers: {'Content-Type': 'application/json'},
|
|
3951
|
+
body: JSON.stringify({ collection_id: collectionId })
|
|
3952
|
+
});
|
|
3953
|
+
if (res.ok) {
|
|
3954
|
+
alert('Re-indexing started. Page will refresh in a moment.');
|
|
3955
|
+
setTimeout(() => location.reload(), 2000);
|
|
3956
|
+
} else {
|
|
3957
|
+
alert('Failed to start re-indexing. Please try again.');
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
// Poll for index status updates
|
|
3962
|
+
setInterval(async () => {
|
|
3963
|
+
const res = await fetch('/admin/plugins/ai-search/api/status');
|
|
3964
|
+
if (res.ok) {
|
|
3965
|
+
const { data } = await res.json();
|
|
3966
|
+
// Update status indicators if needed
|
|
3967
|
+
// For now, just reload every 30 seconds if indexing is in progress
|
|
3968
|
+
const hasIndexing = Object.values(data).some((s) => s.status === 'indexing');
|
|
3969
|
+
if (hasIndexing) {
|
|
3970
|
+
location.reload();
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
}, 30000);
|
|
3974
|
+
</script>
|
|
3975
|
+
`;
|
|
3976
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayout({
|
|
3977
|
+
title: "AI Search Settings",
|
|
3978
|
+
pageTitle: "AI Search Settings",
|
|
3979
|
+
currentPath: "/admin/plugins/ai-search/settings",
|
|
3980
|
+
user: data.user,
|
|
3981
|
+
content: content2
|
|
3982
|
+
});
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
// src/plugins/core-plugins/ai-search-plugin/routes/admin.ts
|
|
3986
|
+
var adminRoutes = new hono.Hono();
|
|
3987
|
+
adminRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
3988
|
+
adminRoutes.get("/", async (c) => {
|
|
3989
|
+
try {
|
|
3990
|
+
const user = c.get("user");
|
|
3991
|
+
const db = c.env.DB;
|
|
3992
|
+
const ai = c.env.AI;
|
|
3993
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
3994
|
+
const service = new AISearchService(db, ai, vectorize);
|
|
3995
|
+
const indexer = new IndexManager(db, ai, vectorize);
|
|
3996
|
+
const settings = await service.getSettings();
|
|
3997
|
+
console.log("[AI Search Settings Route] Settings loaded:", !!settings);
|
|
3998
|
+
const collections2 = await service.getAllCollections();
|
|
3999
|
+
console.log("[AI Search Settings Route] Collections returned:", collections2.length);
|
|
4000
|
+
if (collections2.length === 0) {
|
|
4001
|
+
const directQuery = await db.prepare("SELECT id, name, display_name FROM collections WHERE is_active = 1").all();
|
|
4002
|
+
console.log("[AI Search Settings Route] Direct DB query found:", directQuery.results?.length || 0, "collections");
|
|
4003
|
+
if (directQuery.results && directQuery.results.length > 0) {
|
|
4004
|
+
console.log("[AI Search Settings Route] Sample from DB:", directQuery.results[0]);
|
|
4005
|
+
}
|
|
4006
|
+
} else if (collections2.length > 0 && collections2[0]) {
|
|
4007
|
+
console.log("[AI Search Settings Route] First collection:", {
|
|
4008
|
+
id: collections2[0].id,
|
|
4009
|
+
name: collections2[0].name,
|
|
4010
|
+
display_name: collections2[0].display_name
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
const newCollections = await service.detectNewCollections();
|
|
4014
|
+
console.log("AI Search: New collections:", newCollections.length);
|
|
4015
|
+
const indexStatus = await indexer.getAllIndexStatus();
|
|
4016
|
+
console.log("AI Search: Index status:", Object.keys(indexStatus).length);
|
|
4017
|
+
const analytics = await service.getSearchAnalytics();
|
|
4018
|
+
return c.html(
|
|
4019
|
+
renderSettingsPage({
|
|
4020
|
+
settings,
|
|
4021
|
+
collections: collections2 || [],
|
|
4022
|
+
newCollections: newCollections || [],
|
|
4023
|
+
indexStatus: indexStatus || {},
|
|
4024
|
+
analytics,
|
|
4025
|
+
user: {
|
|
4026
|
+
name: user.email,
|
|
4027
|
+
email: user.email,
|
|
4028
|
+
role: user.role
|
|
4029
|
+
}
|
|
4030
|
+
})
|
|
4031
|
+
);
|
|
4032
|
+
} catch (error) {
|
|
4033
|
+
console.error("Error rendering AI Search settings:", error);
|
|
4034
|
+
return c.html(`<p>Error loading settings: ${error instanceof Error ? error.message : String(error)}</p>`, 500);
|
|
4035
|
+
}
|
|
4036
|
+
});
|
|
4037
|
+
adminRoutes.post("/", async (c) => {
|
|
4038
|
+
try {
|
|
4039
|
+
const db = c.env.DB;
|
|
4040
|
+
const ai = c.env.AI;
|
|
4041
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
4042
|
+
const service = new AISearchService(db, ai, vectorize);
|
|
4043
|
+
const indexer = new IndexManager(db, ai, vectorize);
|
|
4044
|
+
const body = await c.req.json();
|
|
4045
|
+
console.log("[AI Search POST] Received body:", JSON.stringify(body, null, 2));
|
|
4046
|
+
const currentSettings = await service.getSettings();
|
|
4047
|
+
console.log("[AI Search POST] Current settings selected_collections:", currentSettings?.selected_collections);
|
|
4048
|
+
const updatedSettings = {
|
|
4049
|
+
enabled: body.enabled !== void 0 ? Boolean(body.enabled) : currentSettings?.enabled,
|
|
4050
|
+
ai_mode_enabled: body.ai_mode_enabled !== void 0 ? Boolean(body.ai_mode_enabled) : currentSettings?.ai_mode_enabled,
|
|
4051
|
+
selected_collections: Array.isArray(body.selected_collections) ? body.selected_collections.map(String) : currentSettings?.selected_collections || [],
|
|
4052
|
+
dismissed_collections: Array.isArray(body.dismissed_collections) ? body.dismissed_collections.map(String) : currentSettings?.dismissed_collections || [],
|
|
4053
|
+
autocomplete_enabled: body.autocomplete_enabled !== void 0 ? Boolean(body.autocomplete_enabled) : currentSettings?.autocomplete_enabled,
|
|
4054
|
+
cache_duration: body.cache_duration ? Number(body.cache_duration) : currentSettings?.cache_duration,
|
|
4055
|
+
results_limit: body.results_limit ? Number(body.results_limit) : currentSettings?.results_limit,
|
|
4056
|
+
index_media: body.index_media !== void 0 ? Boolean(body.index_media) : currentSettings?.index_media
|
|
4057
|
+
};
|
|
4058
|
+
console.log("[AI Search POST] Updated settings selected_collections:", updatedSettings.selected_collections);
|
|
4059
|
+
const collectionsChanged = JSON.stringify(updatedSettings.selected_collections) !== JSON.stringify(currentSettings?.selected_collections || []);
|
|
4060
|
+
const saved = await service.updateSettings(updatedSettings);
|
|
4061
|
+
console.log("[AI Search POST] Settings saved, selected_collections:", saved.selected_collections);
|
|
4062
|
+
if (collectionsChanged && updatedSettings.selected_collections) {
|
|
4063
|
+
console.log("[AI Search POST] Collections changed, starting background indexing");
|
|
4064
|
+
c.executionCtx.waitUntil(
|
|
4065
|
+
indexer.syncAll(updatedSettings.selected_collections).then(() => console.log("[AI Search POST] Background indexing completed")).catch((error) => console.error("[AI Search POST] Background indexing error:", error))
|
|
4066
|
+
);
|
|
4067
|
+
}
|
|
4068
|
+
return c.json({ success: true, settings: saved });
|
|
4069
|
+
} catch (error) {
|
|
4070
|
+
console.error("Error updating AI Search settings:", error);
|
|
4071
|
+
return c.json({ error: "Failed to update settings" }, 500);
|
|
4072
|
+
}
|
|
4073
|
+
});
|
|
4074
|
+
adminRoutes.get("/api/settings", async (c) => {
|
|
4075
|
+
try {
|
|
4076
|
+
const db = c.env.DB;
|
|
4077
|
+
const ai = c.env.AI;
|
|
4078
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
4079
|
+
const service = new AISearchService(db, ai, vectorize);
|
|
4080
|
+
const settings = await service.getSettings();
|
|
4081
|
+
return c.json({ success: true, data: settings });
|
|
4082
|
+
} catch (error) {
|
|
4083
|
+
console.error("Error fetching settings:", error);
|
|
4084
|
+
return c.json({ error: "Failed to fetch settings" }, 500);
|
|
4085
|
+
}
|
|
4086
|
+
});
|
|
4087
|
+
adminRoutes.get("/api/new-collections", async (c) => {
|
|
4088
|
+
try {
|
|
4089
|
+
const db = c.env.DB;
|
|
4090
|
+
const ai = c.env.AI;
|
|
4091
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
4092
|
+
const service = new AISearchService(db, ai, vectorize);
|
|
4093
|
+
const notifications = await service.detectNewCollections();
|
|
4094
|
+
return c.json({ success: true, data: notifications });
|
|
4095
|
+
} catch (error) {
|
|
4096
|
+
console.error("Error detecting new collections:", error);
|
|
4097
|
+
return c.json({ error: "Failed to detect new collections" }, 500);
|
|
4098
|
+
}
|
|
4099
|
+
});
|
|
4100
|
+
adminRoutes.get("/api/status", async (c) => {
|
|
4101
|
+
try {
|
|
4102
|
+
const db = c.env.DB;
|
|
4103
|
+
const ai = c.env.AI;
|
|
4104
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
4105
|
+
const indexer = new IndexManager(db, ai, vectorize);
|
|
4106
|
+
const status = await indexer.getAllIndexStatus();
|
|
4107
|
+
return c.json({ success: true, data: status });
|
|
4108
|
+
} catch (error) {
|
|
4109
|
+
console.error("Error fetching index status:", error);
|
|
4110
|
+
return c.json({ error: "Failed to fetch status" }, 500);
|
|
4111
|
+
}
|
|
4112
|
+
});
|
|
4113
|
+
adminRoutes.post("/api/reindex", async (c) => {
|
|
4114
|
+
try {
|
|
4115
|
+
const db = c.env.DB;
|
|
4116
|
+
const ai = c.env.AI;
|
|
4117
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
4118
|
+
const indexer = new IndexManager(db, ai, vectorize);
|
|
4119
|
+
const body = await c.req.json();
|
|
4120
|
+
const collectionIdRaw = body.collection_id;
|
|
4121
|
+
const collectionId = collectionIdRaw ? String(collectionIdRaw) : "";
|
|
4122
|
+
if (!collectionId || collectionId === "undefined" || collectionId === "null") {
|
|
4123
|
+
return c.json({ error: "collection_id is required" }, 400);
|
|
4124
|
+
}
|
|
4125
|
+
c.executionCtx.waitUntil(
|
|
4126
|
+
indexer.indexCollection(collectionId).then(() => console.log(`[AI Search Reindex] Completed for collection ${collectionId}`)).catch((error) => console.error(`[AI Search Reindex] Error for collection ${collectionId}:`, error))
|
|
4127
|
+
);
|
|
4128
|
+
return c.json({ success: true, message: "Re-indexing started" });
|
|
4129
|
+
} catch (error) {
|
|
4130
|
+
console.error("Error starting re-index:", error);
|
|
4131
|
+
return c.json({ error: "Failed to start re-indexing" }, 500);
|
|
4132
|
+
}
|
|
4133
|
+
});
|
|
4134
|
+
var admin_default = adminRoutes;
|
|
4135
|
+
var apiRoutes = new hono.Hono();
|
|
4136
|
+
apiRoutes.post("/", async (c) => {
|
|
4137
|
+
try {
|
|
4138
|
+
const db = c.env.DB;
|
|
4139
|
+
const ai = c.env.AI;
|
|
4140
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
4141
|
+
const service = new AISearchService(db, ai, vectorize);
|
|
4142
|
+
const body = await c.req.json();
|
|
4143
|
+
const query = {
|
|
4144
|
+
query: body.query || "",
|
|
4145
|
+
mode: body.mode || "keyword",
|
|
4146
|
+
filters: body.filters || {},
|
|
4147
|
+
limit: body.limit ? Number(body.limit) : void 0,
|
|
4148
|
+
offset: body.offset ? Number(body.offset) : void 0
|
|
4149
|
+
};
|
|
4150
|
+
if (query.filters?.dateRange) {
|
|
4151
|
+
if (typeof query.filters.dateRange.start === "string") {
|
|
4152
|
+
query.filters.dateRange.start = new Date(query.filters.dateRange.start);
|
|
4153
|
+
}
|
|
4154
|
+
if (typeof query.filters.dateRange.end === "string") {
|
|
4155
|
+
query.filters.dateRange.end = new Date(query.filters.dateRange.end);
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
const results = await service.search(query);
|
|
4159
|
+
return c.json({
|
|
4160
|
+
success: true,
|
|
4161
|
+
data: results
|
|
4162
|
+
});
|
|
4163
|
+
} catch (error) {
|
|
4164
|
+
console.error("Search error:", error);
|
|
4165
|
+
return c.json(
|
|
4166
|
+
{
|
|
4167
|
+
success: false,
|
|
4168
|
+
error: "Search failed",
|
|
4169
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4170
|
+
},
|
|
4171
|
+
500
|
|
4172
|
+
);
|
|
4173
|
+
}
|
|
4174
|
+
});
|
|
4175
|
+
apiRoutes.get("/suggest", async (c) => {
|
|
4176
|
+
try {
|
|
4177
|
+
const db = c.env.DB;
|
|
4178
|
+
const ai = c.env.AI;
|
|
4179
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
4180
|
+
const service = new AISearchService(db, ai, vectorize);
|
|
4181
|
+
const query = c.req.query("q") || "";
|
|
4182
|
+
if (!query || query.length < 2) {
|
|
4183
|
+
return c.json({ success: true, data: [] });
|
|
4184
|
+
}
|
|
4185
|
+
const suggestions = await service.getSearchSuggestions(query);
|
|
4186
|
+
return c.json({
|
|
4187
|
+
success: true,
|
|
4188
|
+
data: suggestions
|
|
4189
|
+
});
|
|
4190
|
+
} catch (error) {
|
|
4191
|
+
console.error("Suggestions error:", error);
|
|
4192
|
+
return c.json(
|
|
4193
|
+
{
|
|
4194
|
+
success: false,
|
|
4195
|
+
error: "Failed to get suggestions"
|
|
4196
|
+
},
|
|
4197
|
+
500
|
|
4198
|
+
);
|
|
4199
|
+
}
|
|
4200
|
+
});
|
|
4201
|
+
apiRoutes.get("/analytics", async (c) => {
|
|
4202
|
+
try {
|
|
4203
|
+
const db = c.env.DB;
|
|
4204
|
+
const ai = c.env.AI;
|
|
4205
|
+
const vectorize = c.env.VECTORIZE_INDEX;
|
|
4206
|
+
const service = new AISearchService(db, ai, vectorize);
|
|
4207
|
+
const analytics = await service.getSearchAnalytics();
|
|
4208
|
+
return c.json({
|
|
4209
|
+
success: true,
|
|
4210
|
+
data: analytics
|
|
4211
|
+
});
|
|
4212
|
+
} catch (error) {
|
|
4213
|
+
console.error("Analytics error:", error);
|
|
4214
|
+
return c.json(
|
|
4215
|
+
{
|
|
4216
|
+
success: false,
|
|
4217
|
+
error: "Failed to get analytics"
|
|
4218
|
+
},
|
|
4219
|
+
500
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
});
|
|
4223
|
+
var api_default2 = apiRoutes;
|
|
4224
|
+
|
|
4225
|
+
// src/plugins/core-plugins/ai-search-plugin/manifest.json
|
|
4226
|
+
var manifest_default = {
|
|
4227
|
+
name: "AI Search",
|
|
4228
|
+
description: "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
|
|
4229
|
+
version: "1.0.0",
|
|
4230
|
+
author: "SonicJS"};
|
|
4231
|
+
|
|
4232
|
+
// src/plugins/core-plugins/ai-search-plugin/index.ts
|
|
4233
|
+
var aiSearchPlugin = new chunkYHW27CBV_cjs.PluginBuilder({
|
|
4234
|
+
name: manifest_default.name,
|
|
4235
|
+
version: manifest_default.version,
|
|
4236
|
+
description: manifest_default.description,
|
|
4237
|
+
author: { name: manifest_default.author }
|
|
4238
|
+
}).metadata({
|
|
4239
|
+
description: manifest_default.description,
|
|
4240
|
+
author: { name: manifest_default.author }
|
|
4241
|
+
}).addService("aiSearch", AISearchService).addService("indexManager", IndexManager).addRoute("/admin/plugins/ai-search", admin_default).addRoute("/api/search", api_default2).build();
|
|
4242
|
+
var magicLinkRequestSchema = zod.z.object({
|
|
4243
|
+
email: zod.z.string().email("Valid email is required")
|
|
4244
|
+
});
|
|
4245
|
+
function createMagicLinkAuthPlugin() {
|
|
4246
|
+
const magicLinkRoutes = new hono.Hono();
|
|
4247
|
+
magicLinkRoutes.post("/request", async (c) => {
|
|
4248
|
+
try {
|
|
4249
|
+
const body = await c.req.json();
|
|
4250
|
+
const validation = magicLinkRequestSchema.safeParse(body);
|
|
4251
|
+
if (!validation.success) {
|
|
4252
|
+
return c.json({
|
|
4253
|
+
error: "Validation failed",
|
|
4254
|
+
details: validation.error.issues
|
|
4255
|
+
}, 400);
|
|
4256
|
+
}
|
|
4257
|
+
const { email } = validation.data;
|
|
4258
|
+
const normalizedEmail = email.toLowerCase();
|
|
4259
|
+
const db = c.env.DB;
|
|
4260
|
+
const oneHourAgo = Date.now() - 60 * 60 * 1e3;
|
|
4261
|
+
const recentLinks = await db.prepare(`
|
|
4262
|
+
SELECT COUNT(*) as count
|
|
4263
|
+
FROM magic_links
|
|
4264
|
+
WHERE user_email = ? AND created_at > ?
|
|
4265
|
+
`).bind(normalizedEmail, oneHourAgo).first();
|
|
4266
|
+
const rateLimitPerHour = 5;
|
|
4267
|
+
if (recentLinks && recentLinks.count >= rateLimitPerHour) {
|
|
4268
|
+
return c.json({
|
|
4269
|
+
error: "Too many requests. Please try again later."
|
|
4270
|
+
}, 429);
|
|
4271
|
+
}
|
|
4272
|
+
const user = await db.prepare(`
|
|
4273
|
+
SELECT id, email, role, is_active
|
|
4274
|
+
FROM users
|
|
4275
|
+
WHERE email = ?
|
|
4276
|
+
`).bind(normalizedEmail).first();
|
|
4277
|
+
const allowNewUsers = false;
|
|
4278
|
+
if (!user && !allowNewUsers) {
|
|
4279
|
+
return c.json({
|
|
4280
|
+
message: "If an account exists for this email, you will receive a magic link shortly."
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
if (user && !user.is_active) {
|
|
4284
|
+
return c.json({
|
|
4285
|
+
error: "This account has been deactivated."
|
|
4286
|
+
}, 403);
|
|
4287
|
+
}
|
|
4288
|
+
const token = crypto.randomUUID() + "-" + crypto.randomUUID();
|
|
4289
|
+
const tokenId = crypto.randomUUID();
|
|
4290
|
+
const linkExpiryMinutes = 15;
|
|
4291
|
+
const expiresAt = Date.now() + linkExpiryMinutes * 60 * 1e3;
|
|
4292
|
+
await db.prepare(`
|
|
4293
|
+
INSERT INTO magic_links (
|
|
4294
|
+
id, user_email, token, expires_at, used, created_at, ip_address, user_agent
|
|
4295
|
+
) VALUES (?, ?, ?, ?, 0, ?, ?, ?)
|
|
4296
|
+
`).bind(
|
|
4297
|
+
tokenId,
|
|
4298
|
+
normalizedEmail,
|
|
4299
|
+
token,
|
|
4300
|
+
expiresAt,
|
|
4301
|
+
Date.now(),
|
|
4302
|
+
c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || "unknown",
|
|
4303
|
+
c.req.header("user-agent") || "unknown"
|
|
4304
|
+
).run();
|
|
4305
|
+
const baseUrl = new URL(c.req.url).origin;
|
|
4306
|
+
const magicLink = `${baseUrl}/auth/magic-link/verify?token=${token}`;
|
|
4307
|
+
try {
|
|
4308
|
+
const emailPlugin2 = c.env.plugins?.get("email");
|
|
4309
|
+
if (emailPlugin2 && emailPlugin2.sendEmail) {
|
|
4310
|
+
await emailPlugin2.sendEmail({
|
|
4311
|
+
to: normalizedEmail,
|
|
4312
|
+
subject: "Your Magic Link to Sign In",
|
|
4313
|
+
html: renderMagicLinkEmail(magicLink, linkExpiryMinutes)
|
|
4314
|
+
});
|
|
4315
|
+
} else {
|
|
4316
|
+
console.error("Email plugin not available");
|
|
4317
|
+
console.log(`Magic link for ${normalizedEmail}: ${magicLink}`);
|
|
4318
|
+
}
|
|
4319
|
+
} catch (error) {
|
|
4320
|
+
console.error("Failed to send magic link email:", error);
|
|
4321
|
+
return c.json({
|
|
4322
|
+
error: "Failed to send email. Please try again later."
|
|
4323
|
+
}, 500);
|
|
4324
|
+
}
|
|
4325
|
+
return c.json({
|
|
4326
|
+
message: "If an account exists for this email, you will receive a magic link shortly.",
|
|
4327
|
+
// For development only - remove in production
|
|
4328
|
+
...c.env.ENVIRONMENT === "development" && { dev_link: magicLink }
|
|
4329
|
+
});
|
|
4330
|
+
} catch (error) {
|
|
4331
|
+
console.error("Magic link request error:", error);
|
|
4332
|
+
return c.json({ error: "Failed to process request" }, 500);
|
|
4333
|
+
}
|
|
4334
|
+
});
|
|
4335
|
+
magicLinkRoutes.get("/verify", async (c) => {
|
|
4336
|
+
try {
|
|
4337
|
+
const token = c.req.query("token");
|
|
4338
|
+
if (!token) {
|
|
4339
|
+
return c.redirect("/auth/login?error=Invalid magic link");
|
|
4340
|
+
}
|
|
4341
|
+
const db = c.env.DB;
|
|
4342
|
+
const magicLink = await db.prepare(`
|
|
4343
|
+
SELECT * FROM magic_links
|
|
4344
|
+
WHERE token = ? AND used = 0
|
|
4345
|
+
`).bind(token).first();
|
|
4346
|
+
if (!magicLink) {
|
|
4347
|
+
return c.redirect("/auth/login?error=Invalid or expired magic link");
|
|
4348
|
+
}
|
|
4349
|
+
if (magicLink.expires_at < Date.now()) {
|
|
4350
|
+
return c.redirect("/auth/login?error=This magic link has expired");
|
|
4351
|
+
}
|
|
4352
|
+
let user = await db.prepare(`
|
|
4353
|
+
SELECT * FROM users WHERE email = ? AND is_active = 1
|
|
4354
|
+
`).bind(magicLink.user_email).first();
|
|
4355
|
+
const allowNewUsers = false;
|
|
4356
|
+
if (!user && allowNewUsers) {
|
|
4357
|
+
const userId = crypto.randomUUID();
|
|
4358
|
+
const username = magicLink.user_email.split("@")[0];
|
|
4359
|
+
const now = Date.now();
|
|
4360
|
+
await db.prepare(`
|
|
4361
|
+
INSERT INTO users (
|
|
4362
|
+
id, email, username, first_name, last_name,
|
|
4363
|
+
password_hash, role, is_active, created_at, updated_at
|
|
4364
|
+
) VALUES (?, ?, ?, ?, ?, NULL, 'viewer', 1, ?, ?)
|
|
4365
|
+
`).bind(
|
|
4366
|
+
userId,
|
|
4367
|
+
magicLink.user_email,
|
|
4368
|
+
username,
|
|
4369
|
+
username,
|
|
4370
|
+
"",
|
|
4371
|
+
now,
|
|
4372
|
+
now
|
|
4373
|
+
).run();
|
|
4374
|
+
user = {
|
|
4375
|
+
id: userId,
|
|
4376
|
+
email: magicLink.user_email,
|
|
4377
|
+
username,
|
|
4378
|
+
role: "viewer"
|
|
4379
|
+
};
|
|
4380
|
+
} else if (!user) {
|
|
4381
|
+
return c.redirect("/auth/login?error=No account found for this email");
|
|
4382
|
+
}
|
|
4383
|
+
await db.prepare(`
|
|
4384
|
+
UPDATE magic_links
|
|
4385
|
+
SET used = 1, used_at = ?
|
|
4386
|
+
WHERE id = ?
|
|
4387
|
+
`).bind(Date.now(), magicLink.id).run();
|
|
4388
|
+
const jwtToken = await chunkE2BXLXPW_cjs.AuthManager.generateToken(
|
|
4389
|
+
user.id,
|
|
4390
|
+
user.email,
|
|
4391
|
+
user.role
|
|
4392
|
+
);
|
|
4393
|
+
chunkE2BXLXPW_cjs.AuthManager.setAuthCookie(c, jwtToken);
|
|
4394
|
+
await db.prepare(`
|
|
4395
|
+
UPDATE users SET last_login_at = ? WHERE id = ?
|
|
4396
|
+
`).bind(Date.now(), user.id).run();
|
|
4397
|
+
return c.redirect("/admin/dashboard?message=Successfully signed in");
|
|
4398
|
+
} catch (error) {
|
|
4399
|
+
console.error("Magic link verification error:", error);
|
|
4400
|
+
return c.redirect("/auth/login?error=Authentication failed");
|
|
4401
|
+
}
|
|
4402
|
+
});
|
|
4403
|
+
return {
|
|
4404
|
+
name: "magic-link-auth",
|
|
4405
|
+
version: "1.0.0",
|
|
4406
|
+
description: "Passwordless authentication via email magic links",
|
|
4407
|
+
author: {
|
|
4408
|
+
name: "SonicJS Team",
|
|
4409
|
+
email: "team@sonicjs.com"
|
|
4410
|
+
},
|
|
4411
|
+
dependencies: ["email"],
|
|
4412
|
+
routes: [{
|
|
4413
|
+
path: "/auth/magic-link",
|
|
4414
|
+
handler: magicLinkRoutes,
|
|
4415
|
+
description: "Magic link authentication endpoints",
|
|
4416
|
+
requiresAuth: false
|
|
4417
|
+
}],
|
|
4418
|
+
async install(context) {
|
|
4419
|
+
console.log("Installing magic-link-auth plugin...");
|
|
4420
|
+
},
|
|
4421
|
+
async activate(context) {
|
|
4422
|
+
console.log("Magic link authentication activated");
|
|
4423
|
+
console.log("Users can now sign in via /auth/magic-link/request");
|
|
4424
|
+
},
|
|
4425
|
+
async deactivate(context) {
|
|
4426
|
+
console.log("Magic link authentication deactivated");
|
|
4427
|
+
},
|
|
4428
|
+
async uninstall(context) {
|
|
4429
|
+
console.log("Uninstalling magic-link-auth plugin...");
|
|
4430
|
+
}
|
|
4431
|
+
};
|
|
4432
|
+
}
|
|
4433
|
+
function renderMagicLinkEmail(magicLink, expiryMinutes) {
|
|
4434
|
+
return `
|
|
4435
|
+
<!DOCTYPE html>
|
|
4436
|
+
<html>
|
|
4437
|
+
<head>
|
|
4438
|
+
<meta charset="utf-8">
|
|
4439
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4440
|
+
<title>Your Magic Link</title>
|
|
4441
|
+
<style>
|
|
4442
|
+
body {
|
|
4443
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
4444
|
+
line-height: 1.6;
|
|
4445
|
+
color: #333;
|
|
4446
|
+
max-width: 600px;
|
|
4447
|
+
margin: 0 auto;
|
|
4448
|
+
padding: 20px;
|
|
4449
|
+
}
|
|
4450
|
+
.container {
|
|
4451
|
+
background: #ffffff;
|
|
4452
|
+
border-radius: 8px;
|
|
4453
|
+
padding: 40px;
|
|
4454
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
4455
|
+
}
|
|
4456
|
+
.header {
|
|
4457
|
+
text-align: center;
|
|
4458
|
+
margin-bottom: 30px;
|
|
4459
|
+
}
|
|
4460
|
+
.header h1 {
|
|
4461
|
+
color: #0ea5e9;
|
|
4462
|
+
margin: 0;
|
|
4463
|
+
font-size: 24px;
|
|
4464
|
+
}
|
|
4465
|
+
.content {
|
|
4466
|
+
margin-bottom: 30px;
|
|
4467
|
+
}
|
|
4468
|
+
.button {
|
|
4469
|
+
display: inline-block;
|
|
4470
|
+
padding: 14px 32px;
|
|
4471
|
+
background: linear-gradient(135deg, #0ea5e9 0%, #06b6d4 100%);
|
|
4472
|
+
color: #ffffff !important;
|
|
4473
|
+
text-decoration: none;
|
|
4474
|
+
border-radius: 6px;
|
|
4475
|
+
font-weight: 600;
|
|
4476
|
+
text-align: center;
|
|
4477
|
+
margin: 20px 0;
|
|
4478
|
+
}
|
|
4479
|
+
.button:hover {
|
|
4480
|
+
opacity: 0.9;
|
|
4481
|
+
}
|
|
4482
|
+
.expiry {
|
|
4483
|
+
color: #ef4444;
|
|
4484
|
+
font-size: 14px;
|
|
4485
|
+
margin-top: 20px;
|
|
4486
|
+
}
|
|
4487
|
+
.footer {
|
|
4488
|
+
margin-top: 40px;
|
|
4489
|
+
padding-top: 20px;
|
|
4490
|
+
border-top: 1px solid #e5e7eb;
|
|
4491
|
+
font-size: 12px;
|
|
4492
|
+
color: #6b7280;
|
|
4493
|
+
text-align: center;
|
|
4494
|
+
}
|
|
4495
|
+
.security-note {
|
|
4496
|
+
background: #fef3c7;
|
|
4497
|
+
border-left: 4px solid #f59e0b;
|
|
4498
|
+
padding: 12px 16px;
|
|
4499
|
+
margin-top: 20px;
|
|
4500
|
+
border-radius: 4px;
|
|
4501
|
+
font-size: 14px;
|
|
4502
|
+
}
|
|
4503
|
+
</style>
|
|
4504
|
+
</head>
|
|
4505
|
+
<body>
|
|
4506
|
+
<div class="container">
|
|
4507
|
+
<div class="header">
|
|
4508
|
+
<h1>\u{1F517} Your Magic Link</h1>
|
|
4509
|
+
</div>
|
|
4510
|
+
|
|
4511
|
+
<div class="content">
|
|
4512
|
+
<p>Hello!</p>
|
|
4513
|
+
<p>You requested a magic link to sign in to your account. Click the button below to continue:</p>
|
|
4514
|
+
|
|
4515
|
+
<div style="text-align: center;">
|
|
4516
|
+
<a href="${magicLink}" class="button">Sign In</a>
|
|
4517
|
+
</div>
|
|
4518
|
+
|
|
4519
|
+
<p class="expiry">\u23F0 This link expires in ${expiryMinutes} minutes</p>
|
|
4520
|
+
|
|
4521
|
+
<div class="security-note">
|
|
4522
|
+
<strong>Security Notice:</strong> If you didn't request this link, you can safely ignore this email.
|
|
4523
|
+
Someone may have entered your email address by mistake.
|
|
4524
|
+
</div>
|
|
4525
|
+
</div>
|
|
4526
|
+
|
|
4527
|
+
<div class="footer">
|
|
4528
|
+
<p>This is an automated email from SonicJS.</p>
|
|
4529
|
+
<p>For security, this link can only be used once.</p>
|
|
4530
|
+
</div>
|
|
4531
|
+
</div>
|
|
4532
|
+
</body>
|
|
4533
|
+
</html>
|
|
4534
|
+
`;
|
|
4535
|
+
}
|
|
4536
|
+
createMagicLinkAuthPlugin();
|
|
4537
|
+
|
|
4538
|
+
// src/plugins/cache/services/cache-config.ts
|
|
4539
|
+
var CACHE_CONFIGS = {
|
|
4540
|
+
// Content (high read, low write)
|
|
4541
|
+
content: {
|
|
4542
|
+
ttl: 3600,
|
|
4543
|
+
// 1 hour
|
|
4544
|
+
kvEnabled: true,
|
|
4545
|
+
memoryEnabled: true,
|
|
4546
|
+
namespace: "content",
|
|
4547
|
+
invalidateOn: ["content.update", "content.delete", "content.publish"],
|
|
4548
|
+
version: "v1"
|
|
4549
|
+
},
|
|
4550
|
+
// User data (medium read, medium write)
|
|
4551
|
+
user: {
|
|
4552
|
+
ttl: 900,
|
|
4553
|
+
// 15 minutes
|
|
4554
|
+
kvEnabled: true,
|
|
4555
|
+
memoryEnabled: true,
|
|
4556
|
+
namespace: "user",
|
|
4557
|
+
invalidateOn: ["user.update", "user.delete", "auth.login"],
|
|
4558
|
+
version: "v1"
|
|
4559
|
+
},
|
|
4560
|
+
// Configuration (high read, very low write)
|
|
4561
|
+
config: {
|
|
4562
|
+
ttl: 7200,
|
|
4563
|
+
// 2 hours
|
|
4564
|
+
kvEnabled: true,
|
|
4565
|
+
memoryEnabled: true,
|
|
4566
|
+
namespace: "config",
|
|
4567
|
+
invalidateOn: ["config.update", "plugin.activate", "plugin.deactivate"],
|
|
4568
|
+
version: "v1"
|
|
4569
|
+
},
|
|
4570
|
+
// Media metadata (high read, low write)
|
|
4571
|
+
media: {
|
|
4572
|
+
ttl: 3600,
|
|
4573
|
+
// 1 hour
|
|
4574
|
+
kvEnabled: true,
|
|
4575
|
+
memoryEnabled: true,
|
|
4576
|
+
namespace: "media",
|
|
4577
|
+
invalidateOn: ["media.upload", "media.delete", "media.update"],
|
|
4578
|
+
version: "v1"
|
|
4579
|
+
},
|
|
4580
|
+
// API responses (very high read, low write)
|
|
4581
|
+
api: {
|
|
4582
|
+
ttl: 300,
|
|
4583
|
+
// 5 minutes
|
|
4584
|
+
kvEnabled: true,
|
|
4585
|
+
memoryEnabled: true,
|
|
4586
|
+
namespace: "api",
|
|
4587
|
+
invalidateOn: ["content.update", "content.publish"],
|
|
4588
|
+
version: "v1"
|
|
4589
|
+
},
|
|
4590
|
+
// Session data (very high read, medium write)
|
|
4591
|
+
session: {
|
|
4592
|
+
ttl: 1800,
|
|
4593
|
+
// 30 minutes
|
|
4594
|
+
kvEnabled: false,
|
|
4595
|
+
// Only in-memory for sessions
|
|
4596
|
+
memoryEnabled: true,
|
|
4597
|
+
namespace: "session",
|
|
4598
|
+
invalidateOn: ["auth.logout"],
|
|
4599
|
+
version: "v1"
|
|
4600
|
+
},
|
|
4601
|
+
// Plugin data
|
|
4602
|
+
plugin: {
|
|
4603
|
+
ttl: 3600,
|
|
4604
|
+
// 1 hour
|
|
4605
|
+
kvEnabled: true,
|
|
4606
|
+
memoryEnabled: true,
|
|
4607
|
+
namespace: "plugin",
|
|
4608
|
+
invalidateOn: ["plugin.activate", "plugin.deactivate", "plugin.update"],
|
|
4609
|
+
version: "v1"
|
|
4610
|
+
},
|
|
4611
|
+
// Collections/schema
|
|
4612
|
+
collection: {
|
|
4613
|
+
ttl: 7200,
|
|
4614
|
+
// 2 hours
|
|
4615
|
+
kvEnabled: true,
|
|
4616
|
+
memoryEnabled: true,
|
|
4617
|
+
namespace: "collection",
|
|
4618
|
+
invalidateOn: ["collection.update", "collection.delete"],
|
|
4619
|
+
version: "v1"
|
|
4620
|
+
}
|
|
4621
|
+
};
|
|
4622
|
+
function getCacheConfig(namespace) {
|
|
4623
|
+
return CACHE_CONFIGS[namespace] || {
|
|
4624
|
+
ttl: 3600,
|
|
4625
|
+
kvEnabled: true,
|
|
4626
|
+
memoryEnabled: true,
|
|
4627
|
+
namespace,
|
|
4628
|
+
invalidateOn: [],
|
|
4629
|
+
version: "v1"
|
|
4630
|
+
};
|
|
4631
|
+
}
|
|
4632
|
+
function generateCacheKey(namespace, type, identifier, version) {
|
|
4633
|
+
const v = version || getCacheConfig(namespace).version || "v1";
|
|
4634
|
+
return `${namespace}:${type}:${identifier}:${v}`;
|
|
4635
|
+
}
|
|
4636
|
+
function parseCacheKey(key) {
|
|
4637
|
+
const parts = key.split(":");
|
|
4638
|
+
if (parts.length !== 4) {
|
|
4639
|
+
return null;
|
|
4640
|
+
}
|
|
4641
|
+
return {
|
|
4642
|
+
namespace: parts[0] || "",
|
|
4643
|
+
type: parts[1] || "",
|
|
4644
|
+
identifier: parts[2] || "",
|
|
4645
|
+
version: parts[3] || ""
|
|
4646
|
+
};
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
// src/plugins/cache/services/cache.ts
|
|
4650
|
+
var MemoryCache = class {
|
|
4651
|
+
cache = /* @__PURE__ */ new Map();
|
|
4652
|
+
maxSize = 50 * 1024 * 1024;
|
|
4653
|
+
// 50MB
|
|
4654
|
+
currentSize = 0;
|
|
4655
|
+
/**
|
|
4656
|
+
* Get item from memory cache
|
|
4657
|
+
*/
|
|
4658
|
+
get(key) {
|
|
4659
|
+
const entry = this.cache.get(key);
|
|
4660
|
+
if (!entry) {
|
|
4661
|
+
return null;
|
|
4662
|
+
}
|
|
4663
|
+
if (Date.now() > entry.expiresAt) {
|
|
4664
|
+
this.delete(key);
|
|
4665
|
+
return null;
|
|
4666
|
+
}
|
|
4667
|
+
return entry.data;
|
|
4668
|
+
}
|
|
4669
|
+
/**
|
|
4670
|
+
* Set item in memory cache
|
|
4671
|
+
*/
|
|
4672
|
+
set(key, value, ttl, version = "v1") {
|
|
4673
|
+
const now = Date.now();
|
|
4674
|
+
const entry = {
|
|
4675
|
+
data: value,
|
|
4676
|
+
timestamp: now,
|
|
4677
|
+
expiresAt: now + ttl * 1e3,
|
|
4678
|
+
version
|
|
4679
|
+
};
|
|
4680
|
+
const entrySize = JSON.stringify(entry).length * 2;
|
|
4681
|
+
if (this.currentSize + entrySize > this.maxSize) {
|
|
4682
|
+
this.evictLRU(entrySize);
|
|
4683
|
+
}
|
|
4684
|
+
if (this.cache.has(key)) {
|
|
4685
|
+
this.delete(key);
|
|
4686
|
+
}
|
|
4687
|
+
this.cache.set(key, entry);
|
|
4688
|
+
this.currentSize += entrySize;
|
|
4689
|
+
}
|
|
4690
|
+
/**
|
|
4691
|
+
* Delete item from memory cache
|
|
4692
|
+
*/
|
|
4693
|
+
delete(key) {
|
|
4694
|
+
const entry = this.cache.get(key);
|
|
4695
|
+
if (entry) {
|
|
4696
|
+
const entrySize = JSON.stringify(entry).length * 2;
|
|
4697
|
+
this.currentSize -= entrySize;
|
|
4698
|
+
return this.cache.delete(key);
|
|
4699
|
+
}
|
|
4700
|
+
return false;
|
|
4701
|
+
}
|
|
4702
|
+
/**
|
|
4703
|
+
* Clear all items from memory cache
|
|
4704
|
+
*/
|
|
4705
|
+
clear() {
|
|
4706
|
+
this.cache.clear();
|
|
4707
|
+
this.currentSize = 0;
|
|
4708
|
+
}
|
|
4709
|
+
/**
|
|
4710
|
+
* Get cache statistics
|
|
4711
|
+
*/
|
|
4712
|
+
getStats() {
|
|
4713
|
+
return {
|
|
4714
|
+
size: this.currentSize,
|
|
4715
|
+
count: this.cache.size
|
|
4716
|
+
};
|
|
4717
|
+
}
|
|
4718
|
+
/**
|
|
4719
|
+
* Evict least recently used items to make space
|
|
4720
|
+
*/
|
|
4721
|
+
evictLRU(neededSpace) {
|
|
4722
|
+
const entries = Array.from(this.cache.entries()).sort(
|
|
4723
|
+
(a, b) => a[1].timestamp - b[1].timestamp
|
|
4724
|
+
);
|
|
4725
|
+
let freedSpace = 0;
|
|
4726
|
+
for (const [key, entry] of entries) {
|
|
4727
|
+
if (freedSpace >= neededSpace) break;
|
|
4728
|
+
const entrySize = JSON.stringify(entry).length * 2;
|
|
4729
|
+
this.delete(key);
|
|
4730
|
+
freedSpace += entrySize;
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
/**
|
|
4734
|
+
* Delete items matching a pattern
|
|
4735
|
+
*/
|
|
4736
|
+
invalidatePattern(pattern) {
|
|
4737
|
+
const regex = new RegExp(
|
|
4738
|
+
"^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
4739
|
+
);
|
|
4740
|
+
let count = 0;
|
|
4741
|
+
for (const key of this.cache.keys()) {
|
|
4742
|
+
if (regex.test(key)) {
|
|
4743
|
+
this.delete(key);
|
|
4744
|
+
count++;
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
return count;
|
|
4748
|
+
}
|
|
4749
|
+
};
|
|
4750
|
+
var CacheService = class {
|
|
4751
|
+
memoryCache;
|
|
4752
|
+
config;
|
|
4753
|
+
stats;
|
|
4754
|
+
kvNamespace;
|
|
4755
|
+
constructor(config, kvNamespace) {
|
|
4756
|
+
this.memoryCache = new MemoryCache();
|
|
4757
|
+
this.config = config;
|
|
4758
|
+
this.kvNamespace = kvNamespace;
|
|
4759
|
+
this.stats = {
|
|
4760
|
+
memoryHits: 0,
|
|
4761
|
+
memoryMisses: 0,
|
|
4762
|
+
kvHits: 0,
|
|
4763
|
+
kvMisses: 0,
|
|
4764
|
+
dbHits: 0,
|
|
4765
|
+
totalRequests: 0,
|
|
4766
|
+
hitRate: 0,
|
|
4767
|
+
memorySize: 0,
|
|
4768
|
+
entryCount: 0
|
|
4769
|
+
};
|
|
4770
|
+
}
|
|
4771
|
+
/**
|
|
4772
|
+
* Get value from cache (tries memory first, then KV)
|
|
4773
|
+
*/
|
|
4774
|
+
async get(key) {
|
|
4775
|
+
this.stats.totalRequests++;
|
|
4776
|
+
if (this.config.memoryEnabled) {
|
|
4777
|
+
const memoryValue = this.memoryCache.get(key);
|
|
4778
|
+
if (memoryValue !== null) {
|
|
4779
|
+
this.stats.memoryHits++;
|
|
4780
|
+
this.updateHitRate();
|
|
4781
|
+
return memoryValue;
|
|
4782
|
+
}
|
|
4783
|
+
this.stats.memoryMisses++;
|
|
4784
|
+
}
|
|
4785
|
+
if (this.config.kvEnabled && this.kvNamespace) {
|
|
4786
|
+
try {
|
|
4787
|
+
const kvValue = await this.kvNamespace.get(key, "json");
|
|
4788
|
+
if (kvValue !== null) {
|
|
4789
|
+
this.stats.kvHits++;
|
|
4790
|
+
if (this.config.memoryEnabled) {
|
|
4791
|
+
this.memoryCache.set(key, kvValue, this.config.ttl, this.config.version);
|
|
4792
|
+
}
|
|
4793
|
+
this.updateHitRate();
|
|
4794
|
+
return kvValue;
|
|
4795
|
+
}
|
|
4796
|
+
this.stats.kvMisses++;
|
|
4797
|
+
} catch (error) {
|
|
4798
|
+
console.error("KV cache read error:", error);
|
|
4799
|
+
this.stats.kvMisses++;
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
this.updateHitRate();
|
|
4803
|
+
return null;
|
|
4804
|
+
}
|
|
4805
|
+
/**
|
|
4806
|
+
* Get value from cache with source information
|
|
4807
|
+
*/
|
|
4808
|
+
async getWithSource(key) {
|
|
4809
|
+
this.stats.totalRequests++;
|
|
4810
|
+
if (this.config.memoryEnabled) {
|
|
4811
|
+
const memoryValue = this.memoryCache.get(key);
|
|
4812
|
+
if (memoryValue !== null) {
|
|
4813
|
+
this.stats.memoryHits++;
|
|
4814
|
+
this.updateHitRate();
|
|
4815
|
+
const entry = await this.getEntry(key);
|
|
4816
|
+
return {
|
|
4817
|
+
data: memoryValue,
|
|
4818
|
+
source: "memory",
|
|
4819
|
+
hit: true,
|
|
4820
|
+
timestamp: entry?.timestamp,
|
|
4821
|
+
ttl: entry?.ttl
|
|
4822
|
+
};
|
|
4823
|
+
}
|
|
4824
|
+
this.stats.memoryMisses++;
|
|
4825
|
+
}
|
|
4826
|
+
if (this.config.kvEnabled && this.kvNamespace) {
|
|
4827
|
+
try {
|
|
4828
|
+
const kvValue = await this.kvNamespace.get(key, "json");
|
|
4829
|
+
if (kvValue !== null) {
|
|
4830
|
+
this.stats.kvHits++;
|
|
4831
|
+
if (this.config.memoryEnabled) {
|
|
4832
|
+
this.memoryCache.set(key, kvValue, this.config.ttl, this.config.version);
|
|
4833
|
+
}
|
|
4834
|
+
this.updateHitRate();
|
|
4835
|
+
return {
|
|
4836
|
+
data: kvValue,
|
|
4837
|
+
source: "kv",
|
|
4838
|
+
hit: true
|
|
4839
|
+
};
|
|
4840
|
+
}
|
|
4841
|
+
this.stats.kvMisses++;
|
|
4842
|
+
} catch (error) {
|
|
4843
|
+
console.error("KV cache read error:", error);
|
|
4844
|
+
this.stats.kvMisses++;
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
this.updateHitRate();
|
|
4848
|
+
return {
|
|
4849
|
+
data: null,
|
|
4850
|
+
source: "miss",
|
|
4851
|
+
hit: false
|
|
4852
|
+
};
|
|
4853
|
+
}
|
|
4854
|
+
/**
|
|
4855
|
+
* Set value in cache (stores in both memory and KV)
|
|
4856
|
+
*/
|
|
4857
|
+
async set(key, value, customConfig) {
|
|
4858
|
+
const config = { ...this.config, ...customConfig };
|
|
4859
|
+
if (config.memoryEnabled) {
|
|
4860
|
+
this.memoryCache.set(key, value, config.ttl, config.version);
|
|
4861
|
+
}
|
|
4862
|
+
if (config.kvEnabled && this.kvNamespace) {
|
|
4863
|
+
try {
|
|
4864
|
+
await this.kvNamespace.put(key, JSON.stringify(value), {
|
|
4865
|
+
expirationTtl: config.ttl
|
|
4866
|
+
});
|
|
4867
|
+
} catch (error) {
|
|
4868
|
+
console.error("KV cache write error:", error);
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
/**
|
|
4873
|
+
* Delete value from cache (removes from both memory and KV)
|
|
4874
|
+
*/
|
|
4875
|
+
async delete(key) {
|
|
4876
|
+
if (this.config.memoryEnabled) {
|
|
4877
|
+
this.memoryCache.delete(key);
|
|
4878
|
+
}
|
|
4879
|
+
if (this.config.kvEnabled && this.kvNamespace) {
|
|
4880
|
+
try {
|
|
4881
|
+
await this.kvNamespace.delete(key);
|
|
4882
|
+
} catch (error) {
|
|
4883
|
+
console.error("KV cache delete error:", error);
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
/**
|
|
4888
|
+
* Clear all cache entries for this namespace
|
|
4889
|
+
*/
|
|
4890
|
+
async clear() {
|
|
4891
|
+
if (this.config.memoryEnabled) {
|
|
4892
|
+
this.memoryCache.clear();
|
|
4893
|
+
}
|
|
4894
|
+
this.stats = {
|
|
4895
|
+
memoryHits: 0,
|
|
4896
|
+
memoryMisses: 0,
|
|
4897
|
+
kvHits: 0,
|
|
4898
|
+
kvMisses: 0,
|
|
4899
|
+
dbHits: 0,
|
|
4900
|
+
totalRequests: 0,
|
|
4901
|
+
hitRate: 0,
|
|
4902
|
+
memorySize: 0,
|
|
4903
|
+
entryCount: 0
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
/**
|
|
4907
|
+
* Invalidate cache entries matching a pattern
|
|
4908
|
+
*/
|
|
4909
|
+
async invalidate(pattern) {
|
|
4910
|
+
let count = 0;
|
|
4911
|
+
if (this.config.memoryEnabled) {
|
|
4912
|
+
count += this.memoryCache.invalidatePattern(pattern);
|
|
4913
|
+
}
|
|
4914
|
+
if (this.config.kvEnabled && this.kvNamespace) {
|
|
4915
|
+
try {
|
|
4916
|
+
const regex = new RegExp(
|
|
4917
|
+
"^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
4918
|
+
);
|
|
4919
|
+
const prefix = this.config.namespace + ":";
|
|
4920
|
+
const list = await this.kvNamespace.list({ prefix });
|
|
4921
|
+
for (const key of list.keys) {
|
|
4922
|
+
if (regex.test(key.name)) {
|
|
4923
|
+
await this.kvNamespace.delete(key.name);
|
|
4924
|
+
count++;
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
} catch (error) {
|
|
4928
|
+
console.error("KV cache invalidation error:", error);
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
return count;
|
|
4932
|
+
}
|
|
4933
|
+
/**
|
|
4934
|
+
* Invalidate cache entries matching a pattern (alias for invalidate)
|
|
4935
|
+
*/
|
|
4936
|
+
async invalidatePattern(pattern) {
|
|
4937
|
+
return this.invalidate(pattern);
|
|
4938
|
+
}
|
|
4939
|
+
/**
|
|
4940
|
+
* Get cache statistics
|
|
4941
|
+
*/
|
|
4942
|
+
getStats() {
|
|
4943
|
+
const memStats = this.memoryCache.getStats();
|
|
4944
|
+
return {
|
|
4945
|
+
...this.stats,
|
|
4946
|
+
memorySize: memStats.size,
|
|
4947
|
+
entryCount: memStats.count
|
|
4948
|
+
};
|
|
4949
|
+
}
|
|
4950
|
+
/**
|
|
4951
|
+
* Update hit rate calculation
|
|
4952
|
+
*/
|
|
4953
|
+
updateHitRate() {
|
|
4954
|
+
const totalHits = this.stats.memoryHits + this.stats.kvHits + this.stats.dbHits;
|
|
4955
|
+
this.stats.hitRate = this.stats.totalRequests > 0 ? totalHits / this.stats.totalRequests * 100 : 0;
|
|
4956
|
+
}
|
|
4957
|
+
/**
|
|
4958
|
+
* Generate a cache key using the configured namespace
|
|
4959
|
+
*/
|
|
4960
|
+
generateKey(type, identifier) {
|
|
4961
|
+
return generateCacheKey(
|
|
4962
|
+
this.config.namespace,
|
|
4963
|
+
type,
|
|
4964
|
+
identifier,
|
|
4965
|
+
this.config.version
|
|
4966
|
+
);
|
|
4967
|
+
}
|
|
4968
|
+
/**
|
|
4969
|
+
* Warm cache with multiple entries
|
|
4970
|
+
*/
|
|
4971
|
+
async warmCache(entries) {
|
|
4972
|
+
for (const entry of entries) {
|
|
4973
|
+
await this.set(entry.key, entry.value);
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
/**
|
|
4977
|
+
* Check if a key exists in cache
|
|
4978
|
+
*/
|
|
4979
|
+
async has(key) {
|
|
4980
|
+
const value = await this.get(key);
|
|
4981
|
+
return value !== null;
|
|
4982
|
+
}
|
|
4983
|
+
/**
|
|
4984
|
+
* Get multiple values at once
|
|
4985
|
+
*/
|
|
4986
|
+
async getMany(keys) {
|
|
4987
|
+
const results = /* @__PURE__ */ new Map();
|
|
4988
|
+
for (const key of keys) {
|
|
4989
|
+
const value = await this.get(key);
|
|
4990
|
+
if (value !== null) {
|
|
4991
|
+
results.set(key, value);
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
return results;
|
|
4995
|
+
}
|
|
4996
|
+
/**
|
|
4997
|
+
* Set multiple values at once
|
|
4998
|
+
*/
|
|
4999
|
+
async setMany(entries, customConfig) {
|
|
5000
|
+
for (const entry of entries) {
|
|
5001
|
+
await this.set(entry.key, entry.value, customConfig);
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
/**
|
|
5005
|
+
* Delete multiple keys at once
|
|
5006
|
+
*/
|
|
5007
|
+
async deleteMany(keys) {
|
|
5008
|
+
for (const key of keys) {
|
|
5009
|
+
await this.delete(key);
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
/**
|
|
5013
|
+
* Get or set pattern - fetch from cache or compute if not found
|
|
5014
|
+
*/
|
|
5015
|
+
async getOrSet(key, fetcher, customConfig) {
|
|
5016
|
+
const cached = await this.get(key);
|
|
5017
|
+
if (cached !== null) {
|
|
5018
|
+
return cached;
|
|
5019
|
+
}
|
|
5020
|
+
const value = await fetcher();
|
|
5021
|
+
await this.set(key, value, customConfig);
|
|
5022
|
+
return value;
|
|
5023
|
+
}
|
|
5024
|
+
/**
|
|
5025
|
+
* List all cache keys with metadata
|
|
5026
|
+
*/
|
|
5027
|
+
async listKeys() {
|
|
5028
|
+
const keys = [];
|
|
5029
|
+
if (this.config.memoryEnabled) {
|
|
5030
|
+
const cache = this.memoryCache.cache;
|
|
5031
|
+
for (const [key, entry] of cache.entries()) {
|
|
5032
|
+
const size = JSON.stringify(entry).length * 2;
|
|
5033
|
+
const age = Date.now() - entry.timestamp;
|
|
5034
|
+
keys.push({
|
|
5035
|
+
key,
|
|
5036
|
+
size,
|
|
5037
|
+
expiresAt: entry.expiresAt,
|
|
5038
|
+
age
|
|
5039
|
+
});
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
return keys.sort((a, b) => a.age - b.age);
|
|
5043
|
+
}
|
|
5044
|
+
/**
|
|
5045
|
+
* Get cache entry with full metadata
|
|
5046
|
+
*/
|
|
5047
|
+
async getEntry(key) {
|
|
5048
|
+
if (!this.config.memoryEnabled) {
|
|
5049
|
+
return null;
|
|
5050
|
+
}
|
|
5051
|
+
const cache = this.memoryCache.cache;
|
|
5052
|
+
const entry = cache.get(key);
|
|
5053
|
+
if (!entry) {
|
|
5054
|
+
return null;
|
|
5055
|
+
}
|
|
5056
|
+
if (Date.now() > entry.expiresAt) {
|
|
5057
|
+
await this.delete(key);
|
|
5058
|
+
return null;
|
|
5059
|
+
}
|
|
5060
|
+
const size = JSON.stringify(entry).length * 2;
|
|
5061
|
+
const ttl = Math.max(0, entry.expiresAt - Date.now()) / 1e3;
|
|
5062
|
+
return {
|
|
5063
|
+
data: entry.data,
|
|
5064
|
+
timestamp: entry.timestamp,
|
|
5065
|
+
expiresAt: entry.expiresAt,
|
|
5066
|
+
ttl,
|
|
5067
|
+
size
|
|
5068
|
+
};
|
|
5069
|
+
}
|
|
5070
|
+
};
|
|
5071
|
+
var cacheInstances = /* @__PURE__ */ new Map();
|
|
5072
|
+
var globalKVNamespace;
|
|
5073
|
+
function getCacheService(config, kvNamespace) {
|
|
5074
|
+
const key = config.namespace;
|
|
5075
|
+
if (!cacheInstances.has(key)) {
|
|
5076
|
+
const kv = globalKVNamespace;
|
|
5077
|
+
cacheInstances.set(key, new CacheService(config, kv));
|
|
5078
|
+
}
|
|
5079
|
+
return cacheInstances.get(key);
|
|
5080
|
+
}
|
|
5081
|
+
async function clearAllCaches() {
|
|
5082
|
+
for (const cache of cacheInstances.values()) {
|
|
5083
|
+
await cache.clear();
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
function getAllCacheStats() {
|
|
5087
|
+
const stats = {};
|
|
5088
|
+
for (const [namespace, cache] of cacheInstances.entries()) {
|
|
5089
|
+
stats[namespace] = cache.getStats();
|
|
5090
|
+
}
|
|
5091
|
+
return stats;
|
|
5092
|
+
}
|
|
5093
|
+
|
|
5094
|
+
// src/plugins/cache/services/event-bus.ts
|
|
5095
|
+
var EventBus = class {
|
|
5096
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
5097
|
+
eventLog = [];
|
|
5098
|
+
maxLogSize = 100;
|
|
5099
|
+
/**
|
|
5100
|
+
* Subscribe to an event
|
|
5101
|
+
*/
|
|
5102
|
+
on(event, handler) {
|
|
5103
|
+
if (!this.subscriptions.has(event)) {
|
|
5104
|
+
this.subscriptions.set(event, []);
|
|
5105
|
+
}
|
|
5106
|
+
this.subscriptions.get(event).push(handler);
|
|
5107
|
+
return () => {
|
|
5108
|
+
const handlers = this.subscriptions.get(event);
|
|
5109
|
+
if (handlers) {
|
|
5110
|
+
const index = handlers.indexOf(handler);
|
|
5111
|
+
if (index > -1) {
|
|
5112
|
+
handlers.splice(index, 1);
|
|
5113
|
+
}
|
|
5114
|
+
}
|
|
5115
|
+
};
|
|
5116
|
+
}
|
|
5117
|
+
/**
|
|
5118
|
+
* Emit an event to all subscribers
|
|
5119
|
+
*/
|
|
5120
|
+
async emit(event, data) {
|
|
5121
|
+
this.logEvent(event, data);
|
|
5122
|
+
const handlers = this.subscriptions.get(event) || [];
|
|
5123
|
+
await Promise.all(
|
|
5124
|
+
handlers.map(async (handler) => {
|
|
5125
|
+
try {
|
|
5126
|
+
await handler(data);
|
|
5127
|
+
} catch (error) {
|
|
5128
|
+
console.error(`Error in event handler for ${event}:`, error);
|
|
5129
|
+
}
|
|
5130
|
+
})
|
|
5131
|
+
);
|
|
5132
|
+
const wildcardHandlers = this.subscriptions.get("*") || [];
|
|
5133
|
+
await Promise.all(
|
|
5134
|
+
wildcardHandlers.map(async (handler) => {
|
|
5135
|
+
try {
|
|
5136
|
+
await handler({ event, data });
|
|
5137
|
+
} catch (error) {
|
|
5138
|
+
console.error(`Error in wildcard event handler for ${event}:`, error);
|
|
5139
|
+
}
|
|
5140
|
+
})
|
|
5141
|
+
);
|
|
5142
|
+
}
|
|
5143
|
+
/**
|
|
5144
|
+
* Remove all subscribers for an event
|
|
5145
|
+
*/
|
|
5146
|
+
off(event) {
|
|
5147
|
+
this.subscriptions.delete(event);
|
|
5148
|
+
}
|
|
5149
|
+
/**
|
|
5150
|
+
* Get all registered events
|
|
5151
|
+
*/
|
|
5152
|
+
getEvents() {
|
|
5153
|
+
return Array.from(this.subscriptions.keys());
|
|
5154
|
+
}
|
|
5155
|
+
/**
|
|
5156
|
+
* Get subscriber count for an event
|
|
5157
|
+
*/
|
|
5158
|
+
getSubscriberCount(event) {
|
|
5159
|
+
return this.subscriptions.get(event)?.length || 0;
|
|
5160
|
+
}
|
|
5161
|
+
/**
|
|
5162
|
+
* Log an event for debugging
|
|
5163
|
+
*/
|
|
5164
|
+
logEvent(event, data) {
|
|
5165
|
+
this.eventLog.push({
|
|
5166
|
+
event,
|
|
5167
|
+
timestamp: Date.now(),
|
|
5168
|
+
data
|
|
5169
|
+
});
|
|
5170
|
+
if (this.eventLog.length > this.maxLogSize) {
|
|
5171
|
+
this.eventLog.shift();
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
/**
|
|
5175
|
+
* Get recent event log
|
|
5176
|
+
*/
|
|
5177
|
+
getEventLog(limit = 50) {
|
|
5178
|
+
return this.eventLog.slice(-limit);
|
|
5179
|
+
}
|
|
5180
|
+
/**
|
|
5181
|
+
* Clear event log
|
|
5182
|
+
*/
|
|
5183
|
+
clearEventLog() {
|
|
5184
|
+
this.eventLog = [];
|
|
5185
|
+
}
|
|
5186
|
+
/**
|
|
5187
|
+
* Get statistics
|
|
5188
|
+
*/
|
|
5189
|
+
getStats() {
|
|
5190
|
+
const eventCounts = {};
|
|
5191
|
+
for (const log of this.eventLog) {
|
|
5192
|
+
eventCounts[log.event] = (eventCounts[log.event] || 0) + 1;
|
|
5193
|
+
}
|
|
5194
|
+
return {
|
|
5195
|
+
totalEvents: this.eventLog.length,
|
|
5196
|
+
totalSubscriptions: this.subscriptions.size,
|
|
5197
|
+
eventCounts
|
|
5198
|
+
};
|
|
5199
|
+
}
|
|
5200
|
+
};
|
|
5201
|
+
var globalEventBus = null;
|
|
5202
|
+
function getEventBus() {
|
|
5203
|
+
if (!globalEventBus) {
|
|
5204
|
+
globalEventBus = new EventBus();
|
|
5205
|
+
}
|
|
5206
|
+
return globalEventBus;
|
|
5207
|
+
}
|
|
5208
|
+
function onEvent(event, handler) {
|
|
5209
|
+
const bus = getEventBus();
|
|
5210
|
+
return bus.on(event, handler);
|
|
5211
|
+
}
|
|
5212
|
+
|
|
5213
|
+
// src/plugins/cache/services/cache-invalidation.ts
|
|
5214
|
+
function setupCacheInvalidation() {
|
|
5215
|
+
getEventBus();
|
|
5216
|
+
setupContentInvalidation();
|
|
5217
|
+
setupUserInvalidation();
|
|
5218
|
+
setupConfigInvalidation();
|
|
5219
|
+
setupMediaInvalidation();
|
|
5220
|
+
setupAPIInvalidation();
|
|
5221
|
+
setupCollectionInvalidation();
|
|
5222
|
+
console.log("Cache invalidation listeners registered");
|
|
5223
|
+
}
|
|
5224
|
+
function setupContentInvalidation() {
|
|
5225
|
+
const config = CACHE_CONFIGS.content;
|
|
5226
|
+
if (!config) return;
|
|
5227
|
+
const contentCache = getCacheService(config);
|
|
5228
|
+
onEvent("content.create", async (_data) => {
|
|
5229
|
+
await contentCache.invalidate("content:*");
|
|
5230
|
+
console.log("Cache invalidated: content.create");
|
|
5231
|
+
});
|
|
5232
|
+
onEvent("content.update", async (data) => {
|
|
5233
|
+
if (data?.id) {
|
|
5234
|
+
await contentCache.delete(contentCache.generateKey("item", data.id));
|
|
5235
|
+
}
|
|
5236
|
+
await contentCache.invalidate("content:list:*");
|
|
5237
|
+
console.log("Cache invalidated: content.update", data?.id);
|
|
5238
|
+
});
|
|
5239
|
+
onEvent("content.delete", async (data) => {
|
|
5240
|
+
if (data?.id) {
|
|
5241
|
+
await contentCache.delete(contentCache.generateKey("item", data.id));
|
|
5242
|
+
}
|
|
5243
|
+
await contentCache.invalidate("content:*");
|
|
5244
|
+
console.log("Cache invalidated: content.delete", data?.id);
|
|
5245
|
+
});
|
|
5246
|
+
onEvent("content.publish", async (_data) => {
|
|
5247
|
+
await contentCache.invalidate("content:*");
|
|
5248
|
+
console.log("Cache invalidated: content.publish");
|
|
5249
|
+
});
|
|
5250
|
+
}
|
|
5251
|
+
function setupUserInvalidation() {
|
|
5252
|
+
const config = CACHE_CONFIGS.user;
|
|
5253
|
+
if (!config) return;
|
|
5254
|
+
const userCache = getCacheService(config);
|
|
5255
|
+
onEvent("user.update", async (data) => {
|
|
5256
|
+
if (data?.id) {
|
|
5257
|
+
await userCache.delete(userCache.generateKey("id", data.id));
|
|
5258
|
+
}
|
|
5259
|
+
if (data?.email) {
|
|
5260
|
+
await userCache.delete(userCache.generateKey("email", data.email));
|
|
5261
|
+
}
|
|
5262
|
+
console.log("Cache invalidated: user.update", data?.id);
|
|
5263
|
+
});
|
|
5264
|
+
onEvent("user.delete", async (data) => {
|
|
5265
|
+
if (data?.id) {
|
|
5266
|
+
await userCache.delete(userCache.generateKey("id", data.id));
|
|
5267
|
+
}
|
|
5268
|
+
if (data?.email) {
|
|
5269
|
+
await userCache.delete(userCache.generateKey("email", data.email));
|
|
5270
|
+
}
|
|
5271
|
+
console.log("Cache invalidated: user.delete", data?.id);
|
|
5272
|
+
});
|
|
5273
|
+
onEvent("auth.login", async (data) => {
|
|
5274
|
+
if (data?.userId) {
|
|
5275
|
+
await userCache.delete(userCache.generateKey("id", data.userId));
|
|
5276
|
+
}
|
|
5277
|
+
console.log("Cache invalidated: auth.login", data?.userId);
|
|
5278
|
+
});
|
|
5279
|
+
onEvent("auth.logout", async (data) => {
|
|
5280
|
+
const sessionConfig = CACHE_CONFIGS.session;
|
|
5281
|
+
if (sessionConfig) {
|
|
5282
|
+
const sessionCache = getCacheService(sessionConfig);
|
|
5283
|
+
if (data?.sessionId) {
|
|
5284
|
+
await sessionCache.delete(sessionCache.generateKey("session", data.sessionId));
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
console.log("Cache invalidated: auth.logout");
|
|
5288
|
+
});
|
|
5289
|
+
}
|
|
5290
|
+
function setupConfigInvalidation() {
|
|
5291
|
+
const configConfig = CACHE_CONFIGS.config;
|
|
5292
|
+
if (!configConfig) return;
|
|
5293
|
+
const configCache = getCacheService(configConfig);
|
|
5294
|
+
onEvent("config.update", async (_data) => {
|
|
5295
|
+
await configCache.invalidate("config:*");
|
|
5296
|
+
console.log("Cache invalidated: config.update");
|
|
5297
|
+
});
|
|
5298
|
+
onEvent("plugin.activate", async (data) => {
|
|
5299
|
+
await configCache.invalidate("config:*");
|
|
5300
|
+
const pluginConfig = CACHE_CONFIGS.plugin;
|
|
5301
|
+
if (pluginConfig) {
|
|
5302
|
+
const pluginCache = getCacheService(pluginConfig);
|
|
5303
|
+
await pluginCache.invalidate("plugin:*");
|
|
5304
|
+
}
|
|
5305
|
+
console.log("Cache invalidated: plugin.activate", data?.pluginId);
|
|
5306
|
+
});
|
|
5307
|
+
onEvent("plugin.deactivate", async (data) => {
|
|
5308
|
+
await configCache.invalidate("config:*");
|
|
5309
|
+
const pluginConfig = CACHE_CONFIGS.plugin;
|
|
5310
|
+
if (pluginConfig) {
|
|
5311
|
+
const pluginCache = getCacheService(pluginConfig);
|
|
5312
|
+
await pluginCache.invalidate("plugin:*");
|
|
5313
|
+
}
|
|
5314
|
+
console.log("Cache invalidated: plugin.deactivate", data?.pluginId);
|
|
5315
|
+
});
|
|
5316
|
+
onEvent("plugin.update", async (data) => {
|
|
5317
|
+
const pluginConfig = CACHE_CONFIGS.plugin;
|
|
5318
|
+
if (!pluginConfig) return;
|
|
5319
|
+
const pluginCache = getCacheService(pluginConfig);
|
|
5320
|
+
await pluginCache.invalidate("plugin:*");
|
|
5321
|
+
console.log("Cache invalidated: plugin.update", data?.pluginId);
|
|
5322
|
+
});
|
|
5323
|
+
}
|
|
5324
|
+
function setupMediaInvalidation() {
|
|
5325
|
+
const config = CACHE_CONFIGS.media;
|
|
5326
|
+
if (!config) return;
|
|
5327
|
+
const mediaCache = getCacheService(config);
|
|
5328
|
+
onEvent("media.upload", async (_data) => {
|
|
5329
|
+
await mediaCache.invalidate("media:*");
|
|
5330
|
+
console.log("Cache invalidated: media.upload");
|
|
5331
|
+
});
|
|
5332
|
+
onEvent("media.delete", async (data) => {
|
|
5333
|
+
if (data?.id) {
|
|
5334
|
+
await mediaCache.delete(mediaCache.generateKey("item", data.id));
|
|
5335
|
+
}
|
|
5336
|
+
await mediaCache.invalidate("media:list:*");
|
|
5337
|
+
console.log("Cache invalidated: media.delete", data?.id);
|
|
5338
|
+
});
|
|
5339
|
+
onEvent("media.update", async (data) => {
|
|
5340
|
+
if (data?.id) {
|
|
5341
|
+
await mediaCache.delete(mediaCache.generateKey("item", data.id));
|
|
5342
|
+
}
|
|
5343
|
+
await mediaCache.invalidate("media:list:*");
|
|
5344
|
+
console.log("Cache invalidated: media.update", data?.id);
|
|
5345
|
+
});
|
|
5346
|
+
}
|
|
5347
|
+
function setupAPIInvalidation() {
|
|
5348
|
+
const config = CACHE_CONFIGS.api;
|
|
5349
|
+
if (!config) return;
|
|
5350
|
+
const apiCache = getCacheService(config);
|
|
5351
|
+
onEvent("content.update", async (_data) => {
|
|
5352
|
+
await apiCache.invalidate("api:*");
|
|
5353
|
+
console.log("Cache invalidated: api (content.update)");
|
|
5354
|
+
});
|
|
5355
|
+
onEvent("content.publish", async (_data) => {
|
|
5356
|
+
await apiCache.invalidate("api:*");
|
|
5357
|
+
console.log("Cache invalidated: api (content.publish)");
|
|
5358
|
+
});
|
|
5359
|
+
onEvent("content.create", async (_data) => {
|
|
5360
|
+
await apiCache.invalidate("api:*");
|
|
5361
|
+
console.log("Cache invalidated: api (content.create)");
|
|
5362
|
+
});
|
|
5363
|
+
onEvent("content.delete", async (_data) => {
|
|
5364
|
+
await apiCache.invalidate("api:*");
|
|
5365
|
+
console.log("Cache invalidated: api (content.delete)");
|
|
5366
|
+
});
|
|
5367
|
+
onEvent("collection.update", async (_data) => {
|
|
5368
|
+
await apiCache.invalidate("api:*");
|
|
5369
|
+
console.log("Cache invalidated: api (collection.update)");
|
|
5370
|
+
});
|
|
5371
|
+
}
|
|
5372
|
+
function setupCollectionInvalidation() {
|
|
5373
|
+
const config = CACHE_CONFIGS.collection;
|
|
5374
|
+
if (!config) return;
|
|
5375
|
+
const collectionCache = getCacheService(config);
|
|
5376
|
+
onEvent("collection.create", async (_data) => {
|
|
5377
|
+
await collectionCache.invalidate("collection:*");
|
|
5378
|
+
console.log("Cache invalidated: collection.create");
|
|
5379
|
+
});
|
|
5380
|
+
onEvent("collection.update", async (data) => {
|
|
5381
|
+
if (data?.id) {
|
|
5382
|
+
await collectionCache.delete(collectionCache.generateKey("item", data.id));
|
|
5383
|
+
}
|
|
5384
|
+
await collectionCache.invalidate("collection:*");
|
|
5385
|
+
console.log("Cache invalidated: collection.update", data?.id);
|
|
5386
|
+
});
|
|
5387
|
+
onEvent("collection.delete", async (data) => {
|
|
5388
|
+
await collectionCache.invalidate("collection:*");
|
|
5389
|
+
console.log("Cache invalidated: collection.delete", data?.id);
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
function getCacheInvalidationStats() {
|
|
5393
|
+
const eventBus = getEventBus();
|
|
5394
|
+
return eventBus.getStats();
|
|
5395
|
+
}
|
|
5396
|
+
function getRecentInvalidations(limit = 50) {
|
|
5397
|
+
const eventBus = getEventBus();
|
|
5398
|
+
return eventBus.getEventLog(limit);
|
|
5399
|
+
}
|
|
5400
|
+
|
|
5401
|
+
// src/plugins/cache/services/cache-warming.ts
|
|
5402
|
+
async function warmCommonCaches(db) {
|
|
5403
|
+
let totalWarmed = 0;
|
|
5404
|
+
let totalErrors = 0;
|
|
5405
|
+
const details = [];
|
|
5406
|
+
try {
|
|
5407
|
+
const collectionCount = await warmCollections(db);
|
|
5408
|
+
totalWarmed += collectionCount;
|
|
5409
|
+
details.push({ namespace: "collection", count: collectionCount });
|
|
5410
|
+
const contentCount = await warmRecentContent(db);
|
|
5411
|
+
totalWarmed += contentCount;
|
|
5412
|
+
details.push({ namespace: "content", count: contentCount });
|
|
5413
|
+
const mediaCount = await warmRecentMedia(db);
|
|
5414
|
+
totalWarmed += mediaCount;
|
|
5415
|
+
details.push({ namespace: "media", count: mediaCount });
|
|
5416
|
+
} catch (error) {
|
|
5417
|
+
console.error("Error warming caches:", error);
|
|
5418
|
+
totalErrors++;
|
|
5419
|
+
}
|
|
5420
|
+
return {
|
|
5421
|
+
warmed: totalWarmed,
|
|
5422
|
+
errors: totalErrors,
|
|
5423
|
+
details
|
|
5424
|
+
};
|
|
5425
|
+
}
|
|
5426
|
+
async function warmCollections(db) {
|
|
5427
|
+
const config = CACHE_CONFIGS.collection;
|
|
5428
|
+
if (!config) return 0;
|
|
5429
|
+
const collectionCache = getCacheService(config);
|
|
5430
|
+
let count = 0;
|
|
5431
|
+
try {
|
|
5432
|
+
const stmt = db.prepare("SELECT * FROM collections WHERE is_active = 1");
|
|
5433
|
+
const { results } = await stmt.all();
|
|
5434
|
+
for (const collection of results) {
|
|
5435
|
+
const key = collectionCache.generateKey("item", collection.id);
|
|
5436
|
+
await collectionCache.set(key, collection);
|
|
5437
|
+
count++;
|
|
5438
|
+
}
|
|
5439
|
+
const listKey = collectionCache.generateKey("list", "all");
|
|
5440
|
+
await collectionCache.set(listKey, results);
|
|
5441
|
+
count++;
|
|
5442
|
+
} catch (error) {
|
|
5443
|
+
console.error("Error warming collections cache:", error);
|
|
5444
|
+
}
|
|
5445
|
+
return count;
|
|
5446
|
+
}
|
|
5447
|
+
async function warmRecentContent(db, limit = 50) {
|
|
5448
|
+
const config = CACHE_CONFIGS.content;
|
|
5449
|
+
if (!config) return 0;
|
|
5450
|
+
const contentCache = getCacheService(config);
|
|
5451
|
+
let count = 0;
|
|
5452
|
+
try {
|
|
5453
|
+
const stmt = db.prepare(`SELECT * FROM content ORDER BY created_at DESC LIMIT ${limit}`);
|
|
5454
|
+
const { results } = await stmt.all();
|
|
5455
|
+
for (const content2 of results) {
|
|
5456
|
+
const key = contentCache.generateKey("item", content2.id);
|
|
5457
|
+
await contentCache.set(key, content2);
|
|
5458
|
+
count++;
|
|
5459
|
+
}
|
|
5460
|
+
const listKey = contentCache.generateKey("list", "recent");
|
|
5461
|
+
await contentCache.set(listKey, results);
|
|
5462
|
+
count++;
|
|
5463
|
+
} catch (error) {
|
|
5464
|
+
console.error("Error warming content cache:", error);
|
|
5465
|
+
}
|
|
5466
|
+
return count;
|
|
5467
|
+
}
|
|
5468
|
+
async function warmRecentMedia(db, limit = 50) {
|
|
5469
|
+
const config = CACHE_CONFIGS.media;
|
|
5470
|
+
if (!config) return 0;
|
|
5471
|
+
const mediaCache = getCacheService(config);
|
|
5472
|
+
let count = 0;
|
|
5473
|
+
try {
|
|
5474
|
+
const stmt = db.prepare(`SELECT * FROM media WHERE deleted_at IS NULL ORDER BY uploaded_at DESC LIMIT ${limit}`);
|
|
5475
|
+
const { results } = await stmt.all();
|
|
5476
|
+
for (const media2 of results) {
|
|
5477
|
+
const key = mediaCache.generateKey("item", media2.id);
|
|
5478
|
+
await mediaCache.set(key, media2);
|
|
5479
|
+
count++;
|
|
5480
|
+
}
|
|
5481
|
+
const listKey = mediaCache.generateKey("list", "recent");
|
|
5482
|
+
await mediaCache.set(listKey, results);
|
|
5483
|
+
count++;
|
|
5484
|
+
} catch (error) {
|
|
5485
|
+
console.error("Error warming media cache:", error);
|
|
5486
|
+
}
|
|
5487
|
+
return count;
|
|
5488
|
+
}
|
|
5489
|
+
async function warmNamespace(namespace, entries) {
|
|
5490
|
+
const config = CACHE_CONFIGS[namespace];
|
|
5491
|
+
if (!config) {
|
|
5492
|
+
throw new Error(`Unknown namespace: ${namespace}`);
|
|
5493
|
+
}
|
|
5494
|
+
const cache = getCacheService(config);
|
|
5495
|
+
await cache.setMany(entries);
|
|
5496
|
+
return entries.length;
|
|
5497
|
+
}
|
|
5498
|
+
|
|
5499
|
+
// src/templates/pages/admin-cache.template.ts
|
|
5500
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
5501
|
+
function renderCacheDashboard(data) {
|
|
5502
|
+
const pageContent = `
|
|
5503
|
+
<div class="space-y-6">
|
|
5504
|
+
<!-- Header -->
|
|
5505
|
+
<div class="flex items-center justify-between">
|
|
5506
|
+
<div>
|
|
5507
|
+
<h1 class="text-2xl font-semibold text-zinc-950 dark:text-white">Cache System</h1>
|
|
5508
|
+
<p class="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
|
|
5509
|
+
Monitor and manage cache performance across all namespaces
|
|
5510
|
+
</p>
|
|
5511
|
+
</div>
|
|
5512
|
+
<div class="flex gap-3">
|
|
5513
|
+
<button
|
|
5514
|
+
onclick="refreshStats()"
|
|
5515
|
+
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-zinc-900 px-4 py-2 text-sm font-medium text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-800"
|
|
5516
|
+
>
|
|
5517
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5518
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
5519
|
+
</svg>
|
|
5520
|
+
Refresh
|
|
5521
|
+
</button>
|
|
5522
|
+
<button
|
|
5523
|
+
onclick="clearAllCaches()"
|
|
5524
|
+
class="inline-flex items-center gap-2 rounded-lg bg-red-600 dark:bg-red-500 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 dark:hover:bg-red-600"
|
|
5525
|
+
>
|
|
5526
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5527
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
5528
|
+
</svg>
|
|
5529
|
+
Clear All
|
|
5530
|
+
</button>
|
|
2702
5531
|
</div>
|
|
5532
|
+
</div>
|
|
2703
5533
|
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
5534
|
+
<!-- Overall Stats Cards -->
|
|
5535
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
5536
|
+
${renderStatCard("Total Requests", data.totals.requests.toLocaleString(), "lime", `
|
|
5537
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5538
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
|
|
5539
|
+
</svg>
|
|
5540
|
+
`)}
|
|
2707
5541
|
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
5542
|
+
${renderStatCard("Hit Rate", data.totals.hitRate + "%", "blue", `
|
|
5543
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5544
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
5545
|
+
</svg>
|
|
5546
|
+
`, parseFloat(data.totals.hitRate) > 70 ? "lime" : parseFloat(data.totals.hitRate) > 40 ? "amber" : "red")}
|
|
2711
5547
|
|
|
2712
|
-
|
|
5548
|
+
${renderStatCard("Memory Usage", formatBytes(data.totals.memorySize), "purple", `
|
|
5549
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5550
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
|
|
5551
|
+
</svg>
|
|
5552
|
+
`)}
|
|
2713
5553
|
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
5554
|
+
${renderStatCard("Cached Entries", data.totals.entryCount.toLocaleString(), "sky", `
|
|
5555
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5556
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/>
|
|
5557
|
+
</svg>
|
|
5558
|
+
`)}
|
|
5559
|
+
</div>
|
|
5560
|
+
|
|
5561
|
+
<!-- Namespace Statistics -->
|
|
5562
|
+
<div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
|
|
5563
|
+
<div class="px-6 py-4 border-b border-zinc-950/5 dark:border-white/10">
|
|
5564
|
+
<h2 class="text-lg font-semibold text-zinc-950 dark:text-white">Cache Namespaces</h2>
|
|
5565
|
+
</div>
|
|
5566
|
+
<div class="overflow-x-auto">
|
|
5567
|
+
<table class="min-w-full divide-y divide-zinc-950/5 dark:divide-white/10">
|
|
5568
|
+
<thead class="bg-zinc-50 dark:bg-zinc-800/50">
|
|
5569
|
+
<tr>
|
|
5570
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
|
|
5571
|
+
Namespace
|
|
5572
|
+
</th>
|
|
5573
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
|
|
5574
|
+
Requests
|
|
5575
|
+
</th>
|
|
5576
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
|
|
5577
|
+
Hit Rate
|
|
5578
|
+
</th>
|
|
5579
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
|
|
5580
|
+
Memory Hits
|
|
5581
|
+
</th>
|
|
5582
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
|
|
5583
|
+
KV Hits
|
|
5584
|
+
</th>
|
|
5585
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
|
|
5586
|
+
Entries
|
|
5587
|
+
</th>
|
|
5588
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
|
|
5589
|
+
Size
|
|
5590
|
+
</th>
|
|
5591
|
+
<th class="px-6 py-3 text-right text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
|
|
5592
|
+
Actions
|
|
5593
|
+
</th>
|
|
5594
|
+
</tr>
|
|
5595
|
+
</thead>
|
|
5596
|
+
<tbody class="divide-y divide-zinc-950/5 dark:divide-white/10">
|
|
5597
|
+
${data.namespaces.map((namespace) => {
|
|
5598
|
+
const stat = data.stats[namespace];
|
|
5599
|
+
if (!stat) return "";
|
|
5600
|
+
return renderNamespaceRow(namespace, stat);
|
|
5601
|
+
}).join("")}
|
|
5602
|
+
</tbody>
|
|
5603
|
+
</table>
|
|
5604
|
+
</div>
|
|
5605
|
+
</div>
|
|
5606
|
+
|
|
5607
|
+
<!-- Performance Chart Placeholder -->
|
|
5608
|
+
<div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
|
|
5609
|
+
<div class="px-6 py-4 border-b border-zinc-950/5 dark:border-white/10">
|
|
5610
|
+
<h2 class="text-lg font-semibold text-zinc-950 dark:text-white">Performance Overview</h2>
|
|
5611
|
+
</div>
|
|
5612
|
+
<div class="p-6">
|
|
5613
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
5614
|
+
${renderPerformanceMetric("Memory Cache", data.totals.hits, data.totals.misses)}
|
|
5615
|
+
${renderHealthStatus(parseFloat(data.totals.hitRate))}
|
|
2717
5616
|
</div>
|
|
2718
5617
|
</div>
|
|
5618
|
+
</div>
|
|
5619
|
+
</div>
|
|
2719
5620
|
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
5621
|
+
<script>
|
|
5622
|
+
async function refreshStats() {
|
|
5623
|
+
window.location.reload()
|
|
5624
|
+
}
|
|
5625
|
+
|
|
5626
|
+
async function clearAllCaches() {
|
|
5627
|
+
showConfirmDialog('clear-all-cache-confirm')
|
|
5628
|
+
}
|
|
5629
|
+
|
|
5630
|
+
async function performClearAllCaches() {
|
|
5631
|
+
try {
|
|
5632
|
+
const response = await fetch('/admin/cache/clear', {
|
|
5633
|
+
method: 'POST'
|
|
5634
|
+
})
|
|
5635
|
+
|
|
5636
|
+
const result = await response.json()
|
|
5637
|
+
if (result.success) {
|
|
5638
|
+
alert('All caches cleared successfully')
|
|
5639
|
+
window.location.reload()
|
|
5640
|
+
} else {
|
|
5641
|
+
alert('Error clearing caches: ' + result.error)
|
|
5642
|
+
}
|
|
5643
|
+
} catch (error) {
|
|
5644
|
+
alert('Error clearing caches: ' + error.message)
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
|
|
5648
|
+
let namespaceToDelete = null
|
|
5649
|
+
|
|
5650
|
+
async function clearNamespaceCache(namespace) {
|
|
5651
|
+
namespaceToDelete = namespace
|
|
5652
|
+
showConfirmDialog('clear-namespace-cache-confirm')
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5655
|
+
async function performClearNamespaceCache() {
|
|
5656
|
+
if (!namespaceToDelete) return
|
|
5657
|
+
|
|
5658
|
+
try {
|
|
5659
|
+
const response = await fetch(\`/admin/cache/clear/\${namespaceToDelete}\`, {
|
|
5660
|
+
method: 'POST'
|
|
5661
|
+
})
|
|
5662
|
+
|
|
5663
|
+
const result = await response.json()
|
|
5664
|
+
if (result.success) {
|
|
5665
|
+
alert('Cache cleared successfully')
|
|
5666
|
+
window.location.reload()
|
|
5667
|
+
} else {
|
|
5668
|
+
alert('Error clearing cache: ' + result.error)
|
|
5669
|
+
}
|
|
5670
|
+
} catch (error) {
|
|
5671
|
+
alert('Error clearing cache: ' + error.message)
|
|
5672
|
+
} finally {
|
|
5673
|
+
namespaceToDelete = null
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
</script>
|
|
5677
|
+
|
|
5678
|
+
<!-- Confirmation Dialogs -->
|
|
5679
|
+
${chunkJ7F3NPAP_cjs.renderConfirmationDialog({
|
|
5680
|
+
id: "clear-all-cache-confirm",
|
|
5681
|
+
title: "Clear All Cache",
|
|
5682
|
+
message: "Are you sure you want to clear all cache entries? This cannot be undone.",
|
|
5683
|
+
confirmText: "Clear All",
|
|
5684
|
+
cancelText: "Cancel",
|
|
5685
|
+
iconColor: "yellow",
|
|
5686
|
+
confirmClass: "bg-yellow-500 hover:bg-yellow-400",
|
|
5687
|
+
onConfirm: "performClearAllCaches()"
|
|
5688
|
+
})}
|
|
5689
|
+
|
|
5690
|
+
${chunkJ7F3NPAP_cjs.renderConfirmationDialog({
|
|
5691
|
+
id: "clear-namespace-cache-confirm",
|
|
5692
|
+
title: "Clear Namespace Cache",
|
|
5693
|
+
message: "Clear cache for this namespace?",
|
|
5694
|
+
confirmText: "Clear",
|
|
5695
|
+
cancelText: "Cancel",
|
|
5696
|
+
iconColor: "yellow",
|
|
5697
|
+
confirmClass: "bg-yellow-500 hover:bg-yellow-400",
|
|
5698
|
+
onConfirm: "performClearNamespaceCache()"
|
|
5699
|
+
})}
|
|
5700
|
+
|
|
5701
|
+
${chunkJ7F3NPAP_cjs.getConfirmationDialogScript()}
|
|
5702
|
+
`;
|
|
5703
|
+
const layoutData = {
|
|
5704
|
+
title: "Cache System",
|
|
5705
|
+
pageTitle: "Cache System",
|
|
5706
|
+
currentPath: "/admin/cache",
|
|
5707
|
+
user: data.user,
|
|
5708
|
+
version: data.version,
|
|
5709
|
+
content: pageContent
|
|
5710
|
+
};
|
|
5711
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
5712
|
+
}
|
|
5713
|
+
function renderStatCard(label, value, color, icon, colorOverride) {
|
|
5714
|
+
const finalColor = colorOverride || color;
|
|
5715
|
+
const colorClasses = {
|
|
5716
|
+
lime: "bg-lime-50 dark:bg-lime-500/10 text-lime-600 dark:text-lime-400 ring-lime-600/20 dark:ring-lime-500/20",
|
|
5717
|
+
blue: "bg-blue-50 dark:bg-blue-500/10 text-blue-600 dark:text-blue-400 ring-blue-600/20 dark:ring-blue-500/20",
|
|
5718
|
+
purple: "bg-purple-50 dark:bg-purple-500/10 text-purple-600 dark:text-purple-400 ring-purple-600/20 dark:ring-purple-500/20",
|
|
5719
|
+
sky: "bg-sky-50 dark:bg-sky-500/10 text-sky-600 dark:text-sky-400 ring-sky-600/20 dark:ring-sky-500/20",
|
|
5720
|
+
amber: "bg-amber-50 dark:bg-amber-500/10 text-amber-600 dark:text-amber-400 ring-amber-600/20 dark:ring-amber-500/20",
|
|
5721
|
+
red: "bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-400 ring-red-600/20 dark:ring-red-500/20"
|
|
5722
|
+
};
|
|
5723
|
+
return `
|
|
5724
|
+
<div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
|
|
5725
|
+
<div class="p-6">
|
|
5726
|
+
<div class="flex items-center justify-between">
|
|
5727
|
+
<div class="flex items-center gap-3">
|
|
5728
|
+
<div class="rounded-lg p-2 ring-1 ring-inset ${colorClasses[finalColor]}">
|
|
5729
|
+
${icon}
|
|
5730
|
+
</div>
|
|
5731
|
+
<div>
|
|
5732
|
+
<p class="text-sm text-zinc-600 dark:text-zinc-400">${label}</p>
|
|
5733
|
+
<p class="mt-1 text-2xl font-semibold text-zinc-950 dark:text-white">${value}</p>
|
|
5734
|
+
</div>
|
|
5735
|
+
</div>
|
|
2723
5736
|
</div>
|
|
2724
5737
|
</div>
|
|
2725
|
-
</
|
|
2726
|
-
</html>
|
|
5738
|
+
</div>
|
|
2727
5739
|
`;
|
|
2728
5740
|
}
|
|
2729
|
-
|
|
5741
|
+
function renderNamespaceRow(namespace, stat) {
|
|
5742
|
+
const hitRate = stat.hitRate.toFixed(1);
|
|
5743
|
+
const hitRateColor = stat.hitRate > 70 ? "text-lime-600 dark:text-lime-400" : stat.hitRate > 40 ? "text-amber-600 dark:text-amber-400" : "text-red-600 dark:text-red-400";
|
|
5744
|
+
return `
|
|
5745
|
+
<tr class="hover:bg-zinc-50 dark:hover:bg-zinc-800/50">
|
|
5746
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
5747
|
+
<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium bg-zinc-100 dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 ring-1 ring-inset ring-zinc-200 dark:ring-zinc-700">
|
|
5748
|
+
${namespace}
|
|
5749
|
+
</span>
|
|
5750
|
+
</td>
|
|
5751
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-900 dark:text-zinc-100">
|
|
5752
|
+
${stat.totalRequests.toLocaleString()}
|
|
5753
|
+
</td>
|
|
5754
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
5755
|
+
<span class="text-sm font-medium ${hitRateColor}">
|
|
5756
|
+
${hitRate}%
|
|
5757
|
+
</span>
|
|
5758
|
+
</td>
|
|
5759
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
|
|
5760
|
+
${stat.memoryHits.toLocaleString()}
|
|
5761
|
+
</td>
|
|
5762
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
|
|
5763
|
+
${stat.kvHits.toLocaleString()}
|
|
5764
|
+
</td>
|
|
5765
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
|
|
5766
|
+
${stat.entryCount.toLocaleString()}
|
|
5767
|
+
</td>
|
|
5768
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
|
|
5769
|
+
${formatBytes(stat.memorySize)}
|
|
5770
|
+
</td>
|
|
5771
|
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm">
|
|
5772
|
+
<button
|
|
5773
|
+
onclick="clearNamespaceCache('${namespace}')"
|
|
5774
|
+
class="text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300"
|
|
5775
|
+
>
|
|
5776
|
+
Clear
|
|
5777
|
+
</button>
|
|
5778
|
+
</td>
|
|
5779
|
+
</tr>
|
|
5780
|
+
`;
|
|
5781
|
+
}
|
|
5782
|
+
function renderPerformanceMetric(label, hits, misses) {
|
|
5783
|
+
const total = hits + misses;
|
|
5784
|
+
const hitPercentage = total > 0 ? hits / total * 100 : 0;
|
|
5785
|
+
return `
|
|
5786
|
+
<div>
|
|
5787
|
+
<h3 class="text-sm font-medium text-zinc-900 dark:text-zinc-100 mb-3">${label}</h3>
|
|
5788
|
+
<div class="space-y-2">
|
|
5789
|
+
<div class="flex items-center justify-between text-sm">
|
|
5790
|
+
<span class="text-zinc-600 dark:text-zinc-400">Hits</span>
|
|
5791
|
+
<span class="font-medium text-zinc-900 dark:text-zinc-100">${hits.toLocaleString()}</span>
|
|
5792
|
+
</div>
|
|
5793
|
+
<div class="flex items-center justify-between text-sm">
|
|
5794
|
+
<span class="text-zinc-600 dark:text-zinc-400">Misses</span>
|
|
5795
|
+
<span class="font-medium text-zinc-900 dark:text-zinc-100">${misses.toLocaleString()}</span>
|
|
5796
|
+
</div>
|
|
5797
|
+
<div class="mt-3">
|
|
5798
|
+
<div class="flex items-center justify-between text-sm mb-1">
|
|
5799
|
+
<span class="text-zinc-600 dark:text-zinc-400">Hit Rate</span>
|
|
5800
|
+
<span class="font-medium text-zinc-900 dark:text-zinc-100">${hitPercentage.toFixed(1)}%</span>
|
|
5801
|
+
</div>
|
|
5802
|
+
<div class="h-2 bg-zinc-200 dark:bg-zinc-700 rounded-full overflow-hidden">
|
|
5803
|
+
<div class="h-full bg-lime-500 dark:bg-lime-400" style="width: ${hitPercentage}%"></div>
|
|
5804
|
+
</div>
|
|
5805
|
+
</div>
|
|
5806
|
+
</div>
|
|
5807
|
+
</div>
|
|
5808
|
+
`;
|
|
5809
|
+
}
|
|
5810
|
+
function renderHealthStatus(hitRate) {
|
|
5811
|
+
const status = hitRate > 70 ? "healthy" : hitRate > 40 ? "warning" : "critical";
|
|
5812
|
+
const statusConfig = {
|
|
5813
|
+
healthy: {
|
|
5814
|
+
label: "Healthy",
|
|
5815
|
+
color: "lime",
|
|
5816
|
+
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5817
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
5818
|
+
</svg>`
|
|
5819
|
+
},
|
|
5820
|
+
warning: {
|
|
5821
|
+
label: "Needs Attention",
|
|
5822
|
+
color: "amber",
|
|
5823
|
+
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5824
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
|
5825
|
+
</svg>`
|
|
5826
|
+
},
|
|
5827
|
+
critical: {
|
|
5828
|
+
label: "Critical",
|
|
5829
|
+
color: "red",
|
|
5830
|
+
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5831
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
5832
|
+
</svg>`
|
|
5833
|
+
}
|
|
5834
|
+
};
|
|
5835
|
+
const config = statusConfig[status];
|
|
5836
|
+
const colorClasses = {
|
|
5837
|
+
lime: "bg-lime-50 dark:bg-lime-500/10 text-lime-600 dark:text-lime-400 ring-lime-600/20 dark:ring-lime-500/20",
|
|
5838
|
+
amber: "bg-amber-50 dark:bg-amber-500/10 text-amber-600 dark:text-amber-400 ring-amber-600/20 dark:ring-amber-500/20",
|
|
5839
|
+
red: "bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-400 ring-red-600/20 dark:ring-red-500/20"
|
|
5840
|
+
};
|
|
5841
|
+
return `
|
|
5842
|
+
<div>
|
|
5843
|
+
<h3 class="text-sm font-medium text-zinc-900 dark:text-zinc-100 mb-3">System Health</h3>
|
|
5844
|
+
<div class="flex items-center gap-3 p-4 rounded-lg ring-1 ring-inset ${colorClasses[config.color]}">
|
|
5845
|
+
${config.icon}
|
|
5846
|
+
<div>
|
|
5847
|
+
<p class="text-sm font-medium">${config.label}</p>
|
|
5848
|
+
<p class="text-xs mt-0.5 opacity-80">
|
|
5849
|
+
${status === "healthy" ? "Cache is performing well" : status === "warning" ? "Consider increasing cache TTL or capacity" : "Cache hit rate is too low"}
|
|
5850
|
+
</p>
|
|
5851
|
+
</div>
|
|
5852
|
+
</div>
|
|
5853
|
+
</div>
|
|
5854
|
+
`;
|
|
5855
|
+
}
|
|
5856
|
+
function formatBytes(bytes) {
|
|
5857
|
+
if (bytes === 0) return "0 B";
|
|
5858
|
+
const k = 1024;
|
|
5859
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
5860
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
5861
|
+
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
|
|
5862
|
+
}
|
|
5863
|
+
|
|
5864
|
+
// src/plugins/cache/routes.ts
|
|
5865
|
+
var app = new hono.Hono();
|
|
5866
|
+
app.get("/", async (c) => {
|
|
5867
|
+
const stats = getAllCacheStats();
|
|
5868
|
+
const user = c.get("user");
|
|
5869
|
+
let totalHits = 0;
|
|
5870
|
+
let totalMisses = 0;
|
|
5871
|
+
let totalSize = 0;
|
|
5872
|
+
let totalEntries = 0;
|
|
5873
|
+
Object.values(stats).forEach((stat) => {
|
|
5874
|
+
totalHits += stat.memoryHits + stat.kvHits;
|
|
5875
|
+
totalMisses += stat.memoryMisses + stat.kvMisses;
|
|
5876
|
+
totalSize += stat.memorySize;
|
|
5877
|
+
totalEntries += stat.entryCount;
|
|
5878
|
+
});
|
|
5879
|
+
const totalRequests = totalHits + totalMisses;
|
|
5880
|
+
const overallHitRate = totalRequests > 0 ? totalHits / totalRequests * 100 : 0;
|
|
5881
|
+
const dashboardData = {
|
|
5882
|
+
stats,
|
|
5883
|
+
totals: {
|
|
5884
|
+
hits: totalHits,
|
|
5885
|
+
misses: totalMisses,
|
|
5886
|
+
requests: totalRequests,
|
|
5887
|
+
hitRate: overallHitRate.toFixed(2),
|
|
5888
|
+
memorySize: totalSize,
|
|
5889
|
+
entryCount: totalEntries
|
|
5890
|
+
},
|
|
5891
|
+
namespaces: Object.keys(stats),
|
|
5892
|
+
user: user ? {
|
|
5893
|
+
name: user.email,
|
|
5894
|
+
email: user.email,
|
|
5895
|
+
role: user.role
|
|
5896
|
+
} : void 0,
|
|
5897
|
+
version: c.get("appVersion")
|
|
5898
|
+
};
|
|
5899
|
+
return c.html(renderCacheDashboard(dashboardData));
|
|
5900
|
+
});
|
|
5901
|
+
app.get("/stats", async (c) => {
|
|
5902
|
+
const stats = getAllCacheStats();
|
|
5903
|
+
return c.json({
|
|
5904
|
+
success: true,
|
|
5905
|
+
data: stats,
|
|
5906
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5907
|
+
});
|
|
5908
|
+
});
|
|
5909
|
+
app.get("/stats/:namespace", async (c) => {
|
|
5910
|
+
const namespace = c.req.param("namespace");
|
|
5911
|
+
const config = CACHE_CONFIGS[namespace];
|
|
5912
|
+
if (!config) {
|
|
5913
|
+
return c.json({
|
|
5914
|
+
success: false,
|
|
5915
|
+
error: `Unknown namespace: ${namespace}`
|
|
5916
|
+
}, 404);
|
|
5917
|
+
}
|
|
5918
|
+
const cache = getCacheService(config);
|
|
5919
|
+
const stats = cache.getStats();
|
|
5920
|
+
return c.json({
|
|
5921
|
+
success: true,
|
|
5922
|
+
data: {
|
|
5923
|
+
namespace,
|
|
5924
|
+
config,
|
|
5925
|
+
stats
|
|
5926
|
+
},
|
|
5927
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5928
|
+
});
|
|
5929
|
+
});
|
|
5930
|
+
app.post("/clear", async (c) => {
|
|
5931
|
+
await clearAllCaches();
|
|
5932
|
+
return c.json({
|
|
5933
|
+
success: true,
|
|
5934
|
+
message: "All cache entries cleared",
|
|
5935
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5936
|
+
});
|
|
5937
|
+
});
|
|
5938
|
+
app.post("/clear/:namespace", async (c) => {
|
|
5939
|
+
const namespace = c.req.param("namespace");
|
|
5940
|
+
const config = CACHE_CONFIGS[namespace];
|
|
5941
|
+
if (!config) {
|
|
5942
|
+
return c.json({
|
|
5943
|
+
success: false,
|
|
5944
|
+
error: `Unknown namespace: ${namespace}`
|
|
5945
|
+
}, 404);
|
|
5946
|
+
}
|
|
5947
|
+
const cache = getCacheService(config);
|
|
5948
|
+
await cache.clear();
|
|
5949
|
+
return c.json({
|
|
5950
|
+
success: true,
|
|
5951
|
+
message: `Cache cleared for namespace: ${namespace}`,
|
|
5952
|
+
namespace,
|
|
5953
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5954
|
+
});
|
|
5955
|
+
});
|
|
5956
|
+
app.post("/invalidate", async (c) => {
|
|
5957
|
+
const body = await c.req.json();
|
|
5958
|
+
const { pattern, namespace } = body;
|
|
5959
|
+
if (!pattern) {
|
|
5960
|
+
return c.json({
|
|
5961
|
+
success: false,
|
|
5962
|
+
error: "Pattern is required"
|
|
5963
|
+
}, 400);
|
|
5964
|
+
}
|
|
5965
|
+
let totalInvalidated = 0;
|
|
5966
|
+
if (namespace) {
|
|
5967
|
+
const config = CACHE_CONFIGS[namespace];
|
|
5968
|
+
if (!config) {
|
|
5969
|
+
return c.json({
|
|
5970
|
+
success: false,
|
|
5971
|
+
error: `Unknown namespace: ${namespace}`
|
|
5972
|
+
}, 404);
|
|
5973
|
+
}
|
|
5974
|
+
const cache = getCacheService(config);
|
|
5975
|
+
totalInvalidated = await cache.invalidate(pattern);
|
|
5976
|
+
} else {
|
|
5977
|
+
for (const config of Object.values(CACHE_CONFIGS)) {
|
|
5978
|
+
const cache = getCacheService(config);
|
|
5979
|
+
totalInvalidated += await cache.invalidate(pattern);
|
|
5980
|
+
}
|
|
5981
|
+
}
|
|
5982
|
+
return c.json({
|
|
5983
|
+
success: true,
|
|
5984
|
+
invalidated: totalInvalidated,
|
|
5985
|
+
pattern,
|
|
5986
|
+
namespace: namespace || "all",
|
|
5987
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5988
|
+
});
|
|
5989
|
+
});
|
|
5990
|
+
app.get("/health", async (c) => {
|
|
5991
|
+
const stats = getAllCacheStats();
|
|
5992
|
+
const namespaces = Object.entries(stats);
|
|
5993
|
+
const healthChecks = namespaces.map(([name, stat]) => {
|
|
5994
|
+
const hitRate = stat.hitRate;
|
|
5995
|
+
const memoryUsage = stat.memorySize / (50 * 1024 * 1024);
|
|
5996
|
+
return {
|
|
5997
|
+
namespace: name,
|
|
5998
|
+
status: hitRate > 70 ? "healthy" : hitRate > 40 ? "warning" : "unhealthy",
|
|
5999
|
+
hitRate,
|
|
6000
|
+
memoryUsage: (memoryUsage * 100).toFixed(2) + "%",
|
|
6001
|
+
entryCount: stat.entryCount
|
|
6002
|
+
};
|
|
6003
|
+
});
|
|
6004
|
+
const overallStatus = healthChecks.every((h) => h.status === "healthy") ? "healthy" : healthChecks.some((h) => h.status === "unhealthy") ? "unhealthy" : "warning";
|
|
6005
|
+
return c.json({
|
|
6006
|
+
success: true,
|
|
6007
|
+
data: {
|
|
6008
|
+
status: overallStatus,
|
|
6009
|
+
namespaces: healthChecks,
|
|
6010
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6011
|
+
}
|
|
6012
|
+
});
|
|
6013
|
+
});
|
|
6014
|
+
app.get("/browser", async (c) => {
|
|
6015
|
+
const namespace = c.req.query("namespace") || "all";
|
|
6016
|
+
const search = c.req.query("search") || "";
|
|
6017
|
+
const sortBy = c.req.query("sort") || "age";
|
|
6018
|
+
const limit = parseInt(c.req.query("limit") || "100");
|
|
6019
|
+
const entries = [];
|
|
6020
|
+
const namespaces = namespace === "all" ? Object.keys(CACHE_CONFIGS) : [namespace];
|
|
6021
|
+
for (const ns of namespaces) {
|
|
6022
|
+
const config = CACHE_CONFIGS[ns];
|
|
6023
|
+
if (!config) continue;
|
|
6024
|
+
const cache = getCacheService(config);
|
|
6025
|
+
const keys = await cache.listKeys();
|
|
6026
|
+
for (const keyInfo of keys) {
|
|
6027
|
+
if (search && !keyInfo.key.toLowerCase().includes(search.toLowerCase())) {
|
|
6028
|
+
continue;
|
|
6029
|
+
}
|
|
6030
|
+
const parsed = parseCacheKey(keyInfo.key);
|
|
6031
|
+
const ttl = Math.max(0, keyInfo.expiresAt - Date.now()) / 1e3;
|
|
6032
|
+
entries.push({
|
|
6033
|
+
namespace: ns,
|
|
6034
|
+
key: keyInfo.key,
|
|
6035
|
+
size: keyInfo.size,
|
|
6036
|
+
age: keyInfo.age,
|
|
6037
|
+
ttl,
|
|
6038
|
+
expiresAt: keyInfo.expiresAt,
|
|
6039
|
+
parsed
|
|
6040
|
+
});
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
if (sortBy === "size") {
|
|
6044
|
+
entries.sort((a, b) => b.size - a.size);
|
|
6045
|
+
} else if (sortBy === "age") {
|
|
6046
|
+
entries.sort((a, b) => a.age - b.age);
|
|
6047
|
+
} else if (sortBy === "key") {
|
|
6048
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
6049
|
+
}
|
|
6050
|
+
const limitedEntries = entries.slice(0, limit);
|
|
6051
|
+
return c.json({
|
|
6052
|
+
success: true,
|
|
6053
|
+
data: {
|
|
6054
|
+
entries: limitedEntries,
|
|
6055
|
+
total: entries.length,
|
|
6056
|
+
showing: limitedEntries.length,
|
|
6057
|
+
namespace,
|
|
6058
|
+
search,
|
|
6059
|
+
sortBy
|
|
6060
|
+
},
|
|
6061
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6062
|
+
});
|
|
6063
|
+
});
|
|
6064
|
+
app.get("/browser/:namespace/:key", async (c) => {
|
|
6065
|
+
const namespace = c.req.param("namespace");
|
|
6066
|
+
const key = decodeURIComponent(c.req.param("key"));
|
|
6067
|
+
const config = CACHE_CONFIGS[namespace];
|
|
6068
|
+
if (!config) {
|
|
6069
|
+
return c.json({
|
|
6070
|
+
success: false,
|
|
6071
|
+
error: `Unknown namespace: ${namespace}`
|
|
6072
|
+
}, 404);
|
|
6073
|
+
}
|
|
6074
|
+
const cache = getCacheService(config);
|
|
6075
|
+
const entry = await cache.getEntry(key);
|
|
6076
|
+
if (!entry) {
|
|
6077
|
+
return c.json({
|
|
6078
|
+
success: false,
|
|
6079
|
+
error: "Cache entry not found or expired"
|
|
6080
|
+
}, 404);
|
|
6081
|
+
}
|
|
6082
|
+
const parsed = parseCacheKey(key);
|
|
6083
|
+
return c.json({
|
|
6084
|
+
success: true,
|
|
6085
|
+
data: {
|
|
6086
|
+
key,
|
|
6087
|
+
namespace,
|
|
6088
|
+
parsed,
|
|
6089
|
+
...entry,
|
|
6090
|
+
createdAt: new Date(entry.timestamp).toISOString(),
|
|
6091
|
+
expiresAt: new Date(entry.expiresAt).toISOString()
|
|
6092
|
+
},
|
|
6093
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6094
|
+
});
|
|
6095
|
+
});
|
|
6096
|
+
app.get("/analytics", async (c) => {
|
|
6097
|
+
const stats = getAllCacheStats();
|
|
6098
|
+
const invalidationStats = getCacheInvalidationStats();
|
|
6099
|
+
const recentInvalidations = getRecentInvalidations(20);
|
|
6100
|
+
let totalHits = 0;
|
|
6101
|
+
let totalMisses = 0;
|
|
6102
|
+
let totalSize = 0;
|
|
6103
|
+
let totalEntries = 0;
|
|
6104
|
+
const namespacesAnalytics = [];
|
|
6105
|
+
for (const [namespace, stat] of Object.entries(stats)) {
|
|
6106
|
+
totalHits += stat.memoryHits + stat.kvHits;
|
|
6107
|
+
totalMisses += stat.memoryMisses + stat.kvMisses;
|
|
6108
|
+
totalSize += stat.memorySize;
|
|
6109
|
+
totalEntries += stat.entryCount;
|
|
6110
|
+
const totalRequests2 = stat.memoryHits + stat.kvHits + stat.memoryMisses + stat.kvMisses;
|
|
6111
|
+
const hitRate = totalRequests2 > 0 ? (stat.memoryHits + stat.kvHits) / totalRequests2 * 100 : 0;
|
|
6112
|
+
const avgEntrySize = stat.entryCount > 0 ? stat.memorySize / stat.entryCount : 0;
|
|
6113
|
+
namespacesAnalytics.push({
|
|
6114
|
+
namespace,
|
|
6115
|
+
hitRate: hitRate.toFixed(2),
|
|
6116
|
+
totalRequests: totalRequests2,
|
|
6117
|
+
memoryHitRate: totalRequests2 > 0 ? (stat.memoryHits / totalRequests2 * 100).toFixed(2) : "0",
|
|
6118
|
+
kvHitRate: totalRequests2 > 0 ? (stat.kvHits / totalRequests2 * 100).toFixed(2) : "0",
|
|
6119
|
+
avgEntrySize: Math.round(avgEntrySize),
|
|
6120
|
+
totalSize: stat.memorySize,
|
|
6121
|
+
entryCount: stat.entryCount,
|
|
6122
|
+
efficiency: totalRequests2 > 0 ? ((stat.memoryHits + stat.kvHits) / (stat.memoryHits + stat.kvHits + stat.dbHits + 1)).toFixed(2) : "0"
|
|
6123
|
+
});
|
|
6124
|
+
}
|
|
6125
|
+
namespacesAnalytics.sort((a, b) => parseFloat(b.hitRate) - parseFloat(a.hitRate));
|
|
6126
|
+
const totalRequests = totalHits + totalMisses;
|
|
6127
|
+
const overallHitRate = totalRequests > 0 ? totalHits / totalRequests * 100 : 0;
|
|
6128
|
+
const dbQueriesAvoided = totalHits;
|
|
6129
|
+
const timeSaved = dbQueriesAvoided * 48;
|
|
6130
|
+
const estimatedCostSavings = dbQueriesAvoided / 1e6 * 0.5;
|
|
6131
|
+
return c.json({
|
|
6132
|
+
success: true,
|
|
6133
|
+
data: {
|
|
6134
|
+
overview: {
|
|
6135
|
+
totalHits,
|
|
6136
|
+
totalMisses,
|
|
6137
|
+
totalRequests,
|
|
6138
|
+
overallHitRate: overallHitRate.toFixed(2),
|
|
6139
|
+
totalSize,
|
|
6140
|
+
totalEntries,
|
|
6141
|
+
avgEntrySize: totalEntries > 0 ? Math.round(totalSize / totalEntries) : 0
|
|
6142
|
+
},
|
|
6143
|
+
performance: {
|
|
6144
|
+
dbQueriesAvoided,
|
|
6145
|
+
timeSavedMs: timeSaved,
|
|
6146
|
+
timeSavedMinutes: (timeSaved / 1e3 / 60).toFixed(2),
|
|
6147
|
+
estimatedCostSavings: estimatedCostSavings.toFixed(4)
|
|
6148
|
+
},
|
|
6149
|
+
namespaces: namespacesAnalytics,
|
|
6150
|
+
invalidation: {
|
|
6151
|
+
...invalidationStats,
|
|
6152
|
+
recent: recentInvalidations
|
|
6153
|
+
}
|
|
6154
|
+
},
|
|
6155
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6156
|
+
});
|
|
6157
|
+
});
|
|
6158
|
+
app.get("/analytics/trends", async (c) => {
|
|
6159
|
+
const stats = getAllCacheStats();
|
|
6160
|
+
const dataPoint = {
|
|
6161
|
+
timestamp: Date.now(),
|
|
6162
|
+
stats: Object.entries(stats).map(([namespace, stat]) => ({
|
|
6163
|
+
namespace,
|
|
6164
|
+
hitRate: stat.hitRate,
|
|
6165
|
+
entryCount: stat.entryCount,
|
|
6166
|
+
memorySize: stat.memorySize,
|
|
6167
|
+
totalRequests: stat.totalRequests
|
|
6168
|
+
}))
|
|
6169
|
+
};
|
|
6170
|
+
return c.json({
|
|
6171
|
+
success: true,
|
|
6172
|
+
data: {
|
|
6173
|
+
trends: [dataPoint],
|
|
6174
|
+
note: "Historical trends require persistent storage. This returns current snapshot only."
|
|
6175
|
+
},
|
|
6176
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6177
|
+
});
|
|
6178
|
+
});
|
|
6179
|
+
app.get("/analytics/top-keys", async (c) => {
|
|
6180
|
+
c.req.query("namespace") || "all";
|
|
6181
|
+
parseInt(c.req.query("limit") || "10");
|
|
6182
|
+
return c.json({
|
|
6183
|
+
success: true,
|
|
6184
|
+
data: {
|
|
6185
|
+
topKeys: [],
|
|
6186
|
+
note: "Top keys tracking requires per-key hit counting. Feature not yet implemented."
|
|
6187
|
+
},
|
|
6188
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6189
|
+
});
|
|
6190
|
+
});
|
|
6191
|
+
app.post("/warm", async (c) => {
|
|
6192
|
+
try {
|
|
6193
|
+
const db = c.env.DB;
|
|
6194
|
+
const result = await warmCommonCaches(db);
|
|
6195
|
+
return c.json({
|
|
6196
|
+
success: true,
|
|
6197
|
+
message: "Cache warming completed",
|
|
6198
|
+
...result,
|
|
6199
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6200
|
+
});
|
|
6201
|
+
} catch (error) {
|
|
6202
|
+
console.error("Cache warming error:", error);
|
|
6203
|
+
return c.json({
|
|
6204
|
+
success: false,
|
|
6205
|
+
error: "Cache warming failed",
|
|
6206
|
+
details: error instanceof Error ? error.message : "Unknown error"
|
|
6207
|
+
}, 500);
|
|
6208
|
+
}
|
|
6209
|
+
});
|
|
6210
|
+
app.post("/warm/:namespace", async (c) => {
|
|
6211
|
+
try {
|
|
6212
|
+
const namespace = c.req.param("namespace");
|
|
6213
|
+
const body = await c.req.json();
|
|
6214
|
+
const { entries } = body;
|
|
6215
|
+
if (!entries || !Array.isArray(entries)) {
|
|
6216
|
+
return c.json({
|
|
6217
|
+
success: false,
|
|
6218
|
+
error: "Entries array is required"
|
|
6219
|
+
}, 400);
|
|
6220
|
+
}
|
|
6221
|
+
const count = await warmNamespace(namespace, entries);
|
|
6222
|
+
return c.json({
|
|
6223
|
+
success: true,
|
|
6224
|
+
message: `Warmed ${count} entries in namespace: ${namespace}`,
|
|
6225
|
+
namespace,
|
|
6226
|
+
count,
|
|
6227
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6228
|
+
});
|
|
6229
|
+
} catch (error) {
|
|
6230
|
+
console.error("Namespace warming error:", error);
|
|
6231
|
+
return c.json({
|
|
6232
|
+
success: false,
|
|
6233
|
+
error: "Namespace warming failed",
|
|
6234
|
+
details: error instanceof Error ? error.message : "Unknown error"
|
|
6235
|
+
}, 500);
|
|
6236
|
+
}
|
|
6237
|
+
});
|
|
6238
|
+
var routes_default = app;
|
|
6239
|
+
|
|
6240
|
+
// src/plugins/cache/index.ts
|
|
6241
|
+
var CachePlugin = class {
|
|
6242
|
+
_context = null;
|
|
6243
|
+
/**
|
|
6244
|
+
* Get plugin routes
|
|
6245
|
+
*/
|
|
6246
|
+
getRoutes() {
|
|
6247
|
+
return routes_default;
|
|
6248
|
+
}
|
|
6249
|
+
/**
|
|
6250
|
+
* Activate the cache plugin
|
|
6251
|
+
*/
|
|
6252
|
+
async activate(context) {
|
|
6253
|
+
this._context = context;
|
|
6254
|
+
const settings = context.config || {};
|
|
6255
|
+
console.log("\u2705 Cache plugin activated", {
|
|
6256
|
+
memoryEnabled: settings.memoryEnabled ?? true,
|
|
6257
|
+
kvEnabled: settings.kvEnabled ?? false,
|
|
6258
|
+
defaultTTL: settings.defaultTTL ?? 3600
|
|
6259
|
+
});
|
|
6260
|
+
for (const [_namespace, config] of Object.entries(CACHE_CONFIGS)) {
|
|
6261
|
+
getCacheService({
|
|
6262
|
+
...config,
|
|
6263
|
+
memoryEnabled: settings.memoryEnabled ?? config.memoryEnabled,
|
|
6264
|
+
kvEnabled: settings.kvEnabled ?? config.kvEnabled,
|
|
6265
|
+
ttl: settings.defaultTTL ?? config.ttl
|
|
6266
|
+
});
|
|
6267
|
+
}
|
|
6268
|
+
setupCacheInvalidation();
|
|
6269
|
+
}
|
|
6270
|
+
/**
|
|
6271
|
+
* Deactivate the cache plugin
|
|
6272
|
+
*/
|
|
6273
|
+
async deactivate() {
|
|
6274
|
+
console.log("\u274C Cache plugin deactivated - clearing all caches");
|
|
6275
|
+
await clearAllCaches();
|
|
6276
|
+
this._context = null;
|
|
6277
|
+
}
|
|
6278
|
+
/**
|
|
6279
|
+
* Configure the cache plugin
|
|
6280
|
+
*/
|
|
6281
|
+
async configure(settings) {
|
|
6282
|
+
console.log("\u2699\uFE0F Cache plugin configured", settings);
|
|
6283
|
+
for (const [_namespace, config] of Object.entries(CACHE_CONFIGS)) {
|
|
6284
|
+
getCacheService({
|
|
6285
|
+
...config,
|
|
6286
|
+
memoryEnabled: settings.memoryEnabled ?? config.memoryEnabled,
|
|
6287
|
+
kvEnabled: settings.kvEnabled ?? config.kvEnabled,
|
|
6288
|
+
ttl: settings.defaultTTL ?? config.ttl
|
|
6289
|
+
});
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
/**
|
|
6293
|
+
* Get cache statistics
|
|
6294
|
+
*/
|
|
6295
|
+
async getStats(c) {
|
|
6296
|
+
const stats = getAllCacheStats();
|
|
6297
|
+
return c.json({
|
|
6298
|
+
success: true,
|
|
6299
|
+
data: stats,
|
|
6300
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6301
|
+
});
|
|
6302
|
+
}
|
|
6303
|
+
/**
|
|
6304
|
+
* Clear all cache entries
|
|
6305
|
+
*/
|
|
6306
|
+
async clearCache(c) {
|
|
6307
|
+
await clearAllCaches();
|
|
6308
|
+
return c.json({
|
|
6309
|
+
success: true,
|
|
6310
|
+
message: "All cache entries cleared",
|
|
6311
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6312
|
+
});
|
|
6313
|
+
}
|
|
6314
|
+
/**
|
|
6315
|
+
* Invalidate cache entries matching pattern
|
|
6316
|
+
*/
|
|
6317
|
+
async invalidatePattern(c) {
|
|
6318
|
+
const body = await c.req.json();
|
|
6319
|
+
const { pattern, namespace: _namespace } = body;
|
|
6320
|
+
if (!pattern) {
|
|
6321
|
+
return c.json({
|
|
6322
|
+
success: false,
|
|
6323
|
+
error: "Pattern is required"
|
|
6324
|
+
}, 400);
|
|
6325
|
+
}
|
|
6326
|
+
let totalInvalidated = 0;
|
|
6327
|
+
if (_namespace) {
|
|
6328
|
+
const cache = getCacheService(CACHE_CONFIGS[_namespace] || {
|
|
6329
|
+
ttl: 3600,
|
|
6330
|
+
kvEnabled: false,
|
|
6331
|
+
memoryEnabled: true,
|
|
6332
|
+
namespace: _namespace,
|
|
6333
|
+
invalidateOn: [],
|
|
6334
|
+
version: "v1"
|
|
6335
|
+
});
|
|
6336
|
+
totalInvalidated = await cache.invalidate(pattern);
|
|
6337
|
+
} else {
|
|
6338
|
+
for (const config of Object.values(CACHE_CONFIGS)) {
|
|
6339
|
+
const cache = getCacheService(config);
|
|
6340
|
+
totalInvalidated += await cache.invalidate(pattern);
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
return c.json({
|
|
6344
|
+
success: true,
|
|
6345
|
+
invalidated: totalInvalidated,
|
|
6346
|
+
pattern,
|
|
6347
|
+
namespace: _namespace || "all",
|
|
6348
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6349
|
+
});
|
|
6350
|
+
}
|
|
6351
|
+
};
|
|
6352
|
+
var plugin = new CachePlugin();
|
|
6353
|
+
var cache_default = plugin;
|
|
6354
|
+
|
|
6355
|
+
// src/assets/favicon.ts
|
|
6356
|
+
var faviconSvg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
6357
|
+
<svg
|
|
6358
|
+
version="1.1"
|
|
6359
|
+
id="Layer_1"
|
|
6360
|
+
x="0px"
|
|
6361
|
+
y="0px"
|
|
6362
|
+
viewBox="380 1300 257.89001 278.8855"
|
|
6363
|
+
xml:space="preserve"
|
|
6364
|
+
width="257.89001"
|
|
6365
|
+
height="278.8855"
|
|
6366
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
6367
|
+
<g
|
|
6368
|
+
id="g10"
|
|
6369
|
+
transform="translate(-383.935,-60.555509)">
|
|
6370
|
+
<g
|
|
6371
|
+
id="g9">
|
|
6372
|
+
<path
|
|
6373
|
+
fill="#f1f2f2"
|
|
6374
|
+
d="m 974.78,1398.211 c -5.016,6.574 -10.034,13.146 -15.048,19.721 -1.828,2.398 -3.657,4.796 -5.487,7.194 1.994,1.719 3.958,3.51 5.873,5.424 18.724,18.731 28.089,41.216 28.089,67.459 0,26.251 -9.366,48.658 -28.089,67.237 -18.731,18.579 -41.215,27.868 -67.459,27.868 -9.848,0 -19.156,-1.308 -27.923,-3.923 l -4.185,3.354 c -8.587,6.885 -17.154,13.796 -25.725,20.702 17.52,8.967 36.86,13.487 58.054,13.487 35.533,0 65.91,-12.608 91.124,-37.821 25.214,-25.215 37.821,-55.584 37.821,-91.125 0,-35.534 -12.607,-65.911 -37.821,-91.126 -3,-2.999 -6.078,-5.808 -9.224,-8.451 z"
|
|
6375
|
+
id="path2" />
|
|
6376
|
+
<path
|
|
6377
|
+
fill="#34d399"
|
|
6378
|
+
d="m 854.024,1585.195 20.001,-16.028 c 16.616,-13.507 33.04,-27.265 50.086,-40.251 1.13,-0.861 2.9,-1.686 2.003,-3.516 -0.843,-1.716 -2.481,-2.302 -4.484,-2.123 -8.514,0.765 -17.016,-0.538 -25.537,-0.353 -1.124,0.024 -2.768,0.221 -3.163,-1.25 -0.371,-1.369 1.088,-2.063 1.919,-2.894 6.26,-6.242 12.574,-12.43 18.816,-18.691 9.303,-9.327 18.565,-18.714 27.851,-28.066 1.848,-1.859 3.701,-3.713 5.549,-5.572 2.655,-2.661 5.309,-5.315 7.958,-7.982 0.574,-0.579 1.259,-1.141 1.246,-1.94 -0.004,-0.257 -0.078,-0.538 -0.254,-0.853 -0.556,-0.981 -1.441,-1.1 -2.469,-0.957 -0.658,0.096 -1.315,0.185 -1.973,0.275 -3.844,0.538 -7.689,1.076 -11.533,1.608 -3.641,0.505 -7.281,1.02 -10.922,1.529 -4.162,0.582 -8.324,1.158 -12.486,1.748 -1.142,0.161 -2.409,1.662 -3.354,0.508 -0.419,-0.508 -0.431,-1.028 -0.251,-1.531 0.269,-0.741 0.957,-1.441 1.387,-2.021 3.414,-4.58 6.882,-9.124 10.356,-13.662 1.74,-2.272 3.48,-4.544 5.214,-6.822 4.682,-6.141 9.369,-12.281 14.051,-18.422 0.09,-0.119 0.181,-0.237 0.271,-0.355 6.848,-8.98 13.7,-17.958 20.553,-26.936 0.488,-0.64 0.977,-1.28 1.465,-1.92 2.159,-2.828 4.315,-5.658 6.476,-8.486 4.197,-5.501 8.454,-10.954 12.67,-16.442 0.263,-0.347 0.538,-0.718 0.717,-1.106 0.269,-0.586 0.299,-1.196 -0.335,-1.776 -0.825,-0.753 -1.8,-0.15 -2.595,0.419 -0.67,0.472 -1.333,0.957 -1.955,1.489 -2.206,1.889 -4.401,3.797 -6.595,5.698 -3.958,3.438 -7.922,6.876 -11.976,10.194 -2.443,2.003 -4.865,4.028 -7.301,6.038 -18.689,-10.581 -39.53,-15.906 -62.549,-15.906 -35.54,0 -65.911,12.607 -91.125,37.82 -25.214,25.215 -37.821,55.592 -37.821,91.126 0,35.54 12.607,65.91 37.821,91.125 4.146,4.146 8.445,7.916 12.87,11.381 -9.015,11.14 -18.036,22.277 -27.034,33.429 -1.208,1.489 -3.755,3.151 -2.745,4.891 0.078,0.144 0.173,0.281 0.305,0.425 1.321,1.429 3.492,-1.303 4.933,-2.457 6.673,-5.333 13.333,-10.685 19.982,-16.042 3.707,-2.984 7.417,-5.965 11.124,-8.952 1.474,-1.188 2.951,-2.373 4.425,-3.561 6.41,-5.164 12.816,-10.333 19.238,-15.481 z m -56.472,-87.186 c 0,-26.243 9.29,-48.728 27.868,-67.459 18.579,-18.723 40.987,-28.089 67.238,-28.089 12.273,0 23.712,2.075 34.34,6.171 -3.37,2.905 -6.734,5.816 -10.069,8.762 -6.075,5.351 -12.365,10.469 -18.667,15.564 -4.179,3.378 -8.371,6.744 -12.514,10.164 -7.54,6.23 -15.037,12.52 -22.529,18.804 -7.091,5.955 -14.182,11.904 -21.19,17.949 -1.136,0.974 -3.055,1.907 -2.135,3.94 0.831,1.836 2.774,1.417 4.341,1.578 l 12.145,-0.599 14.151,-0.698 c 1.031,-0.102 2.192,-0.257 2.89,0.632 0.034,0.044 0.073,0.078 0.106,0.127 1.017,1.561 -0.67,2.105 -1.387,2.942 -6.308,7.318 -12.616,14.637 -18.978,21.907 -8.161,9.339 -16.353,18.649 -24.544,27.958 -2.146,2.433 -4.275,4.879 -6.422,7.312 -1.034,1.172 -2.129,2.272 -1.238,3.922 0.933,1.728 2.685,1.752 4.323,1.602 4.134,-0.367 8.263,-0.489 12.396,-0.492 0.242,0 0.485,-0.01 0.728,0 2.711,0.01 5.422,0.068 8.134,0.145 2.582,0.074 5.166,0.165 7.752,0.249 0.275,1.62 -0.879,2.356 -1.62,3.259 -1.333,1.626 -2.667,3.247 -4,4.867 -4.315,5.252 -8.62,10.514 -12.928,15.772 -3.562,-2.725 -7.007,-5.733 -10.324,-9.051 -18.577,-18.576 -27.867,-40.983 -27.867,-67.234 z"
|
|
6379
|
+
id="path9" />
|
|
6380
|
+
</g>
|
|
6381
|
+
</g>
|
|
6382
|
+
</svg>`;
|
|
2730
6383
|
|
|
2731
6384
|
// src/app.ts
|
|
2732
6385
|
function createSonicJSApp(config = {}) {
|
|
2733
|
-
const
|
|
2734
|
-
const appVersion = config.version ||
|
|
6386
|
+
const app2 = new hono.Hono();
|
|
6387
|
+
const appVersion = config.version || chunkMYB5RY7H_cjs.getCoreVersion();
|
|
2735
6388
|
const appName = config.name || "SonicJS AI";
|
|
2736
|
-
|
|
6389
|
+
app2.use("*", async (c, next) => {
|
|
2737
6390
|
c.set("appVersion", appVersion);
|
|
2738
6391
|
await next();
|
|
2739
6392
|
});
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
app.use("*", adminSetupMiddleware());
|
|
6393
|
+
app2.use("*", chunkE2BXLXPW_cjs.metricsMiddleware());
|
|
6394
|
+
app2.use("*", chunkE2BXLXPW_cjs.bootstrapMiddleware(config));
|
|
2743
6395
|
if (config.middleware?.beforeAuth) {
|
|
2744
6396
|
for (const middleware of config.middleware.beforeAuth) {
|
|
2745
|
-
|
|
6397
|
+
app2.use("*", middleware);
|
|
2746
6398
|
}
|
|
2747
6399
|
}
|
|
2748
|
-
|
|
6400
|
+
app2.use("*", async (_c, next) => {
|
|
2749
6401
|
await next();
|
|
2750
6402
|
});
|
|
2751
|
-
|
|
6403
|
+
app2.use("*", async (_c, next) => {
|
|
2752
6404
|
await next();
|
|
2753
6405
|
});
|
|
2754
6406
|
if (config.middleware?.afterAuth) {
|
|
2755
6407
|
for (const middleware of config.middleware.afterAuth) {
|
|
2756
|
-
|
|
2757
|
-
}
|
|
2758
|
-
}
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
6408
|
+
app2.use("*", middleware);
|
|
6409
|
+
}
|
|
6410
|
+
}
|
|
6411
|
+
app2.route("/api", chunkJ7F3NPAP_cjs.api_default);
|
|
6412
|
+
app2.route("/api/media", chunkJ7F3NPAP_cjs.api_media_default);
|
|
6413
|
+
app2.route("/api/system", chunkJ7F3NPAP_cjs.api_system_default);
|
|
6414
|
+
app2.route("/admin/api", chunkJ7F3NPAP_cjs.admin_api_default);
|
|
6415
|
+
app2.route("/admin/dashboard", chunkJ7F3NPAP_cjs.router);
|
|
6416
|
+
app2.route("/admin/collections", chunkJ7F3NPAP_cjs.adminCollectionsRoutes);
|
|
6417
|
+
app2.route("/admin/settings", chunkJ7F3NPAP_cjs.adminSettingsRoutes);
|
|
6418
|
+
app2.route("/admin/database-tools", createDatabaseToolsAdminRoutes());
|
|
6419
|
+
app2.route("/admin/seed-data", createSeedDataAdminRoutes());
|
|
6420
|
+
app2.route("/admin/content", chunkJ7F3NPAP_cjs.admin_content_default);
|
|
6421
|
+
app2.route("/admin/media", chunkJ7F3NPAP_cjs.adminMediaRoutes);
|
|
6422
|
+
if (aiSearchPlugin.routes && aiSearchPlugin.routes.length > 0) {
|
|
6423
|
+
for (const route of aiSearchPlugin.routes) {
|
|
6424
|
+
app2.route(route.path, route.handler);
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
app2.route("/admin/cache", cache_default.getRoutes());
|
|
6428
|
+
app2.route("/admin/plugins", chunkJ7F3NPAP_cjs.adminPluginRoutes);
|
|
6429
|
+
app2.route("/admin/logs", chunkJ7F3NPAP_cjs.adminLogsRoutes);
|
|
6430
|
+
app2.route("/admin", chunkJ7F3NPAP_cjs.userRoutes);
|
|
6431
|
+
app2.route("/auth", chunkJ7F3NPAP_cjs.auth_default);
|
|
6432
|
+
app2.route("/", chunkJ7F3NPAP_cjs.test_cleanup_default);
|
|
2775
6433
|
if (emailPlugin.routes && emailPlugin.routes.length > 0) {
|
|
2776
6434
|
for (const route of emailPlugin.routes) {
|
|
2777
|
-
|
|
6435
|
+
app2.route(route.path, route.handler);
|
|
2778
6436
|
}
|
|
2779
6437
|
}
|
|
2780
6438
|
if (otpLoginPlugin.routes && otpLoginPlugin.routes.length > 0) {
|
|
2781
6439
|
for (const route of otpLoginPlugin.routes) {
|
|
2782
|
-
|
|
6440
|
+
app2.route(route.path, route.handler);
|
|
2783
6441
|
}
|
|
2784
6442
|
}
|
|
2785
6443
|
const magicLinkPlugin = createMagicLinkAuthPlugin();
|
|
2786
6444
|
if (magicLinkPlugin.routes && magicLinkPlugin.routes.length > 0) {
|
|
2787
6445
|
for (const route of magicLinkPlugin.routes) {
|
|
2788
|
-
|
|
6446
|
+
app2.route(route.path, route.handler);
|
|
2789
6447
|
}
|
|
2790
6448
|
}
|
|
2791
|
-
|
|
6449
|
+
app2.get("/favicon.svg", (c) => {
|
|
6450
|
+
return new Response(faviconSvg, {
|
|
6451
|
+
headers: {
|
|
6452
|
+
"Content-Type": "image/svg+xml",
|
|
6453
|
+
"Cache-Control": "public, max-age=31536000"
|
|
6454
|
+
}
|
|
6455
|
+
});
|
|
6456
|
+
});
|
|
6457
|
+
app2.get("/files/*", async (c) => {
|
|
2792
6458
|
try {
|
|
2793
6459
|
const url = new URL(c.req.url);
|
|
2794
6460
|
const pathname = url.pathname;
|
|
@@ -2817,13 +6483,13 @@ function createSonicJSApp(config = {}) {
|
|
|
2817
6483
|
});
|
|
2818
6484
|
if (config.routes) {
|
|
2819
6485
|
for (const route of config.routes) {
|
|
2820
|
-
|
|
6486
|
+
app2.route(route.path, route.handler);
|
|
2821
6487
|
}
|
|
2822
6488
|
}
|
|
2823
|
-
|
|
6489
|
+
app2.get("/", (c) => {
|
|
2824
6490
|
return c.redirect("/auth/login");
|
|
2825
6491
|
});
|
|
2826
|
-
|
|
6492
|
+
app2.get("/health", (c) => {
|
|
2827
6493
|
return c.json({
|
|
2828
6494
|
name: appName,
|
|
2829
6495
|
version: appVersion,
|
|
@@ -2831,14 +6497,14 @@ function createSonicJSApp(config = {}) {
|
|
|
2831
6497
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2832
6498
|
});
|
|
2833
6499
|
});
|
|
2834
|
-
|
|
6500
|
+
app2.notFound((c) => {
|
|
2835
6501
|
return c.json({ error: "Not Found", status: 404 }, 404);
|
|
2836
6502
|
});
|
|
2837
|
-
|
|
6503
|
+
app2.onError((err, c) => {
|
|
2838
6504
|
console.error(err);
|
|
2839
6505
|
return c.json({ error: "Internal Server Error", status: 500 }, 500);
|
|
2840
6506
|
});
|
|
2841
|
-
return
|
|
6507
|
+
return app2;
|
|
2842
6508
|
}
|
|
2843
6509
|
function setupCoreMiddleware(_app) {
|
|
2844
6510
|
console.warn("setupCoreMiddleware is deprecated. Use createSonicJSApp() instead.");
|
|
@@ -2851,83 +6517,83 @@ function createDb(d1$1) {
|
|
|
2851
6517
|
}
|
|
2852
6518
|
|
|
2853
6519
|
// src/index.ts
|
|
2854
|
-
var VERSION =
|
|
6520
|
+
var VERSION = chunkMYB5RY7H_cjs.package_default.version;
|
|
2855
6521
|
|
|
2856
6522
|
Object.defineProperty(exports, "ROUTES_INFO", {
|
|
2857
6523
|
enumerable: true,
|
|
2858
|
-
get: function () { return
|
|
6524
|
+
get: function () { return chunkJ7F3NPAP_cjs.ROUTES_INFO; }
|
|
2859
6525
|
});
|
|
2860
6526
|
Object.defineProperty(exports, "adminApiRoutes", {
|
|
2861
6527
|
enumerable: true,
|
|
2862
|
-
get: function () { return
|
|
6528
|
+
get: function () { return chunkJ7F3NPAP_cjs.admin_api_default; }
|
|
2863
6529
|
});
|
|
2864
6530
|
Object.defineProperty(exports, "adminCheckboxRoutes", {
|
|
2865
6531
|
enumerable: true,
|
|
2866
|
-
get: function () { return
|
|
6532
|
+
get: function () { return chunkJ7F3NPAP_cjs.adminCheckboxRoutes; }
|
|
2867
6533
|
});
|
|
2868
6534
|
Object.defineProperty(exports, "adminCodeExamplesRoutes", {
|
|
2869
6535
|
enumerable: true,
|
|
2870
|
-
get: function () { return
|
|
6536
|
+
get: function () { return chunkJ7F3NPAP_cjs.admin_code_examples_default; }
|
|
2871
6537
|
});
|
|
2872
6538
|
Object.defineProperty(exports, "adminCollectionsRoutes", {
|
|
2873
6539
|
enumerable: true,
|
|
2874
|
-
get: function () { return
|
|
6540
|
+
get: function () { return chunkJ7F3NPAP_cjs.adminCollectionsRoutes; }
|
|
2875
6541
|
});
|
|
2876
6542
|
Object.defineProperty(exports, "adminContentRoutes", {
|
|
2877
6543
|
enumerable: true,
|
|
2878
|
-
get: function () { return
|
|
6544
|
+
get: function () { return chunkJ7F3NPAP_cjs.admin_content_default; }
|
|
2879
6545
|
});
|
|
2880
6546
|
Object.defineProperty(exports, "adminDashboardRoutes", {
|
|
2881
6547
|
enumerable: true,
|
|
2882
|
-
get: function () { return
|
|
6548
|
+
get: function () { return chunkJ7F3NPAP_cjs.router; }
|
|
2883
6549
|
});
|
|
2884
6550
|
Object.defineProperty(exports, "adminDesignRoutes", {
|
|
2885
6551
|
enumerable: true,
|
|
2886
|
-
get: function () { return
|
|
6552
|
+
get: function () { return chunkJ7F3NPAP_cjs.adminDesignRoutes; }
|
|
2887
6553
|
});
|
|
2888
6554
|
Object.defineProperty(exports, "adminLogsRoutes", {
|
|
2889
6555
|
enumerable: true,
|
|
2890
|
-
get: function () { return
|
|
6556
|
+
get: function () { return chunkJ7F3NPAP_cjs.adminLogsRoutes; }
|
|
2891
6557
|
});
|
|
2892
6558
|
Object.defineProperty(exports, "adminMediaRoutes", {
|
|
2893
6559
|
enumerable: true,
|
|
2894
|
-
get: function () { return
|
|
6560
|
+
get: function () { return chunkJ7F3NPAP_cjs.adminMediaRoutes; }
|
|
2895
6561
|
});
|
|
2896
6562
|
Object.defineProperty(exports, "adminPluginRoutes", {
|
|
2897
6563
|
enumerable: true,
|
|
2898
|
-
get: function () { return
|
|
6564
|
+
get: function () { return chunkJ7F3NPAP_cjs.adminPluginRoutes; }
|
|
2899
6565
|
});
|
|
2900
6566
|
Object.defineProperty(exports, "adminSettingsRoutes", {
|
|
2901
6567
|
enumerable: true,
|
|
2902
|
-
get: function () { return
|
|
6568
|
+
get: function () { return chunkJ7F3NPAP_cjs.adminSettingsRoutes; }
|
|
2903
6569
|
});
|
|
2904
6570
|
Object.defineProperty(exports, "adminTestimonialsRoutes", {
|
|
2905
6571
|
enumerable: true,
|
|
2906
|
-
get: function () { return
|
|
6572
|
+
get: function () { return chunkJ7F3NPAP_cjs.admin_testimonials_default; }
|
|
2907
6573
|
});
|
|
2908
6574
|
Object.defineProperty(exports, "adminUsersRoutes", {
|
|
2909
6575
|
enumerable: true,
|
|
2910
|
-
get: function () { return
|
|
6576
|
+
get: function () { return chunkJ7F3NPAP_cjs.userRoutes; }
|
|
2911
6577
|
});
|
|
2912
6578
|
Object.defineProperty(exports, "apiContentCrudRoutes", {
|
|
2913
6579
|
enumerable: true,
|
|
2914
|
-
get: function () { return
|
|
6580
|
+
get: function () { return chunkJ7F3NPAP_cjs.api_content_crud_default; }
|
|
2915
6581
|
});
|
|
2916
6582
|
Object.defineProperty(exports, "apiMediaRoutes", {
|
|
2917
6583
|
enumerable: true,
|
|
2918
|
-
get: function () { return
|
|
6584
|
+
get: function () { return chunkJ7F3NPAP_cjs.api_media_default; }
|
|
2919
6585
|
});
|
|
2920
6586
|
Object.defineProperty(exports, "apiRoutes", {
|
|
2921
6587
|
enumerable: true,
|
|
2922
|
-
get: function () { return
|
|
6588
|
+
get: function () { return chunkJ7F3NPAP_cjs.api_default; }
|
|
2923
6589
|
});
|
|
2924
6590
|
Object.defineProperty(exports, "apiSystemRoutes", {
|
|
2925
6591
|
enumerable: true,
|
|
2926
|
-
get: function () { return
|
|
6592
|
+
get: function () { return chunkJ7F3NPAP_cjs.api_system_default; }
|
|
2927
6593
|
});
|
|
2928
6594
|
Object.defineProperty(exports, "authRoutes", {
|
|
2929
6595
|
enumerable: true,
|
|
2930
|
-
get: function () { return
|
|
6596
|
+
get: function () { return chunkJ7F3NPAP_cjs.auth_default; }
|
|
2931
6597
|
});
|
|
2932
6598
|
Object.defineProperty(exports, "Logger", {
|
|
2933
6599
|
enumerable: true,
|
|
@@ -3095,235 +6761,243 @@ Object.defineProperty(exports, "workflowHistory", {
|
|
|
3095
6761
|
});
|
|
3096
6762
|
Object.defineProperty(exports, "AuthManager", {
|
|
3097
6763
|
enumerable: true,
|
|
3098
|
-
get: function () { return
|
|
6764
|
+
get: function () { return chunkE2BXLXPW_cjs.AuthManager; }
|
|
3099
6765
|
});
|
|
3100
6766
|
Object.defineProperty(exports, "PermissionManager", {
|
|
3101
6767
|
enumerable: true,
|
|
3102
|
-
get: function () { return
|
|
6768
|
+
get: function () { return chunkE2BXLXPW_cjs.PermissionManager; }
|
|
3103
6769
|
});
|
|
3104
6770
|
Object.defineProperty(exports, "bootstrapMiddleware", {
|
|
3105
6771
|
enumerable: true,
|
|
3106
|
-
get: function () { return
|
|
6772
|
+
get: function () { return chunkE2BXLXPW_cjs.bootstrapMiddleware; }
|
|
3107
6773
|
});
|
|
3108
6774
|
Object.defineProperty(exports, "cacheHeaders", {
|
|
3109
6775
|
enumerable: true,
|
|
3110
|
-
get: function () { return
|
|
6776
|
+
get: function () { return chunkE2BXLXPW_cjs.cacheHeaders; }
|
|
3111
6777
|
});
|
|
3112
6778
|
Object.defineProperty(exports, "compressionMiddleware", {
|
|
3113
6779
|
enumerable: true,
|
|
3114
|
-
get: function () { return
|
|
6780
|
+
get: function () { return chunkE2BXLXPW_cjs.compressionMiddleware; }
|
|
3115
6781
|
});
|
|
3116
6782
|
Object.defineProperty(exports, "detailedLoggingMiddleware", {
|
|
3117
6783
|
enumerable: true,
|
|
3118
|
-
get: function () { return
|
|
6784
|
+
get: function () { return chunkE2BXLXPW_cjs.detailedLoggingMiddleware; }
|
|
3119
6785
|
});
|
|
3120
6786
|
Object.defineProperty(exports, "getActivePlugins", {
|
|
3121
6787
|
enumerable: true,
|
|
3122
|
-
get: function () { return
|
|
6788
|
+
get: function () { return chunkE2BXLXPW_cjs.getActivePlugins; }
|
|
3123
6789
|
});
|
|
3124
6790
|
Object.defineProperty(exports, "isPluginActive", {
|
|
3125
6791
|
enumerable: true,
|
|
3126
|
-
get: function () { return
|
|
6792
|
+
get: function () { return chunkE2BXLXPW_cjs.isPluginActive; }
|
|
3127
6793
|
});
|
|
3128
6794
|
Object.defineProperty(exports, "logActivity", {
|
|
3129
6795
|
enumerable: true,
|
|
3130
|
-
get: function () { return
|
|
6796
|
+
get: function () { return chunkE2BXLXPW_cjs.logActivity; }
|
|
3131
6797
|
});
|
|
3132
6798
|
Object.defineProperty(exports, "loggingMiddleware", {
|
|
3133
6799
|
enumerable: true,
|
|
3134
|
-
get: function () { return
|
|
6800
|
+
get: function () { return chunkE2BXLXPW_cjs.loggingMiddleware; }
|
|
3135
6801
|
});
|
|
3136
6802
|
Object.defineProperty(exports, "optionalAuth", {
|
|
3137
6803
|
enumerable: true,
|
|
3138
|
-
get: function () { return
|
|
6804
|
+
get: function () { return chunkE2BXLXPW_cjs.optionalAuth; }
|
|
3139
6805
|
});
|
|
3140
6806
|
Object.defineProperty(exports, "performanceLoggingMiddleware", {
|
|
3141
6807
|
enumerable: true,
|
|
3142
|
-
get: function () { return
|
|
6808
|
+
get: function () { return chunkE2BXLXPW_cjs.performanceLoggingMiddleware; }
|
|
3143
6809
|
});
|
|
3144
6810
|
Object.defineProperty(exports, "requireActivePlugin", {
|
|
3145
6811
|
enumerable: true,
|
|
3146
|
-
get: function () { return
|
|
6812
|
+
get: function () { return chunkE2BXLXPW_cjs.requireActivePlugin; }
|
|
3147
6813
|
});
|
|
3148
6814
|
Object.defineProperty(exports, "requireActivePlugins", {
|
|
3149
6815
|
enumerable: true,
|
|
3150
|
-
get: function () { return
|
|
6816
|
+
get: function () { return chunkE2BXLXPW_cjs.requireActivePlugins; }
|
|
3151
6817
|
});
|
|
3152
6818
|
Object.defineProperty(exports, "requireAnyPermission", {
|
|
3153
6819
|
enumerable: true,
|
|
3154
|
-
get: function () { return
|
|
6820
|
+
get: function () { return chunkE2BXLXPW_cjs.requireAnyPermission; }
|
|
3155
6821
|
});
|
|
3156
6822
|
Object.defineProperty(exports, "requireAuth", {
|
|
3157
6823
|
enumerable: true,
|
|
3158
|
-
get: function () { return
|
|
6824
|
+
get: function () { return chunkE2BXLXPW_cjs.requireAuth; }
|
|
3159
6825
|
});
|
|
3160
6826
|
Object.defineProperty(exports, "requirePermission", {
|
|
3161
6827
|
enumerable: true,
|
|
3162
|
-
get: function () { return
|
|
6828
|
+
get: function () { return chunkE2BXLXPW_cjs.requirePermission; }
|
|
3163
6829
|
});
|
|
3164
6830
|
Object.defineProperty(exports, "requireRole", {
|
|
3165
6831
|
enumerable: true,
|
|
3166
|
-
get: function () { return
|
|
6832
|
+
get: function () { return chunkE2BXLXPW_cjs.requireRole; }
|
|
3167
6833
|
});
|
|
3168
6834
|
Object.defineProperty(exports, "securityHeaders", {
|
|
3169
6835
|
enumerable: true,
|
|
3170
|
-
get: function () { return
|
|
6836
|
+
get: function () { return chunkE2BXLXPW_cjs.securityHeaders; }
|
|
3171
6837
|
});
|
|
3172
6838
|
Object.defineProperty(exports, "securityLoggingMiddleware", {
|
|
3173
6839
|
enumerable: true,
|
|
3174
|
-
get: function () { return
|
|
6840
|
+
get: function () { return chunkE2BXLXPW_cjs.securityLoggingMiddleware; }
|
|
3175
6841
|
});
|
|
3176
6842
|
Object.defineProperty(exports, "PluginBootstrapService", {
|
|
3177
6843
|
enumerable: true,
|
|
3178
|
-
get: function () { return
|
|
6844
|
+
get: function () { return chunkMPT5PA6U_cjs.PluginBootstrapService; }
|
|
3179
6845
|
});
|
|
3180
6846
|
Object.defineProperty(exports, "PluginServiceClass", {
|
|
3181
6847
|
enumerable: true,
|
|
3182
|
-
get: function () { return
|
|
6848
|
+
get: function () { return chunkMPT5PA6U_cjs.PluginService; }
|
|
3183
6849
|
});
|
|
3184
6850
|
Object.defineProperty(exports, "cleanupRemovedCollections", {
|
|
3185
6851
|
enumerable: true,
|
|
3186
|
-
get: function () { return
|
|
6852
|
+
get: function () { return chunkMPT5PA6U_cjs.cleanupRemovedCollections; }
|
|
3187
6853
|
});
|
|
3188
6854
|
Object.defineProperty(exports, "fullCollectionSync", {
|
|
3189
6855
|
enumerable: true,
|
|
3190
|
-
get: function () { return
|
|
6856
|
+
get: function () { return chunkMPT5PA6U_cjs.fullCollectionSync; }
|
|
3191
6857
|
});
|
|
3192
6858
|
Object.defineProperty(exports, "getAvailableCollectionNames", {
|
|
3193
6859
|
enumerable: true,
|
|
3194
|
-
get: function () { return
|
|
6860
|
+
get: function () { return chunkMPT5PA6U_cjs.getAvailableCollectionNames; }
|
|
3195
6861
|
});
|
|
3196
6862
|
Object.defineProperty(exports, "getManagedCollections", {
|
|
3197
6863
|
enumerable: true,
|
|
3198
|
-
get: function () { return
|
|
6864
|
+
get: function () { return chunkMPT5PA6U_cjs.getManagedCollections; }
|
|
3199
6865
|
});
|
|
3200
6866
|
Object.defineProperty(exports, "isCollectionManaged", {
|
|
3201
6867
|
enumerable: true,
|
|
3202
|
-
get: function () { return
|
|
6868
|
+
get: function () { return chunkMPT5PA6U_cjs.isCollectionManaged; }
|
|
3203
6869
|
});
|
|
3204
6870
|
Object.defineProperty(exports, "loadCollectionConfig", {
|
|
3205
6871
|
enumerable: true,
|
|
3206
|
-
get: function () { return
|
|
6872
|
+
get: function () { return chunkMPT5PA6U_cjs.loadCollectionConfig; }
|
|
3207
6873
|
});
|
|
3208
6874
|
Object.defineProperty(exports, "loadCollectionConfigs", {
|
|
3209
6875
|
enumerable: true,
|
|
3210
|
-
get: function () { return
|
|
6876
|
+
get: function () { return chunkMPT5PA6U_cjs.loadCollectionConfigs; }
|
|
3211
6877
|
});
|
|
3212
6878
|
Object.defineProperty(exports, "registerCollections", {
|
|
3213
6879
|
enumerable: true,
|
|
3214
|
-
get: function () { return
|
|
6880
|
+
get: function () { return chunkMPT5PA6U_cjs.registerCollections; }
|
|
3215
6881
|
});
|
|
3216
6882
|
Object.defineProperty(exports, "syncCollection", {
|
|
3217
6883
|
enumerable: true,
|
|
3218
|
-
get: function () { return
|
|
6884
|
+
get: function () { return chunkMPT5PA6U_cjs.syncCollection; }
|
|
3219
6885
|
});
|
|
3220
6886
|
Object.defineProperty(exports, "syncCollections", {
|
|
3221
6887
|
enumerable: true,
|
|
3222
|
-
get: function () { return
|
|
6888
|
+
get: function () { return chunkMPT5PA6U_cjs.syncCollections; }
|
|
3223
6889
|
});
|
|
3224
6890
|
Object.defineProperty(exports, "validateCollectionConfig", {
|
|
3225
6891
|
enumerable: true,
|
|
3226
|
-
get: function () { return
|
|
6892
|
+
get: function () { return chunkMPT5PA6U_cjs.validateCollectionConfig; }
|
|
3227
6893
|
});
|
|
3228
6894
|
Object.defineProperty(exports, "MigrationService", {
|
|
3229
6895
|
enumerable: true,
|
|
3230
|
-
get: function () { return
|
|
6896
|
+
get: function () { return chunkYRFAQ6MI_cjs.MigrationService; }
|
|
3231
6897
|
});
|
|
3232
6898
|
Object.defineProperty(exports, "renderFilterBar", {
|
|
3233
6899
|
enumerable: true,
|
|
3234
|
-
get: function () { return
|
|
6900
|
+
get: function () { return chunkAYPF6C4D_cjs.renderFilterBar; }
|
|
3235
6901
|
});
|
|
3236
6902
|
Object.defineProperty(exports, "getConfirmationDialogScript", {
|
|
3237
6903
|
enumerable: true,
|
|
3238
|
-
get: function () { return
|
|
6904
|
+
get: function () { return chunkEHSZ6TAN_cjs.getConfirmationDialogScript; }
|
|
3239
6905
|
});
|
|
3240
6906
|
Object.defineProperty(exports, "renderAlert", {
|
|
3241
6907
|
enumerable: true,
|
|
3242
|
-
get: function () { return
|
|
6908
|
+
get: function () { return chunkEHSZ6TAN_cjs.renderAlert; }
|
|
3243
6909
|
});
|
|
3244
6910
|
Object.defineProperty(exports, "renderConfirmationDialog", {
|
|
3245
6911
|
enumerable: true,
|
|
3246
|
-
get: function () { return
|
|
6912
|
+
get: function () { return chunkEHSZ6TAN_cjs.renderConfirmationDialog; }
|
|
3247
6913
|
});
|
|
3248
6914
|
Object.defineProperty(exports, "renderForm", {
|
|
3249
6915
|
enumerable: true,
|
|
3250
|
-
get: function () { return
|
|
6916
|
+
get: function () { return chunkEHSZ6TAN_cjs.renderForm; }
|
|
3251
6917
|
});
|
|
3252
6918
|
Object.defineProperty(exports, "renderFormField", {
|
|
3253
6919
|
enumerable: true,
|
|
3254
|
-
get: function () { return
|
|
6920
|
+
get: function () { return chunkEHSZ6TAN_cjs.renderFormField; }
|
|
3255
6921
|
});
|
|
3256
6922
|
Object.defineProperty(exports, "renderPagination", {
|
|
3257
6923
|
enumerable: true,
|
|
3258
|
-
get: function () { return
|
|
6924
|
+
get: function () { return chunkEHSZ6TAN_cjs.renderPagination; }
|
|
3259
6925
|
});
|
|
3260
6926
|
Object.defineProperty(exports, "renderTable", {
|
|
3261
6927
|
enumerable: true,
|
|
3262
|
-
get: function () { return
|
|
6928
|
+
get: function () { return chunkEHSZ6TAN_cjs.renderTable; }
|
|
3263
6929
|
});
|
|
3264
6930
|
Object.defineProperty(exports, "HookSystemImpl", {
|
|
3265
6931
|
enumerable: true,
|
|
3266
|
-
get: function () { return
|
|
6932
|
+
get: function () { return chunkY72M3MVX_cjs.HookSystemImpl; }
|
|
3267
6933
|
});
|
|
3268
6934
|
Object.defineProperty(exports, "HookUtils", {
|
|
3269
6935
|
enumerable: true,
|
|
3270
|
-
get: function () { return
|
|
6936
|
+
get: function () { return chunkY72M3MVX_cjs.HookUtils; }
|
|
3271
6937
|
});
|
|
3272
6938
|
Object.defineProperty(exports, "PluginManagerClass", {
|
|
3273
6939
|
enumerable: true,
|
|
3274
|
-
get: function () { return
|
|
6940
|
+
get: function () { return chunkY72M3MVX_cjs.PluginManager; }
|
|
3275
6941
|
});
|
|
3276
6942
|
Object.defineProperty(exports, "PluginRegistryImpl", {
|
|
3277
6943
|
enumerable: true,
|
|
3278
|
-
get: function () { return
|
|
6944
|
+
get: function () { return chunkY72M3MVX_cjs.PluginRegistryImpl; }
|
|
3279
6945
|
});
|
|
3280
6946
|
Object.defineProperty(exports, "PluginValidatorClass", {
|
|
3281
6947
|
enumerable: true,
|
|
3282
|
-
get: function () { return
|
|
6948
|
+
get: function () { return chunkY72M3MVX_cjs.PluginValidator; }
|
|
3283
6949
|
});
|
|
3284
6950
|
Object.defineProperty(exports, "ScopedHookSystemClass", {
|
|
3285
6951
|
enumerable: true,
|
|
3286
|
-
get: function () { return
|
|
6952
|
+
get: function () { return chunkY72M3MVX_cjs.ScopedHookSystem; }
|
|
6953
|
+
});
|
|
6954
|
+
Object.defineProperty(exports, "PluginBuilder", {
|
|
6955
|
+
enumerable: true,
|
|
6956
|
+
get: function () { return chunkYHW27CBV_cjs.PluginBuilder; }
|
|
6957
|
+
});
|
|
6958
|
+
Object.defineProperty(exports, "PluginHelpers", {
|
|
6959
|
+
enumerable: true,
|
|
6960
|
+
get: function () { return chunkYHW27CBV_cjs.PluginHelpers; }
|
|
3287
6961
|
});
|
|
3288
6962
|
Object.defineProperty(exports, "QueryFilterBuilder", {
|
|
3289
6963
|
enumerable: true,
|
|
3290
|
-
get: function () { return
|
|
6964
|
+
get: function () { return chunkMYB5RY7H_cjs.QueryFilterBuilder; }
|
|
3291
6965
|
});
|
|
3292
6966
|
Object.defineProperty(exports, "SONICJS_VERSION", {
|
|
3293
6967
|
enumerable: true,
|
|
3294
|
-
get: function () { return
|
|
6968
|
+
get: function () { return chunkMYB5RY7H_cjs.SONICJS_VERSION; }
|
|
3295
6969
|
});
|
|
3296
6970
|
Object.defineProperty(exports, "TemplateRenderer", {
|
|
3297
6971
|
enumerable: true,
|
|
3298
|
-
get: function () { return
|
|
6972
|
+
get: function () { return chunkMYB5RY7H_cjs.TemplateRenderer; }
|
|
3299
6973
|
});
|
|
3300
6974
|
Object.defineProperty(exports, "buildQuery", {
|
|
3301
6975
|
enumerable: true,
|
|
3302
|
-
get: function () { return
|
|
6976
|
+
get: function () { return chunkMYB5RY7H_cjs.buildQuery; }
|
|
3303
6977
|
});
|
|
3304
6978
|
Object.defineProperty(exports, "escapeHtml", {
|
|
3305
6979
|
enumerable: true,
|
|
3306
|
-
get: function () { return
|
|
6980
|
+
get: function () { return chunkMYB5RY7H_cjs.escapeHtml; }
|
|
3307
6981
|
});
|
|
3308
6982
|
Object.defineProperty(exports, "getCoreVersion", {
|
|
3309
6983
|
enumerable: true,
|
|
3310
|
-
get: function () { return
|
|
6984
|
+
get: function () { return chunkMYB5RY7H_cjs.getCoreVersion; }
|
|
3311
6985
|
});
|
|
3312
6986
|
Object.defineProperty(exports, "renderTemplate", {
|
|
3313
6987
|
enumerable: true,
|
|
3314
|
-
get: function () { return
|
|
6988
|
+
get: function () { return chunkMYB5RY7H_cjs.renderTemplate; }
|
|
3315
6989
|
});
|
|
3316
6990
|
Object.defineProperty(exports, "sanitizeInput", {
|
|
3317
6991
|
enumerable: true,
|
|
3318
|
-
get: function () { return
|
|
6992
|
+
get: function () { return chunkMYB5RY7H_cjs.sanitizeInput; }
|
|
3319
6993
|
});
|
|
3320
6994
|
Object.defineProperty(exports, "sanitizeObject", {
|
|
3321
6995
|
enumerable: true,
|
|
3322
|
-
get: function () { return
|
|
6996
|
+
get: function () { return chunkMYB5RY7H_cjs.sanitizeObject; }
|
|
3323
6997
|
});
|
|
3324
6998
|
Object.defineProperty(exports, "templateRenderer", {
|
|
3325
6999
|
enumerable: true,
|
|
3326
|
-
get: function () { return
|
|
7000
|
+
get: function () { return chunkMYB5RY7H_cjs.templateRenderer; }
|
|
3327
7001
|
});
|
|
3328
7002
|
Object.defineProperty(exports, "metricsTracker", {
|
|
3329
7003
|
enumerable: true,
|