@sonicjs-cms/core 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{chunk-63K7XXRX.cjs → chunk-AYPF6C4D.cjs} +5 -5
  2. package/dist/{chunk-63K7XXRX.cjs.map → chunk-AYPF6C4D.cjs.map} +1 -1
  3. package/dist/{chunk-7DL5SPPX.js → chunk-DNHJS6RN.js} +3 -3
  4. package/dist/{chunk-7DL5SPPX.js.map → chunk-DNHJS6RN.js.map} +1 -1
  5. package/dist/{chunk-T3YIKW2A.cjs → chunk-E2BXLXPW.cjs} +4 -4
  6. package/dist/{chunk-T3YIKW2A.cjs.map → chunk-E2BXLXPW.cjs.map} +1 -1
  7. package/dist/{chunk-BZC4FYW7.cjs → chunk-EHSZ6TAN.cjs} +9 -2
  8. package/dist/chunk-EHSZ6TAN.cjs.map +1 -0
  9. package/dist/{chunk-KA2PDJNB.js → chunk-GRN3GHUG.js} +9 -2
  10. package/dist/chunk-GRN3GHUG.js.map +1 -0
  11. package/dist/{chunk-N7TDLOUE.cjs → chunk-J7F3NPAP.cjs} +208 -182
  12. package/dist/chunk-J7F3NPAP.cjs.map +1 -0
  13. package/dist/{chunk-EVZOVYLO.js → chunk-L2IDZI7F.js} +2 -2
  14. package/dist/{chunk-EVZOVYLO.js.map → chunk-L2IDZI7F.js.map} +1 -1
  15. package/dist/{chunk-YMTTGHEK.cjs → chunk-MYB5RY7H.cjs} +3 -3
  16. package/dist/{chunk-YMTTGHEK.cjs.map → chunk-MYB5RY7H.cjs.map} +1 -1
  17. package/dist/{chunk-F6GZURXJ.js → chunk-UISZ2MBW.js} +50 -26
  18. package/dist/chunk-UISZ2MBW.js.map +1 -0
  19. package/dist/{chunk-KAOWRIFD.js → chunk-V3KVSEG6.js} +3 -3
  20. package/dist/{chunk-KAOWRIFD.js.map → chunk-V3KVSEG6.js.map} +1 -1
  21. package/dist/{chunk-EYWR6UA2.js → chunk-Y3EWJQ4D.js} +3 -3
  22. package/dist/{chunk-EYWR6UA2.js.map → chunk-Y3EWJQ4D.js.map} +1 -1
  23. package/dist/{chunk-IIRVZSP2.cjs → chunk-YRFAQ6MI.cjs} +2 -2
  24. package/dist/{chunk-IIRVZSP2.cjs.map → chunk-YRFAQ6MI.cjs.map} +1 -1
  25. package/dist/index.cjs +1940 -122
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +1874 -56
  28. package/dist/index.js.map +1 -1
  29. package/dist/middleware.cjs +23 -23
  30. package/dist/middleware.js +2 -2
  31. package/dist/migrations-LEMFV2ND.cjs +13 -0
  32. package/dist/{migrations-QNYAWQLB.cjs.map → migrations-LEMFV2ND.cjs.map} +1 -1
  33. package/dist/migrations-RKQES6XY.js +4 -0
  34. package/dist/{migrations-R6NQBKQV.js.map → migrations-RKQES6XY.js.map} +1 -1
  35. package/dist/routes.cjs +25 -25
  36. package/dist/routes.js +5 -5
  37. package/dist/services.cjs +2 -2
  38. package/dist/services.js +1 -1
  39. package/dist/templates.cjs +17 -17
  40. package/dist/templates.js +2 -2
  41. package/dist/utils.cjs +14 -14
  42. package/dist/utils.js +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-BZC4FYW7.cjs.map +0 -1
  45. package/dist/chunk-F6GZURXJ.js.map +0 -1
  46. package/dist/chunk-KA2PDJNB.js.map +0 -1
  47. package/dist/chunk-N7TDLOUE.cjs.map +0 -1
  48. package/dist/migrations-QNYAWQLB.cjs +0 -13
  49. package/dist/migrations-R6NQBKQV.js +0 -4
package/dist/index.js CHANGED
@@ -1,19 +1,19 @@
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';
1
+ import { renderConfirmationDialog, getConfirmationDialogScript, 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-UISZ2MBW.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-UISZ2MBW.js';
3
3
  import { schema_exports } from './chunk-3YNNVSMC.js';
4
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';
5
+ import { requireAuth, AuthManager, metricsMiddleware, bootstrapMiddleware } from './chunk-Y3EWJQ4D.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-Y3EWJQ4D.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';
8
+ export { MigrationService } from './chunk-L2IDZI7F.js';
9
+ export { renderFilterBar } from './chunk-V3KVSEG6.js';
10
+ import { init_admin_layout_catalyst_template, renderAdminLayout, adminLayoutV2, renderAdminLayoutCatalyst } from './chunk-GRN3GHUG.js';
11
+ export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-GRN3GHUG.js';
12
12
  export { HookSystemImpl, HookUtils, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, ScopedHookSystem as ScopedHookSystemClass } from './chunk-F332TENF.js';
13
13
  import { PluginBuilder } from './chunk-CLIH2T74.js';
14
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';
15
+ import { package_default, getCoreVersion } from './chunk-DNHJS6RN.js';
16
+ export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-DNHJS6RN.js';
17
17
  import './chunk-X7ZAEI5S.js';
18
18
  export { metricsTracker } from './chunk-FICTAGD4.js';
19
19
  export { HOOKS } from './chunk-LOUJRBXV.js';
@@ -1328,10 +1328,10 @@ function createEmailPlugin() {
1328
1328
  emailRoutes.get("/settings", async (c) => {
1329
1329
  const user = c.get("user");
1330
1330
  const db = c.env.DB;
1331
- const plugin = await db.prepare(`
1331
+ const plugin2 = await db.prepare(`
1332
1332
  SELECT settings FROM plugins WHERE id = 'email'
1333
1333
  `).first();
1334
- const settings = plugin?.settings ? JSON.parse(plugin.settings) : {};
1334
+ const settings = plugin2?.settings ? JSON.parse(plugin2.settings) : {};
1335
1335
  const contentHTML = await html`
1336
1336
  <div class="p-8">
1337
1337
  <!-- Header -->
@@ -1592,16 +1592,16 @@ function createEmailPlugin() {
1592
1592
  try {
1593
1593
  const db = c.env.DB;
1594
1594
  const body = await c.req.json();
1595
- const plugin = await db.prepare(`
1595
+ const plugin2 = await db.prepare(`
1596
1596
  SELECT settings FROM plugins WHERE id = 'email'
1597
1597
  `).first();
1598
- if (!plugin?.settings) {
1598
+ if (!plugin2?.settings) {
1599
1599
  return c.json({
1600
1600
  success: false,
1601
1601
  error: "Email settings not configured. Please save your settings first."
1602
1602
  }, 400);
1603
1603
  }
1604
- const settings = JSON.parse(plugin.settings);
1604
+ const settings = JSON.parse(plugin2.settings);
1605
1605
  if (!settings.apiKey || !settings.fromEmail || !settings.fromName) {
1606
1606
  return c.json({
1607
1607
  success: false,
@@ -2915,11 +2915,11 @@ var AISearchService = class {
2915
2915
  */
2916
2916
  async getSettings() {
2917
2917
  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) {
2918
+ const plugin2 = await this.db.prepare(`SELECT settings FROM plugins WHERE id = ? LIMIT 1`).bind("ai-search").first();
2919
+ if (!plugin2 || !plugin2.settings) {
2920
2920
  return this.getDefaultSettings();
2921
2921
  }
2922
- return JSON.parse(plugin.settings);
2922
+ return JSON.parse(plugin2.settings);
2923
2923
  } catch (error) {
2924
2924
  console.error("Error fetching AI Search settings:", error);
2925
2925
  return this.getDefaultSettings();
@@ -4539,6 +4539,1823 @@ function renderMagicLinkEmail(magicLink, expiryMinutes) {
4539
4539
  }
4540
4540
  createMagicLinkAuthPlugin();
4541
4541
 
4542
+ // src/plugins/cache/services/cache-config.ts
4543
+ var CACHE_CONFIGS = {
4544
+ // Content (high read, low write)
4545
+ content: {
4546
+ ttl: 3600,
4547
+ // 1 hour
4548
+ kvEnabled: true,
4549
+ memoryEnabled: true,
4550
+ namespace: "content",
4551
+ invalidateOn: ["content.update", "content.delete", "content.publish"],
4552
+ version: "v1"
4553
+ },
4554
+ // User data (medium read, medium write)
4555
+ user: {
4556
+ ttl: 900,
4557
+ // 15 minutes
4558
+ kvEnabled: true,
4559
+ memoryEnabled: true,
4560
+ namespace: "user",
4561
+ invalidateOn: ["user.update", "user.delete", "auth.login"],
4562
+ version: "v1"
4563
+ },
4564
+ // Configuration (high read, very low write)
4565
+ config: {
4566
+ ttl: 7200,
4567
+ // 2 hours
4568
+ kvEnabled: true,
4569
+ memoryEnabled: true,
4570
+ namespace: "config",
4571
+ invalidateOn: ["config.update", "plugin.activate", "plugin.deactivate"],
4572
+ version: "v1"
4573
+ },
4574
+ // Media metadata (high read, low write)
4575
+ media: {
4576
+ ttl: 3600,
4577
+ // 1 hour
4578
+ kvEnabled: true,
4579
+ memoryEnabled: true,
4580
+ namespace: "media",
4581
+ invalidateOn: ["media.upload", "media.delete", "media.update"],
4582
+ version: "v1"
4583
+ },
4584
+ // API responses (very high read, low write)
4585
+ api: {
4586
+ ttl: 300,
4587
+ // 5 minutes
4588
+ kvEnabled: true,
4589
+ memoryEnabled: true,
4590
+ namespace: "api",
4591
+ invalidateOn: ["content.update", "content.publish"],
4592
+ version: "v1"
4593
+ },
4594
+ // Session data (very high read, medium write)
4595
+ session: {
4596
+ ttl: 1800,
4597
+ // 30 minutes
4598
+ kvEnabled: false,
4599
+ // Only in-memory for sessions
4600
+ memoryEnabled: true,
4601
+ namespace: "session",
4602
+ invalidateOn: ["auth.logout"],
4603
+ version: "v1"
4604
+ },
4605
+ // Plugin data
4606
+ plugin: {
4607
+ ttl: 3600,
4608
+ // 1 hour
4609
+ kvEnabled: true,
4610
+ memoryEnabled: true,
4611
+ namespace: "plugin",
4612
+ invalidateOn: ["plugin.activate", "plugin.deactivate", "plugin.update"],
4613
+ version: "v1"
4614
+ },
4615
+ // Collections/schema
4616
+ collection: {
4617
+ ttl: 7200,
4618
+ // 2 hours
4619
+ kvEnabled: true,
4620
+ memoryEnabled: true,
4621
+ namespace: "collection",
4622
+ invalidateOn: ["collection.update", "collection.delete"],
4623
+ version: "v1"
4624
+ }
4625
+ };
4626
+ function getCacheConfig(namespace) {
4627
+ return CACHE_CONFIGS[namespace] || {
4628
+ ttl: 3600,
4629
+ kvEnabled: true,
4630
+ memoryEnabled: true,
4631
+ namespace,
4632
+ invalidateOn: [],
4633
+ version: "v1"
4634
+ };
4635
+ }
4636
+ function generateCacheKey(namespace, type, identifier, version) {
4637
+ const v = version || getCacheConfig(namespace).version || "v1";
4638
+ return `${namespace}:${type}:${identifier}:${v}`;
4639
+ }
4640
+ function parseCacheKey(key) {
4641
+ const parts = key.split(":");
4642
+ if (parts.length !== 4) {
4643
+ return null;
4644
+ }
4645
+ return {
4646
+ namespace: parts[0] || "",
4647
+ type: parts[1] || "",
4648
+ identifier: parts[2] || "",
4649
+ version: parts[3] || ""
4650
+ };
4651
+ }
4652
+
4653
+ // src/plugins/cache/services/cache.ts
4654
+ var MemoryCache = class {
4655
+ cache = /* @__PURE__ */ new Map();
4656
+ maxSize = 50 * 1024 * 1024;
4657
+ // 50MB
4658
+ currentSize = 0;
4659
+ /**
4660
+ * Get item from memory cache
4661
+ */
4662
+ get(key) {
4663
+ const entry = this.cache.get(key);
4664
+ if (!entry) {
4665
+ return null;
4666
+ }
4667
+ if (Date.now() > entry.expiresAt) {
4668
+ this.delete(key);
4669
+ return null;
4670
+ }
4671
+ return entry.data;
4672
+ }
4673
+ /**
4674
+ * Set item in memory cache
4675
+ */
4676
+ set(key, value, ttl, version = "v1") {
4677
+ const now = Date.now();
4678
+ const entry = {
4679
+ data: value,
4680
+ timestamp: now,
4681
+ expiresAt: now + ttl * 1e3,
4682
+ version
4683
+ };
4684
+ const entrySize = JSON.stringify(entry).length * 2;
4685
+ if (this.currentSize + entrySize > this.maxSize) {
4686
+ this.evictLRU(entrySize);
4687
+ }
4688
+ if (this.cache.has(key)) {
4689
+ this.delete(key);
4690
+ }
4691
+ this.cache.set(key, entry);
4692
+ this.currentSize += entrySize;
4693
+ }
4694
+ /**
4695
+ * Delete item from memory cache
4696
+ */
4697
+ delete(key) {
4698
+ const entry = this.cache.get(key);
4699
+ if (entry) {
4700
+ const entrySize = JSON.stringify(entry).length * 2;
4701
+ this.currentSize -= entrySize;
4702
+ return this.cache.delete(key);
4703
+ }
4704
+ return false;
4705
+ }
4706
+ /**
4707
+ * Clear all items from memory cache
4708
+ */
4709
+ clear() {
4710
+ this.cache.clear();
4711
+ this.currentSize = 0;
4712
+ }
4713
+ /**
4714
+ * Get cache statistics
4715
+ */
4716
+ getStats() {
4717
+ return {
4718
+ size: this.currentSize,
4719
+ count: this.cache.size
4720
+ };
4721
+ }
4722
+ /**
4723
+ * Evict least recently used items to make space
4724
+ */
4725
+ evictLRU(neededSpace) {
4726
+ const entries = Array.from(this.cache.entries()).sort(
4727
+ (a, b) => a[1].timestamp - b[1].timestamp
4728
+ );
4729
+ let freedSpace = 0;
4730
+ for (const [key, entry] of entries) {
4731
+ if (freedSpace >= neededSpace) break;
4732
+ const entrySize = JSON.stringify(entry).length * 2;
4733
+ this.delete(key);
4734
+ freedSpace += entrySize;
4735
+ }
4736
+ }
4737
+ /**
4738
+ * Delete items matching a pattern
4739
+ */
4740
+ invalidatePattern(pattern) {
4741
+ const regex = new RegExp(
4742
+ "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
4743
+ );
4744
+ let count = 0;
4745
+ for (const key of this.cache.keys()) {
4746
+ if (regex.test(key)) {
4747
+ this.delete(key);
4748
+ count++;
4749
+ }
4750
+ }
4751
+ return count;
4752
+ }
4753
+ };
4754
+ var CacheService = class {
4755
+ memoryCache;
4756
+ config;
4757
+ stats;
4758
+ kvNamespace;
4759
+ constructor(config, kvNamespace) {
4760
+ this.memoryCache = new MemoryCache();
4761
+ this.config = config;
4762
+ this.kvNamespace = kvNamespace;
4763
+ this.stats = {
4764
+ memoryHits: 0,
4765
+ memoryMisses: 0,
4766
+ kvHits: 0,
4767
+ kvMisses: 0,
4768
+ dbHits: 0,
4769
+ totalRequests: 0,
4770
+ hitRate: 0,
4771
+ memorySize: 0,
4772
+ entryCount: 0
4773
+ };
4774
+ }
4775
+ /**
4776
+ * Get value from cache (tries memory first, then KV)
4777
+ */
4778
+ async get(key) {
4779
+ this.stats.totalRequests++;
4780
+ if (this.config.memoryEnabled) {
4781
+ const memoryValue = this.memoryCache.get(key);
4782
+ if (memoryValue !== null) {
4783
+ this.stats.memoryHits++;
4784
+ this.updateHitRate();
4785
+ return memoryValue;
4786
+ }
4787
+ this.stats.memoryMisses++;
4788
+ }
4789
+ if (this.config.kvEnabled && this.kvNamespace) {
4790
+ try {
4791
+ const kvValue = await this.kvNamespace.get(key, "json");
4792
+ if (kvValue !== null) {
4793
+ this.stats.kvHits++;
4794
+ if (this.config.memoryEnabled) {
4795
+ this.memoryCache.set(key, kvValue, this.config.ttl, this.config.version);
4796
+ }
4797
+ this.updateHitRate();
4798
+ return kvValue;
4799
+ }
4800
+ this.stats.kvMisses++;
4801
+ } catch (error) {
4802
+ console.error("KV cache read error:", error);
4803
+ this.stats.kvMisses++;
4804
+ }
4805
+ }
4806
+ this.updateHitRate();
4807
+ return null;
4808
+ }
4809
+ /**
4810
+ * Get value from cache with source information
4811
+ */
4812
+ async getWithSource(key) {
4813
+ this.stats.totalRequests++;
4814
+ if (this.config.memoryEnabled) {
4815
+ const memoryValue = this.memoryCache.get(key);
4816
+ if (memoryValue !== null) {
4817
+ this.stats.memoryHits++;
4818
+ this.updateHitRate();
4819
+ const entry = await this.getEntry(key);
4820
+ return {
4821
+ data: memoryValue,
4822
+ source: "memory",
4823
+ hit: true,
4824
+ timestamp: entry?.timestamp,
4825
+ ttl: entry?.ttl
4826
+ };
4827
+ }
4828
+ this.stats.memoryMisses++;
4829
+ }
4830
+ if (this.config.kvEnabled && this.kvNamespace) {
4831
+ try {
4832
+ const kvValue = await this.kvNamespace.get(key, "json");
4833
+ if (kvValue !== null) {
4834
+ this.stats.kvHits++;
4835
+ if (this.config.memoryEnabled) {
4836
+ this.memoryCache.set(key, kvValue, this.config.ttl, this.config.version);
4837
+ }
4838
+ this.updateHitRate();
4839
+ return {
4840
+ data: kvValue,
4841
+ source: "kv",
4842
+ hit: true
4843
+ };
4844
+ }
4845
+ this.stats.kvMisses++;
4846
+ } catch (error) {
4847
+ console.error("KV cache read error:", error);
4848
+ this.stats.kvMisses++;
4849
+ }
4850
+ }
4851
+ this.updateHitRate();
4852
+ return {
4853
+ data: null,
4854
+ source: "miss",
4855
+ hit: false
4856
+ };
4857
+ }
4858
+ /**
4859
+ * Set value in cache (stores in both memory and KV)
4860
+ */
4861
+ async set(key, value, customConfig) {
4862
+ const config = { ...this.config, ...customConfig };
4863
+ if (config.memoryEnabled) {
4864
+ this.memoryCache.set(key, value, config.ttl, config.version);
4865
+ }
4866
+ if (config.kvEnabled && this.kvNamespace) {
4867
+ try {
4868
+ await this.kvNamespace.put(key, JSON.stringify(value), {
4869
+ expirationTtl: config.ttl
4870
+ });
4871
+ } catch (error) {
4872
+ console.error("KV cache write error:", error);
4873
+ }
4874
+ }
4875
+ }
4876
+ /**
4877
+ * Delete value from cache (removes from both memory and KV)
4878
+ */
4879
+ async delete(key) {
4880
+ if (this.config.memoryEnabled) {
4881
+ this.memoryCache.delete(key);
4882
+ }
4883
+ if (this.config.kvEnabled && this.kvNamespace) {
4884
+ try {
4885
+ await this.kvNamespace.delete(key);
4886
+ } catch (error) {
4887
+ console.error("KV cache delete error:", error);
4888
+ }
4889
+ }
4890
+ }
4891
+ /**
4892
+ * Clear all cache entries for this namespace
4893
+ */
4894
+ async clear() {
4895
+ if (this.config.memoryEnabled) {
4896
+ this.memoryCache.clear();
4897
+ }
4898
+ this.stats = {
4899
+ memoryHits: 0,
4900
+ memoryMisses: 0,
4901
+ kvHits: 0,
4902
+ kvMisses: 0,
4903
+ dbHits: 0,
4904
+ totalRequests: 0,
4905
+ hitRate: 0,
4906
+ memorySize: 0,
4907
+ entryCount: 0
4908
+ };
4909
+ }
4910
+ /**
4911
+ * Invalidate cache entries matching a pattern
4912
+ */
4913
+ async invalidate(pattern) {
4914
+ let count = 0;
4915
+ if (this.config.memoryEnabled) {
4916
+ count += this.memoryCache.invalidatePattern(pattern);
4917
+ }
4918
+ if (this.config.kvEnabled && this.kvNamespace) {
4919
+ try {
4920
+ const regex = new RegExp(
4921
+ "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
4922
+ );
4923
+ const prefix = this.config.namespace + ":";
4924
+ const list = await this.kvNamespace.list({ prefix });
4925
+ for (const key of list.keys) {
4926
+ if (regex.test(key.name)) {
4927
+ await this.kvNamespace.delete(key.name);
4928
+ count++;
4929
+ }
4930
+ }
4931
+ } catch (error) {
4932
+ console.error("KV cache invalidation error:", error);
4933
+ }
4934
+ }
4935
+ return count;
4936
+ }
4937
+ /**
4938
+ * Invalidate cache entries matching a pattern (alias for invalidate)
4939
+ */
4940
+ async invalidatePattern(pattern) {
4941
+ return this.invalidate(pattern);
4942
+ }
4943
+ /**
4944
+ * Get cache statistics
4945
+ */
4946
+ getStats() {
4947
+ const memStats = this.memoryCache.getStats();
4948
+ return {
4949
+ ...this.stats,
4950
+ memorySize: memStats.size,
4951
+ entryCount: memStats.count
4952
+ };
4953
+ }
4954
+ /**
4955
+ * Update hit rate calculation
4956
+ */
4957
+ updateHitRate() {
4958
+ const totalHits = this.stats.memoryHits + this.stats.kvHits + this.stats.dbHits;
4959
+ this.stats.hitRate = this.stats.totalRequests > 0 ? totalHits / this.stats.totalRequests * 100 : 0;
4960
+ }
4961
+ /**
4962
+ * Generate a cache key using the configured namespace
4963
+ */
4964
+ generateKey(type, identifier) {
4965
+ return generateCacheKey(
4966
+ this.config.namespace,
4967
+ type,
4968
+ identifier,
4969
+ this.config.version
4970
+ );
4971
+ }
4972
+ /**
4973
+ * Warm cache with multiple entries
4974
+ */
4975
+ async warmCache(entries) {
4976
+ for (const entry of entries) {
4977
+ await this.set(entry.key, entry.value);
4978
+ }
4979
+ }
4980
+ /**
4981
+ * Check if a key exists in cache
4982
+ */
4983
+ async has(key) {
4984
+ const value = await this.get(key);
4985
+ return value !== null;
4986
+ }
4987
+ /**
4988
+ * Get multiple values at once
4989
+ */
4990
+ async getMany(keys) {
4991
+ const results = /* @__PURE__ */ new Map();
4992
+ for (const key of keys) {
4993
+ const value = await this.get(key);
4994
+ if (value !== null) {
4995
+ results.set(key, value);
4996
+ }
4997
+ }
4998
+ return results;
4999
+ }
5000
+ /**
5001
+ * Set multiple values at once
5002
+ */
5003
+ async setMany(entries, customConfig) {
5004
+ for (const entry of entries) {
5005
+ await this.set(entry.key, entry.value, customConfig);
5006
+ }
5007
+ }
5008
+ /**
5009
+ * Delete multiple keys at once
5010
+ */
5011
+ async deleteMany(keys) {
5012
+ for (const key of keys) {
5013
+ await this.delete(key);
5014
+ }
5015
+ }
5016
+ /**
5017
+ * Get or set pattern - fetch from cache or compute if not found
5018
+ */
5019
+ async getOrSet(key, fetcher, customConfig) {
5020
+ const cached = await this.get(key);
5021
+ if (cached !== null) {
5022
+ return cached;
5023
+ }
5024
+ const value = await fetcher();
5025
+ await this.set(key, value, customConfig);
5026
+ return value;
5027
+ }
5028
+ /**
5029
+ * List all cache keys with metadata
5030
+ */
5031
+ async listKeys() {
5032
+ const keys = [];
5033
+ if (this.config.memoryEnabled) {
5034
+ const cache = this.memoryCache.cache;
5035
+ for (const [key, entry] of cache.entries()) {
5036
+ const size = JSON.stringify(entry).length * 2;
5037
+ const age = Date.now() - entry.timestamp;
5038
+ keys.push({
5039
+ key,
5040
+ size,
5041
+ expiresAt: entry.expiresAt,
5042
+ age
5043
+ });
5044
+ }
5045
+ }
5046
+ return keys.sort((a, b) => a.age - b.age);
5047
+ }
5048
+ /**
5049
+ * Get cache entry with full metadata
5050
+ */
5051
+ async getEntry(key) {
5052
+ if (!this.config.memoryEnabled) {
5053
+ return null;
5054
+ }
5055
+ const cache = this.memoryCache.cache;
5056
+ const entry = cache.get(key);
5057
+ if (!entry) {
5058
+ return null;
5059
+ }
5060
+ if (Date.now() > entry.expiresAt) {
5061
+ await this.delete(key);
5062
+ return null;
5063
+ }
5064
+ const size = JSON.stringify(entry).length * 2;
5065
+ const ttl = Math.max(0, entry.expiresAt - Date.now()) / 1e3;
5066
+ return {
5067
+ data: entry.data,
5068
+ timestamp: entry.timestamp,
5069
+ expiresAt: entry.expiresAt,
5070
+ ttl,
5071
+ size
5072
+ };
5073
+ }
5074
+ };
5075
+ var cacheInstances = /* @__PURE__ */ new Map();
5076
+ var globalKVNamespace;
5077
+ function getCacheService(config, kvNamespace) {
5078
+ const key = config.namespace;
5079
+ if (!cacheInstances.has(key)) {
5080
+ const kv = globalKVNamespace;
5081
+ cacheInstances.set(key, new CacheService(config, kv));
5082
+ }
5083
+ return cacheInstances.get(key);
5084
+ }
5085
+ async function clearAllCaches() {
5086
+ for (const cache of cacheInstances.values()) {
5087
+ await cache.clear();
5088
+ }
5089
+ }
5090
+ function getAllCacheStats() {
5091
+ const stats = {};
5092
+ for (const [namespace, cache] of cacheInstances.entries()) {
5093
+ stats[namespace] = cache.getStats();
5094
+ }
5095
+ return stats;
5096
+ }
5097
+
5098
+ // src/plugins/cache/services/event-bus.ts
5099
+ var EventBus = class {
5100
+ subscriptions = /* @__PURE__ */ new Map();
5101
+ eventLog = [];
5102
+ maxLogSize = 100;
5103
+ /**
5104
+ * Subscribe to an event
5105
+ */
5106
+ on(event, handler) {
5107
+ if (!this.subscriptions.has(event)) {
5108
+ this.subscriptions.set(event, []);
5109
+ }
5110
+ this.subscriptions.get(event).push(handler);
5111
+ return () => {
5112
+ const handlers = this.subscriptions.get(event);
5113
+ if (handlers) {
5114
+ const index = handlers.indexOf(handler);
5115
+ if (index > -1) {
5116
+ handlers.splice(index, 1);
5117
+ }
5118
+ }
5119
+ };
5120
+ }
5121
+ /**
5122
+ * Emit an event to all subscribers
5123
+ */
5124
+ async emit(event, data) {
5125
+ this.logEvent(event, data);
5126
+ const handlers = this.subscriptions.get(event) || [];
5127
+ await Promise.all(
5128
+ handlers.map(async (handler) => {
5129
+ try {
5130
+ await handler(data);
5131
+ } catch (error) {
5132
+ console.error(`Error in event handler for ${event}:`, error);
5133
+ }
5134
+ })
5135
+ );
5136
+ const wildcardHandlers = this.subscriptions.get("*") || [];
5137
+ await Promise.all(
5138
+ wildcardHandlers.map(async (handler) => {
5139
+ try {
5140
+ await handler({ event, data });
5141
+ } catch (error) {
5142
+ console.error(`Error in wildcard event handler for ${event}:`, error);
5143
+ }
5144
+ })
5145
+ );
5146
+ }
5147
+ /**
5148
+ * Remove all subscribers for an event
5149
+ */
5150
+ off(event) {
5151
+ this.subscriptions.delete(event);
5152
+ }
5153
+ /**
5154
+ * Get all registered events
5155
+ */
5156
+ getEvents() {
5157
+ return Array.from(this.subscriptions.keys());
5158
+ }
5159
+ /**
5160
+ * Get subscriber count for an event
5161
+ */
5162
+ getSubscriberCount(event) {
5163
+ return this.subscriptions.get(event)?.length || 0;
5164
+ }
5165
+ /**
5166
+ * Log an event for debugging
5167
+ */
5168
+ logEvent(event, data) {
5169
+ this.eventLog.push({
5170
+ event,
5171
+ timestamp: Date.now(),
5172
+ data
5173
+ });
5174
+ if (this.eventLog.length > this.maxLogSize) {
5175
+ this.eventLog.shift();
5176
+ }
5177
+ }
5178
+ /**
5179
+ * Get recent event log
5180
+ */
5181
+ getEventLog(limit = 50) {
5182
+ return this.eventLog.slice(-limit);
5183
+ }
5184
+ /**
5185
+ * Clear event log
5186
+ */
5187
+ clearEventLog() {
5188
+ this.eventLog = [];
5189
+ }
5190
+ /**
5191
+ * Get statistics
5192
+ */
5193
+ getStats() {
5194
+ const eventCounts = {};
5195
+ for (const log of this.eventLog) {
5196
+ eventCounts[log.event] = (eventCounts[log.event] || 0) + 1;
5197
+ }
5198
+ return {
5199
+ totalEvents: this.eventLog.length,
5200
+ totalSubscriptions: this.subscriptions.size,
5201
+ eventCounts
5202
+ };
5203
+ }
5204
+ };
5205
+ var globalEventBus = null;
5206
+ function getEventBus() {
5207
+ if (!globalEventBus) {
5208
+ globalEventBus = new EventBus();
5209
+ }
5210
+ return globalEventBus;
5211
+ }
5212
+ function onEvent(event, handler) {
5213
+ const bus = getEventBus();
5214
+ return bus.on(event, handler);
5215
+ }
5216
+
5217
+ // src/plugins/cache/services/cache-invalidation.ts
5218
+ function setupCacheInvalidation() {
5219
+ getEventBus();
5220
+ setupContentInvalidation();
5221
+ setupUserInvalidation();
5222
+ setupConfigInvalidation();
5223
+ setupMediaInvalidation();
5224
+ setupAPIInvalidation();
5225
+ setupCollectionInvalidation();
5226
+ console.log("Cache invalidation listeners registered");
5227
+ }
5228
+ function setupContentInvalidation() {
5229
+ const config = CACHE_CONFIGS.content;
5230
+ if (!config) return;
5231
+ const contentCache = getCacheService(config);
5232
+ onEvent("content.create", async (_data) => {
5233
+ await contentCache.invalidate("content:*");
5234
+ console.log("Cache invalidated: content.create");
5235
+ });
5236
+ onEvent("content.update", async (data) => {
5237
+ if (data?.id) {
5238
+ await contentCache.delete(contentCache.generateKey("item", data.id));
5239
+ }
5240
+ await contentCache.invalidate("content:list:*");
5241
+ console.log("Cache invalidated: content.update", data?.id);
5242
+ });
5243
+ onEvent("content.delete", async (data) => {
5244
+ if (data?.id) {
5245
+ await contentCache.delete(contentCache.generateKey("item", data.id));
5246
+ }
5247
+ await contentCache.invalidate("content:*");
5248
+ console.log("Cache invalidated: content.delete", data?.id);
5249
+ });
5250
+ onEvent("content.publish", async (_data) => {
5251
+ await contentCache.invalidate("content:*");
5252
+ console.log("Cache invalidated: content.publish");
5253
+ });
5254
+ }
5255
+ function setupUserInvalidation() {
5256
+ const config = CACHE_CONFIGS.user;
5257
+ if (!config) return;
5258
+ const userCache = getCacheService(config);
5259
+ onEvent("user.update", async (data) => {
5260
+ if (data?.id) {
5261
+ await userCache.delete(userCache.generateKey("id", data.id));
5262
+ }
5263
+ if (data?.email) {
5264
+ await userCache.delete(userCache.generateKey("email", data.email));
5265
+ }
5266
+ console.log("Cache invalidated: user.update", data?.id);
5267
+ });
5268
+ onEvent("user.delete", async (data) => {
5269
+ if (data?.id) {
5270
+ await userCache.delete(userCache.generateKey("id", data.id));
5271
+ }
5272
+ if (data?.email) {
5273
+ await userCache.delete(userCache.generateKey("email", data.email));
5274
+ }
5275
+ console.log("Cache invalidated: user.delete", data?.id);
5276
+ });
5277
+ onEvent("auth.login", async (data) => {
5278
+ if (data?.userId) {
5279
+ await userCache.delete(userCache.generateKey("id", data.userId));
5280
+ }
5281
+ console.log("Cache invalidated: auth.login", data?.userId);
5282
+ });
5283
+ onEvent("auth.logout", async (data) => {
5284
+ const sessionConfig = CACHE_CONFIGS.session;
5285
+ if (sessionConfig) {
5286
+ const sessionCache = getCacheService(sessionConfig);
5287
+ if (data?.sessionId) {
5288
+ await sessionCache.delete(sessionCache.generateKey("session", data.sessionId));
5289
+ }
5290
+ }
5291
+ console.log("Cache invalidated: auth.logout");
5292
+ });
5293
+ }
5294
+ function setupConfigInvalidation() {
5295
+ const configConfig = CACHE_CONFIGS.config;
5296
+ if (!configConfig) return;
5297
+ const configCache = getCacheService(configConfig);
5298
+ onEvent("config.update", async (_data) => {
5299
+ await configCache.invalidate("config:*");
5300
+ console.log("Cache invalidated: config.update");
5301
+ });
5302
+ onEvent("plugin.activate", async (data) => {
5303
+ await configCache.invalidate("config:*");
5304
+ const pluginConfig = CACHE_CONFIGS.plugin;
5305
+ if (pluginConfig) {
5306
+ const pluginCache = getCacheService(pluginConfig);
5307
+ await pluginCache.invalidate("plugin:*");
5308
+ }
5309
+ console.log("Cache invalidated: plugin.activate", data?.pluginId);
5310
+ });
5311
+ onEvent("plugin.deactivate", async (data) => {
5312
+ await configCache.invalidate("config:*");
5313
+ const pluginConfig = CACHE_CONFIGS.plugin;
5314
+ if (pluginConfig) {
5315
+ const pluginCache = getCacheService(pluginConfig);
5316
+ await pluginCache.invalidate("plugin:*");
5317
+ }
5318
+ console.log("Cache invalidated: plugin.deactivate", data?.pluginId);
5319
+ });
5320
+ onEvent("plugin.update", async (data) => {
5321
+ const pluginConfig = CACHE_CONFIGS.plugin;
5322
+ if (!pluginConfig) return;
5323
+ const pluginCache = getCacheService(pluginConfig);
5324
+ await pluginCache.invalidate("plugin:*");
5325
+ console.log("Cache invalidated: plugin.update", data?.pluginId);
5326
+ });
5327
+ }
5328
+ function setupMediaInvalidation() {
5329
+ const config = CACHE_CONFIGS.media;
5330
+ if (!config) return;
5331
+ const mediaCache = getCacheService(config);
5332
+ onEvent("media.upload", async (_data) => {
5333
+ await mediaCache.invalidate("media:*");
5334
+ console.log("Cache invalidated: media.upload");
5335
+ });
5336
+ onEvent("media.delete", async (data) => {
5337
+ if (data?.id) {
5338
+ await mediaCache.delete(mediaCache.generateKey("item", data.id));
5339
+ }
5340
+ await mediaCache.invalidate("media:list:*");
5341
+ console.log("Cache invalidated: media.delete", data?.id);
5342
+ });
5343
+ onEvent("media.update", async (data) => {
5344
+ if (data?.id) {
5345
+ await mediaCache.delete(mediaCache.generateKey("item", data.id));
5346
+ }
5347
+ await mediaCache.invalidate("media:list:*");
5348
+ console.log("Cache invalidated: media.update", data?.id);
5349
+ });
5350
+ }
5351
+ function setupAPIInvalidation() {
5352
+ const config = CACHE_CONFIGS.api;
5353
+ if (!config) return;
5354
+ const apiCache = getCacheService(config);
5355
+ onEvent("content.update", async (_data) => {
5356
+ await apiCache.invalidate("api:*");
5357
+ console.log("Cache invalidated: api (content.update)");
5358
+ });
5359
+ onEvent("content.publish", async (_data) => {
5360
+ await apiCache.invalidate("api:*");
5361
+ console.log("Cache invalidated: api (content.publish)");
5362
+ });
5363
+ onEvent("content.create", async (_data) => {
5364
+ await apiCache.invalidate("api:*");
5365
+ console.log("Cache invalidated: api (content.create)");
5366
+ });
5367
+ onEvent("content.delete", async (_data) => {
5368
+ await apiCache.invalidate("api:*");
5369
+ console.log("Cache invalidated: api (content.delete)");
5370
+ });
5371
+ onEvent("collection.update", async (_data) => {
5372
+ await apiCache.invalidate("api:*");
5373
+ console.log("Cache invalidated: api (collection.update)");
5374
+ });
5375
+ }
5376
+ function setupCollectionInvalidation() {
5377
+ const config = CACHE_CONFIGS.collection;
5378
+ if (!config) return;
5379
+ const collectionCache = getCacheService(config);
5380
+ onEvent("collection.create", async (_data) => {
5381
+ await collectionCache.invalidate("collection:*");
5382
+ console.log("Cache invalidated: collection.create");
5383
+ });
5384
+ onEvent("collection.update", async (data) => {
5385
+ if (data?.id) {
5386
+ await collectionCache.delete(collectionCache.generateKey("item", data.id));
5387
+ }
5388
+ await collectionCache.invalidate("collection:*");
5389
+ console.log("Cache invalidated: collection.update", data?.id);
5390
+ });
5391
+ onEvent("collection.delete", async (data) => {
5392
+ await collectionCache.invalidate("collection:*");
5393
+ console.log("Cache invalidated: collection.delete", data?.id);
5394
+ });
5395
+ }
5396
+ function getCacheInvalidationStats() {
5397
+ const eventBus = getEventBus();
5398
+ return eventBus.getStats();
5399
+ }
5400
+ function getRecentInvalidations(limit = 50) {
5401
+ const eventBus = getEventBus();
5402
+ return eventBus.getEventLog(limit);
5403
+ }
5404
+
5405
+ // src/plugins/cache/services/cache-warming.ts
5406
+ async function warmCommonCaches(db) {
5407
+ let totalWarmed = 0;
5408
+ let totalErrors = 0;
5409
+ const details = [];
5410
+ try {
5411
+ const collectionCount = await warmCollections(db);
5412
+ totalWarmed += collectionCount;
5413
+ details.push({ namespace: "collection", count: collectionCount });
5414
+ const contentCount = await warmRecentContent(db);
5415
+ totalWarmed += contentCount;
5416
+ details.push({ namespace: "content", count: contentCount });
5417
+ const mediaCount = await warmRecentMedia(db);
5418
+ totalWarmed += mediaCount;
5419
+ details.push({ namespace: "media", count: mediaCount });
5420
+ } catch (error) {
5421
+ console.error("Error warming caches:", error);
5422
+ totalErrors++;
5423
+ }
5424
+ return {
5425
+ warmed: totalWarmed,
5426
+ errors: totalErrors,
5427
+ details
5428
+ };
5429
+ }
5430
+ async function warmCollections(db) {
5431
+ const config = CACHE_CONFIGS.collection;
5432
+ if (!config) return 0;
5433
+ const collectionCache = getCacheService(config);
5434
+ let count = 0;
5435
+ try {
5436
+ const stmt = db.prepare("SELECT * FROM collections WHERE is_active = 1");
5437
+ const { results } = await stmt.all();
5438
+ for (const collection of results) {
5439
+ const key = collectionCache.generateKey("item", collection.id);
5440
+ await collectionCache.set(key, collection);
5441
+ count++;
5442
+ }
5443
+ const listKey = collectionCache.generateKey("list", "all");
5444
+ await collectionCache.set(listKey, results);
5445
+ count++;
5446
+ } catch (error) {
5447
+ console.error("Error warming collections cache:", error);
5448
+ }
5449
+ return count;
5450
+ }
5451
+ async function warmRecentContent(db, limit = 50) {
5452
+ const config = CACHE_CONFIGS.content;
5453
+ if (!config) return 0;
5454
+ const contentCache = getCacheService(config);
5455
+ let count = 0;
5456
+ try {
5457
+ const stmt = db.prepare(`SELECT * FROM content ORDER BY created_at DESC LIMIT ${limit}`);
5458
+ const { results } = await stmt.all();
5459
+ for (const content2 of results) {
5460
+ const key = contentCache.generateKey("item", content2.id);
5461
+ await contentCache.set(key, content2);
5462
+ count++;
5463
+ }
5464
+ const listKey = contentCache.generateKey("list", "recent");
5465
+ await contentCache.set(listKey, results);
5466
+ count++;
5467
+ } catch (error) {
5468
+ console.error("Error warming content cache:", error);
5469
+ }
5470
+ return count;
5471
+ }
5472
+ async function warmRecentMedia(db, limit = 50) {
5473
+ const config = CACHE_CONFIGS.media;
5474
+ if (!config) return 0;
5475
+ const mediaCache = getCacheService(config);
5476
+ let count = 0;
5477
+ try {
5478
+ const stmt = db.prepare(`SELECT * FROM media WHERE deleted_at IS NULL ORDER BY uploaded_at DESC LIMIT ${limit}`);
5479
+ const { results } = await stmt.all();
5480
+ for (const media2 of results) {
5481
+ const key = mediaCache.generateKey("item", media2.id);
5482
+ await mediaCache.set(key, media2);
5483
+ count++;
5484
+ }
5485
+ const listKey = mediaCache.generateKey("list", "recent");
5486
+ await mediaCache.set(listKey, results);
5487
+ count++;
5488
+ } catch (error) {
5489
+ console.error("Error warming media cache:", error);
5490
+ }
5491
+ return count;
5492
+ }
5493
+ async function warmNamespace(namespace, entries) {
5494
+ const config = CACHE_CONFIGS[namespace];
5495
+ if (!config) {
5496
+ throw new Error(`Unknown namespace: ${namespace}`);
5497
+ }
5498
+ const cache = getCacheService(config);
5499
+ await cache.setMany(entries);
5500
+ return entries.length;
5501
+ }
5502
+
5503
+ // src/templates/pages/admin-cache.template.ts
5504
+ init_admin_layout_catalyst_template();
5505
+ function renderCacheDashboard(data) {
5506
+ const pageContent = `
5507
+ <div class="space-y-6">
5508
+ <!-- Header -->
5509
+ <div class="flex items-center justify-between">
5510
+ <div>
5511
+ <h1 class="text-2xl font-semibold text-zinc-950 dark:text-white">Cache System</h1>
5512
+ <p class="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
5513
+ Monitor and manage cache performance across all namespaces
5514
+ </p>
5515
+ </div>
5516
+ <div class="flex gap-3">
5517
+ <button
5518
+ onclick="refreshStats()"
5519
+ 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"
5520
+ >
5521
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5522
+ <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"/>
5523
+ </svg>
5524
+ Refresh
5525
+ </button>
5526
+ <button
5527
+ onclick="clearAllCaches()"
5528
+ 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"
5529
+ >
5530
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5531
+ <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"/>
5532
+ </svg>
5533
+ Clear All
5534
+ </button>
5535
+ </div>
5536
+ </div>
5537
+
5538
+ <!-- Overall Stats Cards -->
5539
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
5540
+ ${renderStatCard("Total Requests", data.totals.requests.toLocaleString(), "lime", `
5541
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5542
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
5543
+ </svg>
5544
+ `)}
5545
+
5546
+ ${renderStatCard("Hit Rate", data.totals.hitRate + "%", "blue", `
5547
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5548
+ <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"/>
5549
+ </svg>
5550
+ `, parseFloat(data.totals.hitRate) > 70 ? "lime" : parseFloat(data.totals.hitRate) > 40 ? "amber" : "red")}
5551
+
5552
+ ${renderStatCard("Memory Usage", formatBytes(data.totals.memorySize), "purple", `
5553
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5554
+ <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"/>
5555
+ </svg>
5556
+ `)}
5557
+
5558
+ ${renderStatCard("Cached Entries", data.totals.entryCount.toLocaleString(), "sky", `
5559
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5560
+ <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"/>
5561
+ </svg>
5562
+ `)}
5563
+ </div>
5564
+
5565
+ <!-- Namespace Statistics -->
5566
+ <div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
5567
+ <div class="px-6 py-4 border-b border-zinc-950/5 dark:border-white/10">
5568
+ <h2 class="text-lg font-semibold text-zinc-950 dark:text-white">Cache Namespaces</h2>
5569
+ </div>
5570
+ <div class="overflow-x-auto">
5571
+ <table class="min-w-full divide-y divide-zinc-950/5 dark:divide-white/10">
5572
+ <thead class="bg-zinc-50 dark:bg-zinc-800/50">
5573
+ <tr>
5574
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5575
+ Namespace
5576
+ </th>
5577
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5578
+ Requests
5579
+ </th>
5580
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5581
+ Hit Rate
5582
+ </th>
5583
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5584
+ Memory Hits
5585
+ </th>
5586
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5587
+ KV Hits
5588
+ </th>
5589
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5590
+ Entries
5591
+ </th>
5592
+ <th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5593
+ Size
5594
+ </th>
5595
+ <th class="px-6 py-3 text-right text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">
5596
+ Actions
5597
+ </th>
5598
+ </tr>
5599
+ </thead>
5600
+ <tbody class="divide-y divide-zinc-950/5 dark:divide-white/10">
5601
+ ${data.namespaces.map((namespace) => {
5602
+ const stat = data.stats[namespace];
5603
+ if (!stat) return "";
5604
+ return renderNamespaceRow(namespace, stat);
5605
+ }).join("")}
5606
+ </tbody>
5607
+ </table>
5608
+ </div>
5609
+ </div>
5610
+
5611
+ <!-- Performance Chart Placeholder -->
5612
+ <div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
5613
+ <div class="px-6 py-4 border-b border-zinc-950/5 dark:border-white/10">
5614
+ <h2 class="text-lg font-semibold text-zinc-950 dark:text-white">Performance Overview</h2>
5615
+ </div>
5616
+ <div class="p-6">
5617
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
5618
+ ${renderPerformanceMetric("Memory Cache", data.totals.hits, data.totals.misses)}
5619
+ ${renderHealthStatus(parseFloat(data.totals.hitRate))}
5620
+ </div>
5621
+ </div>
5622
+ </div>
5623
+ </div>
5624
+
5625
+ <script>
5626
+ async function refreshStats() {
5627
+ window.location.reload()
5628
+ }
5629
+
5630
+ async function clearAllCaches() {
5631
+ showConfirmDialog('clear-all-cache-confirm')
5632
+ }
5633
+
5634
+ async function performClearAllCaches() {
5635
+ try {
5636
+ const response = await fetch('/admin/cache/clear', {
5637
+ method: 'POST'
5638
+ })
5639
+
5640
+ const result = await response.json()
5641
+ if (result.success) {
5642
+ alert('All caches cleared successfully')
5643
+ window.location.reload()
5644
+ } else {
5645
+ alert('Error clearing caches: ' + result.error)
5646
+ }
5647
+ } catch (error) {
5648
+ alert('Error clearing caches: ' + error.message)
5649
+ }
5650
+ }
5651
+
5652
+ let namespaceToDelete = null
5653
+
5654
+ async function clearNamespaceCache(namespace) {
5655
+ namespaceToDelete = namespace
5656
+ showConfirmDialog('clear-namespace-cache-confirm')
5657
+ }
5658
+
5659
+ async function performClearNamespaceCache() {
5660
+ if (!namespaceToDelete) return
5661
+
5662
+ try {
5663
+ const response = await fetch(\`/admin/cache/clear/\${namespaceToDelete}\`, {
5664
+ method: 'POST'
5665
+ })
5666
+
5667
+ const result = await response.json()
5668
+ if (result.success) {
5669
+ alert('Cache cleared successfully')
5670
+ window.location.reload()
5671
+ } else {
5672
+ alert('Error clearing cache: ' + result.error)
5673
+ }
5674
+ } catch (error) {
5675
+ alert('Error clearing cache: ' + error.message)
5676
+ } finally {
5677
+ namespaceToDelete = null
5678
+ }
5679
+ }
5680
+ </script>
5681
+
5682
+ <!-- Confirmation Dialogs -->
5683
+ ${renderConfirmationDialog({
5684
+ id: "clear-all-cache-confirm",
5685
+ title: "Clear All Cache",
5686
+ message: "Are you sure you want to clear all cache entries? This cannot be undone.",
5687
+ confirmText: "Clear All",
5688
+ cancelText: "Cancel",
5689
+ iconColor: "yellow",
5690
+ confirmClass: "bg-yellow-500 hover:bg-yellow-400",
5691
+ onConfirm: "performClearAllCaches()"
5692
+ })}
5693
+
5694
+ ${renderConfirmationDialog({
5695
+ id: "clear-namespace-cache-confirm",
5696
+ title: "Clear Namespace Cache",
5697
+ message: "Clear cache for this namespace?",
5698
+ confirmText: "Clear",
5699
+ cancelText: "Cancel",
5700
+ iconColor: "yellow",
5701
+ confirmClass: "bg-yellow-500 hover:bg-yellow-400",
5702
+ onConfirm: "performClearNamespaceCache()"
5703
+ })}
5704
+
5705
+ ${getConfirmationDialogScript()}
5706
+ `;
5707
+ const layoutData = {
5708
+ title: "Cache System",
5709
+ pageTitle: "Cache System",
5710
+ currentPath: "/admin/cache",
5711
+ user: data.user,
5712
+ version: data.version,
5713
+ content: pageContent
5714
+ };
5715
+ return renderAdminLayoutCatalyst(layoutData);
5716
+ }
5717
+ function renderStatCard(label, value, color, icon, colorOverride) {
5718
+ const finalColor = colorOverride || color;
5719
+ const colorClasses = {
5720
+ 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",
5721
+ 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",
5722
+ 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",
5723
+ 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",
5724
+ 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",
5725
+ 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"
5726
+ };
5727
+ return `
5728
+ <div class="overflow-hidden rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10">
5729
+ <div class="p-6">
5730
+ <div class="flex items-center justify-between">
5731
+ <div class="flex items-center gap-3">
5732
+ <div class="rounded-lg p-2 ring-1 ring-inset ${colorClasses[finalColor]}">
5733
+ ${icon}
5734
+ </div>
5735
+ <div>
5736
+ <p class="text-sm text-zinc-600 dark:text-zinc-400">${label}</p>
5737
+ <p class="mt-1 text-2xl font-semibold text-zinc-950 dark:text-white">${value}</p>
5738
+ </div>
5739
+ </div>
5740
+ </div>
5741
+ </div>
5742
+ </div>
5743
+ `;
5744
+ }
5745
+ function renderNamespaceRow(namespace, stat) {
5746
+ const hitRate = stat.hitRate.toFixed(1);
5747
+ 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";
5748
+ return `
5749
+ <tr class="hover:bg-zinc-50 dark:hover:bg-zinc-800/50">
5750
+ <td class="px-6 py-4 whitespace-nowrap">
5751
+ <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">
5752
+ ${namespace}
5753
+ </span>
5754
+ </td>
5755
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-900 dark:text-zinc-100">
5756
+ ${stat.totalRequests.toLocaleString()}
5757
+ </td>
5758
+ <td class="px-6 py-4 whitespace-nowrap">
5759
+ <span class="text-sm font-medium ${hitRateColor}">
5760
+ ${hitRate}%
5761
+ </span>
5762
+ </td>
5763
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
5764
+ ${stat.memoryHits.toLocaleString()}
5765
+ </td>
5766
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
5767
+ ${stat.kvHits.toLocaleString()}
5768
+ </td>
5769
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
5770
+ ${stat.entryCount.toLocaleString()}
5771
+ </td>
5772
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-600 dark:text-zinc-400">
5773
+ ${formatBytes(stat.memorySize)}
5774
+ </td>
5775
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm">
5776
+ <button
5777
+ onclick="clearNamespaceCache('${namespace}')"
5778
+ class="text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300"
5779
+ >
5780
+ Clear
5781
+ </button>
5782
+ </td>
5783
+ </tr>
5784
+ `;
5785
+ }
5786
+ function renderPerformanceMetric(label, hits, misses) {
5787
+ const total = hits + misses;
5788
+ const hitPercentage = total > 0 ? hits / total * 100 : 0;
5789
+ return `
5790
+ <div>
5791
+ <h3 class="text-sm font-medium text-zinc-900 dark:text-zinc-100 mb-3">${label}</h3>
5792
+ <div class="space-y-2">
5793
+ <div class="flex items-center justify-between text-sm">
5794
+ <span class="text-zinc-600 dark:text-zinc-400">Hits</span>
5795
+ <span class="font-medium text-zinc-900 dark:text-zinc-100">${hits.toLocaleString()}</span>
5796
+ </div>
5797
+ <div class="flex items-center justify-between text-sm">
5798
+ <span class="text-zinc-600 dark:text-zinc-400">Misses</span>
5799
+ <span class="font-medium text-zinc-900 dark:text-zinc-100">${misses.toLocaleString()}</span>
5800
+ </div>
5801
+ <div class="mt-3">
5802
+ <div class="flex items-center justify-between text-sm mb-1">
5803
+ <span class="text-zinc-600 dark:text-zinc-400">Hit Rate</span>
5804
+ <span class="font-medium text-zinc-900 dark:text-zinc-100">${hitPercentage.toFixed(1)}%</span>
5805
+ </div>
5806
+ <div class="h-2 bg-zinc-200 dark:bg-zinc-700 rounded-full overflow-hidden">
5807
+ <div class="h-full bg-lime-500 dark:bg-lime-400" style="width: ${hitPercentage}%"></div>
5808
+ </div>
5809
+ </div>
5810
+ </div>
5811
+ </div>
5812
+ `;
5813
+ }
5814
+ function renderHealthStatus(hitRate) {
5815
+ const status = hitRate > 70 ? "healthy" : hitRate > 40 ? "warning" : "critical";
5816
+ const statusConfig = {
5817
+ healthy: {
5818
+ label: "Healthy",
5819
+ color: "lime",
5820
+ icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5821
+ <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"/>
5822
+ </svg>`
5823
+ },
5824
+ warning: {
5825
+ label: "Needs Attention",
5826
+ color: "amber",
5827
+ icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5828
+ <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"/>
5829
+ </svg>`
5830
+ },
5831
+ critical: {
5832
+ label: "Critical",
5833
+ color: "red",
5834
+ icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5835
+ <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"/>
5836
+ </svg>`
5837
+ }
5838
+ };
5839
+ const config = statusConfig[status];
5840
+ const colorClasses = {
5841
+ 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",
5842
+ 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",
5843
+ 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"
5844
+ };
5845
+ return `
5846
+ <div>
5847
+ <h3 class="text-sm font-medium text-zinc-900 dark:text-zinc-100 mb-3">System Health</h3>
5848
+ <div class="flex items-center gap-3 p-4 rounded-lg ring-1 ring-inset ${colorClasses[config.color]}">
5849
+ ${config.icon}
5850
+ <div>
5851
+ <p class="text-sm font-medium">${config.label}</p>
5852
+ <p class="text-xs mt-0.5 opacity-80">
5853
+ ${status === "healthy" ? "Cache is performing well" : status === "warning" ? "Consider increasing cache TTL or capacity" : "Cache hit rate is too low"}
5854
+ </p>
5855
+ </div>
5856
+ </div>
5857
+ </div>
5858
+ `;
5859
+ }
5860
+ function formatBytes(bytes) {
5861
+ if (bytes === 0) return "0 B";
5862
+ const k = 1024;
5863
+ const sizes = ["B", "KB", "MB", "GB"];
5864
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
5865
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
5866
+ }
5867
+
5868
+ // src/plugins/cache/routes.ts
5869
+ var app = new Hono();
5870
+ app.get("/", async (c) => {
5871
+ const stats = getAllCacheStats();
5872
+ const user = c.get("user");
5873
+ let totalHits = 0;
5874
+ let totalMisses = 0;
5875
+ let totalSize = 0;
5876
+ let totalEntries = 0;
5877
+ Object.values(stats).forEach((stat) => {
5878
+ totalHits += stat.memoryHits + stat.kvHits;
5879
+ totalMisses += stat.memoryMisses + stat.kvMisses;
5880
+ totalSize += stat.memorySize;
5881
+ totalEntries += stat.entryCount;
5882
+ });
5883
+ const totalRequests = totalHits + totalMisses;
5884
+ const overallHitRate = totalRequests > 0 ? totalHits / totalRequests * 100 : 0;
5885
+ const dashboardData = {
5886
+ stats,
5887
+ totals: {
5888
+ hits: totalHits,
5889
+ misses: totalMisses,
5890
+ requests: totalRequests,
5891
+ hitRate: overallHitRate.toFixed(2),
5892
+ memorySize: totalSize,
5893
+ entryCount: totalEntries
5894
+ },
5895
+ namespaces: Object.keys(stats),
5896
+ user: user ? {
5897
+ name: user.email,
5898
+ email: user.email,
5899
+ role: user.role
5900
+ } : void 0,
5901
+ version: c.get("appVersion")
5902
+ };
5903
+ return c.html(renderCacheDashboard(dashboardData));
5904
+ });
5905
+ app.get("/stats", async (c) => {
5906
+ const stats = getAllCacheStats();
5907
+ return c.json({
5908
+ success: true,
5909
+ data: stats,
5910
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5911
+ });
5912
+ });
5913
+ app.get("/stats/:namespace", async (c) => {
5914
+ const namespace = c.req.param("namespace");
5915
+ const config = CACHE_CONFIGS[namespace];
5916
+ if (!config) {
5917
+ return c.json({
5918
+ success: false,
5919
+ error: `Unknown namespace: ${namespace}`
5920
+ }, 404);
5921
+ }
5922
+ const cache = getCacheService(config);
5923
+ const stats = cache.getStats();
5924
+ return c.json({
5925
+ success: true,
5926
+ data: {
5927
+ namespace,
5928
+ config,
5929
+ stats
5930
+ },
5931
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5932
+ });
5933
+ });
5934
+ app.post("/clear", async (c) => {
5935
+ await clearAllCaches();
5936
+ return c.json({
5937
+ success: true,
5938
+ message: "All cache entries cleared",
5939
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5940
+ });
5941
+ });
5942
+ app.post("/clear/:namespace", async (c) => {
5943
+ const namespace = c.req.param("namespace");
5944
+ const config = CACHE_CONFIGS[namespace];
5945
+ if (!config) {
5946
+ return c.json({
5947
+ success: false,
5948
+ error: `Unknown namespace: ${namespace}`
5949
+ }, 404);
5950
+ }
5951
+ const cache = getCacheService(config);
5952
+ await cache.clear();
5953
+ return c.json({
5954
+ success: true,
5955
+ message: `Cache cleared for namespace: ${namespace}`,
5956
+ namespace,
5957
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5958
+ });
5959
+ });
5960
+ app.post("/invalidate", async (c) => {
5961
+ const body = await c.req.json();
5962
+ const { pattern, namespace } = body;
5963
+ if (!pattern) {
5964
+ return c.json({
5965
+ success: false,
5966
+ error: "Pattern is required"
5967
+ }, 400);
5968
+ }
5969
+ let totalInvalidated = 0;
5970
+ if (namespace) {
5971
+ const config = CACHE_CONFIGS[namespace];
5972
+ if (!config) {
5973
+ return c.json({
5974
+ success: false,
5975
+ error: `Unknown namespace: ${namespace}`
5976
+ }, 404);
5977
+ }
5978
+ const cache = getCacheService(config);
5979
+ totalInvalidated = await cache.invalidate(pattern);
5980
+ } else {
5981
+ for (const config of Object.values(CACHE_CONFIGS)) {
5982
+ const cache = getCacheService(config);
5983
+ totalInvalidated += await cache.invalidate(pattern);
5984
+ }
5985
+ }
5986
+ return c.json({
5987
+ success: true,
5988
+ invalidated: totalInvalidated,
5989
+ pattern,
5990
+ namespace: namespace || "all",
5991
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5992
+ });
5993
+ });
5994
+ app.get("/health", async (c) => {
5995
+ const stats = getAllCacheStats();
5996
+ const namespaces = Object.entries(stats);
5997
+ const healthChecks = namespaces.map(([name, stat]) => {
5998
+ const hitRate = stat.hitRate;
5999
+ const memoryUsage = stat.memorySize / (50 * 1024 * 1024);
6000
+ return {
6001
+ namespace: name,
6002
+ status: hitRate > 70 ? "healthy" : hitRate > 40 ? "warning" : "unhealthy",
6003
+ hitRate,
6004
+ memoryUsage: (memoryUsage * 100).toFixed(2) + "%",
6005
+ entryCount: stat.entryCount
6006
+ };
6007
+ });
6008
+ const overallStatus = healthChecks.every((h) => h.status === "healthy") ? "healthy" : healthChecks.some((h) => h.status === "unhealthy") ? "unhealthy" : "warning";
6009
+ return c.json({
6010
+ success: true,
6011
+ data: {
6012
+ status: overallStatus,
6013
+ namespaces: healthChecks,
6014
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6015
+ }
6016
+ });
6017
+ });
6018
+ app.get("/browser", async (c) => {
6019
+ const namespace = c.req.query("namespace") || "all";
6020
+ const search = c.req.query("search") || "";
6021
+ const sortBy = c.req.query("sort") || "age";
6022
+ const limit = parseInt(c.req.query("limit") || "100");
6023
+ const entries = [];
6024
+ const namespaces = namespace === "all" ? Object.keys(CACHE_CONFIGS) : [namespace];
6025
+ for (const ns of namespaces) {
6026
+ const config = CACHE_CONFIGS[ns];
6027
+ if (!config) continue;
6028
+ const cache = getCacheService(config);
6029
+ const keys = await cache.listKeys();
6030
+ for (const keyInfo of keys) {
6031
+ if (search && !keyInfo.key.toLowerCase().includes(search.toLowerCase())) {
6032
+ continue;
6033
+ }
6034
+ const parsed = parseCacheKey(keyInfo.key);
6035
+ const ttl = Math.max(0, keyInfo.expiresAt - Date.now()) / 1e3;
6036
+ entries.push({
6037
+ namespace: ns,
6038
+ key: keyInfo.key,
6039
+ size: keyInfo.size,
6040
+ age: keyInfo.age,
6041
+ ttl,
6042
+ expiresAt: keyInfo.expiresAt,
6043
+ parsed
6044
+ });
6045
+ }
6046
+ }
6047
+ if (sortBy === "size") {
6048
+ entries.sort((a, b) => b.size - a.size);
6049
+ } else if (sortBy === "age") {
6050
+ entries.sort((a, b) => a.age - b.age);
6051
+ } else if (sortBy === "key") {
6052
+ entries.sort((a, b) => a.key.localeCompare(b.key));
6053
+ }
6054
+ const limitedEntries = entries.slice(0, limit);
6055
+ return c.json({
6056
+ success: true,
6057
+ data: {
6058
+ entries: limitedEntries,
6059
+ total: entries.length,
6060
+ showing: limitedEntries.length,
6061
+ namespace,
6062
+ search,
6063
+ sortBy
6064
+ },
6065
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6066
+ });
6067
+ });
6068
+ app.get("/browser/:namespace/:key", async (c) => {
6069
+ const namespace = c.req.param("namespace");
6070
+ const key = decodeURIComponent(c.req.param("key"));
6071
+ const config = CACHE_CONFIGS[namespace];
6072
+ if (!config) {
6073
+ return c.json({
6074
+ success: false,
6075
+ error: `Unknown namespace: ${namespace}`
6076
+ }, 404);
6077
+ }
6078
+ const cache = getCacheService(config);
6079
+ const entry = await cache.getEntry(key);
6080
+ if (!entry) {
6081
+ return c.json({
6082
+ success: false,
6083
+ error: "Cache entry not found or expired"
6084
+ }, 404);
6085
+ }
6086
+ const parsed = parseCacheKey(key);
6087
+ return c.json({
6088
+ success: true,
6089
+ data: {
6090
+ key,
6091
+ namespace,
6092
+ parsed,
6093
+ ...entry,
6094
+ createdAt: new Date(entry.timestamp).toISOString(),
6095
+ expiresAt: new Date(entry.expiresAt).toISOString()
6096
+ },
6097
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6098
+ });
6099
+ });
6100
+ app.get("/analytics", async (c) => {
6101
+ const stats = getAllCacheStats();
6102
+ const invalidationStats = getCacheInvalidationStats();
6103
+ const recentInvalidations = getRecentInvalidations(20);
6104
+ let totalHits = 0;
6105
+ let totalMisses = 0;
6106
+ let totalSize = 0;
6107
+ let totalEntries = 0;
6108
+ const namespacesAnalytics = [];
6109
+ for (const [namespace, stat] of Object.entries(stats)) {
6110
+ totalHits += stat.memoryHits + stat.kvHits;
6111
+ totalMisses += stat.memoryMisses + stat.kvMisses;
6112
+ totalSize += stat.memorySize;
6113
+ totalEntries += stat.entryCount;
6114
+ const totalRequests2 = stat.memoryHits + stat.kvHits + stat.memoryMisses + stat.kvMisses;
6115
+ const hitRate = totalRequests2 > 0 ? (stat.memoryHits + stat.kvHits) / totalRequests2 * 100 : 0;
6116
+ const avgEntrySize = stat.entryCount > 0 ? stat.memorySize / stat.entryCount : 0;
6117
+ namespacesAnalytics.push({
6118
+ namespace,
6119
+ hitRate: hitRate.toFixed(2),
6120
+ totalRequests: totalRequests2,
6121
+ memoryHitRate: totalRequests2 > 0 ? (stat.memoryHits / totalRequests2 * 100).toFixed(2) : "0",
6122
+ kvHitRate: totalRequests2 > 0 ? (stat.kvHits / totalRequests2 * 100).toFixed(2) : "0",
6123
+ avgEntrySize: Math.round(avgEntrySize),
6124
+ totalSize: stat.memorySize,
6125
+ entryCount: stat.entryCount,
6126
+ efficiency: totalRequests2 > 0 ? ((stat.memoryHits + stat.kvHits) / (stat.memoryHits + stat.kvHits + stat.dbHits + 1)).toFixed(2) : "0"
6127
+ });
6128
+ }
6129
+ namespacesAnalytics.sort((a, b) => parseFloat(b.hitRate) - parseFloat(a.hitRate));
6130
+ const totalRequests = totalHits + totalMisses;
6131
+ const overallHitRate = totalRequests > 0 ? totalHits / totalRequests * 100 : 0;
6132
+ const dbQueriesAvoided = totalHits;
6133
+ const timeSaved = dbQueriesAvoided * 48;
6134
+ const estimatedCostSavings = dbQueriesAvoided / 1e6 * 0.5;
6135
+ return c.json({
6136
+ success: true,
6137
+ data: {
6138
+ overview: {
6139
+ totalHits,
6140
+ totalMisses,
6141
+ totalRequests,
6142
+ overallHitRate: overallHitRate.toFixed(2),
6143
+ totalSize,
6144
+ totalEntries,
6145
+ avgEntrySize: totalEntries > 0 ? Math.round(totalSize / totalEntries) : 0
6146
+ },
6147
+ performance: {
6148
+ dbQueriesAvoided,
6149
+ timeSavedMs: timeSaved,
6150
+ timeSavedMinutes: (timeSaved / 1e3 / 60).toFixed(2),
6151
+ estimatedCostSavings: estimatedCostSavings.toFixed(4)
6152
+ },
6153
+ namespaces: namespacesAnalytics,
6154
+ invalidation: {
6155
+ ...invalidationStats,
6156
+ recent: recentInvalidations
6157
+ }
6158
+ },
6159
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6160
+ });
6161
+ });
6162
+ app.get("/analytics/trends", async (c) => {
6163
+ const stats = getAllCacheStats();
6164
+ const dataPoint = {
6165
+ timestamp: Date.now(),
6166
+ stats: Object.entries(stats).map(([namespace, stat]) => ({
6167
+ namespace,
6168
+ hitRate: stat.hitRate,
6169
+ entryCount: stat.entryCount,
6170
+ memorySize: stat.memorySize,
6171
+ totalRequests: stat.totalRequests
6172
+ }))
6173
+ };
6174
+ return c.json({
6175
+ success: true,
6176
+ data: {
6177
+ trends: [dataPoint],
6178
+ note: "Historical trends require persistent storage. This returns current snapshot only."
6179
+ },
6180
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6181
+ });
6182
+ });
6183
+ app.get("/analytics/top-keys", async (c) => {
6184
+ c.req.query("namespace") || "all";
6185
+ parseInt(c.req.query("limit") || "10");
6186
+ return c.json({
6187
+ success: true,
6188
+ data: {
6189
+ topKeys: [],
6190
+ note: "Top keys tracking requires per-key hit counting. Feature not yet implemented."
6191
+ },
6192
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6193
+ });
6194
+ });
6195
+ app.post("/warm", async (c) => {
6196
+ try {
6197
+ const db = c.env.DB;
6198
+ const result = await warmCommonCaches(db);
6199
+ return c.json({
6200
+ success: true,
6201
+ message: "Cache warming completed",
6202
+ ...result,
6203
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6204
+ });
6205
+ } catch (error) {
6206
+ console.error("Cache warming error:", error);
6207
+ return c.json({
6208
+ success: false,
6209
+ error: "Cache warming failed",
6210
+ details: error instanceof Error ? error.message : "Unknown error"
6211
+ }, 500);
6212
+ }
6213
+ });
6214
+ app.post("/warm/:namespace", async (c) => {
6215
+ try {
6216
+ const namespace = c.req.param("namespace");
6217
+ const body = await c.req.json();
6218
+ const { entries } = body;
6219
+ if (!entries || !Array.isArray(entries)) {
6220
+ return c.json({
6221
+ success: false,
6222
+ error: "Entries array is required"
6223
+ }, 400);
6224
+ }
6225
+ const count = await warmNamespace(namespace, entries);
6226
+ return c.json({
6227
+ success: true,
6228
+ message: `Warmed ${count} entries in namespace: ${namespace}`,
6229
+ namespace,
6230
+ count,
6231
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6232
+ });
6233
+ } catch (error) {
6234
+ console.error("Namespace warming error:", error);
6235
+ return c.json({
6236
+ success: false,
6237
+ error: "Namespace warming failed",
6238
+ details: error instanceof Error ? error.message : "Unknown error"
6239
+ }, 500);
6240
+ }
6241
+ });
6242
+ var routes_default = app;
6243
+
6244
+ // src/plugins/cache/index.ts
6245
+ var CachePlugin = class {
6246
+ _context = null;
6247
+ /**
6248
+ * Get plugin routes
6249
+ */
6250
+ getRoutes() {
6251
+ return routes_default;
6252
+ }
6253
+ /**
6254
+ * Activate the cache plugin
6255
+ */
6256
+ async activate(context) {
6257
+ this._context = context;
6258
+ const settings = context.config || {};
6259
+ console.log("\u2705 Cache plugin activated", {
6260
+ memoryEnabled: settings.memoryEnabled ?? true,
6261
+ kvEnabled: settings.kvEnabled ?? false,
6262
+ defaultTTL: settings.defaultTTL ?? 3600
6263
+ });
6264
+ for (const [_namespace, config] of Object.entries(CACHE_CONFIGS)) {
6265
+ getCacheService({
6266
+ ...config,
6267
+ memoryEnabled: settings.memoryEnabled ?? config.memoryEnabled,
6268
+ kvEnabled: settings.kvEnabled ?? config.kvEnabled,
6269
+ ttl: settings.defaultTTL ?? config.ttl
6270
+ });
6271
+ }
6272
+ setupCacheInvalidation();
6273
+ }
6274
+ /**
6275
+ * Deactivate the cache plugin
6276
+ */
6277
+ async deactivate() {
6278
+ console.log("\u274C Cache plugin deactivated - clearing all caches");
6279
+ await clearAllCaches();
6280
+ this._context = null;
6281
+ }
6282
+ /**
6283
+ * Configure the cache plugin
6284
+ */
6285
+ async configure(settings) {
6286
+ console.log("\u2699\uFE0F Cache plugin configured", settings);
6287
+ for (const [_namespace, config] of Object.entries(CACHE_CONFIGS)) {
6288
+ getCacheService({
6289
+ ...config,
6290
+ memoryEnabled: settings.memoryEnabled ?? config.memoryEnabled,
6291
+ kvEnabled: settings.kvEnabled ?? config.kvEnabled,
6292
+ ttl: settings.defaultTTL ?? config.ttl
6293
+ });
6294
+ }
6295
+ }
6296
+ /**
6297
+ * Get cache statistics
6298
+ */
6299
+ async getStats(c) {
6300
+ const stats = getAllCacheStats();
6301
+ return c.json({
6302
+ success: true,
6303
+ data: stats,
6304
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6305
+ });
6306
+ }
6307
+ /**
6308
+ * Clear all cache entries
6309
+ */
6310
+ async clearCache(c) {
6311
+ await clearAllCaches();
6312
+ return c.json({
6313
+ success: true,
6314
+ message: "All cache entries cleared",
6315
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6316
+ });
6317
+ }
6318
+ /**
6319
+ * Invalidate cache entries matching pattern
6320
+ */
6321
+ async invalidatePattern(c) {
6322
+ const body = await c.req.json();
6323
+ const { pattern, namespace: _namespace } = body;
6324
+ if (!pattern) {
6325
+ return c.json({
6326
+ success: false,
6327
+ error: "Pattern is required"
6328
+ }, 400);
6329
+ }
6330
+ let totalInvalidated = 0;
6331
+ if (_namespace) {
6332
+ const cache = getCacheService(CACHE_CONFIGS[_namespace] || {
6333
+ ttl: 3600,
6334
+ kvEnabled: false,
6335
+ memoryEnabled: true,
6336
+ namespace: _namespace,
6337
+ invalidateOn: [],
6338
+ version: "v1"
6339
+ });
6340
+ totalInvalidated = await cache.invalidate(pattern);
6341
+ } else {
6342
+ for (const config of Object.values(CACHE_CONFIGS)) {
6343
+ const cache = getCacheService(config);
6344
+ totalInvalidated += await cache.invalidate(pattern);
6345
+ }
6346
+ }
6347
+ return c.json({
6348
+ success: true,
6349
+ invalidated: totalInvalidated,
6350
+ pattern,
6351
+ namespace: _namespace || "all",
6352
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6353
+ });
6354
+ }
6355
+ };
6356
+ var plugin = new CachePlugin();
6357
+ var cache_default = plugin;
6358
+
4542
6359
  // src/assets/favicon.ts
4543
6360
  var faviconSvg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
4544
6361
  <svg
@@ -4570,69 +6387,70 @@ var faviconSvg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
4570
6387
 
4571
6388
  // src/app.ts
4572
6389
  function createSonicJSApp(config = {}) {
4573
- const app = new Hono();
6390
+ const app2 = new Hono();
4574
6391
  const appVersion = config.version || getCoreVersion();
4575
6392
  const appName = config.name || "SonicJS AI";
4576
- app.use("*", async (c, next) => {
6393
+ app2.use("*", async (c, next) => {
4577
6394
  c.set("appVersion", appVersion);
4578
6395
  await next();
4579
6396
  });
4580
- app.use("*", metricsMiddleware());
4581
- app.use("*", bootstrapMiddleware(config));
6397
+ app2.use("*", metricsMiddleware());
6398
+ app2.use("*", bootstrapMiddleware(config));
4582
6399
  if (config.middleware?.beforeAuth) {
4583
6400
  for (const middleware of config.middleware.beforeAuth) {
4584
- app.use("*", middleware);
6401
+ app2.use("*", middleware);
4585
6402
  }
4586
6403
  }
4587
- app.use("*", async (_c, next) => {
6404
+ app2.use("*", async (_c, next) => {
4588
6405
  await next();
4589
6406
  });
4590
- app.use("*", async (_c, next) => {
6407
+ app2.use("*", async (_c, next) => {
4591
6408
  await next();
4592
6409
  });
4593
6410
  if (config.middleware?.afterAuth) {
4594
6411
  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);
6412
+ app2.use("*", middleware);
6413
+ }
6414
+ }
6415
+ app2.route("/api", api_default);
6416
+ app2.route("/api/media", api_media_default);
6417
+ app2.route("/api/system", api_system_default);
6418
+ app2.route("/admin/api", admin_api_default);
6419
+ app2.route("/admin/dashboard", router);
6420
+ app2.route("/admin/collections", adminCollectionsRoutes);
6421
+ app2.route("/admin/settings", adminSettingsRoutes);
6422
+ app2.route("/admin/database-tools", createDatabaseToolsAdminRoutes());
6423
+ app2.route("/admin/seed-data", createSeedDataAdminRoutes());
6424
+ app2.route("/admin/content", admin_content_default);
6425
+ app2.route("/admin/media", adminMediaRoutes);
4609
6426
  if (aiSearchPlugin.routes && aiSearchPlugin.routes.length > 0) {
4610
6427
  for (const route of aiSearchPlugin.routes) {
4611
- app.route(route.path, route.handler);
6428
+ app2.route(route.path, route.handler);
4612
6429
  }
4613
6430
  }
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);
6431
+ app2.route("/admin/cache", cache_default.getRoutes());
6432
+ app2.route("/admin/plugins", adminPluginRoutes);
6433
+ app2.route("/admin/logs", adminLogsRoutes);
6434
+ app2.route("/admin", userRoutes);
6435
+ app2.route("/auth", auth_default);
6436
+ app2.route("/", test_cleanup_default);
4619
6437
  if (emailPlugin.routes && emailPlugin.routes.length > 0) {
4620
6438
  for (const route of emailPlugin.routes) {
4621
- app.route(route.path, route.handler);
6439
+ app2.route(route.path, route.handler);
4622
6440
  }
4623
6441
  }
4624
6442
  if (otpLoginPlugin.routes && otpLoginPlugin.routes.length > 0) {
4625
6443
  for (const route of otpLoginPlugin.routes) {
4626
- app.route(route.path, route.handler);
6444
+ app2.route(route.path, route.handler);
4627
6445
  }
4628
6446
  }
4629
6447
  const magicLinkPlugin = createMagicLinkAuthPlugin();
4630
6448
  if (magicLinkPlugin.routes && magicLinkPlugin.routes.length > 0) {
4631
6449
  for (const route of magicLinkPlugin.routes) {
4632
- app.route(route.path, route.handler);
6450
+ app2.route(route.path, route.handler);
4633
6451
  }
4634
6452
  }
4635
- app.get("/favicon.svg", (c) => {
6453
+ app2.get("/favicon.svg", (c) => {
4636
6454
  return new Response(faviconSvg, {
4637
6455
  headers: {
4638
6456
  "Content-Type": "image/svg+xml",
@@ -4640,7 +6458,7 @@ function createSonicJSApp(config = {}) {
4640
6458
  }
4641
6459
  });
4642
6460
  });
4643
- app.get("/files/*", async (c) => {
6461
+ app2.get("/files/*", async (c) => {
4644
6462
  try {
4645
6463
  const url = new URL(c.req.url);
4646
6464
  const pathname = url.pathname;
@@ -4669,13 +6487,13 @@ function createSonicJSApp(config = {}) {
4669
6487
  });
4670
6488
  if (config.routes) {
4671
6489
  for (const route of config.routes) {
4672
- app.route(route.path, route.handler);
6490
+ app2.route(route.path, route.handler);
4673
6491
  }
4674
6492
  }
4675
- app.get("/", (c) => {
6493
+ app2.get("/", (c) => {
4676
6494
  return c.redirect("/auth/login");
4677
6495
  });
4678
- app.get("/health", (c) => {
6496
+ app2.get("/health", (c) => {
4679
6497
  return c.json({
4680
6498
  name: appName,
4681
6499
  version: appVersion,
@@ -4683,14 +6501,14 @@ function createSonicJSApp(config = {}) {
4683
6501
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4684
6502
  });
4685
6503
  });
4686
- app.notFound((c) => {
6504
+ app2.notFound((c) => {
4687
6505
  return c.json({ error: "Not Found", status: 404 }, 404);
4688
6506
  });
4689
- app.onError((err, c) => {
6507
+ app2.onError((err, c) => {
4690
6508
  console.error(err);
4691
6509
  return c.json({ error: "Internal Server Error", status: 500 }, 500);
4692
6510
  });
4693
- return app;
6511
+ return app2;
4694
6512
  }
4695
6513
  function setupCoreMiddleware(_app) {
4696
6514
  console.warn("setupCoreMiddleware is deprecated. Use createSonicJSApp() instead.");