@sonicjs-cms/core 2.6.0 → 2.8.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.
Files changed (96) hide show
  1. package/dist/{app-DV27cjPy.d.cts → app-CYEm1ytG.d.cts} +1 -0
  2. package/dist/{app-DV27cjPy.d.ts → app-CYEm1ytG.d.ts} +1 -0
  3. package/dist/{chunk-7DL5SPPX.js → chunk-34QIAULP.js} +4 -4
  4. package/dist/{chunk-7DL5SPPX.js.map → chunk-34QIAULP.js.map} +1 -1
  5. package/dist/{chunk-EYWR6UA2.js → chunk-3E76TKR5.js} +3 -3
  6. package/dist/{chunk-EYWR6UA2.js.map → chunk-3E76TKR5.js.map} +1 -1
  7. package/dist/{chunk-IIRVZSP2.cjs → chunk-5CENPGR2.cjs} +219 -14
  8. package/dist/chunk-5CENPGR2.cjs.map +1 -0
  9. package/dist/{chunk-YMTTGHEK.cjs → chunk-5HMR2SJW.cjs} +4 -4
  10. package/dist/{chunk-YMTTGHEK.cjs.map → chunk-5HMR2SJW.cjs.map} +1 -1
  11. package/dist/{chunk-YHW27CBV.cjs → chunk-6FHNRRJ3.cjs} +190 -2
  12. package/dist/chunk-6FHNRRJ3.cjs.map +1 -0
  13. package/dist/{chunk-F6GZURXJ.js → chunk-BAWMAS5S.js} +5478 -1459
  14. package/dist/chunk-BAWMAS5S.js.map +1 -0
  15. package/dist/{chunk-F332TENF.js → chunk-CJYFSKH7.js} +4 -190
  16. package/dist/chunk-CJYFSKH7.js.map +1 -0
  17. package/dist/{chunk-3YNNVSMC.js → chunk-G44QUVNM.js} +90 -2
  18. package/dist/chunk-G44QUVNM.js.map +1 -0
  19. package/dist/{chunk-T3YIKW2A.cjs → chunk-GPTMGUFN.cjs} +4 -4
  20. package/dist/{chunk-T3YIKW2A.cjs.map → chunk-GPTMGUFN.cjs.map} +1 -1
  21. package/dist/chunk-H7AMQWVI.js +2466 -0
  22. package/dist/chunk-H7AMQWVI.js.map +1 -0
  23. package/dist/{chunk-CLIH2T74.js → chunk-J5WGMRSU.js} +189 -3
  24. package/dist/chunk-J5WGMRSU.js.map +1 -0
  25. package/dist/{chunk-EVZOVYLO.js → chunk-JDFPB6UW.js} +219 -14
  26. package/dist/chunk-JDFPB6UW.js.map +1 -0
  27. package/dist/{chunk-Y72M3MVX.cjs → chunk-MNFY6DWY.cjs} +13 -200
  28. package/dist/chunk-MNFY6DWY.cjs.map +1 -0
  29. package/dist/chunk-S6K2H2TS.cjs +2470 -0
  30. package/dist/chunk-S6K2H2TS.cjs.map +1 -0
  31. package/dist/{chunk-BZC4FYW7.cjs → chunk-SHCYIZAN.cjs} +16 -2
  32. package/dist/chunk-SHCYIZAN.cjs.map +1 -0
  33. package/dist/{chunk-KA2PDJNB.js → chunk-VCH6HXVP.js} +16 -2
  34. package/dist/chunk-VCH6HXVP.js.map +1 -0
  35. package/dist/{chunk-7FOAMNTI.cjs → chunk-VNLR35GO.cjs} +90 -2
  36. package/dist/chunk-VNLR35GO.cjs.map +1 -0
  37. package/dist/{chunk-N7TDLOUE.cjs → chunk-YE2MU7CN.cjs} +5234 -1210
  38. package/dist/chunk-YE2MU7CN.cjs.map +1 -0
  39. package/dist/index.cjs +2086 -674
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +3 -3
  42. package/dist/index.d.ts +3 -3
  43. package/dist/index.js +1973 -561
  44. package/dist/index.js.map +1 -1
  45. package/dist/middleware.cjs +23 -23
  46. package/dist/middleware.d.cts +1 -1
  47. package/dist/middleware.d.ts +1 -1
  48. package/dist/middleware.js +2 -2
  49. package/dist/migrations-7JGSFOCM.cjs +13 -0
  50. package/dist/{migrations-QNYAWQLB.cjs.map → migrations-7JGSFOCM.cjs.map} +1 -1
  51. package/dist/migrations-YB77VTVF.js +4 -0
  52. package/dist/{migrations-R6NQBKQV.js.map → migrations-YB77VTVF.js.map} +1 -1
  53. package/dist/{plugin-bootstrap-CB-xaBfK.d.ts → plugin-bootstrap-C7Mj00Ud.d.ts} +2455 -1
  54. package/dist/{plugin-bootstrap-U-cw9jn3.d.cts → plugin-bootstrap-DKB5f8-E.d.cts} +2455 -1
  55. package/dist/plugins.cjs +14 -14
  56. package/dist/plugins.js +2 -2
  57. package/dist/routes.cjs +39 -27
  58. package/dist/routes.d.cts +126 -53
  59. package/dist/routes.d.ts +126 -53
  60. package/dist/routes.js +7 -7
  61. package/dist/services.cjs +14 -14
  62. package/dist/services.d.cts +1 -1
  63. package/dist/services.d.ts +1 -1
  64. package/dist/services.js +2 -2
  65. package/dist/templates.cjs +25 -17
  66. package/dist/templates.d.cts +21 -1
  67. package/dist/templates.d.ts +21 -1
  68. package/dist/templates.js +2 -2
  69. package/dist/utils.cjs +14 -14
  70. package/dist/utils.js +1 -1
  71. package/migrations/014_fix_plugin_registry.sql +1 -1
  72. package/migrations/020_add_email_plugin.sql +1 -1
  73. package/migrations/026_add_otp_login.sql +1 -1
  74. package/migrations/029_add_forms_system.sql +184 -0
  75. package/migrations/030_add_turnstile_to_forms.sql +14 -0
  76. package/package.json +2 -2
  77. package/dist/chunk-3YNNVSMC.js.map +0 -1
  78. package/dist/chunk-63K7XXRX.cjs +0 -76
  79. package/dist/chunk-63K7XXRX.cjs.map +0 -1
  80. package/dist/chunk-7FOAMNTI.cjs.map +0 -1
  81. package/dist/chunk-BZC4FYW7.cjs.map +0 -1
  82. package/dist/chunk-CLIH2T74.js.map +0 -1
  83. package/dist/chunk-EVZOVYLO.js.map +0 -1
  84. package/dist/chunk-F332TENF.js.map +0 -1
  85. package/dist/chunk-F6GZURXJ.js.map +0 -1
  86. package/dist/chunk-IIRVZSP2.cjs.map +0 -1
  87. package/dist/chunk-KA2PDJNB.js.map +0 -1
  88. package/dist/chunk-KAOWRIFD.js +0 -74
  89. package/dist/chunk-KAOWRIFD.js.map +0 -1
  90. package/dist/chunk-N7TDLOUE.cjs.map +0 -1
  91. package/dist/chunk-Y72M3MVX.cjs.map +0 -1
  92. package/dist/chunk-YHW27CBV.cjs.map +0 -1
  93. package/dist/migrations-QNYAWQLB.cjs +0 -13
  94. package/dist/migrations-R6NQBKQV.js +0 -4
  95. package/migrations/025_rename_mdxeditor_to_easy_mdx.sql +0 -22
  96. /package/migrations/{029_ai_search_plugin.sql → 031_ai_search_plugin.sql} +0 -0
package/dist/index.js CHANGED
@@ -1,25 +1,24 @@
1
- import { api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminSettingsRoutes, admin_content_default, adminMediaRoutes, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-F6GZURXJ.js';
2
- export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes } from './chunk-F6GZURXJ.js';
3
- import { schema_exports } from './chunk-3YNNVSMC.js';
4
- export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-3YNNVSMC.js';
5
- import { requireAuth, AuthManager, metricsMiddleware, bootstrapMiddleware } from './chunk-EYWR6UA2.js';
6
- export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeaders, securityLoggingMiddleware } from './chunk-EYWR6UA2.js';
1
+ import { renderConfirmationDialog, getConfirmationDialogScript, api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminFormsRoutes, adminSettingsRoutes, public_forms_default, router2, admin_content_default, adminMediaRoutes, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-BAWMAS5S.js';
2
+ export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes } from './chunk-BAWMAS5S.js';
3
+ import { SettingsService, schema_exports } from './chunk-G44QUVNM.js';
4
+ export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-G44QUVNM.js';
5
+ import { requireAuth, AuthManager, metricsMiddleware, bootstrapMiddleware } from './chunk-3E76TKR5.js';
6
+ export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeaders, securityLoggingMiddleware } from './chunk-3E76TKR5.js';
7
7
  export { PluginBootstrapService, PluginService as PluginServiceClass, cleanupRemovedCollections, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, registerCollections, syncCollection, syncCollections, validateCollectionConfig } from './chunk-YFJJU26H.js';
8
- export { MigrationService } from './chunk-EVZOVYLO.js';
9
- export { renderFilterBar } from './chunk-KAOWRIFD.js';
10
- import { init_admin_layout_catalyst_template, renderAdminLayout, adminLayoutV2, renderAdminLayoutCatalyst } from './chunk-KA2PDJNB.js';
11
- export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-KA2PDJNB.js';
12
- export { HookSystemImpl, HookUtils, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, ScopedHookSystem as ScopedHookSystemClass } from './chunk-F332TENF.js';
13
- import { PluginBuilder } from './chunk-CLIH2T74.js';
14
- export { PluginBuilder, PluginHelpers } from './chunk-CLIH2T74.js';
15
- import { package_default, getCoreVersion } from './chunk-7DL5SPPX.js';
16
- export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-7DL5SPPX.js';
8
+ export { MigrationService } from './chunk-JDFPB6UW.js';
9
+ export { renderFilterBar } from './chunk-H7AMQWVI.js';
10
+ import { init_admin_layout_catalyst_template, renderAdminLayout, renderAdminLayoutCatalyst } from './chunk-VCH6HXVP.js';
11
+ export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-VCH6HXVP.js';
12
+ export { HookSystemImpl, HookUtils, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, ScopedHookSystem as ScopedHookSystemClass } from './chunk-CJYFSKH7.js';
13
+ import { PluginBuilder } from './chunk-J5WGMRSU.js';
14
+ export { PluginBuilder, PluginHelpers } from './chunk-J5WGMRSU.js';
15
+ import { package_default, getCoreVersion } from './chunk-34QIAULP.js';
16
+ export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-34QIAULP.js';
17
17
  import './chunk-X7ZAEI5S.js';
18
18
  export { metricsTracker } from './chunk-FICTAGD4.js';
19
19
  export { HOOKS } from './chunk-LOUJRBXV.js';
20
20
  import './chunk-V4OQ3NZ2.js';
21
21
  import { Hono } from 'hono';
22
- import { html } from 'hono/html';
23
22
  import { setCookie } from 'hono/cookie';
24
23
  import { z } from 'zod';
25
24
  import { drizzle } from 'drizzle-orm/d1';
@@ -562,9 +561,9 @@ function formatCellValue(value) {
562
561
 
563
562
  // src/plugins/core-plugins/database-tools-plugin/admin-routes.ts
564
563
  function createDatabaseToolsAdminRoutes() {
565
- const router2 = new Hono();
566
- router2.use("*", requireAuth());
567
- router2.get("/api/stats", async (c) => {
564
+ const router3 = new Hono();
565
+ router3.use("*", requireAuth());
566
+ router3.get("/api/stats", async (c) => {
568
567
  try {
569
568
  const user = c.get("user");
570
569
  if (!user || user.role !== "admin") {
@@ -588,7 +587,7 @@ function createDatabaseToolsAdminRoutes() {
588
587
  }, 500);
589
588
  }
590
589
  });
591
- router2.post("/api/truncate", async (c) => {
590
+ router3.post("/api/truncate", async (c) => {
592
591
  try {
593
592
  const user = c.get("user");
594
593
  if (!user || user.role !== "admin") {
@@ -625,7 +624,7 @@ function createDatabaseToolsAdminRoutes() {
625
624
  }, 500);
626
625
  }
627
626
  });
628
- router2.post("/api/backup", async (c) => {
627
+ router3.post("/api/backup", async (c) => {
629
628
  try {
630
629
  const user = c.get("user");
631
630
  if (!user || user.role !== "admin") {
@@ -652,7 +651,7 @@ function createDatabaseToolsAdminRoutes() {
652
651
  }, 500);
653
652
  }
654
653
  });
655
- router2.get("/api/validate", async (c) => {
654
+ router3.get("/api/validate", async (c) => {
656
655
  try {
657
656
  const user = c.get("user");
658
657
  if (!user || user.role !== "admin") {
@@ -676,7 +675,7 @@ function createDatabaseToolsAdminRoutes() {
676
675
  }, 500);
677
676
  }
678
677
  });
679
- router2.get("/api/tables/:tableName", async (c) => {
678
+ router3.get("/api/tables/:tableName", async (c) => {
680
679
  try {
681
680
  const user = c.get("user");
682
681
  if (!user || user.role !== "admin") {
@@ -705,7 +704,7 @@ function createDatabaseToolsAdminRoutes() {
705
704
  }, 500);
706
705
  }
707
706
  });
708
- router2.get("/tables/:tableName", async (c) => {
707
+ router3.get("/tables/:tableName", async (c) => {
709
708
  try {
710
709
  const user = c.get("user");
711
710
  if (!user || user.role !== "admin") {
@@ -741,7 +740,7 @@ function createDatabaseToolsAdminRoutes() {
741
740
  return c.text(`Error: ${error}`, 500);
742
741
  }
743
742
  });
744
- return router2;
743
+ return router3;
745
744
  }
746
745
 
747
746
  // src/plugins/core-plugins/seed-data-plugin/services/seed-data-service.ts
@@ -1031,7 +1030,7 @@ var SeedDataService = class {
1031
1030
  function createSeedDataAdminRoutes() {
1032
1031
  const routes = new Hono();
1033
1032
  routes.get("/", async (c) => {
1034
- const html3 = `
1033
+ const html = `
1035
1034
  <!DOCTYPE html>
1036
1035
  <html>
1037
1036
  <head>
@@ -1274,7 +1273,7 @@ function createSeedDataAdminRoutes() {
1274
1273
  </body>
1275
1274
  </html>
1276
1275
  `;
1277
- return c.html(html3);
1276
+ return c.html(html);
1278
1277
  });
1279
1278
  routes.post("/generate", async (c) => {
1280
1279
  try {
@@ -1325,253 +1324,6 @@ function createEmailPlugin() {
1325
1324
  compatibility: "^2.0.0"
1326
1325
  });
1327
1326
  const emailRoutes = new Hono();
1328
- emailRoutes.get("/settings", async (c) => {
1329
- const user = c.get("user");
1330
- const db = c.env.DB;
1331
- const plugin = await db.prepare(`
1332
- SELECT settings FROM plugins WHERE id = 'email'
1333
- `).first();
1334
- const settings = plugin?.settings ? JSON.parse(plugin.settings) : {};
1335
- const contentHTML = await html`
1336
- <div class="p-8">
1337
- <!-- Header -->
1338
- <div class="mb-8">
1339
- <h1 class="text-3xl font-bold text-zinc-950 dark:text-white mb-2">Email Settings</h1>
1340
- <p class="text-zinc-600 dark:text-zinc-400">Configure Resend API for sending transactional emails</p>
1341
- </div>
1342
-
1343
- <!-- Settings Form -->
1344
- <div class="max-w-3xl">
1345
- <!-- Main Settings Card -->
1346
- <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">
1347
- <h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">Resend Configuration</h2>
1348
-
1349
- <form id="emailSettingsForm" class="space-y-6">
1350
- <!-- API Key -->
1351
- <div>
1352
- <label for="apiKey" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
1353
- Resend API Key <span class="text-red-500">*</span>
1354
- </label>
1355
- <input
1356
- type="password"
1357
- id="apiKey"
1358
- name="apiKey"
1359
- value="${settings.apiKey || ""}"
1360
- 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 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
1361
- placeholder="re_..."
1362
- required
1363
- />
1364
- <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
1365
- Get your API key from <a href="https://resend.com/api-keys" target="_blank" class="text-indigo-600 dark:text-indigo-400 hover:underline">resend.com/api-keys</a>
1366
- </p>
1367
- </div>
1368
-
1369
- <!-- From Email -->
1370
- <div>
1371
- <label for="fromEmail" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
1372
- From Email <span class="text-red-500">*</span>
1373
- </label>
1374
- <input
1375
- type="email"
1376
- id="fromEmail"
1377
- name="fromEmail"
1378
- value="${settings.fromEmail || ""}"
1379
- 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 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
1380
- placeholder="noreply@yourdomain.com"
1381
- required
1382
- />
1383
- <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
1384
- Must be a verified domain in Resend
1385
- </p>
1386
- </div>
1387
-
1388
- <!-- From Name -->
1389
- <div>
1390
- <label for="fromName" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
1391
- From Name <span class="text-red-500">*</span>
1392
- </label>
1393
- <input
1394
- type="text"
1395
- id="fromName"
1396
- name="fromName"
1397
- value="${settings.fromName || ""}"
1398
- 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 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
1399
- placeholder="Your App Name"
1400
- required
1401
- />
1402
- </div>
1403
-
1404
- <!-- Reply To -->
1405
- <div>
1406
- <label for="replyTo" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
1407
- Reply-To Email
1408
- </label>
1409
- <input
1410
- type="email"
1411
- id="replyTo"
1412
- name="replyTo"
1413
- value="${settings.replyTo || ""}"
1414
- 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 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
1415
- placeholder="support@yourdomain.com"
1416
- />
1417
- </div>
1418
-
1419
- <!-- Logo URL -->
1420
- <div>
1421
- <label for="logoUrl" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
1422
- Logo URL
1423
- </label>
1424
- <input
1425
- type="url"
1426
- id="logoUrl"
1427
- name="logoUrl"
1428
- value="${settings.logoUrl || ""}"
1429
- 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 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
1430
- placeholder="https://yourdomain.com/logo.png"
1431
- />
1432
- <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
1433
- Logo to display in email templates
1434
- </p>
1435
- </div>
1436
-
1437
- <!-- Action Buttons -->
1438
- <div class="flex gap-3 pt-4">
1439
- <button
1440
- type="submit"
1441
- class="inline-flex items-center justify-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm"
1442
- >
1443
- Save Settings
1444
- </button>
1445
- <button
1446
- type="button"
1447
- id="testEmailBtn"
1448
- 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"
1449
- >
1450
- Send Test Email
1451
- </button>
1452
- <button
1453
- type="button"
1454
- id="resetBtn"
1455
- 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"
1456
- >
1457
- Reset
1458
- </button>
1459
- </div>
1460
- </form>
1461
- </div>
1462
-
1463
- <!-- Status Message -->
1464
- <div id="statusMessage" class="hidden rounded-xl p-4 mb-6"></div>
1465
-
1466
- <!-- Info Card -->
1467
- <div class="rounded-xl bg-indigo-50 dark:bg-indigo-950/30 ring-1 ring-indigo-100 dark:ring-indigo-900/50 p-6">
1468
- <h3 class="font-semibold text-indigo-900 dark:text-indigo-300 mb-3">
1469
- 📧 Email Templates Included
1470
- </h3>
1471
- <ul class="text-sm text-indigo-800 dark:text-indigo-200 space-y-2">
1472
- <li>✓ Registration confirmation</li>
1473
- <li>✓ Email verification</li>
1474
- <li>✓ Password reset</li>
1475
- <li>✓ One-time code (2FA)</li>
1476
- </ul>
1477
- <p class="text-xs text-indigo-700 dark:text-indigo-300 mt-4">
1478
- Templates are code-based and can be customized by editing the plugin files.
1479
- </p>
1480
- </div>
1481
- </div>
1482
- </div>
1483
-
1484
- <script>
1485
- // Form submission handler
1486
- document.getElementById('emailSettingsForm').addEventListener('submit', async (e) => {
1487
- e.preventDefault()
1488
- const formData = new FormData(e.target)
1489
- const data = Object.fromEntries(formData.entries())
1490
-
1491
- const statusEl = document.getElementById('statusMessage')
1492
-
1493
- try {
1494
- const response = await fetch('/admin/plugins/email/settings', {
1495
- method: 'POST',
1496
- headers: { 'Content-Type': 'application/json' },
1497
- body: JSON.stringify(data)
1498
- })
1499
-
1500
- if (response.ok) {
1501
- statusEl.className = 'rounded-xl bg-green-50 dark:bg-green-950/30 ring-1 ring-green-100 dark:ring-green-900/50 p-4 mb-6 text-green-900 dark:text-green-200'
1502
- statusEl.innerHTML = '✅ Settings saved successfully!'
1503
- statusEl.classList.remove('hidden')
1504
- setTimeout(() => statusEl.classList.add('hidden'), 3000)
1505
- } else {
1506
- throw new Error('Failed to save settings')
1507
- }
1508
- } catch (error) {
1509
- statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
1510
- statusEl.innerHTML = '❌ Failed to save settings. Please try again.'
1511
- statusEl.classList.remove('hidden')
1512
- }
1513
- })
1514
-
1515
- // Test email handler
1516
- document.getElementById('testEmailBtn').addEventListener('click', async () => {
1517
- // Prompt for destination email
1518
- const toEmail = prompt('Enter destination email address for test:')
1519
- if (!toEmail) return
1520
-
1521
- // Basic email validation
1522
- if (!toEmail.match(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/)) {
1523
- alert('Please enter a valid email address')
1524
- return
1525
- }
1526
-
1527
- const statusEl = document.getElementById('statusMessage')
1528
-
1529
- statusEl.className = 'rounded-xl bg-indigo-50 dark:bg-indigo-950/30 ring-1 ring-indigo-100 dark:ring-indigo-900/50 p-4 mb-6 text-indigo-900 dark:text-indigo-200'
1530
- statusEl.innerHTML = \`📧 Sending test email to \${toEmail}...\`
1531
- statusEl.classList.remove('hidden')
1532
-
1533
- try {
1534
- const response = await fetch('/admin/plugins/email/test', {
1535
- method: 'POST',
1536
- headers: { 'Content-Type': 'application/json' },
1537
- body: JSON.stringify({ toEmail })
1538
- })
1539
-
1540
- const data = await response.json()
1541
-
1542
- if (response.ok) {
1543
- statusEl.className = 'rounded-xl bg-green-50 dark:bg-green-950/30 ring-1 ring-green-100 dark:ring-green-900/50 p-4 mb-6 text-green-900 dark:text-green-200'
1544
- statusEl.innerHTML = \`✅ \${data.message || 'Test email sent! Check your inbox.'}\`
1545
- } else {
1546
- statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
1547
- statusEl.innerHTML = \`❌ \${data.error || 'Failed to send test email. Check your settings.'}\`
1548
- }
1549
- } catch (error) {
1550
- statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
1551
- statusEl.innerHTML = '❌ Network error. Please try again.'
1552
- }
1553
- })
1554
-
1555
- // Reset button handler
1556
- document.getElementById('resetBtn').addEventListener('click', () => {
1557
- document.getElementById('emailSettingsForm').reset()
1558
- })
1559
- </script>
1560
- `;
1561
- const templateUser = user ? {
1562
- name: user.name ?? user.email ?? "Admin",
1563
- email: user.email ?? "admin@sonicjs.com",
1564
- role: user.role ?? "admin"
1565
- } : void 0;
1566
- return c.html(
1567
- renderAdminLayout({
1568
- title: "Email Settings",
1569
- content: contentHTML,
1570
- user: templateUser,
1571
- currentPath: "/admin/plugins/email/settings"
1572
- })
1573
- );
1574
- });
1575
1327
  emailRoutes.post("/settings", async (c) => {
1576
1328
  try {
1577
1329
  const body = await c.req.json();
@@ -1592,16 +1344,16 @@ function createEmailPlugin() {
1592
1344
  try {
1593
1345
  const db = c.env.DB;
1594
1346
  const body = await c.req.json();
1595
- const plugin = await db.prepare(`
1347
+ const plugin2 = await db.prepare(`
1596
1348
  SELECT settings FROM plugins WHERE id = 'email'
1597
1349
  `).first();
1598
- if (!plugin?.settings) {
1350
+ if (!plugin2?.settings) {
1599
1351
  return c.json({
1600
1352
  success: false,
1601
1353
  error: "Email settings not configured. Please save your settings first."
1602
1354
  }, 400);
1603
1355
  }
1604
- const settings = JSON.parse(plugin.settings);
1356
+ const settings = JSON.parse(plugin2.settings);
1605
1357
  if (!settings.apiKey || !settings.fromEmail || !settings.fromName) {
1606
1358
  return c.json({
1607
1359
  success: false,
@@ -1667,7 +1419,7 @@ function createEmailPlugin() {
1667
1419
  requiresAuth: true,
1668
1420
  priority: 80
1669
1421
  });
1670
- builder.addMenuItem("Email", "/admin/plugins/email/settings", {
1422
+ builder.addMenuItem("Email", "/admin/plugins/email", {
1671
1423
  icon: "envelope",
1672
1424
  order: 80,
1673
1425
  permissions: ["email:manage"]
@@ -1984,8 +1736,7 @@ var DEFAULT_SETTINGS = {
1984
1736
  codeExpiryMinutes: 10,
1985
1737
  maxAttempts: 3,
1986
1738
  rateLimitPerHour: 5,
1987
- allowNewUserRegistration: false,
1988
- appName: "SonicJS"
1739
+ allowNewUserRegistration: false
1989
1740
  };
1990
1741
  function createOTPLoginPlugin() {
1991
1742
  const builder = PluginBuilder.create({
@@ -2016,7 +1767,21 @@ function createOTPLoginPlugin() {
2016
1767
  const normalizedEmail = email.toLowerCase();
2017
1768
  const db = c.env.DB;
2018
1769
  const otpService = new OTPService(db);
2019
- const settings = { ...DEFAULT_SETTINGS };
1770
+ let settings = { ...DEFAULT_SETTINGS };
1771
+ const pluginRow = await db.prepare(`
1772
+ SELECT settings FROM plugins WHERE id = 'otp-login'
1773
+ `).first();
1774
+ if (pluginRow?.settings) {
1775
+ try {
1776
+ const savedSettings = JSON.parse(pluginRow.settings);
1777
+ settings = { ...DEFAULT_SETTINGS, ...savedSettings };
1778
+ } catch (e) {
1779
+ console.warn("Failed to parse OTP plugin settings, using defaults");
1780
+ }
1781
+ }
1782
+ const settingsService = new SettingsService(db);
1783
+ const generalSettings = await settingsService.getGeneralSettings();
1784
+ const siteName = generalSettings.siteName;
2020
1785
  const canRequest = await otpService.checkRateLimit(normalizedEmail, settings);
2021
1786
  if (!canRequest) {
2022
1787
  return c.json({
@@ -2060,7 +1825,7 @@ function createOTPLoginPlugin() {
2060
1825
  email: normalizedEmail,
2061
1826
  ipAddress,
2062
1827
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2063
- appName: settings.appName
1828
+ appName: siteName
2064
1829
  });
2065
1830
  const emailPlugin2 = await db.prepare(`
2066
1831
  SELECT settings FROM plugins WHERE id = 'email'
@@ -2077,7 +1842,7 @@ function createOTPLoginPlugin() {
2077
1842
  body: JSON.stringify({
2078
1843
  from: `${emailSettings.fromName} <${emailSettings.fromEmail}>`,
2079
1844
  to: [normalizedEmail],
2080
- subject: `Your login code for ${settings.appName}`,
1845
+ subject: `Your login code for ${siteName}`,
2081
1846
  html: emailContent.html,
2082
1847
  text: emailContent.text,
2083
1848
  reply_to: emailSettings.replyTo || emailSettings.fromEmail
@@ -2128,7 +1893,18 @@ function createOTPLoginPlugin() {
2128
1893
  const normalizedEmail = email.toLowerCase();
2129
1894
  const db = c.env.DB;
2130
1895
  const otpService = new OTPService(db);
2131
- const settings = { ...DEFAULT_SETTINGS };
1896
+ let settings = { ...DEFAULT_SETTINGS };
1897
+ const pluginRow = await db.prepare(`
1898
+ SELECT settings FROM plugins WHERE id = 'otp-login'
1899
+ `).first();
1900
+ if (pluginRow?.settings) {
1901
+ try {
1902
+ const savedSettings = JSON.parse(pluginRow.settings);
1903
+ settings = { ...DEFAULT_SETTINGS, ...savedSettings };
1904
+ } catch (e) {
1905
+ console.warn("Failed to parse OTP plugin settings, using defaults");
1906
+ }
1907
+ }
2132
1908
  const verification = await otpService.verifyCode(normalizedEmail, code, settings);
2133
1909
  if (!verification.valid) {
2134
1910
  await otpService.incrementAttempts(normalizedEmail, code);
@@ -2207,193 +1983,7 @@ function createOTPLoginPlugin() {
2207
1983
  requiresAuth: false,
2208
1984
  priority: 100
2209
1985
  });
2210
- const adminRoutes2 = new Hono();
2211
- adminRoutes2.get("/settings", async (c) => {
2212
- const user = c.get("user");
2213
- const contentHTML = await html`
2214
- <div class="p-8">
2215
- <div class="mb-8">
2216
- <h1 class="text-3xl font-bold mb-2">OTP Login Settings</h1>
2217
- <p class="text-zinc-600 dark:text-zinc-400">Configure passwordless authentication via email codes</p>
2218
- </div>
2219
-
2220
- <div class="max-w-3xl">
2221
- <div class="backdrop-blur-md bg-black/20 border border-white/10 shadow-xl rounded-xl p-6 mb-6">
2222
- <h2 class="text-xl font-semibold mb-4">Code Settings</h2>
2223
-
2224
- <form id="otpSettingsForm" class="space-y-6">
2225
- <div>
2226
- <label for="codeLength" class="block text-sm font-medium mb-2">
2227
- Code Length
2228
- </label>
2229
- <input
2230
- type="number"
2231
- id="codeLength"
2232
- name="codeLength"
2233
- min="4"
2234
- max="8"
2235
- value="6"
2236
- class="w-full px-4 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none"
2237
- />
2238
- <p class="text-xs text-zinc-500 mt-1">Number of digits in OTP code (4-8)</p>
2239
- </div>
2240
-
2241
- <div>
2242
- <label for="codeExpiryMinutes" class="block text-sm font-medium mb-2">
2243
- Code Expiry (minutes)
2244
- </label>
2245
- <input
2246
- type="number"
2247
- id="codeExpiryMinutes"
2248
- name="codeExpiryMinutes"
2249
- min="5"
2250
- max="60"
2251
- value="10"
2252
- class="w-full px-4 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none"
2253
- />
2254
- <p class="text-xs text-zinc-500 mt-1">How long codes remain valid (5-60 minutes)</p>
2255
- </div>
2256
-
2257
- <div>
2258
- <label for="maxAttempts" class="block text-sm font-medium mb-2">
2259
- Maximum Attempts
2260
- </label>
2261
- <input
2262
- type="number"
2263
- id="maxAttempts"
2264
- name="maxAttempts"
2265
- min="3"
2266
- max="10"
2267
- value="3"
2268
- class="w-full px-4 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none"
2269
- />
2270
- <p class="text-xs text-zinc-500 mt-1">Max verification attempts before invalidation</p>
2271
- </div>
2272
-
2273
- <div>
2274
- <label for="rateLimitPerHour" class="block text-sm font-medium mb-2">
2275
- Rate Limit (per hour)
2276
- </label>
2277
- <input
2278
- type="number"
2279
- id="rateLimitPerHour"
2280
- name="rateLimitPerHour"
2281
- min="3"
2282
- max="20"
2283
- value="5"
2284
- class="w-full px-4 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none"
2285
- />
2286
- <p class="text-xs text-zinc-500 mt-1">Max code requests per email per hour</p>
2287
- </div>
2288
-
2289
- <div class="flex items-center">
2290
- <input
2291
- type="checkbox"
2292
- id="allowNewUserRegistration"
2293
- name="allowNewUserRegistration"
2294
- class="w-4 h-4 rounded border-white/10"
2295
- />
2296
- <label for="allowNewUserRegistration" class="ml-2 text-sm">
2297
- Allow new user registration via OTP
2298
- </label>
2299
- </div>
2300
-
2301
- <div class="flex gap-3 pt-4">
2302
- <button
2303
- type="submit"
2304
- class="px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg font-medium hover:from-blue-600 hover:to-purple-700 transition-all"
2305
- >
2306
- Save Settings
2307
- </button>
2308
- <button
2309
- type="button"
2310
- id="testOTPBtn"
2311
- class="px-6 py-2 bg-white/10 hover:bg-white/20 text-white rounded-lg font-medium transition-all"
2312
- >
2313
- Send Test Code
2314
- </button>
2315
- </div>
2316
- </form>
2317
- </div>
2318
-
2319
- <div id="statusMessage" class="hidden backdrop-blur-md bg-black/20 border border-white/10 rounded-xl p-4 mb-6"></div>
2320
-
2321
- <div class="backdrop-blur-md bg-blue-500/10 border border-blue-500/20 rounded-xl p-6">
2322
- <h3 class="font-semibold text-blue-400 mb-3">
2323
- 🔢 Features
2324
- </h3>
2325
- <ul class="text-sm text-blue-200 space-y-2">
2326
- <li>✓ Passwordless authentication</li>
2327
- <li>✓ Secure random code generation</li>
2328
- <li>✓ Rate limiting protection</li>
2329
- <li>✓ Brute force prevention</li>
2330
- <li>✓ Mobile-friendly UX</li>
2331
- </ul>
2332
- </div>
2333
- </div>
2334
- </div>
2335
-
2336
- <script>
2337
- document.getElementById('otpSettingsForm').addEventListener('submit', async (e) => {
2338
- e.preventDefault()
2339
- const statusEl = document.getElementById('statusMessage')
2340
- statusEl.className = 'backdrop-blur-md bg-green-500/20 border border-green-500/30 rounded-xl p-4 mb-6'
2341
- statusEl.innerHTML = '✅ Settings saved successfully!'
2342
- statusEl.classList.remove('hidden')
2343
- setTimeout(() => statusEl.classList.add('hidden'), 3000)
2344
- })
2345
-
2346
- document.getElementById('testOTPBtn').addEventListener('click', async () => {
2347
- const email = prompt('Enter email address for test:')
2348
- if (!email) return
2349
-
2350
- const statusEl = document.getElementById('statusMessage')
2351
- statusEl.className = 'backdrop-blur-md bg-blue-500/20 border border-blue-500/30 rounded-xl p-4 mb-6'
2352
- statusEl.innerHTML = '📧 Sending test code...'
2353
- statusEl.classList.remove('hidden')
2354
-
2355
- try {
2356
- const response = await fetch('/auth/otp/request', {
2357
- method: 'POST',
2358
- headers: { 'Content-Type': 'application/json' },
2359
- body: JSON.stringify({ email })
2360
- })
2361
-
2362
- const data = await response.json()
2363
-
2364
- if (response.ok) {
2365
- statusEl.className = 'backdrop-blur-md bg-green-500/20 border border-green-500/30 rounded-xl p-4 mb-6'
2366
- statusEl.innerHTML = '✅ Test code sent!' + (data.dev_code ? \` Code: <strong>\${data.dev_code}</strong>\` : '')
2367
- } else {
2368
- throw new Error(data.error || 'Failed')
2369
- }
2370
- } catch (error) {
2371
- statusEl.className = 'backdrop-blur-md bg-red-500/20 border border-red-500/30 rounded-xl p-4 mb-6'
2372
- statusEl.innerHTML = '❌ Failed to send test code'
2373
- }
2374
- })
2375
- </script>
2376
- `;
2377
- const templateUser = user ? {
2378
- name: user.name ?? user.email ?? "Admin",
2379
- email: user.email ?? "admin@sonicjs.com",
2380
- role: user.role ?? "admin"
2381
- } : void 0;
2382
- return c.html(
2383
- adminLayoutV2({
2384
- title: "OTP Login Settings",
2385
- content: contentHTML,
2386
- user: templateUser,
2387
- currentPath: "/admin/plugins/otp-login/settings"
2388
- })
2389
- );
2390
- });
2391
- builder.addRoute("/admin/plugins/otp-login", adminRoutes2, {
2392
- description: "OTP login admin interface",
2393
- requiresAuth: true,
2394
- priority: 85
2395
- });
2396
- builder.addMenuItem("OTP Login", "/admin/plugins/otp-login/settings", {
1986
+ builder.addMenuItem("OTP Login", "/admin/plugins/otp-login", {
2397
1987
  icon: "key",
2398
1988
  order: 85,
2399
1989
  permissions: ["otp:manage"]
@@ -2915,11 +2505,11 @@ var AISearchService = class {
2915
2505
  */
2916
2506
  async getSettings() {
2917
2507
  try {
2918
- const plugin = await this.db.prepare(`SELECT settings FROM plugins WHERE id = ? LIMIT 1`).bind("ai-search").first();
2919
- if (!plugin || !plugin.settings) {
2508
+ const plugin2 = await this.db.prepare(`SELECT settings FROM plugins WHERE id = ? LIMIT 1`).bind("ai-search").first();
2509
+ if (!plugin2 || !plugin2.settings) {
2920
2510
  return this.getDefaultSettings();
2921
2511
  }
2922
- return JSON.parse(plugin.settings);
2512
+ return JSON.parse(plugin2.settings);
2923
2513
  } catch (error) {
2924
2514
  console.error("Error fetching AI Search settings:", error);
2925
2515
  return this.getDefaultSettings();
@@ -4539,100 +4129,1922 @@ function renderMagicLinkEmail(magicLink, expiryMinutes) {
4539
4129
  }
4540
4130
  createMagicLinkAuthPlugin();
4541
4131
 
4542
- // src/assets/favicon.ts
4543
- var faviconSvg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
4544
- <svg
4545
- version="1.1"
4546
- id="Layer_1"
4547
- x="0px"
4548
- y="0px"
4549
- viewBox="380 1300 257.89001 278.8855"
4550
- xml:space="preserve"
4551
- width="257.89001"
4552
- height="278.8855"
4553
- xmlns="http://www.w3.org/2000/svg">
4554
- <g
4555
- id="g10"
4556
- transform="translate(-383.935,-60.555509)">
4557
- <g
4558
- id="g9">
4559
- <path
4560
- fill="#f1f2f2"
4561
- 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"
4562
- id="path2" />
4563
- <path
4564
- fill="#34d399"
4565
- 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"
4566
- id="path9" />
4567
- </g>
4568
- </g>
4569
- </svg>`;
4132
+ // src/plugins/cache/services/cache-config.ts
4133
+ var CACHE_CONFIGS = {
4134
+ // Content (high read, low write)
4135
+ content: {
4136
+ ttl: 3600,
4137
+ // 1 hour
4138
+ kvEnabled: true,
4139
+ memoryEnabled: true,
4140
+ namespace: "content",
4141
+ invalidateOn: ["content.update", "content.delete", "content.publish"],
4142
+ version: "v1"
4143
+ },
4144
+ // User data (medium read, medium write)
4145
+ user: {
4146
+ ttl: 900,
4147
+ // 15 minutes
4148
+ kvEnabled: true,
4149
+ memoryEnabled: true,
4150
+ namespace: "user",
4151
+ invalidateOn: ["user.update", "user.delete", "auth.login"],
4152
+ version: "v1"
4153
+ },
4154
+ // Configuration (high read, very low write)
4155
+ config: {
4156
+ ttl: 7200,
4157
+ // 2 hours
4158
+ kvEnabled: true,
4159
+ memoryEnabled: true,
4160
+ namespace: "config",
4161
+ invalidateOn: ["config.update", "plugin.activate", "plugin.deactivate"],
4162
+ version: "v1"
4163
+ },
4164
+ // Media metadata (high read, low write)
4165
+ media: {
4166
+ ttl: 3600,
4167
+ // 1 hour
4168
+ kvEnabled: true,
4169
+ memoryEnabled: true,
4170
+ namespace: "media",
4171
+ invalidateOn: ["media.upload", "media.delete", "media.update"],
4172
+ version: "v1"
4173
+ },
4174
+ // API responses (very high read, low write)
4175
+ api: {
4176
+ ttl: 300,
4177
+ // 5 minutes
4178
+ kvEnabled: true,
4179
+ memoryEnabled: true,
4180
+ namespace: "api",
4181
+ invalidateOn: ["content.update", "content.publish"],
4182
+ version: "v1"
4183
+ },
4184
+ // Session data (very high read, medium write)
4185
+ session: {
4186
+ ttl: 1800,
4187
+ // 30 minutes
4188
+ kvEnabled: false,
4189
+ // Only in-memory for sessions
4190
+ memoryEnabled: true,
4191
+ namespace: "session",
4192
+ invalidateOn: ["auth.logout"],
4193
+ version: "v1"
4194
+ },
4195
+ // Plugin data
4196
+ plugin: {
4197
+ ttl: 3600,
4198
+ // 1 hour
4199
+ kvEnabled: true,
4200
+ memoryEnabled: true,
4201
+ namespace: "plugin",
4202
+ invalidateOn: ["plugin.activate", "plugin.deactivate", "plugin.update"],
4203
+ version: "v1"
4204
+ },
4205
+ // Collections/schema
4206
+ collection: {
4207
+ ttl: 7200,
4208
+ // 2 hours
4209
+ kvEnabled: true,
4210
+ memoryEnabled: true,
4211
+ namespace: "collection",
4212
+ invalidateOn: ["collection.update", "collection.delete"],
4213
+ version: "v1"
4214
+ }
4215
+ };
4216
+ function getCacheConfig(namespace) {
4217
+ return CACHE_CONFIGS[namespace] || {
4218
+ ttl: 3600,
4219
+ kvEnabled: true,
4220
+ memoryEnabled: true,
4221
+ namespace,
4222
+ invalidateOn: [],
4223
+ version: "v1"
4224
+ };
4225
+ }
4226
+ function generateCacheKey(namespace, type, identifier, version) {
4227
+ const v = version || getCacheConfig(namespace).version || "v1";
4228
+ return `${namespace}:${type}:${identifier}:${v}`;
4229
+ }
4230
+ function parseCacheKey(key) {
4231
+ const parts = key.split(":");
4232
+ if (parts.length !== 4) {
4233
+ return null;
4234
+ }
4235
+ return {
4236
+ namespace: parts[0] || "",
4237
+ type: parts[1] || "",
4238
+ identifier: parts[2] || "",
4239
+ version: parts[3] || ""
4240
+ };
4241
+ }
4570
4242
 
4571
- // src/app.ts
4572
- function createSonicJSApp(config = {}) {
4573
- const app = new Hono();
4574
- const appVersion = config.version || getCoreVersion();
4575
- const appName = config.name || "SonicJS AI";
4576
- app.use("*", async (c, next) => {
4577
- c.set("appVersion", appVersion);
4578
- await next();
4579
- });
4580
- app.use("*", metricsMiddleware());
4581
- app.use("*", bootstrapMiddleware(config));
4582
- if (config.middleware?.beforeAuth) {
4583
- for (const middleware of config.middleware.beforeAuth) {
4584
- app.use("*", middleware);
4243
+ // src/plugins/cache/services/cache.ts
4244
+ var MemoryCache = class {
4245
+ cache = /* @__PURE__ */ new Map();
4246
+ maxSize = 50 * 1024 * 1024;
4247
+ // 50MB
4248
+ currentSize = 0;
4249
+ /**
4250
+ * Get item from memory cache
4251
+ */
4252
+ get(key) {
4253
+ const entry = this.cache.get(key);
4254
+ if (!entry) {
4255
+ return null;
4256
+ }
4257
+ if (Date.now() > entry.expiresAt) {
4258
+ this.delete(key);
4259
+ return null;
4585
4260
  }
4261
+ return entry.data;
4586
4262
  }
4587
- app.use("*", async (_c, next) => {
4588
- await next();
4589
- });
4590
- app.use("*", async (_c, next) => {
4591
- await next();
4592
- });
4593
- if (config.middleware?.afterAuth) {
4594
- for (const middleware of config.middleware.afterAuth) {
4595
- app.use("*", middleware);
4596
- }
4597
- }
4598
- app.route("/api", api_default);
4599
- app.route("/api/media", api_media_default);
4600
- app.route("/api/system", api_system_default);
4601
- app.route("/admin/api", admin_api_default);
4602
- app.route("/admin/dashboard", router);
4603
- app.route("/admin/collections", adminCollectionsRoutes);
4604
- app.route("/admin/settings", adminSettingsRoutes);
4605
- app.route("/admin/database-tools", createDatabaseToolsAdminRoutes());
4606
- app.route("/admin/seed-data", createSeedDataAdminRoutes());
4607
- app.route("/admin/content", admin_content_default);
4608
- app.route("/admin/media", adminMediaRoutes);
4609
- if (aiSearchPlugin.routes && aiSearchPlugin.routes.length > 0) {
4610
- for (const route of aiSearchPlugin.routes) {
4611
- app.route(route.path, route.handler);
4263
+ /**
4264
+ * Set item in memory cache
4265
+ */
4266
+ set(key, value, ttl, version = "v1") {
4267
+ const now = Date.now();
4268
+ const entry = {
4269
+ data: value,
4270
+ timestamp: now,
4271
+ expiresAt: now + ttl * 1e3,
4272
+ version
4273
+ };
4274
+ const entrySize = JSON.stringify(entry).length * 2;
4275
+ if (this.currentSize + entrySize > this.maxSize) {
4276
+ this.evictLRU(entrySize);
4612
4277
  }
4278
+ if (this.cache.has(key)) {
4279
+ this.delete(key);
4280
+ }
4281
+ this.cache.set(key, entry);
4282
+ this.currentSize += entrySize;
4613
4283
  }
4614
- app.route("/admin/plugins", adminPluginRoutes);
4615
- app.route("/admin/logs", adminLogsRoutes);
4616
- app.route("/admin", userRoutes);
4617
- app.route("/auth", auth_default);
4618
- app.route("/", test_cleanup_default);
4619
- if (emailPlugin.routes && emailPlugin.routes.length > 0) {
4620
- for (const route of emailPlugin.routes) {
4621
- app.route(route.path, route.handler);
4284
+ /**
4285
+ * Delete item from memory cache
4286
+ */
4287
+ delete(key) {
4288
+ const entry = this.cache.get(key);
4289
+ if (entry) {
4290
+ const entrySize = JSON.stringify(entry).length * 2;
4291
+ this.currentSize -= entrySize;
4292
+ return this.cache.delete(key);
4622
4293
  }
4294
+ return false;
4623
4295
  }
4624
- if (otpLoginPlugin.routes && otpLoginPlugin.routes.length > 0) {
4625
- for (const route of otpLoginPlugin.routes) {
4626
- app.route(route.path, route.handler);
4296
+ /**
4297
+ * Clear all items from memory cache
4298
+ */
4299
+ clear() {
4300
+ this.cache.clear();
4301
+ this.currentSize = 0;
4302
+ }
4303
+ /**
4304
+ * Get cache statistics
4305
+ */
4306
+ getStats() {
4307
+ return {
4308
+ size: this.currentSize,
4309
+ count: this.cache.size
4310
+ };
4311
+ }
4312
+ /**
4313
+ * Evict least recently used items to make space
4314
+ */
4315
+ evictLRU(neededSpace) {
4316
+ const entries = Array.from(this.cache.entries()).sort(
4317
+ (a, b) => a[1].timestamp - b[1].timestamp
4318
+ );
4319
+ let freedSpace = 0;
4320
+ for (const [key, entry] of entries) {
4321
+ if (freedSpace >= neededSpace) break;
4322
+ const entrySize = JSON.stringify(entry).length * 2;
4323
+ this.delete(key);
4324
+ freedSpace += entrySize;
4325
+ }
4326
+ }
4327
+ /**
4328
+ * Delete items matching a pattern
4329
+ */
4330
+ invalidatePattern(pattern) {
4331
+ const regex = new RegExp(
4332
+ "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
4333
+ );
4334
+ let count = 0;
4335
+ for (const key of this.cache.keys()) {
4336
+ if (regex.test(key)) {
4337
+ this.delete(key);
4338
+ count++;
4339
+ }
4340
+ }
4341
+ return count;
4342
+ }
4343
+ };
4344
+ var CacheService = class {
4345
+ memoryCache;
4346
+ config;
4347
+ stats;
4348
+ kvNamespace;
4349
+ constructor(config, kvNamespace) {
4350
+ this.memoryCache = new MemoryCache();
4351
+ this.config = config;
4352
+ this.kvNamespace = kvNamespace;
4353
+ this.stats = {
4354
+ memoryHits: 0,
4355
+ memoryMisses: 0,
4356
+ kvHits: 0,
4357
+ kvMisses: 0,
4358
+ dbHits: 0,
4359
+ totalRequests: 0,
4360
+ hitRate: 0,
4361
+ memorySize: 0,
4362
+ entryCount: 0
4363
+ };
4364
+ }
4365
+ /**
4366
+ * Get value from cache (tries memory first, then KV)
4367
+ */
4368
+ async get(key) {
4369
+ this.stats.totalRequests++;
4370
+ if (this.config.memoryEnabled) {
4371
+ const memoryValue = this.memoryCache.get(key);
4372
+ if (memoryValue !== null) {
4373
+ this.stats.memoryHits++;
4374
+ this.updateHitRate();
4375
+ return memoryValue;
4376
+ }
4377
+ this.stats.memoryMisses++;
4378
+ }
4379
+ if (this.config.kvEnabled && this.kvNamespace) {
4380
+ try {
4381
+ const kvValue = await this.kvNamespace.get(key, "json");
4382
+ if (kvValue !== null) {
4383
+ this.stats.kvHits++;
4384
+ if (this.config.memoryEnabled) {
4385
+ this.memoryCache.set(key, kvValue, this.config.ttl, this.config.version);
4386
+ }
4387
+ this.updateHitRate();
4388
+ return kvValue;
4389
+ }
4390
+ this.stats.kvMisses++;
4391
+ } catch (error) {
4392
+ console.error("KV cache read error:", error);
4393
+ this.stats.kvMisses++;
4394
+ }
4395
+ }
4396
+ this.updateHitRate();
4397
+ return null;
4398
+ }
4399
+ /**
4400
+ * Get value from cache with source information
4401
+ */
4402
+ async getWithSource(key) {
4403
+ this.stats.totalRequests++;
4404
+ if (this.config.memoryEnabled) {
4405
+ const memoryValue = this.memoryCache.get(key);
4406
+ if (memoryValue !== null) {
4407
+ this.stats.memoryHits++;
4408
+ this.updateHitRate();
4409
+ const entry = await this.getEntry(key);
4410
+ return {
4411
+ data: memoryValue,
4412
+ source: "memory",
4413
+ hit: true,
4414
+ timestamp: entry?.timestamp,
4415
+ ttl: entry?.ttl
4416
+ };
4417
+ }
4418
+ this.stats.memoryMisses++;
4419
+ }
4420
+ if (this.config.kvEnabled && this.kvNamespace) {
4421
+ try {
4422
+ const kvValue = await this.kvNamespace.get(key, "json");
4423
+ if (kvValue !== null) {
4424
+ this.stats.kvHits++;
4425
+ if (this.config.memoryEnabled) {
4426
+ this.memoryCache.set(key, kvValue, this.config.ttl, this.config.version);
4427
+ }
4428
+ this.updateHitRate();
4429
+ return {
4430
+ data: kvValue,
4431
+ source: "kv",
4432
+ hit: true
4433
+ };
4434
+ }
4435
+ this.stats.kvMisses++;
4436
+ } catch (error) {
4437
+ console.error("KV cache read error:", error);
4438
+ this.stats.kvMisses++;
4439
+ }
4440
+ }
4441
+ this.updateHitRate();
4442
+ return {
4443
+ data: null,
4444
+ source: "miss",
4445
+ hit: false
4446
+ };
4447
+ }
4448
+ /**
4449
+ * Set value in cache (stores in both memory and KV)
4450
+ */
4451
+ async set(key, value, customConfig) {
4452
+ const config = { ...this.config, ...customConfig };
4453
+ if (config.memoryEnabled) {
4454
+ this.memoryCache.set(key, value, config.ttl, config.version);
4455
+ }
4456
+ if (config.kvEnabled && this.kvNamespace) {
4457
+ try {
4458
+ await this.kvNamespace.put(key, JSON.stringify(value), {
4459
+ expirationTtl: config.ttl
4460
+ });
4461
+ } catch (error) {
4462
+ console.error("KV cache write error:", error);
4463
+ }
4464
+ }
4465
+ }
4466
+ /**
4467
+ * Delete value from cache (removes from both memory and KV)
4468
+ */
4469
+ async delete(key) {
4470
+ if (this.config.memoryEnabled) {
4471
+ this.memoryCache.delete(key);
4472
+ }
4473
+ if (this.config.kvEnabled && this.kvNamespace) {
4474
+ try {
4475
+ await this.kvNamespace.delete(key);
4476
+ } catch (error) {
4477
+ console.error("KV cache delete error:", error);
4478
+ }
4479
+ }
4480
+ }
4481
+ /**
4482
+ * Clear all cache entries for this namespace
4483
+ */
4484
+ async clear() {
4485
+ if (this.config.memoryEnabled) {
4486
+ this.memoryCache.clear();
4487
+ }
4488
+ this.stats = {
4489
+ memoryHits: 0,
4490
+ memoryMisses: 0,
4491
+ kvHits: 0,
4492
+ kvMisses: 0,
4493
+ dbHits: 0,
4494
+ totalRequests: 0,
4495
+ hitRate: 0,
4496
+ memorySize: 0,
4497
+ entryCount: 0
4498
+ };
4499
+ }
4500
+ /**
4501
+ * Invalidate cache entries matching a pattern
4502
+ */
4503
+ async invalidate(pattern) {
4504
+ let count = 0;
4505
+ if (this.config.memoryEnabled) {
4506
+ count += this.memoryCache.invalidatePattern(pattern);
4507
+ }
4508
+ if (this.config.kvEnabled && this.kvNamespace) {
4509
+ try {
4510
+ const regex = new RegExp(
4511
+ "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
4512
+ );
4513
+ const prefix = this.config.namespace + ":";
4514
+ const list = await this.kvNamespace.list({ prefix });
4515
+ for (const key of list.keys) {
4516
+ if (regex.test(key.name)) {
4517
+ await this.kvNamespace.delete(key.name);
4518
+ count++;
4519
+ }
4520
+ }
4521
+ } catch (error) {
4522
+ console.error("KV cache invalidation error:", error);
4523
+ }
4524
+ }
4525
+ return count;
4526
+ }
4527
+ /**
4528
+ * Invalidate cache entries matching a pattern (alias for invalidate)
4529
+ */
4530
+ async invalidatePattern(pattern) {
4531
+ return this.invalidate(pattern);
4532
+ }
4533
+ /**
4534
+ * Get cache statistics
4535
+ */
4536
+ getStats() {
4537
+ const memStats = this.memoryCache.getStats();
4538
+ return {
4539
+ ...this.stats,
4540
+ memorySize: memStats.size,
4541
+ entryCount: memStats.count
4542
+ };
4543
+ }
4544
+ /**
4545
+ * Update hit rate calculation
4546
+ */
4547
+ updateHitRate() {
4548
+ const totalHits = this.stats.memoryHits + this.stats.kvHits + this.stats.dbHits;
4549
+ this.stats.hitRate = this.stats.totalRequests > 0 ? totalHits / this.stats.totalRequests * 100 : 0;
4550
+ }
4551
+ /**
4552
+ * Generate a cache key using the configured namespace
4553
+ */
4554
+ generateKey(type, identifier) {
4555
+ return generateCacheKey(
4556
+ this.config.namespace,
4557
+ type,
4558
+ identifier,
4559
+ this.config.version
4560
+ );
4561
+ }
4562
+ /**
4563
+ * Warm cache with multiple entries
4564
+ */
4565
+ async warmCache(entries) {
4566
+ for (const entry of entries) {
4567
+ await this.set(entry.key, entry.value);
4568
+ }
4569
+ }
4570
+ /**
4571
+ * Check if a key exists in cache
4572
+ */
4573
+ async has(key) {
4574
+ const value = await this.get(key);
4575
+ return value !== null;
4576
+ }
4577
+ /**
4578
+ * Get multiple values at once
4579
+ */
4580
+ async getMany(keys) {
4581
+ const results = /* @__PURE__ */ new Map();
4582
+ for (const key of keys) {
4583
+ const value = await this.get(key);
4584
+ if (value !== null) {
4585
+ results.set(key, value);
4586
+ }
4587
+ }
4588
+ return results;
4589
+ }
4590
+ /**
4591
+ * Set multiple values at once
4592
+ */
4593
+ async setMany(entries, customConfig) {
4594
+ for (const entry of entries) {
4595
+ await this.set(entry.key, entry.value, customConfig);
4596
+ }
4597
+ }
4598
+ /**
4599
+ * Delete multiple keys at once
4600
+ */
4601
+ async deleteMany(keys) {
4602
+ for (const key of keys) {
4603
+ await this.delete(key);
4604
+ }
4605
+ }
4606
+ /**
4607
+ * Get or set pattern - fetch from cache or compute if not found
4608
+ */
4609
+ async getOrSet(key, fetcher, customConfig) {
4610
+ const cached = await this.get(key);
4611
+ if (cached !== null) {
4612
+ return cached;
4613
+ }
4614
+ const value = await fetcher();
4615
+ await this.set(key, value, customConfig);
4616
+ return value;
4617
+ }
4618
+ /**
4619
+ * List all cache keys with metadata
4620
+ */
4621
+ async listKeys() {
4622
+ const keys = [];
4623
+ if (this.config.memoryEnabled) {
4624
+ const cache = this.memoryCache.cache;
4625
+ for (const [key, entry] of cache.entries()) {
4626
+ const size = JSON.stringify(entry).length * 2;
4627
+ const age = Date.now() - entry.timestamp;
4628
+ keys.push({
4629
+ key,
4630
+ size,
4631
+ expiresAt: entry.expiresAt,
4632
+ age
4633
+ });
4634
+ }
4635
+ }
4636
+ return keys.sort((a, b) => a.age - b.age);
4637
+ }
4638
+ /**
4639
+ * Get cache entry with full metadata
4640
+ */
4641
+ async getEntry(key) {
4642
+ if (!this.config.memoryEnabled) {
4643
+ return null;
4644
+ }
4645
+ const cache = this.memoryCache.cache;
4646
+ const entry = cache.get(key);
4647
+ if (!entry) {
4648
+ return null;
4649
+ }
4650
+ if (Date.now() > entry.expiresAt) {
4651
+ await this.delete(key);
4652
+ return null;
4653
+ }
4654
+ const size = JSON.stringify(entry).length * 2;
4655
+ const ttl = Math.max(0, entry.expiresAt - Date.now()) / 1e3;
4656
+ return {
4657
+ data: entry.data,
4658
+ timestamp: entry.timestamp,
4659
+ expiresAt: entry.expiresAt,
4660
+ ttl,
4661
+ size
4662
+ };
4663
+ }
4664
+ };
4665
+ var cacheInstances = /* @__PURE__ */ new Map();
4666
+ var globalKVNamespace;
4667
+ function getCacheService(config, kvNamespace) {
4668
+ const key = config.namespace;
4669
+ if (!cacheInstances.has(key)) {
4670
+ const kv = globalKVNamespace;
4671
+ cacheInstances.set(key, new CacheService(config, kv));
4672
+ }
4673
+ return cacheInstances.get(key);
4674
+ }
4675
+ async function clearAllCaches() {
4676
+ for (const cache of cacheInstances.values()) {
4677
+ await cache.clear();
4678
+ }
4679
+ }
4680
+ function getAllCacheStats() {
4681
+ const stats = {};
4682
+ for (const [namespace, cache] of cacheInstances.entries()) {
4683
+ stats[namespace] = cache.getStats();
4684
+ }
4685
+ return stats;
4686
+ }
4687
+
4688
+ // src/plugins/cache/services/event-bus.ts
4689
+ var EventBus = class {
4690
+ subscriptions = /* @__PURE__ */ new Map();
4691
+ eventLog = [];
4692
+ maxLogSize = 100;
4693
+ /**
4694
+ * Subscribe to an event
4695
+ */
4696
+ on(event, handler) {
4697
+ if (!this.subscriptions.has(event)) {
4698
+ this.subscriptions.set(event, []);
4699
+ }
4700
+ this.subscriptions.get(event).push(handler);
4701
+ return () => {
4702
+ const handlers = this.subscriptions.get(event);
4703
+ if (handlers) {
4704
+ const index = handlers.indexOf(handler);
4705
+ if (index > -1) {
4706
+ handlers.splice(index, 1);
4707
+ }
4708
+ }
4709
+ };
4710
+ }
4711
+ /**
4712
+ * Emit an event to all subscribers
4713
+ */
4714
+ async emit(event, data) {
4715
+ this.logEvent(event, data);
4716
+ const handlers = this.subscriptions.get(event) || [];
4717
+ await Promise.all(
4718
+ handlers.map(async (handler) => {
4719
+ try {
4720
+ await handler(data);
4721
+ } catch (error) {
4722
+ console.error(`Error in event handler for ${event}:`, error);
4723
+ }
4724
+ })
4725
+ );
4726
+ const wildcardHandlers = this.subscriptions.get("*") || [];
4727
+ await Promise.all(
4728
+ wildcardHandlers.map(async (handler) => {
4729
+ try {
4730
+ await handler({ event, data });
4731
+ } catch (error) {
4732
+ console.error(`Error in wildcard event handler for ${event}:`, error);
4733
+ }
4734
+ })
4735
+ );
4736
+ }
4737
+ /**
4738
+ * Remove all subscribers for an event
4739
+ */
4740
+ off(event) {
4741
+ this.subscriptions.delete(event);
4742
+ }
4743
+ /**
4744
+ * Get all registered events
4745
+ */
4746
+ getEvents() {
4747
+ return Array.from(this.subscriptions.keys());
4748
+ }
4749
+ /**
4750
+ * Get subscriber count for an event
4751
+ */
4752
+ getSubscriberCount(event) {
4753
+ return this.subscriptions.get(event)?.length || 0;
4754
+ }
4755
+ /**
4756
+ * Log an event for debugging
4757
+ */
4758
+ logEvent(event, data) {
4759
+ this.eventLog.push({
4760
+ event,
4761
+ timestamp: Date.now(),
4762
+ data
4763
+ });
4764
+ if (this.eventLog.length > this.maxLogSize) {
4765
+ this.eventLog.shift();
4766
+ }
4767
+ }
4768
+ /**
4769
+ * Get recent event log
4770
+ */
4771
+ getEventLog(limit = 50) {
4772
+ return this.eventLog.slice(-limit);
4773
+ }
4774
+ /**
4775
+ * Clear event log
4776
+ */
4777
+ clearEventLog() {
4778
+ this.eventLog = [];
4779
+ }
4780
+ /**
4781
+ * Get statistics
4782
+ */
4783
+ getStats() {
4784
+ const eventCounts = {};
4785
+ for (const log of this.eventLog) {
4786
+ eventCounts[log.event] = (eventCounts[log.event] || 0) + 1;
4787
+ }
4788
+ return {
4789
+ totalEvents: this.eventLog.length,
4790
+ totalSubscriptions: this.subscriptions.size,
4791
+ eventCounts
4792
+ };
4793
+ }
4794
+ };
4795
+ var globalEventBus = null;
4796
+ function getEventBus() {
4797
+ if (!globalEventBus) {
4798
+ globalEventBus = new EventBus();
4799
+ }
4800
+ return globalEventBus;
4801
+ }
4802
+ function onEvent(event, handler) {
4803
+ const bus = getEventBus();
4804
+ return bus.on(event, handler);
4805
+ }
4806
+
4807
+ // src/plugins/cache/services/cache-invalidation.ts
4808
+ function setupCacheInvalidation() {
4809
+ getEventBus();
4810
+ setupContentInvalidation();
4811
+ setupUserInvalidation();
4812
+ setupConfigInvalidation();
4813
+ setupMediaInvalidation();
4814
+ setupAPIInvalidation();
4815
+ setupCollectionInvalidation();
4816
+ console.log("Cache invalidation listeners registered");
4817
+ }
4818
+ function setupContentInvalidation() {
4819
+ const config = CACHE_CONFIGS.content;
4820
+ if (!config) return;
4821
+ const contentCache = getCacheService(config);
4822
+ onEvent("content.create", async (_data) => {
4823
+ await contentCache.invalidate("content:*");
4824
+ console.log("Cache invalidated: content.create");
4825
+ });
4826
+ onEvent("content.update", async (data) => {
4827
+ if (data?.id) {
4828
+ await contentCache.delete(contentCache.generateKey("item", data.id));
4829
+ }
4830
+ await contentCache.invalidate("content:list:*");
4831
+ console.log("Cache invalidated: content.update", data?.id);
4832
+ });
4833
+ onEvent("content.delete", async (data) => {
4834
+ if (data?.id) {
4835
+ await contentCache.delete(contentCache.generateKey("item", data.id));
4836
+ }
4837
+ await contentCache.invalidate("content:*");
4838
+ console.log("Cache invalidated: content.delete", data?.id);
4839
+ });
4840
+ onEvent("content.publish", async (_data) => {
4841
+ await contentCache.invalidate("content:*");
4842
+ console.log("Cache invalidated: content.publish");
4843
+ });
4844
+ }
4845
+ function setupUserInvalidation() {
4846
+ const config = CACHE_CONFIGS.user;
4847
+ if (!config) return;
4848
+ const userCache = getCacheService(config);
4849
+ onEvent("user.update", async (data) => {
4850
+ if (data?.id) {
4851
+ await userCache.delete(userCache.generateKey("id", data.id));
4852
+ }
4853
+ if (data?.email) {
4854
+ await userCache.delete(userCache.generateKey("email", data.email));
4855
+ }
4856
+ console.log("Cache invalidated: user.update", data?.id);
4857
+ });
4858
+ onEvent("user.delete", async (data) => {
4859
+ if (data?.id) {
4860
+ await userCache.delete(userCache.generateKey("id", data.id));
4861
+ }
4862
+ if (data?.email) {
4863
+ await userCache.delete(userCache.generateKey("email", data.email));
4864
+ }
4865
+ console.log("Cache invalidated: user.delete", data?.id);
4866
+ });
4867
+ onEvent("auth.login", async (data) => {
4868
+ if (data?.userId) {
4869
+ await userCache.delete(userCache.generateKey("id", data.userId));
4870
+ }
4871
+ console.log("Cache invalidated: auth.login", data?.userId);
4872
+ });
4873
+ onEvent("auth.logout", async (data) => {
4874
+ const sessionConfig = CACHE_CONFIGS.session;
4875
+ if (sessionConfig) {
4876
+ const sessionCache = getCacheService(sessionConfig);
4877
+ if (data?.sessionId) {
4878
+ await sessionCache.delete(sessionCache.generateKey("session", data.sessionId));
4879
+ }
4880
+ }
4881
+ console.log("Cache invalidated: auth.logout");
4882
+ });
4883
+ }
4884
+ function setupConfigInvalidation() {
4885
+ const configConfig = CACHE_CONFIGS.config;
4886
+ if (!configConfig) return;
4887
+ const configCache = getCacheService(configConfig);
4888
+ onEvent("config.update", async (_data) => {
4889
+ await configCache.invalidate("config:*");
4890
+ console.log("Cache invalidated: config.update");
4891
+ });
4892
+ onEvent("plugin.activate", async (data) => {
4893
+ await configCache.invalidate("config:*");
4894
+ const pluginConfig = CACHE_CONFIGS.plugin;
4895
+ if (pluginConfig) {
4896
+ const pluginCache = getCacheService(pluginConfig);
4897
+ await pluginCache.invalidate("plugin:*");
4898
+ }
4899
+ console.log("Cache invalidated: plugin.activate", data?.pluginId);
4900
+ });
4901
+ onEvent("plugin.deactivate", async (data) => {
4902
+ await configCache.invalidate("config:*");
4903
+ const pluginConfig = CACHE_CONFIGS.plugin;
4904
+ if (pluginConfig) {
4905
+ const pluginCache = getCacheService(pluginConfig);
4906
+ await pluginCache.invalidate("plugin:*");
4907
+ }
4908
+ console.log("Cache invalidated: plugin.deactivate", data?.pluginId);
4909
+ });
4910
+ onEvent("plugin.update", async (data) => {
4911
+ const pluginConfig = CACHE_CONFIGS.plugin;
4912
+ if (!pluginConfig) return;
4913
+ const pluginCache = getCacheService(pluginConfig);
4914
+ await pluginCache.invalidate("plugin:*");
4915
+ console.log("Cache invalidated: plugin.update", data?.pluginId);
4916
+ });
4917
+ }
4918
+ function setupMediaInvalidation() {
4919
+ const config = CACHE_CONFIGS.media;
4920
+ if (!config) return;
4921
+ const mediaCache = getCacheService(config);
4922
+ onEvent("media.upload", async (_data) => {
4923
+ await mediaCache.invalidate("media:*");
4924
+ console.log("Cache invalidated: media.upload");
4925
+ });
4926
+ onEvent("media.delete", async (data) => {
4927
+ if (data?.id) {
4928
+ await mediaCache.delete(mediaCache.generateKey("item", data.id));
4929
+ }
4930
+ await mediaCache.invalidate("media:list:*");
4931
+ console.log("Cache invalidated: media.delete", data?.id);
4932
+ });
4933
+ onEvent("media.update", async (data) => {
4934
+ if (data?.id) {
4935
+ await mediaCache.delete(mediaCache.generateKey("item", data.id));
4936
+ }
4937
+ await mediaCache.invalidate("media:list:*");
4938
+ console.log("Cache invalidated: media.update", data?.id);
4939
+ });
4940
+ }
4941
+ function setupAPIInvalidation() {
4942
+ const config = CACHE_CONFIGS.api;
4943
+ if (!config) return;
4944
+ const apiCache = getCacheService(config);
4945
+ onEvent("content.update", async (_data) => {
4946
+ await apiCache.invalidate("api:*");
4947
+ console.log("Cache invalidated: api (content.update)");
4948
+ });
4949
+ onEvent("content.publish", async (_data) => {
4950
+ await apiCache.invalidate("api:*");
4951
+ console.log("Cache invalidated: api (content.publish)");
4952
+ });
4953
+ onEvent("content.create", async (_data) => {
4954
+ await apiCache.invalidate("api:*");
4955
+ console.log("Cache invalidated: api (content.create)");
4956
+ });
4957
+ onEvent("content.delete", async (_data) => {
4958
+ await apiCache.invalidate("api:*");
4959
+ console.log("Cache invalidated: api (content.delete)");
4960
+ });
4961
+ onEvent("collection.update", async (_data) => {
4962
+ await apiCache.invalidate("api:*");
4963
+ console.log("Cache invalidated: api (collection.update)");
4964
+ });
4965
+ }
4966
+ function setupCollectionInvalidation() {
4967
+ const config = CACHE_CONFIGS.collection;
4968
+ if (!config) return;
4969
+ const collectionCache = getCacheService(config);
4970
+ onEvent("collection.create", async (_data) => {
4971
+ await collectionCache.invalidate("collection:*");
4972
+ console.log("Cache invalidated: collection.create");
4973
+ });
4974
+ onEvent("collection.update", async (data) => {
4975
+ if (data?.id) {
4976
+ await collectionCache.delete(collectionCache.generateKey("item", data.id));
4977
+ }
4978
+ await collectionCache.invalidate("collection:*");
4979
+ console.log("Cache invalidated: collection.update", data?.id);
4980
+ });
4981
+ onEvent("collection.delete", async (data) => {
4982
+ await collectionCache.invalidate("collection:*");
4983
+ console.log("Cache invalidated: collection.delete", data?.id);
4984
+ });
4985
+ }
4986
+ function getCacheInvalidationStats() {
4987
+ const eventBus = getEventBus();
4988
+ return eventBus.getStats();
4989
+ }
4990
+ function getRecentInvalidations(limit = 50) {
4991
+ const eventBus = getEventBus();
4992
+ return eventBus.getEventLog(limit);
4993
+ }
4994
+
4995
+ // src/plugins/cache/services/cache-warming.ts
4996
+ async function warmCommonCaches(db) {
4997
+ let totalWarmed = 0;
4998
+ let totalErrors = 0;
4999
+ const details = [];
5000
+ try {
5001
+ const collectionCount = await warmCollections(db);
5002
+ totalWarmed += collectionCount;
5003
+ details.push({ namespace: "collection", count: collectionCount });
5004
+ const contentCount = await warmRecentContent(db);
5005
+ totalWarmed += contentCount;
5006
+ details.push({ namespace: "content", count: contentCount });
5007
+ const mediaCount = await warmRecentMedia(db);
5008
+ totalWarmed += mediaCount;
5009
+ details.push({ namespace: "media", count: mediaCount });
5010
+ } catch (error) {
5011
+ console.error("Error warming caches:", error);
5012
+ totalErrors++;
5013
+ }
5014
+ return {
5015
+ warmed: totalWarmed,
5016
+ errors: totalErrors,
5017
+ details
5018
+ };
5019
+ }
5020
+ async function warmCollections(db) {
5021
+ const config = CACHE_CONFIGS.collection;
5022
+ if (!config) return 0;
5023
+ const collectionCache = getCacheService(config);
5024
+ let count = 0;
5025
+ try {
5026
+ const stmt = db.prepare("SELECT * FROM collections WHERE is_active = 1");
5027
+ const { results } = await stmt.all();
5028
+ for (const collection of results) {
5029
+ const key = collectionCache.generateKey("item", collection.id);
5030
+ await collectionCache.set(key, collection);
5031
+ count++;
5032
+ }
5033
+ const listKey = collectionCache.generateKey("list", "all");
5034
+ await collectionCache.set(listKey, results);
5035
+ count++;
5036
+ } catch (error) {
5037
+ console.error("Error warming collections cache:", error);
5038
+ }
5039
+ return count;
5040
+ }
5041
+ async function warmRecentContent(db, limit = 50) {
5042
+ const config = CACHE_CONFIGS.content;
5043
+ if (!config) return 0;
5044
+ const contentCache = getCacheService(config);
5045
+ let count = 0;
5046
+ try {
5047
+ const stmt = db.prepare(`SELECT * FROM content ORDER BY created_at DESC LIMIT ${limit}`);
5048
+ const { results } = await stmt.all();
5049
+ for (const content2 of results) {
5050
+ const key = contentCache.generateKey("item", content2.id);
5051
+ await contentCache.set(key, content2);
5052
+ count++;
5053
+ }
5054
+ const listKey = contentCache.generateKey("list", "recent");
5055
+ await contentCache.set(listKey, results);
5056
+ count++;
5057
+ } catch (error) {
5058
+ console.error("Error warming content cache:", error);
5059
+ }
5060
+ return count;
5061
+ }
5062
+ async function warmRecentMedia(db, limit = 50) {
5063
+ const config = CACHE_CONFIGS.media;
5064
+ if (!config) return 0;
5065
+ const mediaCache = getCacheService(config);
5066
+ let count = 0;
5067
+ try {
5068
+ const stmt = db.prepare(`SELECT * FROM media WHERE deleted_at IS NULL ORDER BY uploaded_at DESC LIMIT ${limit}`);
5069
+ const { results } = await stmt.all();
5070
+ for (const media2 of results) {
5071
+ const key = mediaCache.generateKey("item", media2.id);
5072
+ await mediaCache.set(key, media2);
5073
+ count++;
5074
+ }
5075
+ const listKey = mediaCache.generateKey("list", "recent");
5076
+ await mediaCache.set(listKey, results);
5077
+ count++;
5078
+ } catch (error) {
5079
+ console.error("Error warming media cache:", error);
5080
+ }
5081
+ return count;
5082
+ }
5083
+ async function warmNamespace(namespace, entries) {
5084
+ const config = CACHE_CONFIGS[namespace];
5085
+ if (!config) {
5086
+ throw new Error(`Unknown namespace: ${namespace}`);
5087
+ }
5088
+ const cache = getCacheService(config);
5089
+ await cache.setMany(entries);
5090
+ return entries.length;
5091
+ }
5092
+
5093
+ // src/templates/pages/admin-cache.template.ts
5094
+ init_admin_layout_catalyst_template();
5095
+ function renderCacheDashboard(data) {
5096
+ const pageContent = `
5097
+ <div class="space-y-6">
5098
+ <!-- Header -->
5099
+ <div class="flex items-center justify-between">
5100
+ <div>
5101
+ <h1 class="text-2xl font-semibold text-zinc-950 dark:text-white">Cache System</h1>
5102
+ <p class="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
5103
+ Monitor and manage cache performance across all namespaces
5104
+ </p>
5105
+ </div>
5106
+ <div class="flex gap-3">
5107
+ <button
5108
+ onclick="refreshStats()"
5109
+ 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"
5110
+ >
5111
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5112
+ <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"/>
5113
+ </svg>
5114
+ Refresh
5115
+ </button>
5116
+ <button
5117
+ onclick="clearAllCaches()"
5118
+ 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"
5119
+ >
5120
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5121
+ <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"/>
5122
+ </svg>
5123
+ Clear All
5124
+ </button>
5125
+ </div>
5126
+ </div>
5127
+
5128
+ <!-- Overall Stats Cards -->
5129
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
5130
+ ${renderStatCard("Total Requests", data.totals.requests.toLocaleString(), "lime", `
5131
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5132
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
5133
+ </svg>
5134
+ `)}
5135
+
5136
+ ${renderStatCard("Hit Rate", data.totals.hitRate + "%", "blue", `
5137
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5138
+ <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"/>
5139
+ </svg>
5140
+ `, parseFloat(data.totals.hitRate) > 70 ? "lime" : parseFloat(data.totals.hitRate) > 40 ? "amber" : "red")}
5141
+
5142
+ ${renderStatCard("Memory Usage", formatBytes(data.totals.memorySize), "purple", `
5143
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5144
+ <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"/>
5145
+ </svg>
5146
+ `)}
5147
+
5148
+ ${renderStatCard("Cached Entries", data.totals.entryCount.toLocaleString(), "sky", `
5149
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5150
+ <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"/>
5151
+ </svg>
5152
+ `)}
5153
+ </div>
5154
+
5155
+ <!-- Namespace Statistics -->
5156
+ <div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
5157
+ <div class="px-6 py-4 border-b border-zinc-950/5 dark:border-white/10">
5158
+ <h2 class="text-lg font-semibold text-zinc-950 dark:text-white">Cache Namespaces</h2>
5159
+ </div>
5160
+ <div class="overflow-x-auto">
5161
+ <table class="min-w-full divide-y divide-zinc-950/5 dark:divide-white/10">
5162
+ <thead class="bg-zinc-50 dark:bg-zinc-800/50">
5163
+ <tr>
5164
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5165
+ Namespace
5166
+ </th>
5167
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5168
+ Requests
5169
+ </th>
5170
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5171
+ Hit Rate
5172
+ </th>
5173
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5174
+ Memory Hits
5175
+ </th>
5176
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5177
+ KV Hits
5178
+ </th>
5179
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5180
+ Entries
5181
+ </th>
5182
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5183
+ Size
5184
+ </th>
5185
+ <th class="px-6 py-3 text-right text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5186
+ Actions
5187
+ </th>
5188
+ </tr>
5189
+ </thead>
5190
+ <tbody class="divide-y divide-zinc-950/5 dark:divide-white/10">
5191
+ ${data.namespaces.map((namespace) => {
5192
+ const stat = data.stats[namespace];
5193
+ if (!stat) return "";
5194
+ return renderNamespaceRow(namespace, stat);
5195
+ }).join("")}
5196
+ </tbody>
5197
+ </table>
5198
+ </div>
5199
+ </div>
5200
+
5201
+ <!-- Performance Chart Placeholder -->
5202
+ <div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
5203
+ <div class="px-6 py-4 border-b border-zinc-950/5 dark:border-white/10">
5204
+ <h2 class="text-lg font-semibold text-zinc-950 dark:text-white">Performance Overview</h2>
5205
+ </div>
5206
+ <div class="p-6">
5207
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
5208
+ ${renderPerformanceMetric("Memory Cache", data.totals.hits, data.totals.misses)}
5209
+ ${renderHealthStatus(parseFloat(data.totals.hitRate))}
5210
+ </div>
5211
+ </div>
5212
+ </div>
5213
+ </div>
5214
+
5215
+ <script>
5216
+ async function refreshStats() {
5217
+ window.location.reload()
5218
+ }
5219
+
5220
+ async function clearAllCaches() {
5221
+ showConfirmDialog('clear-all-cache-confirm')
5222
+ }
5223
+
5224
+ async function performClearAllCaches() {
5225
+ try {
5226
+ const response = await fetch('/admin/cache/clear', {
5227
+ method: 'POST'
5228
+ })
5229
+
5230
+ const result = await response.json()
5231
+ if (result.success) {
5232
+ alert('All caches cleared successfully')
5233
+ window.location.reload()
5234
+ } else {
5235
+ alert('Error clearing caches: ' + result.error)
5236
+ }
5237
+ } catch (error) {
5238
+ alert('Error clearing caches: ' + error.message)
5239
+ }
5240
+ }
5241
+
5242
+ let namespaceToDelete = null
5243
+
5244
+ async function clearNamespaceCache(namespace) {
5245
+ namespaceToDelete = namespace
5246
+ showConfirmDialog('clear-namespace-cache-confirm')
5247
+ }
5248
+
5249
+ async function performClearNamespaceCache() {
5250
+ if (!namespaceToDelete) return
5251
+
5252
+ try {
5253
+ const response = await fetch(\`/admin/cache/clear/\${namespaceToDelete}\`, {
5254
+ method: 'POST'
5255
+ })
5256
+
5257
+ const result = await response.json()
5258
+ if (result.success) {
5259
+ alert('Cache cleared successfully')
5260
+ window.location.reload()
5261
+ } else {
5262
+ alert('Error clearing cache: ' + result.error)
5263
+ }
5264
+ } catch (error) {
5265
+ alert('Error clearing cache: ' + error.message)
5266
+ } finally {
5267
+ namespaceToDelete = null
5268
+ }
5269
+ }
5270
+ </script>
5271
+
5272
+ <!-- Confirmation Dialogs -->
5273
+ ${renderConfirmationDialog({
5274
+ id: "clear-all-cache-confirm",
5275
+ title: "Clear All Cache",
5276
+ message: "Are you sure you want to clear all cache entries? This cannot be undone.",
5277
+ confirmText: "Clear All",
5278
+ cancelText: "Cancel",
5279
+ iconColor: "yellow",
5280
+ confirmClass: "bg-yellow-500 hover:bg-yellow-400",
5281
+ onConfirm: "performClearAllCaches()"
5282
+ })}
5283
+
5284
+ ${renderConfirmationDialog({
5285
+ id: "clear-namespace-cache-confirm",
5286
+ title: "Clear Namespace Cache",
5287
+ message: "Clear cache for this namespace?",
5288
+ confirmText: "Clear",
5289
+ cancelText: "Cancel",
5290
+ iconColor: "yellow",
5291
+ confirmClass: "bg-yellow-500 hover:bg-yellow-400",
5292
+ onConfirm: "performClearNamespaceCache()"
5293
+ })}
5294
+
5295
+ ${getConfirmationDialogScript()}
5296
+ `;
5297
+ const layoutData = {
5298
+ title: "Cache System",
5299
+ pageTitle: "Cache System",
5300
+ currentPath: "/admin/cache",
5301
+ user: data.user,
5302
+ version: data.version,
5303
+ content: pageContent
5304
+ };
5305
+ return renderAdminLayoutCatalyst(layoutData);
5306
+ }
5307
+ function renderStatCard(label, value, color, icon, colorOverride) {
5308
+ const finalColor = colorOverride || color;
5309
+ const colorClasses = {
5310
+ 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",
5311
+ 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",
5312
+ 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",
5313
+ 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",
5314
+ 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",
5315
+ 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"
5316
+ };
5317
+ return `
5318
+ <div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
5319
+ <div class="p-6">
5320
+ <div class="flex items-center justify-between">
5321
+ <div class="flex items-center gap-3">
5322
+ <div class="rounded-lg p-2 ring-1 ring-inset ${colorClasses[finalColor]}">
5323
+ ${icon}
5324
+ </div>
5325
+ <div>
5326
+ <p class="text-sm text-zinc-600 dark:text-zinc-400">${label}</p>
5327
+ <p class="mt-1 text-2xl font-semibold text-zinc-950 dark:text-white">${value}</p>
5328
+ </div>
5329
+ </div>
5330
+ </div>
5331
+ </div>
5332
+ </div>
5333
+ `;
5334
+ }
5335
+ function renderNamespaceRow(namespace, stat) {
5336
+ const hitRate = stat.hitRate.toFixed(1);
5337
+ 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";
5338
+ return `
5339
+ <tr class="hover:bg-zinc-50 dark:hover:bg-zinc-800/50">
5340
+ <td class="px-6 py-4 whitespace-nowrap">
5341
+ <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">
5342
+ ${namespace}
5343
+ </span>
5344
+ </td>
5345
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-900 dark:text-zinc-100">
5346
+ ${stat.totalRequests.toLocaleString()}
5347
+ </td>
5348
+ <td class="px-6 py-4 whitespace-nowrap">
5349
+ <span class="text-sm font-medium ${hitRateColor}">
5350
+ ${hitRate}%
5351
+ </span>
5352
+ </td>
5353
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
5354
+ ${stat.memoryHits.toLocaleString()}
5355
+ </td>
5356
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
5357
+ ${stat.kvHits.toLocaleString()}
5358
+ </td>
5359
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
5360
+ ${stat.entryCount.toLocaleString()}
5361
+ </td>
5362
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
5363
+ ${formatBytes(stat.memorySize)}
5364
+ </td>
5365
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm">
5366
+ <button
5367
+ onclick="clearNamespaceCache('${namespace}')"
5368
+ class="text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300"
5369
+ >
5370
+ Clear
5371
+ </button>
5372
+ </td>
5373
+ </tr>
5374
+ `;
5375
+ }
5376
+ function renderPerformanceMetric(label, hits, misses) {
5377
+ const total = hits + misses;
5378
+ const hitPercentage = total > 0 ? hits / total * 100 : 0;
5379
+ return `
5380
+ <div>
5381
+ <h3 class="text-sm font-medium text-zinc-900 dark:text-zinc-100 mb-3">${label}</h3>
5382
+ <div class="space-y-2">
5383
+ <div class="flex items-center justify-between text-sm">
5384
+ <span class="text-zinc-600 dark:text-zinc-400">Hits</span>
5385
+ <span class="font-medium text-zinc-900 dark:text-zinc-100">${hits.toLocaleString()}</span>
5386
+ </div>
5387
+ <div class="flex items-center justify-between text-sm">
5388
+ <span class="text-zinc-600 dark:text-zinc-400">Misses</span>
5389
+ <span class="font-medium text-zinc-900 dark:text-zinc-100">${misses.toLocaleString()}</span>
5390
+ </div>
5391
+ <div class="mt-3">
5392
+ <div class="flex items-center justify-between text-sm mb-1">
5393
+ <span class="text-zinc-600 dark:text-zinc-400">Hit Rate</span>
5394
+ <span class="font-medium text-zinc-900 dark:text-zinc-100">${hitPercentage.toFixed(1)}%</span>
5395
+ </div>
5396
+ <div class="h-2 bg-zinc-200 dark:bg-zinc-700 rounded-full overflow-hidden">
5397
+ <div class="h-full bg-lime-500 dark:bg-lime-400" style="width: ${hitPercentage}%"></div>
5398
+ </div>
5399
+ </div>
5400
+ </div>
5401
+ </div>
5402
+ `;
5403
+ }
5404
+ function renderHealthStatus(hitRate) {
5405
+ const status = hitRate > 70 ? "healthy" : hitRate > 40 ? "warning" : "critical";
5406
+ const statusConfig = {
5407
+ healthy: {
5408
+ label: "Healthy",
5409
+ color: "lime",
5410
+ icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5411
+ <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"/>
5412
+ </svg>`
5413
+ },
5414
+ warning: {
5415
+ label: "Needs Attention",
5416
+ color: "amber",
5417
+ icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5418
+ <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"/>
5419
+ </svg>`
5420
+ },
5421
+ critical: {
5422
+ label: "Critical",
5423
+ color: "red",
5424
+ icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5425
+ <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"/>
5426
+ </svg>`
5427
+ }
5428
+ };
5429
+ const config = statusConfig[status];
5430
+ const colorClasses = {
5431
+ 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",
5432
+ 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",
5433
+ 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"
5434
+ };
5435
+ return `
5436
+ <div>
5437
+ <h3 class="text-sm font-medium text-zinc-900 dark:text-zinc-100 mb-3">System Health</h3>
5438
+ <div class="flex items-center gap-3 p-4 rounded-lg ring-1 ring-inset ${colorClasses[config.color]}">
5439
+ ${config.icon}
5440
+ <div>
5441
+ <p class="text-sm font-medium">${config.label}</p>
5442
+ <p class="text-xs mt-0.5 opacity-80">
5443
+ ${status === "healthy" ? "Cache is performing well" : status === "warning" ? "Consider increasing cache TTL or capacity" : "Cache hit rate is too low"}
5444
+ </p>
5445
+ </div>
5446
+ </div>
5447
+ </div>
5448
+ `;
5449
+ }
5450
+ function formatBytes(bytes) {
5451
+ if (bytes === 0) return "0 B";
5452
+ const k = 1024;
5453
+ const sizes = ["B", "KB", "MB", "GB"];
5454
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
5455
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
5456
+ }
5457
+
5458
+ // src/plugins/cache/routes.ts
5459
+ var app = new Hono();
5460
+ app.get("/", async (c) => {
5461
+ const stats = getAllCacheStats();
5462
+ const user = c.get("user");
5463
+ let totalHits = 0;
5464
+ let totalMisses = 0;
5465
+ let totalSize = 0;
5466
+ let totalEntries = 0;
5467
+ Object.values(stats).forEach((stat) => {
5468
+ totalHits += stat.memoryHits + stat.kvHits;
5469
+ totalMisses += stat.memoryMisses + stat.kvMisses;
5470
+ totalSize += stat.memorySize;
5471
+ totalEntries += stat.entryCount;
5472
+ });
5473
+ const totalRequests = totalHits + totalMisses;
5474
+ const overallHitRate = totalRequests > 0 ? totalHits / totalRequests * 100 : 0;
5475
+ const dashboardData = {
5476
+ stats,
5477
+ totals: {
5478
+ hits: totalHits,
5479
+ misses: totalMisses,
5480
+ requests: totalRequests,
5481
+ hitRate: overallHitRate.toFixed(2),
5482
+ memorySize: totalSize,
5483
+ entryCount: totalEntries
5484
+ },
5485
+ namespaces: Object.keys(stats),
5486
+ user: user ? {
5487
+ name: user.email,
5488
+ email: user.email,
5489
+ role: user.role
5490
+ } : void 0,
5491
+ version: c.get("appVersion")
5492
+ };
5493
+ return c.html(renderCacheDashboard(dashboardData));
5494
+ });
5495
+ app.get("/stats", async (c) => {
5496
+ const stats = getAllCacheStats();
5497
+ return c.json({
5498
+ success: true,
5499
+ data: stats,
5500
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5501
+ });
5502
+ });
5503
+ app.get("/stats/:namespace", async (c) => {
5504
+ const namespace = c.req.param("namespace");
5505
+ const config = CACHE_CONFIGS[namespace];
5506
+ if (!config) {
5507
+ return c.json({
5508
+ success: false,
5509
+ error: `Unknown namespace: ${namespace}`
5510
+ }, 404);
5511
+ }
5512
+ const cache = getCacheService(config);
5513
+ const stats = cache.getStats();
5514
+ return c.json({
5515
+ success: true,
5516
+ data: {
5517
+ namespace,
5518
+ config,
5519
+ stats
5520
+ },
5521
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5522
+ });
5523
+ });
5524
+ app.post("/clear", async (c) => {
5525
+ await clearAllCaches();
5526
+ return c.json({
5527
+ success: true,
5528
+ message: "All cache entries cleared",
5529
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5530
+ });
5531
+ });
5532
+ app.post("/clear/:namespace", async (c) => {
5533
+ const namespace = c.req.param("namespace");
5534
+ const config = CACHE_CONFIGS[namespace];
5535
+ if (!config) {
5536
+ return c.json({
5537
+ success: false,
5538
+ error: `Unknown namespace: ${namespace}`
5539
+ }, 404);
5540
+ }
5541
+ const cache = getCacheService(config);
5542
+ await cache.clear();
5543
+ return c.json({
5544
+ success: true,
5545
+ message: `Cache cleared for namespace: ${namespace}`,
5546
+ namespace,
5547
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5548
+ });
5549
+ });
5550
+ app.post("/invalidate", async (c) => {
5551
+ const body = await c.req.json();
5552
+ const { pattern, namespace } = body;
5553
+ if (!pattern) {
5554
+ return c.json({
5555
+ success: false,
5556
+ error: "Pattern is required"
5557
+ }, 400);
5558
+ }
5559
+ let totalInvalidated = 0;
5560
+ if (namespace) {
5561
+ const config = CACHE_CONFIGS[namespace];
5562
+ if (!config) {
5563
+ return c.json({
5564
+ success: false,
5565
+ error: `Unknown namespace: ${namespace}`
5566
+ }, 404);
5567
+ }
5568
+ const cache = getCacheService(config);
5569
+ totalInvalidated = await cache.invalidate(pattern);
5570
+ } else {
5571
+ for (const config of Object.values(CACHE_CONFIGS)) {
5572
+ const cache = getCacheService(config);
5573
+ totalInvalidated += await cache.invalidate(pattern);
5574
+ }
5575
+ }
5576
+ return c.json({
5577
+ success: true,
5578
+ invalidated: totalInvalidated,
5579
+ pattern,
5580
+ namespace: namespace || "all",
5581
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5582
+ });
5583
+ });
5584
+ app.get("/health", async (c) => {
5585
+ const stats = getAllCacheStats();
5586
+ const namespaces = Object.entries(stats);
5587
+ const healthChecks = namespaces.map(([name, stat]) => {
5588
+ const hitRate = stat.hitRate;
5589
+ const memoryUsage = stat.memorySize / (50 * 1024 * 1024);
5590
+ return {
5591
+ namespace: name,
5592
+ status: hitRate > 70 ? "healthy" : hitRate > 40 ? "warning" : "unhealthy",
5593
+ hitRate,
5594
+ memoryUsage: (memoryUsage * 100).toFixed(2) + "%",
5595
+ entryCount: stat.entryCount
5596
+ };
5597
+ });
5598
+ const overallStatus = healthChecks.every((h) => h.status === "healthy") ? "healthy" : healthChecks.some((h) => h.status === "unhealthy") ? "unhealthy" : "warning";
5599
+ return c.json({
5600
+ success: true,
5601
+ data: {
5602
+ status: overallStatus,
5603
+ namespaces: healthChecks,
5604
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5605
+ }
5606
+ });
5607
+ });
5608
+ app.get("/browser", async (c) => {
5609
+ const namespace = c.req.query("namespace") || "all";
5610
+ const search = c.req.query("search") || "";
5611
+ const sortBy = c.req.query("sort") || "age";
5612
+ const limit = parseInt(c.req.query("limit") || "100");
5613
+ const entries = [];
5614
+ const namespaces = namespace === "all" ? Object.keys(CACHE_CONFIGS) : [namespace];
5615
+ for (const ns of namespaces) {
5616
+ const config = CACHE_CONFIGS[ns];
5617
+ if (!config) continue;
5618
+ const cache = getCacheService(config);
5619
+ const keys = await cache.listKeys();
5620
+ for (const keyInfo of keys) {
5621
+ if (search && !keyInfo.key.toLowerCase().includes(search.toLowerCase())) {
5622
+ continue;
5623
+ }
5624
+ const parsed = parseCacheKey(keyInfo.key);
5625
+ const ttl = Math.max(0, keyInfo.expiresAt - Date.now()) / 1e3;
5626
+ entries.push({
5627
+ namespace: ns,
5628
+ key: keyInfo.key,
5629
+ size: keyInfo.size,
5630
+ age: keyInfo.age,
5631
+ ttl,
5632
+ expiresAt: keyInfo.expiresAt,
5633
+ parsed
5634
+ });
5635
+ }
5636
+ }
5637
+ if (sortBy === "size") {
5638
+ entries.sort((a, b) => b.size - a.size);
5639
+ } else if (sortBy === "age") {
5640
+ entries.sort((a, b) => a.age - b.age);
5641
+ } else if (sortBy === "key") {
5642
+ entries.sort((a, b) => a.key.localeCompare(b.key));
5643
+ }
5644
+ const limitedEntries = entries.slice(0, limit);
5645
+ return c.json({
5646
+ success: true,
5647
+ data: {
5648
+ entries: limitedEntries,
5649
+ total: entries.length,
5650
+ showing: limitedEntries.length,
5651
+ namespace,
5652
+ search,
5653
+ sortBy
5654
+ },
5655
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5656
+ });
5657
+ });
5658
+ app.get("/browser/:namespace/:key", async (c) => {
5659
+ const namespace = c.req.param("namespace");
5660
+ const key = decodeURIComponent(c.req.param("key"));
5661
+ const config = CACHE_CONFIGS[namespace];
5662
+ if (!config) {
5663
+ return c.json({
5664
+ success: false,
5665
+ error: `Unknown namespace: ${namespace}`
5666
+ }, 404);
5667
+ }
5668
+ const cache = getCacheService(config);
5669
+ const entry = await cache.getEntry(key);
5670
+ if (!entry) {
5671
+ return c.json({
5672
+ success: false,
5673
+ error: "Cache entry not found or expired"
5674
+ }, 404);
5675
+ }
5676
+ const parsed = parseCacheKey(key);
5677
+ return c.json({
5678
+ success: true,
5679
+ data: {
5680
+ key,
5681
+ namespace,
5682
+ parsed,
5683
+ ...entry,
5684
+ createdAt: new Date(entry.timestamp).toISOString(),
5685
+ expiresAt: new Date(entry.expiresAt).toISOString()
5686
+ },
5687
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5688
+ });
5689
+ });
5690
+ app.get("/analytics", async (c) => {
5691
+ const stats = getAllCacheStats();
5692
+ const invalidationStats = getCacheInvalidationStats();
5693
+ const recentInvalidations = getRecentInvalidations(20);
5694
+ let totalHits = 0;
5695
+ let totalMisses = 0;
5696
+ let totalSize = 0;
5697
+ let totalEntries = 0;
5698
+ const namespacesAnalytics = [];
5699
+ for (const [namespace, stat] of Object.entries(stats)) {
5700
+ totalHits += stat.memoryHits + stat.kvHits;
5701
+ totalMisses += stat.memoryMisses + stat.kvMisses;
5702
+ totalSize += stat.memorySize;
5703
+ totalEntries += stat.entryCount;
5704
+ const totalRequests2 = stat.memoryHits + stat.kvHits + stat.memoryMisses + stat.kvMisses;
5705
+ const hitRate = totalRequests2 > 0 ? (stat.memoryHits + stat.kvHits) / totalRequests2 * 100 : 0;
5706
+ const avgEntrySize = stat.entryCount > 0 ? stat.memorySize / stat.entryCount : 0;
5707
+ namespacesAnalytics.push({
5708
+ namespace,
5709
+ hitRate: hitRate.toFixed(2),
5710
+ totalRequests: totalRequests2,
5711
+ memoryHitRate: totalRequests2 > 0 ? (stat.memoryHits / totalRequests2 * 100).toFixed(2) : "0",
5712
+ kvHitRate: totalRequests2 > 0 ? (stat.kvHits / totalRequests2 * 100).toFixed(2) : "0",
5713
+ avgEntrySize: Math.round(avgEntrySize),
5714
+ totalSize: stat.memorySize,
5715
+ entryCount: stat.entryCount,
5716
+ efficiency: totalRequests2 > 0 ? ((stat.memoryHits + stat.kvHits) / (stat.memoryHits + stat.kvHits + stat.dbHits + 1)).toFixed(2) : "0"
5717
+ });
5718
+ }
5719
+ namespacesAnalytics.sort((a, b) => parseFloat(b.hitRate) - parseFloat(a.hitRate));
5720
+ const totalRequests = totalHits + totalMisses;
5721
+ const overallHitRate = totalRequests > 0 ? totalHits / totalRequests * 100 : 0;
5722
+ const dbQueriesAvoided = totalHits;
5723
+ const timeSaved = dbQueriesAvoided * 48;
5724
+ const estimatedCostSavings = dbQueriesAvoided / 1e6 * 0.5;
5725
+ return c.json({
5726
+ success: true,
5727
+ data: {
5728
+ overview: {
5729
+ totalHits,
5730
+ totalMisses,
5731
+ totalRequests,
5732
+ overallHitRate: overallHitRate.toFixed(2),
5733
+ totalSize,
5734
+ totalEntries,
5735
+ avgEntrySize: totalEntries > 0 ? Math.round(totalSize / totalEntries) : 0
5736
+ },
5737
+ performance: {
5738
+ dbQueriesAvoided,
5739
+ timeSavedMs: timeSaved,
5740
+ timeSavedMinutes: (timeSaved / 1e3 / 60).toFixed(2),
5741
+ estimatedCostSavings: estimatedCostSavings.toFixed(4)
5742
+ },
5743
+ namespaces: namespacesAnalytics,
5744
+ invalidation: {
5745
+ ...invalidationStats,
5746
+ recent: recentInvalidations
5747
+ }
5748
+ },
5749
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5750
+ });
5751
+ });
5752
+ app.get("/analytics/trends", async (c) => {
5753
+ const stats = getAllCacheStats();
5754
+ const dataPoint = {
5755
+ timestamp: Date.now(),
5756
+ stats: Object.entries(stats).map(([namespace, stat]) => ({
5757
+ namespace,
5758
+ hitRate: stat.hitRate,
5759
+ entryCount: stat.entryCount,
5760
+ memorySize: stat.memorySize,
5761
+ totalRequests: stat.totalRequests
5762
+ }))
5763
+ };
5764
+ return c.json({
5765
+ success: true,
5766
+ data: {
5767
+ trends: [dataPoint],
5768
+ note: "Historical trends require persistent storage. This returns current snapshot only."
5769
+ },
5770
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5771
+ });
5772
+ });
5773
+ app.get("/analytics/top-keys", async (c) => {
5774
+ c.req.query("namespace") || "all";
5775
+ parseInt(c.req.query("limit") || "10");
5776
+ return c.json({
5777
+ success: true,
5778
+ data: {
5779
+ topKeys: [],
5780
+ note: "Top keys tracking requires per-key hit counting. Feature not yet implemented."
5781
+ },
5782
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5783
+ });
5784
+ });
5785
+ app.post("/warm", async (c) => {
5786
+ try {
5787
+ const db = c.env.DB;
5788
+ const result = await warmCommonCaches(db);
5789
+ return c.json({
5790
+ success: true,
5791
+ message: "Cache warming completed",
5792
+ ...result,
5793
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5794
+ });
5795
+ } catch (error) {
5796
+ console.error("Cache warming error:", error);
5797
+ return c.json({
5798
+ success: false,
5799
+ error: "Cache warming failed",
5800
+ details: error instanceof Error ? error.message : "Unknown error"
5801
+ }, 500);
5802
+ }
5803
+ });
5804
+ app.post("/warm/:namespace", async (c) => {
5805
+ try {
5806
+ const namespace = c.req.param("namespace");
5807
+ const body = await c.req.json();
5808
+ const { entries } = body;
5809
+ if (!entries || !Array.isArray(entries)) {
5810
+ return c.json({
5811
+ success: false,
5812
+ error: "Entries array is required"
5813
+ }, 400);
5814
+ }
5815
+ const count = await warmNamespace(namespace, entries);
5816
+ return c.json({
5817
+ success: true,
5818
+ message: `Warmed ${count} entries in namespace: ${namespace}`,
5819
+ namespace,
5820
+ count,
5821
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5822
+ });
5823
+ } catch (error) {
5824
+ console.error("Namespace warming error:", error);
5825
+ return c.json({
5826
+ success: false,
5827
+ error: "Namespace warming failed",
5828
+ details: error instanceof Error ? error.message : "Unknown error"
5829
+ }, 500);
5830
+ }
5831
+ });
5832
+ var routes_default = app;
5833
+
5834
+ // src/plugins/cache/index.ts
5835
+ var CachePlugin = class {
5836
+ _context = null;
5837
+ /**
5838
+ * Get plugin routes
5839
+ */
5840
+ getRoutes() {
5841
+ return routes_default;
5842
+ }
5843
+ /**
5844
+ * Activate the cache plugin
5845
+ */
5846
+ async activate(context) {
5847
+ this._context = context;
5848
+ const settings = context.config || {};
5849
+ console.log("\u2705 Cache plugin activated", {
5850
+ memoryEnabled: settings.memoryEnabled ?? true,
5851
+ kvEnabled: settings.kvEnabled ?? false,
5852
+ defaultTTL: settings.defaultTTL ?? 3600
5853
+ });
5854
+ for (const [_namespace, config] of Object.entries(CACHE_CONFIGS)) {
5855
+ getCacheService({
5856
+ ...config,
5857
+ memoryEnabled: settings.memoryEnabled ?? config.memoryEnabled,
5858
+ kvEnabled: settings.kvEnabled ?? config.kvEnabled,
5859
+ ttl: settings.defaultTTL ?? config.ttl
5860
+ });
5861
+ }
5862
+ setupCacheInvalidation();
5863
+ }
5864
+ /**
5865
+ * Deactivate the cache plugin
5866
+ */
5867
+ async deactivate() {
5868
+ console.log("\u274C Cache plugin deactivated - clearing all caches");
5869
+ await clearAllCaches();
5870
+ this._context = null;
5871
+ }
5872
+ /**
5873
+ * Configure the cache plugin
5874
+ */
5875
+ async configure(settings) {
5876
+ console.log("\u2699\uFE0F Cache plugin configured", settings);
5877
+ for (const [_namespace, config] of Object.entries(CACHE_CONFIGS)) {
5878
+ getCacheService({
5879
+ ...config,
5880
+ memoryEnabled: settings.memoryEnabled ?? config.memoryEnabled,
5881
+ kvEnabled: settings.kvEnabled ?? config.kvEnabled,
5882
+ ttl: settings.defaultTTL ?? config.ttl
5883
+ });
5884
+ }
5885
+ }
5886
+ /**
5887
+ * Get cache statistics
5888
+ */
5889
+ async getStats(c) {
5890
+ const stats = getAllCacheStats();
5891
+ return c.json({
5892
+ success: true,
5893
+ data: stats,
5894
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5895
+ });
5896
+ }
5897
+ /**
5898
+ * Clear all cache entries
5899
+ */
5900
+ async clearCache(c) {
5901
+ await clearAllCaches();
5902
+ return c.json({
5903
+ success: true,
5904
+ message: "All cache entries cleared",
5905
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5906
+ });
5907
+ }
5908
+ /**
5909
+ * Invalidate cache entries matching pattern
5910
+ */
5911
+ async invalidatePattern(c) {
5912
+ const body = await c.req.json();
5913
+ const { pattern, namespace: _namespace } = body;
5914
+ if (!pattern) {
5915
+ return c.json({
5916
+ success: false,
5917
+ error: "Pattern is required"
5918
+ }, 400);
5919
+ }
5920
+ let totalInvalidated = 0;
5921
+ if (_namespace) {
5922
+ const cache = getCacheService(CACHE_CONFIGS[_namespace] || {
5923
+ ttl: 3600,
5924
+ kvEnabled: false,
5925
+ memoryEnabled: true,
5926
+ namespace: _namespace,
5927
+ invalidateOn: [],
5928
+ version: "v1"
5929
+ });
5930
+ totalInvalidated = await cache.invalidate(pattern);
5931
+ } else {
5932
+ for (const config of Object.values(CACHE_CONFIGS)) {
5933
+ const cache = getCacheService(config);
5934
+ totalInvalidated += await cache.invalidate(pattern);
5935
+ }
5936
+ }
5937
+ return c.json({
5938
+ success: true,
5939
+ invalidated: totalInvalidated,
5940
+ pattern,
5941
+ namespace: _namespace || "all",
5942
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5943
+ });
5944
+ }
5945
+ };
5946
+ var plugin = new CachePlugin();
5947
+ var cache_default = plugin;
5948
+
5949
+ // src/assets/favicon.ts
5950
+ var faviconSvg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
5951
+ <svg
5952
+ version="1.1"
5953
+ id="Layer_1"
5954
+ x="0px"
5955
+ y="0px"
5956
+ viewBox="380 1300 257.89001 278.8855"
5957
+ xml:space="preserve"
5958
+ width="257.89001"
5959
+ height="278.8855"
5960
+ xmlns="http://www.w3.org/2000/svg">
5961
+ <g
5962
+ id="g10"
5963
+ transform="translate(-383.935,-60.555509)">
5964
+ <g
5965
+ id="g9">
5966
+ <path
5967
+ fill="#f1f2f2"
5968
+ 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"
5969
+ id="path2" />
5970
+ <path
5971
+ fill="#34d399"
5972
+ 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"
5973
+ id="path9" />
5974
+ </g>
5975
+ </g>
5976
+ </svg>`;
5977
+
5978
+ // src/app.ts
5979
+ function createSonicJSApp(config = {}) {
5980
+ const app2 = new Hono();
5981
+ const appVersion = config.version || getCoreVersion();
5982
+ const appName = config.name || "SonicJS AI";
5983
+ app2.use("*", async (c, next) => {
5984
+ c.set("appVersion", appVersion);
5985
+ await next();
5986
+ });
5987
+ app2.use("*", metricsMiddleware());
5988
+ app2.use("*", bootstrapMiddleware(config));
5989
+ if (config.middleware?.beforeAuth) {
5990
+ for (const middleware of config.middleware.beforeAuth) {
5991
+ app2.use("*", middleware);
5992
+ }
5993
+ }
5994
+ app2.use("*", async (_c, next) => {
5995
+ await next();
5996
+ });
5997
+ app2.use("*", async (_c, next) => {
5998
+ await next();
5999
+ });
6000
+ if (config.middleware?.afterAuth) {
6001
+ for (const middleware of config.middleware.afterAuth) {
6002
+ app2.use("*", middleware);
6003
+ }
6004
+ }
6005
+ app2.route("/api", api_default);
6006
+ app2.route("/api/media", api_media_default);
6007
+ app2.route("/api/system", api_system_default);
6008
+ app2.route("/admin/api", admin_api_default);
6009
+ app2.route("/admin/dashboard", router);
6010
+ app2.route("/admin/collections", adminCollectionsRoutes);
6011
+ app2.route("/admin/forms", adminFormsRoutes);
6012
+ app2.route("/admin/settings", adminSettingsRoutes);
6013
+ app2.route("/forms", public_forms_default);
6014
+ app2.route("/api/forms", public_forms_default);
6015
+ app2.route("/admin/api-reference", router2);
6016
+ app2.route("/admin/database-tools", createDatabaseToolsAdminRoutes());
6017
+ app2.route("/admin/seed-data", createSeedDataAdminRoutes());
6018
+ app2.route("/admin/content", admin_content_default);
6019
+ app2.route("/admin/media", adminMediaRoutes);
6020
+ if (aiSearchPlugin.routes && aiSearchPlugin.routes.length > 0) {
6021
+ for (const route of aiSearchPlugin.routes) {
6022
+ app2.route(route.path, route.handler);
6023
+ }
6024
+ }
6025
+ app2.route("/admin/cache", cache_default.getRoutes());
6026
+ if (otpLoginPlugin.routes && otpLoginPlugin.routes.length > 0) {
6027
+ for (const route of otpLoginPlugin.routes) {
6028
+ app2.route(route.path, route.handler);
6029
+ }
6030
+ }
6031
+ app2.route("/admin/plugins", adminPluginRoutes);
6032
+ app2.route("/admin/logs", adminLogsRoutes);
6033
+ app2.route("/admin", userRoutes);
6034
+ app2.route("/auth", auth_default);
6035
+ app2.route("/", test_cleanup_default);
6036
+ if (emailPlugin.routes && emailPlugin.routes.length > 0) {
6037
+ for (const route of emailPlugin.routes) {
6038
+ app2.route(route.path, route.handler);
4627
6039
  }
4628
6040
  }
4629
6041
  const magicLinkPlugin = createMagicLinkAuthPlugin();
4630
6042
  if (magicLinkPlugin.routes && magicLinkPlugin.routes.length > 0) {
4631
6043
  for (const route of magicLinkPlugin.routes) {
4632
- app.route(route.path, route.handler);
6044
+ app2.route(route.path, route.handler);
4633
6045
  }
4634
6046
  }
4635
- app.get("/favicon.svg", (c) => {
6047
+ app2.get("/favicon.svg", (c) => {
4636
6048
  return new Response(faviconSvg, {
4637
6049
  headers: {
4638
6050
  "Content-Type": "image/svg+xml",
@@ -4640,7 +6052,7 @@ function createSonicJSApp(config = {}) {
4640
6052
  }
4641
6053
  });
4642
6054
  });
4643
- app.get("/files/*", async (c) => {
6055
+ app2.get("/files/*", async (c) => {
4644
6056
  try {
4645
6057
  const url = new URL(c.req.url);
4646
6058
  const pathname = url.pathname;
@@ -4669,13 +6081,13 @@ function createSonicJSApp(config = {}) {
4669
6081
  });
4670
6082
  if (config.routes) {
4671
6083
  for (const route of config.routes) {
4672
- app.route(route.path, route.handler);
6084
+ app2.route(route.path, route.handler);
4673
6085
  }
4674
6086
  }
4675
- app.get("/", (c) => {
6087
+ app2.get("/", (c) => {
4676
6088
  return c.redirect("/auth/login");
4677
6089
  });
4678
- app.get("/health", (c) => {
6090
+ app2.get("/health", (c) => {
4679
6091
  return c.json({
4680
6092
  name: appName,
4681
6093
  version: appVersion,
@@ -4683,14 +6095,14 @@ function createSonicJSApp(config = {}) {
4683
6095
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4684
6096
  });
4685
6097
  });
4686
- app.notFound((c) => {
6098
+ app2.notFound((c) => {
4687
6099
  return c.json({ error: "Not Found", status: 404 }, 404);
4688
6100
  });
4689
- app.onError((err, c) => {
6101
+ app2.onError((err, c) => {
4690
6102
  console.error(err);
4691
6103
  return c.json({ error: "Internal Server Error", status: 500 }, 500);
4692
6104
  });
4693
- return app;
6105
+ return app2;
4694
6106
  }
4695
6107
  function setupCoreMiddleware(_app) {
4696
6108
  console.warn("setupCoreMiddleware is deprecated. Use createSonicJSApp() instead.");