@contentgrowth/content-emailing 0.5.0 → 0.6.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 (37) hide show
  1. package/dist/TemplateManager-Db41KyPN.d.cts +77 -0
  2. package/dist/TemplateManager-Db41KyPN.d.ts +77 -0
  3. package/dist/backend/EmailService.cjs +18 -9
  4. package/dist/backend/EmailService.cjs.map +1 -1
  5. package/dist/backend/EmailService.d.cts +101 -0
  6. package/dist/backend/EmailService.d.ts +101 -0
  7. package/dist/backend/EmailService.js +18 -9
  8. package/dist/backend/EmailService.js.map +1 -1
  9. package/dist/backend/EmailingCacheDO.cjs +0 -1
  10. package/dist/backend/EmailingCacheDO.cjs.map +1 -1
  11. package/dist/backend/EmailingCacheDO.d.cts +66 -0
  12. package/dist/backend/EmailingCacheDO.d.ts +66 -0
  13. package/dist/backend/EmailingCacheDO.js +0 -1
  14. package/dist/backend/EmailingCacheDO.js.map +1 -1
  15. package/dist/backend/routes/index.cjs +18 -9
  16. package/dist/backend/routes/index.cjs.map +1 -1
  17. package/dist/backend/routes/index.d.cts +32 -0
  18. package/dist/backend/routes/index.d.ts +32 -0
  19. package/dist/backend/routes/index.js +18 -9
  20. package/dist/backend/routes/index.js.map +1 -1
  21. package/dist/cli.d.cts +1 -0
  22. package/dist/cli.d.ts +1 -0
  23. package/dist/common/index.d.cts +46 -0
  24. package/dist/common/index.d.ts +46 -0
  25. package/dist/frontend/index.cjs +665 -0
  26. package/dist/frontend/index.cjs.map +1 -0
  27. package/dist/frontend/index.d.cts +32 -0
  28. package/dist/frontend/index.d.ts +32 -0
  29. package/dist/frontend/index.js +626 -0
  30. package/dist/frontend/index.js.map +1 -0
  31. package/dist/index.cjs +413 -108
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +7 -0
  34. package/dist/index.d.ts +7 -0
  35. package/dist/index.js +414 -109
  36. package/dist/index.js.map +1 -1
  37. package/package.json +6 -3
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Create a cache provider wrapper for the DO
3
+ */
4
+ declare function createDOCacheProvider(doStub: any, instanceName?: string): {
5
+ getTemplate(templateId: any): Promise<any>;
6
+ getSettings(profile: any, tenantId: any): Promise<any>;
7
+ putSettings(profile: any, tenantId: any, settings: any): Promise<void>;
8
+ putTemplate(template: any): Promise<void>;
9
+ deleteTemplate(templateId: any): Promise<void>;
10
+ invalidateSettings(profile: any, tenantId: any): Promise<void>;
11
+ };
12
+ /**
13
+ * EmailingCacheDO - Optional read-through cache for email templates and settings
14
+ *
15
+ * This is a Cloudflare Durable Object that provides caching for email templates and settings.
16
+ *
17
+ * Configurable via environment variable:
18
+ * - EMAIL_TABLE_PREFIX: Prefix for tables (default: 'system_email_')
19
+ */
20
+ declare class EmailingCacheDO {
21
+ constructor(state: any, env: any);
22
+ state: any;
23
+ env: any;
24
+ cache: Map<any, any>;
25
+ settingsCache: Map<any, any>;
26
+ cacheTTL: number;
27
+ emailTablePrefix: any;
28
+ settingsTableName: any;
29
+ settingsKeyPrefix: any;
30
+ /**
31
+ * Handle HTTP requests to this Durable Object
32
+ */
33
+ fetch(request: any): Promise<Response>;
34
+ /**
35
+ * Get template (from cache or D1)
36
+ */
37
+ handleGet(request: any): Promise<Response>;
38
+ handleGetSettings(request: any): Promise<Response>;
39
+ handlePutSettings(request: any): Promise<Response>;
40
+ handleInvalidateSettings(request: any): Promise<Response>;
41
+ /**
42
+ * Invalidate specific template(s) from cache
43
+ * Body: { templateId: 'template_id' } or { templateId: '*' } for all
44
+ */
45
+ handleInvalidate(request: any): Promise<Response>;
46
+ /**
47
+ * Clear entire cache (admin operation)
48
+ */
49
+ handleClear(request: any): Promise<Response>;
50
+ /**
51
+ * Get cache statistics
52
+ */
53
+ handleStats(request: any): Promise<Response>;
54
+ /**
55
+ * Fetch template from D1
56
+ */
57
+ fetchTemplateFromD1(templateId: any): Promise<any>;
58
+ /**
59
+ * Fetch settings from D1
60
+ * Only supports default table (e.g. system_settings) for now
61
+ */
62
+ fetchSettingsFromD1(key: any): Promise<{}>;
63
+ saveSettingsToD1(settings: any): Promise<void>;
64
+ }
65
+
66
+ export { EmailingCacheDO, createDOCacheProvider };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Create a cache provider wrapper for the DO
3
+ */
4
+ declare function createDOCacheProvider(doStub: any, instanceName?: string): {
5
+ getTemplate(templateId: any): Promise<any>;
6
+ getSettings(profile: any, tenantId: any): Promise<any>;
7
+ putSettings(profile: any, tenantId: any, settings: any): Promise<void>;
8
+ putTemplate(template: any): Promise<void>;
9
+ deleteTemplate(templateId: any): Promise<void>;
10
+ invalidateSettings(profile: any, tenantId: any): Promise<void>;
11
+ };
12
+ /**
13
+ * EmailingCacheDO - Optional read-through cache for email templates and settings
14
+ *
15
+ * This is a Cloudflare Durable Object that provides caching for email templates and settings.
16
+ *
17
+ * Configurable via environment variable:
18
+ * - EMAIL_TABLE_PREFIX: Prefix for tables (default: 'system_email_')
19
+ */
20
+ declare class EmailingCacheDO {
21
+ constructor(state: any, env: any);
22
+ state: any;
23
+ env: any;
24
+ cache: Map<any, any>;
25
+ settingsCache: Map<any, any>;
26
+ cacheTTL: number;
27
+ emailTablePrefix: any;
28
+ settingsTableName: any;
29
+ settingsKeyPrefix: any;
30
+ /**
31
+ * Handle HTTP requests to this Durable Object
32
+ */
33
+ fetch(request: any): Promise<Response>;
34
+ /**
35
+ * Get template (from cache or D1)
36
+ */
37
+ handleGet(request: any): Promise<Response>;
38
+ handleGetSettings(request: any): Promise<Response>;
39
+ handlePutSettings(request: any): Promise<Response>;
40
+ handleInvalidateSettings(request: any): Promise<Response>;
41
+ /**
42
+ * Invalidate specific template(s) from cache
43
+ * Body: { templateId: 'template_id' } or { templateId: '*' } for all
44
+ */
45
+ handleInvalidate(request: any): Promise<Response>;
46
+ /**
47
+ * Clear entire cache (admin operation)
48
+ */
49
+ handleClear(request: any): Promise<Response>;
50
+ /**
51
+ * Get cache statistics
52
+ */
53
+ handleStats(request: any): Promise<Response>;
54
+ /**
55
+ * Fetch template from D1
56
+ */
57
+ fetchTemplateFromD1(templateId: any): Promise<any>;
58
+ /**
59
+ * Fetch settings from D1
60
+ * Only supports default table (e.g. system_settings) for now
61
+ */
62
+ fetchSettingsFromD1(key: any): Promise<{}>;
63
+ saveSettingsToD1(settings: any): Promise<void>;
64
+ }
65
+
66
+ export { EmailingCacheDO, createDOCacheProvider };
@@ -296,7 +296,6 @@ function createDOCacheProvider(doStub, instanceName = "global") {
296
296
  const data = await response.json();
297
297
  return data.template || null;
298
298
  } catch (e) {
299
- console.warn("[DOCacheProvider] Failed to get template:", e);
300
299
  return null;
301
300
  }
302
301
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/backend/EmailingCacheDO.js"],"sourcesContent":["/**\n * EmailingCacheDO - Optional read-through cache for email templates and settings\n * \n * This is a Cloudflare Durable Object that provides caching for email templates and settings.\n * \n * Configurable via environment variable:\n * - EMAIL_TABLE_PREFIX: Prefix for tables (default: 'system_email_')\n */\nexport class EmailingCacheDO {\n constructor(state, env) {\n this.state = state;\n this.env = env;\n this.cache = new Map(); // templateId -> { data, timestamp }\n this.settingsCache = new Map(); // key -> { data, timestamp }\n this.cacheTTL = 3600000; // 1 hour in milliseconds\n // Templates use the prefix\n this.emailTablePrefix = env.EMAIL_TABLE_PREFIX || 'system_email_';\n // Settings use a specific table name (defaulting to system_settings)\n // This allows decoupling template prefix from settings table\n this.settingsTableName = env.EMAIL_SETTINGS_TABLE || 'system_settings';\n\n // Optional: Filter settings by key prefix (e.g. 'email_')\n // This allows sharing the system_settings table with other apps\n this.settingsKeyPrefix = env.EMAIL_SETTINGS_KEY_PREFIX || '';\n }\n\n /**\n * Handle HTTP requests to this Durable Object\n */\n async fetch(request) {\n const url = new URL(request.url);\n const path = url.pathname;\n\n try {\n if (path === '/get' && request.method === 'GET') {\n return this.handleGet(request);\n } else if (path === '/invalidate' && request.method === 'POST') {\n return this.handleInvalidate(request);\n } else if (path === '/clear' && request.method === 'POST') {\n return this.handleClear(request);\n } else if (path === '/stats' && request.method === 'GET') {\n return this.handleStats(request);\n }\n // Settings endpoints\n else if (path === '/settings/get' && request.method === 'GET') {\n return this.handleGetSettings(request);\n } else if (path === '/settings/put' && request.method === 'POST') {\n return this.handlePutSettings(request);\n } else if (path === '/settings/invalidate' && request.method === 'POST') {\n return this.handleInvalidateSettings(request);\n } else {\n return new Response('Not Found', { status: 404 });\n }\n } catch (error) {\n console.error('[EmailingCacheDO] Error:', error);\n return new Response(JSON.stringify({ error: error.message }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n }\n\n /**\n * Get template (from cache or D1)\n */\n async handleGet(request) {\n const url = new URL(request.url);\n const templateId = url.searchParams.get('templateId');\n const forceRefresh = url.searchParams.get('refresh') === 'true';\n\n if (!templateId) {\n return new Response(JSON.stringify({ error: 'templateId is required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // Check cache (unless force refresh)\n if (!forceRefresh) {\n const cached = this.cache.get(templateId);\n if (cached && Date.now() - cached.timestamp < this.cacheTTL) {\n console.log('[EmailingCacheDO] Cache HIT:', templateId);\n return new Response(JSON.stringify({\n template: cached.data,\n cached: true,\n age: Date.now() - cached.timestamp,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n }\n\n // Cache miss or expired - fetch from D1\n console.log('[EmailingCacheDO] Cache MISS - fetching from D1:', templateId);\n const template = await this.fetchTemplateFromD1(templateId);\n\n if (!template) {\n return new Response(JSON.stringify({\n error: 'Template not found',\n templateId\n }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // Cache the result\n this.cache.set(templateId, {\n data: template,\n timestamp: Date.now(),\n });\n\n return new Response(JSON.stringify({\n template,\n cached: false,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // --- Settings Cache Handlers ---\n\n async handleGetSettings(request) {\n const url = new URL(request.url);\n const key = url.searchParams.get('key');\n\n if (!key) return new Response('Key required', { status: 400 });\n\n // Check Cache\n const cached = this.settingsCache.get(key);\n if (cached && Date.now() - cached.timestamp < this.cacheTTL) {\n console.log('[EmailingCacheDO] Settings Cache HIT:', key);\n return new Response(JSON.stringify({ settings: cached.data }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // Cache Miss - Fetch from D1 (Read-Through)\n // Only fetch from D1 for system settings (consistent with handlePutSettings)\n if (key.startsWith('system')) {\n console.log('[EmailingCacheDO] Settings Cache MISS - fetching from D1:', key);\n const settings = await this.fetchSettingsFromD1(key);\n\n // Even if null, we might want to cache the \"null\" result to prevent hammer? \n // For now, only cache if we found something or explicitly handle negative caching.\n if (settings) {\n this.settingsCache.set(key, {\n data: settings,\n timestamp: Date.now()\n });\n return new Response(JSON.stringify({ settings }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n }\n\n return new Response(JSON.stringify({ settings: null }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n async handlePutSettings(request) {\n try {\n const body = await request.json();\n const { key, settings } = body;\n\n if (!key || !settings) return new Response('Key and settings required', { status: 400 });\n\n // Write to Cache\n this.settingsCache.set(key, {\n data: settings,\n timestamp: Date.now()\n });\n\n // Write to D1 (Write-Through)\n // Note: only supports writing to \"system\" (default table) currently\n if (key.startsWith('system')) {\n await this.saveSettingsToD1(settings);\n }\n\n return new Response(JSON.stringify({ success: true }), {\n headers: { 'Content-Type': 'application/json' },\n });\n } catch (e) {\n console.error('[EmailingCacheDO] PutSettings error:', e);\n return new Response('Error parsing body/saving', { status: 400 });\n }\n }\n\n async handleInvalidateSettings(request) {\n try {\n const body = await request.json();\n const { key } = body;\n\n if (!key) return new Response('Key required', { status: 400 });\n\n const existed = this.settingsCache.has(key);\n this.settingsCache.delete(key);\n console.log('[EmailingCacheDO] Invalidated settings:', key, existed ? '(existed)' : '(not in cache)');\n\n return new Response(JSON.stringify({ success: true, existed }), {\n headers: { 'Content-Type': 'application/json' },\n });\n } catch (e) {\n console.error('[EmailingCacheDO] InvalidateSettings error:', e);\n return new Response('Error invalidated settings', { status: 400 });\n }\n }\n\n /**\n * Invalidate specific template(s) from cache\n * Body: { templateId: 'template_id' } or { templateId: '*' } for all\n */\n async handleInvalidate(request) {\n const body = await request.json();\n const { templateId } = body;\n\n if (!templateId) {\n return new Response(JSON.stringify({ error: 'templateId is required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n if (templateId === '*') {\n // Invalidate all templates\n const count = this.cache.size;\n this.cache.clear();\n console.log('[EmailingCacheDO] Invalidated ALL templates:', count);\n return new Response(JSON.stringify({\n success: true,\n message: `Invalidated ${count} templates`,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // Invalidate specific template\n const existed = this.cache.has(templateId);\n this.cache.delete(templateId);\n console.log('[EmailingCacheDO] Invalidated template:', templateId, existed ? '(existed)' : '(not in cache)');\n\n return new Response(JSON.stringify({\n success: true,\n message: existed ? 'Template invalidated' : 'Template was not in cache',\n templateId,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n /**\n * Clear entire cache (admin operation)\n */\n async handleClear(request) {\n const count = this.cache.size + this.settingsCache.size;\n this.cache.clear();\n this.settingsCache.clear();\n console.log('[EmailingCacheDO] Cache cleared:', count, 'entries (templates + settings)');\n\n return new Response(JSON.stringify({\n success: true,\n message: `Cleared ${count} cached items`,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n /**\n * Get cache statistics\n */\n async handleStats(request) {\n const stats = {\n templates: this.cache.size,\n settings: this.settingsCache.size,\n cacheTTL: this.cacheTTL,\n };\n\n return new Response(JSON.stringify(stats), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n /**\n * Fetch template from D1\n */\n async fetchTemplateFromD1(templateId) {\n const db = this.env.DB;\n const tableName = `${this.emailTablePrefix}templates`;\n\n try {\n const template = await db\n .prepare(`SELECT * FROM ${tableName} WHERE template_id = ? AND is_active = 1`)\n .bind(templateId)\n .first();\n return template;\n } catch (e) {\n console.error(`[EmailingCacheDO] DB Error (${tableName}):`, e);\n return null;\n }\n }\n\n /**\n * Fetch settings from D1\n * Only supports default table (e.g. system_settings) for now\n */\n async fetchSettingsFromD1(key) {\n const db = this.env.DB;\n const tableName = this.settingsTableName;\n const prefix = this.settingsKeyPrefix;\n\n try {\n let results;\n\n if (prefix) {\n // Filter by prefix (e.g. WHERE setting_key LIKE 'email_%')\n // Note: We need to handle both schema variants (setting_key vs key)\n // BUT standard D1 SQL doesn't support \"OR\" in column names easily with LIKE parameter binding\n // for flexible schemas.\n // We will assume 'setting_key' matches if table is system_settings, or try both.\n\n // Construct query dynamically based on likely schema or just try standard first\n try {\n results = await db.prepare(`SELECT * FROM ${tableName} WHERE setting_key LIKE ?`).bind(`${prefix}%`).all();\n } catch (e) {\n // Fallback to 'key' column\n results = await db.prepare(`SELECT * FROM ${tableName} WHERE key LIKE ?`).bind(`${prefix}%`).all();\n }\n } else {\n // No prefix, fetch all\n results = await db.prepare(`SELECT * FROM ${tableName}`).all();\n }\n\n if (!results.results || results.results.length === 0) return null;\n\n const settings = {};\n results.results.forEach(row => {\n const k = row.setting_key || row.key;\n const v = row.setting_value || row.value;\n if (k) settings[k] = v;\n });\n return settings;\n } catch (e) {\n console.warn(`[EmailingCacheDO] Failed to load settings from ${tableName}:`, e);\n return null;\n }\n }\n\n async saveSettingsToD1(settings) {\n const db = this.env.DB;\n const tableName = this.settingsTableName;\n\n // Simple upset not easy in D1 without specific keys.\n // We'll rely on calling code/migration. \n // For now, let's just log or try to update if possible.\n // Actually, writing settings is complex because we don't know the schema perfectly.\n // Let's implement a BEST EFFORT update for x0start schema\n\n const entries = Object.entries(settings);\n for (const [k, v] of entries) {\n try {\n // Heuristic: Try to determine column names based on common conventions\n // We'll try Schema 1 (setting_key/value) first as it's the default internal convention\n // If that fails, we might fall back to Schema 2 (key/value) in a real implementation\n // For now, let's try a best-effort upsert based on x0start schema or generic\n\n try {\n const res = await db.prepare(`UPDATE ${tableName} SET setting_value = ? WHERE setting_key = ?`).bind(v, k).run();\n if (res.meta.changes === 0) {\n await db.prepare(`INSERT INTO ${tableName} (setting_key, setting_value) VALUES (?, ?)`).bind(k, v).run();\n }\n } catch (e) {\n // Fallback to key/value\n await db.prepare(`INSERT OR REPLACE INTO ${tableName} (key, value) VALUES (?, ?)`).bind(k, v).run();\n }\n } catch (e) {\n console.warn('[EmailingCacheDO] Failed to save setting to D1:', k);\n }\n }\n }\n}\n\n/**\n * Create a cache provider wrapper for the DO\n */\nexport function createDOCacheProvider(doStub, instanceName = 'global') {\n if (!doStub) {\n return null;\n }\n\n const stub = doStub.get(doStub.idFromName(instanceName));\n\n return {\n async getTemplate(templateId) {\n try {\n const response = await stub.fetch(`http://do/get?templateId=${templateId}`);\n const data = await response.json();\n return data.template || null;\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to get template:', e);\n return null;\n }\n },\n\n async getSettings(profile, tenantId) {\n const key = `${profile}:${tenantId || ''}`;\n try {\n const response = await stub.fetch(`http://do/settings/get?key=${encodeURIComponent(key)}`);\n const data = await response.json();\n return data.settings || null;\n } catch (e) {\n return null;\n }\n },\n\n async putSettings(profile, tenantId, settings) {\n const key = `${profile}:${tenantId || ''}`;\n try {\n await stub.fetch('http://do/settings/put', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key, settings }),\n });\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to cache settings:', e);\n }\n },\n\n async putTemplate(template) {\n try {\n await stub.fetch('http://do/invalidate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ templateId: template.template_id }),\n });\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to invalidate template:', e);\n }\n },\n\n async deleteTemplate(templateId) {\n try {\n await stub.fetch('http://do/invalidate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ templateId }),\n });\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to invalidate template:', e);\n }\n },\n\n async invalidateSettings(profile, tenantId) {\n const key = `${profile}:${tenantId || ''}`;\n try {\n await stub.fetch('http://do/settings/invalidate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key }),\n });\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to invalidate settings:', e);\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/backend/EmailingCacheDO.js"],"sourcesContent":["/**\n * EmailingCacheDO - Optional read-through cache for email templates and settings\n * \n * This is a Cloudflare Durable Object that provides caching for email templates and settings.\n * \n * Configurable via environment variable:\n * - EMAIL_TABLE_PREFIX: Prefix for tables (default: 'system_email_')\n */\nexport class EmailingCacheDO {\n constructor(state, env) {\n this.state = state;\n this.env = env;\n this.cache = new Map(); // templateId -> { data, timestamp }\n this.settingsCache = new Map(); // key -> { data, timestamp }\n this.cacheTTL = 3600000; // 1 hour in milliseconds\n // Templates use the prefix\n this.emailTablePrefix = env.EMAIL_TABLE_PREFIX || 'system_email_';\n // Settings use a specific table name (defaulting to system_settings)\n // This allows decoupling template prefix from settings table\n this.settingsTableName = env.EMAIL_SETTINGS_TABLE || 'system_settings';\n\n // Optional: Filter settings by key prefix (e.g. 'email_')\n // This allows sharing the system_settings table with other apps\n this.settingsKeyPrefix = env.EMAIL_SETTINGS_KEY_PREFIX || '';\n }\n\n /**\n * Handle HTTP requests to this Durable Object\n */\n async fetch(request) {\n const url = new URL(request.url);\n const path = url.pathname;\n\n try {\n if (path === '/get' && request.method === 'GET') {\n return this.handleGet(request);\n } else if (path === '/invalidate' && request.method === 'POST') {\n return this.handleInvalidate(request);\n } else if (path === '/clear' && request.method === 'POST') {\n return this.handleClear(request);\n } else if (path === '/stats' && request.method === 'GET') {\n return this.handleStats(request);\n }\n // Settings endpoints\n else if (path === '/settings/get' && request.method === 'GET') {\n return this.handleGetSettings(request);\n } else if (path === '/settings/put' && request.method === 'POST') {\n return this.handlePutSettings(request);\n } else if (path === '/settings/invalidate' && request.method === 'POST') {\n return this.handleInvalidateSettings(request);\n } else {\n return new Response('Not Found', { status: 404 });\n }\n } catch (error) {\n console.error('[EmailingCacheDO] Error:', error);\n return new Response(JSON.stringify({ error: error.message }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n }\n\n /**\n * Get template (from cache or D1)\n */\n async handleGet(request) {\n const url = new URL(request.url);\n const templateId = url.searchParams.get('templateId');\n const forceRefresh = url.searchParams.get('refresh') === 'true';\n\n if (!templateId) {\n return new Response(JSON.stringify({ error: 'templateId is required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // Check cache (unless force refresh)\n if (!forceRefresh) {\n const cached = this.cache.get(templateId);\n if (cached && Date.now() - cached.timestamp < this.cacheTTL) {\n console.log('[EmailingCacheDO] Cache HIT:', templateId);\n return new Response(JSON.stringify({\n template: cached.data,\n cached: true,\n age: Date.now() - cached.timestamp,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n }\n\n // Cache miss or expired - fetch from D1\n console.log('[EmailingCacheDO] Cache MISS - fetching from D1:', templateId);\n const template = await this.fetchTemplateFromD1(templateId);\n\n if (!template) {\n return new Response(JSON.stringify({\n error: 'Template not found',\n templateId\n }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // Cache the result\n this.cache.set(templateId, {\n data: template,\n timestamp: Date.now(),\n });\n\n return new Response(JSON.stringify({\n template,\n cached: false,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // --- Settings Cache Handlers ---\n\n async handleGetSettings(request) {\n const url = new URL(request.url);\n const key = url.searchParams.get('key');\n\n if (!key) return new Response('Key required', { status: 400 });\n\n // Check Cache\n const cached = this.settingsCache.get(key);\n if (cached && Date.now() - cached.timestamp < this.cacheTTL) {\n console.log('[EmailingCacheDO] Settings Cache HIT:', key);\n return new Response(JSON.stringify({ settings: cached.data }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // Cache Miss - Fetch from D1 (Read-Through)\n // Only fetch from D1 for system settings (consistent with handlePutSettings)\n if (key.startsWith('system')) {\n console.log('[EmailingCacheDO] Settings Cache MISS - fetching from D1:', key);\n const settings = await this.fetchSettingsFromD1(key);\n\n // Even if null, we might want to cache the \"null\" result to prevent hammer? \n // For now, only cache if we found something or explicitly handle negative caching.\n if (settings) {\n this.settingsCache.set(key, {\n data: settings,\n timestamp: Date.now()\n });\n return new Response(JSON.stringify({ settings }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n }\n\n return new Response(JSON.stringify({ settings: null }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n async handlePutSettings(request) {\n try {\n const body = await request.json();\n const { key, settings } = body;\n\n if (!key || !settings) return new Response('Key and settings required', { status: 400 });\n\n // Write to Cache\n this.settingsCache.set(key, {\n data: settings,\n timestamp: Date.now()\n });\n\n // Write to D1 (Write-Through)\n // Note: only supports writing to \"system\" (default table) currently\n if (key.startsWith('system')) {\n await this.saveSettingsToD1(settings);\n }\n\n return new Response(JSON.stringify({ success: true }), {\n headers: { 'Content-Type': 'application/json' },\n });\n } catch (e) {\n console.error('[EmailingCacheDO] PutSettings error:', e);\n return new Response('Error parsing body/saving', { status: 400 });\n }\n }\n\n async handleInvalidateSettings(request) {\n try {\n const body = await request.json();\n const { key } = body;\n\n if (!key) return new Response('Key required', { status: 400 });\n\n const existed = this.settingsCache.has(key);\n this.settingsCache.delete(key);\n console.log('[EmailingCacheDO] Invalidated settings:', key, existed ? '(existed)' : '(not in cache)');\n\n return new Response(JSON.stringify({ success: true, existed }), {\n headers: { 'Content-Type': 'application/json' },\n });\n } catch (e) {\n console.error('[EmailingCacheDO] InvalidateSettings error:', e);\n return new Response('Error invalidated settings', { status: 400 });\n }\n }\n\n /**\n * Invalidate specific template(s) from cache\n * Body: { templateId: 'template_id' } or { templateId: '*' } for all\n */\n async handleInvalidate(request) {\n const body = await request.json();\n const { templateId } = body;\n\n if (!templateId) {\n return new Response(JSON.stringify({ error: 'templateId is required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n if (templateId === '*') {\n // Invalidate all templates\n const count = this.cache.size;\n this.cache.clear();\n console.log('[EmailingCacheDO] Invalidated ALL templates:', count);\n return new Response(JSON.stringify({\n success: true,\n message: `Invalidated ${count} templates`,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // Invalidate specific template\n const existed = this.cache.has(templateId);\n this.cache.delete(templateId);\n console.log('[EmailingCacheDO] Invalidated template:', templateId, existed ? '(existed)' : '(not in cache)');\n\n return new Response(JSON.stringify({\n success: true,\n message: existed ? 'Template invalidated' : 'Template was not in cache',\n templateId,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n /**\n * Clear entire cache (admin operation)\n */\n async handleClear(request) {\n const count = this.cache.size + this.settingsCache.size;\n this.cache.clear();\n this.settingsCache.clear();\n console.log('[EmailingCacheDO] Cache cleared:', count, 'entries (templates + settings)');\n\n return new Response(JSON.stringify({\n success: true,\n message: `Cleared ${count} cached items`,\n }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n /**\n * Get cache statistics\n */\n async handleStats(request) {\n const stats = {\n templates: this.cache.size,\n settings: this.settingsCache.size,\n cacheTTL: this.cacheTTL,\n };\n\n return new Response(JSON.stringify(stats), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n /**\n * Fetch template from D1\n */\n async fetchTemplateFromD1(templateId) {\n const db = this.env.DB;\n const tableName = `${this.emailTablePrefix}templates`;\n\n try {\n const template = await db\n .prepare(`SELECT * FROM ${tableName} WHERE template_id = ? AND is_active = 1`)\n .bind(templateId)\n .first();\n return template;\n } catch (e) {\n console.error(`[EmailingCacheDO] DB Error (${tableName}):`, e);\n return null;\n }\n }\n\n /**\n * Fetch settings from D1\n * Only supports default table (e.g. system_settings) for now\n */\n async fetchSettingsFromD1(key) {\n const db = this.env.DB;\n const tableName = this.settingsTableName;\n const prefix = this.settingsKeyPrefix;\n\n try {\n let results;\n\n if (prefix) {\n // Filter by prefix (e.g. WHERE setting_key LIKE 'email_%')\n // Note: We need to handle both schema variants (setting_key vs key)\n // BUT standard D1 SQL doesn't support \"OR\" in column names easily with LIKE parameter binding\n // for flexible schemas.\n // We will assume 'setting_key' matches if table is system_settings, or try both.\n\n // Construct query dynamically based on likely schema or just try standard first\n try {\n results = await db.prepare(`SELECT * FROM ${tableName} WHERE setting_key LIKE ?`).bind(`${prefix}%`).all();\n } catch (e) {\n // Fallback to 'key' column\n results = await db.prepare(`SELECT * FROM ${tableName} WHERE key LIKE ?`).bind(`${prefix}%`).all();\n }\n } else {\n // No prefix, fetch all\n results = await db.prepare(`SELECT * FROM ${tableName}`).all();\n }\n\n if (!results.results || results.results.length === 0) return null;\n\n const settings = {};\n results.results.forEach(row => {\n const k = row.setting_key || row.key;\n const v = row.setting_value || row.value;\n if (k) settings[k] = v;\n });\n return settings;\n } catch (e) {\n console.warn(`[EmailingCacheDO] Failed to load settings from ${tableName}:`, e);\n return null;\n }\n }\n\n async saveSettingsToD1(settings) {\n const db = this.env.DB;\n const tableName = this.settingsTableName;\n\n // Simple upset not easy in D1 without specific keys.\n // We'll rely on calling code/migration. \n // For now, let's just log or try to update if possible.\n // Actually, writing settings is complex because we don't know the schema perfectly.\n // Let's implement a BEST EFFORT update for x0start schema\n\n const entries = Object.entries(settings);\n for (const [k, v] of entries) {\n try {\n // Heuristic: Try to determine column names based on common conventions\n // We'll try Schema 1 (setting_key/value) first as it's the default internal convention\n // If that fails, we might fall back to Schema 2 (key/value) in a real implementation\n // For now, let's try a best-effort upsert based on x0start schema or generic\n\n try {\n const res = await db.prepare(`UPDATE ${tableName} SET setting_value = ? WHERE setting_key = ?`).bind(v, k).run();\n if (res.meta.changes === 0) {\n await db.prepare(`INSERT INTO ${tableName} (setting_key, setting_value) VALUES (?, ?)`).bind(k, v).run();\n }\n } catch (e) {\n // Fallback to key/value\n await db.prepare(`INSERT OR REPLACE INTO ${tableName} (key, value) VALUES (?, ?)`).bind(k, v).run();\n }\n } catch (e) {\n console.warn('[EmailingCacheDO] Failed to save setting to D1:', k);\n }\n }\n }\n}\n\n/**\n * Create a cache provider wrapper for the DO\n */\nexport function createDOCacheProvider(doStub, instanceName = 'global') {\n if (!doStub) {\n return null;\n }\n\n const stub = doStub.get(doStub.idFromName(instanceName));\n\n return {\n async getTemplate(templateId) {\n try {\n const response = await stub.fetch(`http://do/get?templateId=${templateId}`);\n const data = await response.json();\n return data.template || null;\n } catch (e) {\n // Silently fall back to DB - DO cache is optional\n return null;\n }\n },\n\n async getSettings(profile, tenantId) {\n const key = `${profile}:${tenantId || ''}`;\n try {\n const response = await stub.fetch(`http://do/settings/get?key=${encodeURIComponent(key)}`);\n const data = await response.json();\n return data.settings || null;\n } catch (e) {\n return null;\n }\n },\n\n async putSettings(profile, tenantId, settings) {\n const key = `${profile}:${tenantId || ''}`;\n try {\n await stub.fetch('http://do/settings/put', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key, settings }),\n });\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to cache settings:', e);\n }\n },\n\n async putTemplate(template) {\n try {\n await stub.fetch('http://do/invalidate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ templateId: template.template_id }),\n });\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to invalidate template:', e);\n }\n },\n\n async deleteTemplate(templateId) {\n try {\n await stub.fetch('http://do/invalidate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ templateId }),\n });\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to invalidate template:', e);\n }\n },\n\n async invalidateSettings(profile, tenantId) {\n const key = `${profile}:${tenantId || ''}`;\n try {\n await stub.fetch('http://do/settings/invalidate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key }),\n });\n } catch (e) {\n console.warn('[DOCacheProvider] Failed to invalidate settings:', e);\n }\n },\n };\n}\n"],"mappings":";AAQO,IAAM,kBAAN,MAAsB;AAAA,EACzB,YAAY,OAAO,KAAK;AACpB,SAAK,QAAQ;AACb,SAAK,MAAM;AACX,SAAK,QAAQ,oBAAI,IAAI;AACrB,SAAK,gBAAgB,oBAAI,IAAI;AAC7B,SAAK,WAAW;AAEhB,SAAK,mBAAmB,IAAI,sBAAsB;AAGlD,SAAK,oBAAoB,IAAI,wBAAwB;AAIrD,SAAK,oBAAoB,IAAI,6BAA6B;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,SAAS;AACjB,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,OAAO,IAAI;AAEjB,QAAI;AACA,UAAI,SAAS,UAAU,QAAQ,WAAW,OAAO;AAC7C,eAAO,KAAK,UAAU,OAAO;AAAA,MACjC,WAAW,SAAS,iBAAiB,QAAQ,WAAW,QAAQ;AAC5D,eAAO,KAAK,iBAAiB,OAAO;AAAA,MACxC,WAAW,SAAS,YAAY,QAAQ,WAAW,QAAQ;AACvD,eAAO,KAAK,YAAY,OAAO;AAAA,MACnC,WAAW,SAAS,YAAY,QAAQ,WAAW,OAAO;AACtD,eAAO,KAAK,YAAY,OAAO;AAAA,MACnC,WAES,SAAS,mBAAmB,QAAQ,WAAW,OAAO;AAC3D,eAAO,KAAK,kBAAkB,OAAO;AAAA,MACzC,WAAW,SAAS,mBAAmB,QAAQ,WAAW,QAAQ;AAC9D,eAAO,KAAK,kBAAkB,OAAO;AAAA,MACzC,WAAW,SAAS,0BAA0B,QAAQ,WAAW,QAAQ;AACrE,eAAO,KAAK,yBAAyB,OAAO;AAAA,MAChD,OAAO;AACH,eAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,IAAI,CAAC;AAAA,MACpD;AAAA,IACJ,SAAS,OAAO;AACZ,cAAQ,MAAM,4BAA4B,KAAK;AAC/C,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,MAAM,QAAQ,CAAC,GAAG;AAAA,QAC1D,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAClD,CAAC;AAAA,IACL;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,SAAS;AACrB,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,aAAa,IAAI,aAAa,IAAI,YAAY;AACpD,UAAM,eAAe,IAAI,aAAa,IAAI,SAAS,MAAM;AAEzD,QAAI,CAAC,YAAY;AACb,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,yBAAyB,CAAC,GAAG;AAAA,QACrE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAClD,CAAC;AAAA,IACL;AAGA,QAAI,CAAC,cAAc;AACf,YAAM,SAAS,KAAK,MAAM,IAAI,UAAU;AACxC,UAAI,UAAU,KAAK,IAAI,IAAI,OAAO,YAAY,KAAK,UAAU;AACzD,gBAAQ,IAAI,gCAAgC,UAAU;AACtD,eAAO,IAAI,SAAS,KAAK,UAAU;AAAA,UAC/B,UAAU,OAAO;AAAA,UACjB,QAAQ;AAAA,UACR,KAAK,KAAK,IAAI,IAAI,OAAO;AAAA,QAC7B,CAAC,GAAG;AAAA,UACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAClD,CAAC;AAAA,MACL;AAAA,IACJ;AAGA,YAAQ,IAAI,oDAAoD,UAAU;AAC1E,UAAM,WAAW,MAAM,KAAK,oBAAoB,UAAU;AAE1D,QAAI,CAAC,UAAU;AACX,aAAO,IAAI,SAAS,KAAK,UAAU;AAAA,QAC/B,OAAO;AAAA,QACP;AAAA,MACJ,CAAC,GAAG;AAAA,QACA,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAClD,CAAC;AAAA,IACL;AAGA,SAAK,MAAM,IAAI,YAAY;AAAA,MACvB,MAAM;AAAA,MACN,WAAW,KAAK,IAAI;AAAA,IACxB,CAAC;AAED,WAAO,IAAI,SAAS,KAAK,UAAU;AAAA,MAC/B;AAAA,MACA,QAAQ;AAAA,IACZ,CAAC,GAAG;AAAA,MACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAClD,CAAC;AAAA,EACL;AAAA;AAAA,EAIA,MAAM,kBAAkB,SAAS;AAC7B,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,MAAM,IAAI,aAAa,IAAI,KAAK;AAEtC,QAAI,CAAC,IAAK,QAAO,IAAI,SAAS,gBAAgB,EAAE,QAAQ,IAAI,CAAC;AAG7D,UAAM,SAAS,KAAK,cAAc,IAAI,GAAG;AACzC,QAAI,UAAU,KAAK,IAAI,IAAI,OAAO,YAAY,KAAK,UAAU;AACzD,cAAQ,IAAI,yCAAyC,GAAG;AACxD,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,UAAU,OAAO,KAAK,CAAC,GAAG;AAAA,QAC3D,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAClD,CAAC;AAAA,IACL;AAIA,QAAI,IAAI,WAAW,QAAQ,GAAG;AAC1B,cAAQ,IAAI,6DAA6D,GAAG;AAC5E,YAAM,WAAW,MAAM,KAAK,oBAAoB,GAAG;AAInD,UAAI,UAAU;AACV,aAAK,cAAc,IAAI,KAAK;AAAA,UACxB,MAAM;AAAA,UACN,WAAW,KAAK,IAAI;AAAA,QACxB,CAAC;AACD,eAAO,IAAI,SAAS,KAAK,UAAU,EAAE,SAAS,CAAC,GAAG;AAAA,UAC9C,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAClD,CAAC;AAAA,MACL;AAAA,IACJ;AAEA,WAAO,IAAI,SAAS,KAAK,UAAU,EAAE,UAAU,KAAK,CAAC,GAAG;AAAA,MACpD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAClD,CAAC;AAAA,EACL;AAAA,EAEA,MAAM,kBAAkB,SAAS;AAC7B,QAAI;AACA,YAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,YAAM,EAAE,KAAK,SAAS,IAAI;AAE1B,UAAI,CAAC,OAAO,CAAC,SAAU,QAAO,IAAI,SAAS,6BAA6B,EAAE,QAAQ,IAAI,CAAC;AAGvF,WAAK,cAAc,IAAI,KAAK;AAAA,QACxB,MAAM;AAAA,QACN,WAAW,KAAK,IAAI;AAAA,MACxB,CAAC;AAID,UAAI,IAAI,WAAW,QAAQ,GAAG;AAC1B,cAAM,KAAK,iBAAiB,QAAQ;AAAA,MACxC;AAEA,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,SAAS,KAAK,CAAC,GAAG;AAAA,QACnD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAClD,CAAC;AAAA,IACL,SAAS,GAAG;AACR,cAAQ,MAAM,wCAAwC,CAAC;AACvD,aAAO,IAAI,SAAS,6BAA6B,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AAAA,EACJ;AAAA,EAEA,MAAM,yBAAyB,SAAS;AACpC,QAAI;AACA,YAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,YAAM,EAAE,IAAI,IAAI;AAEhB,UAAI,CAAC,IAAK,QAAO,IAAI,SAAS,gBAAgB,EAAE,QAAQ,IAAI,CAAC;AAE7D,YAAM,UAAU,KAAK,cAAc,IAAI,GAAG;AAC1C,WAAK,cAAc,OAAO,GAAG;AAC7B,cAAQ,IAAI,2CAA2C,KAAK,UAAU,cAAc,gBAAgB;AAEpG,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,SAAS,MAAM,QAAQ,CAAC,GAAG;AAAA,QAC5D,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAClD,CAAC;AAAA,IACL,SAAS,GAAG;AACR,cAAQ,MAAM,+CAA+C,CAAC;AAC9D,aAAO,IAAI,SAAS,8BAA8B,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAiB,SAAS;AAC5B,UAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,UAAM,EAAE,WAAW,IAAI;AAEvB,QAAI,CAAC,YAAY;AACb,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,yBAAyB,CAAC,GAAG;AAAA,QACrE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAClD,CAAC;AAAA,IACL;AAEA,QAAI,eAAe,KAAK;AAEpB,YAAM,QAAQ,KAAK,MAAM;AACzB,WAAK,MAAM,MAAM;AACjB,cAAQ,IAAI,gDAAgD,KAAK;AACjE,aAAO,IAAI,SAAS,KAAK,UAAU;AAAA,QAC/B,SAAS;AAAA,QACT,SAAS,eAAe,KAAK;AAAA,MACjC,CAAC,GAAG;AAAA,QACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAClD,CAAC;AAAA,IACL;AAGA,UAAM,UAAU,KAAK,MAAM,IAAI,UAAU;AACzC,SAAK,MAAM,OAAO,UAAU;AAC5B,YAAQ,IAAI,2CAA2C,YAAY,UAAU,cAAc,gBAAgB;AAE3G,WAAO,IAAI,SAAS,KAAK,UAAU;AAAA,MAC/B,SAAS;AAAA,MACT,SAAS,UAAU,yBAAyB;AAAA,MAC5C;AAAA,IACJ,CAAC,GAAG;AAAA,MACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAClD,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,SAAS;AACvB,UAAM,QAAQ,KAAK,MAAM,OAAO,KAAK,cAAc;AACnD,SAAK,MAAM,MAAM;AACjB,SAAK,cAAc,MAAM;AACzB,YAAQ,IAAI,oCAAoC,OAAO,gCAAgC;AAEvF,WAAO,IAAI,SAAS,KAAK,UAAU;AAAA,MAC/B,SAAS;AAAA,MACT,SAAS,WAAW,KAAK;AAAA,IAC7B,CAAC,GAAG;AAAA,MACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAClD,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,SAAS;AACvB,UAAM,QAAQ;AAAA,MACV,WAAW,KAAK,MAAM;AAAA,MACtB,UAAU,KAAK,cAAc;AAAA,MAC7B,UAAU,KAAK;AAAA,IACnB;AAEA,WAAO,IAAI,SAAS,KAAK,UAAU,KAAK,GAAG;AAAA,MACvC,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAClD,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAoB,YAAY;AAClC,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,YAAY,GAAG,KAAK,gBAAgB;AAE1C,QAAI;AACA,YAAM,WAAW,MAAM,GAClB,QAAQ,iBAAiB,SAAS,0CAA0C,EAC5E,KAAK,UAAU,EACf,MAAM;AACX,aAAO;AAAA,IACX,SAAS,GAAG;AACR,cAAQ,MAAM,+BAA+B,SAAS,MAAM,CAAC;AAC7D,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,oBAAoB,KAAK;AAC3B,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,YAAY,KAAK;AACvB,UAAM,SAAS,KAAK;AAEpB,QAAI;AACA,UAAI;AAEJ,UAAI,QAAQ;AAQR,YAAI;AACA,oBAAU,MAAM,GAAG,QAAQ,iBAAiB,SAAS,2BAA2B,EAAE,KAAK,GAAG,MAAM,GAAG,EAAE,IAAI;AAAA,QAC7G,SAAS,GAAG;AAER,oBAAU,MAAM,GAAG,QAAQ,iBAAiB,SAAS,mBAAmB,EAAE,KAAK,GAAG,MAAM,GAAG,EAAE,IAAI;AAAA,QACrG;AAAA,MACJ,OAAO;AAEH,kBAAU,MAAM,GAAG,QAAQ,iBAAiB,SAAS,EAAE,EAAE,IAAI;AAAA,MACjE;AAEA,UAAI,CAAC,QAAQ,WAAW,QAAQ,QAAQ,WAAW,EAAG,QAAO;AAE7D,YAAM,WAAW,CAAC;AAClB,cAAQ,QAAQ,QAAQ,SAAO;AAC3B,cAAM,IAAI,IAAI,eAAe,IAAI;AACjC,cAAM,IAAI,IAAI,iBAAiB,IAAI;AACnC,YAAI,EAAG,UAAS,CAAC,IAAI;AAAA,MACzB,CAAC;AACD,aAAO;AAAA,IACX,SAAS,GAAG;AACR,cAAQ,KAAK,kDAAkD,SAAS,KAAK,CAAC;AAC9E,aAAO;AAAA,IACX;AAAA,EACJ;AAAA,EAEA,MAAM,iBAAiB,UAAU;AAC7B,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,YAAY,KAAK;AAQvB,UAAM,UAAU,OAAO,QAAQ,QAAQ;AACvC,eAAW,CAAC,GAAG,CAAC,KAAK,SAAS;AAC1B,UAAI;AAMA,YAAI;AACA,gBAAM,MAAM,MAAM,GAAG,QAAQ,UAAU,SAAS,8CAA8C,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI;AAC/G,cAAI,IAAI,KAAK,YAAY,GAAG;AACxB,kBAAM,GAAG,QAAQ,eAAe,SAAS,6CAA6C,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI;AAAA,UAC3G;AAAA,QACJ,SAAS,GAAG;AAER,gBAAM,GAAG,QAAQ,0BAA0B,SAAS,6BAA6B,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI;AAAA,QACtG;AAAA,MACJ,SAAS,GAAG;AACR,gBAAQ,KAAK,mDAAmD,CAAC;AAAA,MACrE;AAAA,IACJ;AAAA,EACJ;AACJ;AAKO,SAAS,sBAAsB,QAAQ,eAAe,UAAU;AACnE,MAAI,CAAC,QAAQ;AACT,WAAO;AAAA,EACX;AAEA,QAAM,OAAO,OAAO,IAAI,OAAO,WAAW,YAAY,CAAC;AAEvD,SAAO;AAAA,IACH,MAAM,YAAY,YAAY;AAC1B,UAAI;AACA,cAAM,WAAW,MAAM,KAAK,MAAM,4BAA4B,UAAU,EAAE;AAC1E,cAAM,OAAO,MAAM,SAAS,KAAK;AACjC,eAAO,KAAK,YAAY;AAAA,MAC5B,SAAS,GAAG;AAER,eAAO;AAAA,MACX;AAAA,IACJ;AAAA,IAEA,MAAM,YAAY,SAAS,UAAU;AACjC,YAAM,MAAM,GAAG,OAAO,IAAI,YAAY,EAAE;AACxC,UAAI;AACA,cAAM,WAAW,MAAM,KAAK,MAAM,8BAA8B,mBAAmB,GAAG,CAAC,EAAE;AACzF,cAAM,OAAO,MAAM,SAAS,KAAK;AACjC,eAAO,KAAK,YAAY;AAAA,MAC5B,SAAS,GAAG;AACR,eAAO;AAAA,MACX;AAAA,IACJ;AAAA,IAEA,MAAM,YAAY,SAAS,UAAU,UAAU;AAC3C,YAAM,MAAM,GAAG,OAAO,IAAI,YAAY,EAAE;AACxC,UAAI;AACA,cAAM,KAAK,MAAM,0BAA0B;AAAA,UACvC,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,KAAK,SAAS,CAAC;AAAA,QAC1C,CAAC;AAAA,MACL,SAAS,GAAG;AACR,gBAAQ,KAAK,+CAA+C,CAAC;AAAA,MACjE;AAAA,IACJ;AAAA,IAEA,MAAM,YAAY,UAAU;AACxB,UAAI;AACA,cAAM,KAAK,MAAM,wBAAwB;AAAA,UACrC,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,SAAS,YAAY,CAAC;AAAA,QAC7D,CAAC;AAAA,MACL,SAAS,GAAG;AACR,gBAAQ,KAAK,oDAAoD,CAAC;AAAA,MACtE;AAAA,IACJ;AAAA,IAEA,MAAM,eAAe,YAAY;AAC7B,UAAI;AACA,cAAM,KAAK,MAAM,wBAAwB;AAAA,UACrC,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,WAAW,CAAC;AAAA,QACvC,CAAC;AAAA,MACL,SAAS,GAAG;AACR,gBAAQ,KAAK,oDAAoD,CAAC;AAAA,MACtE;AAAA,IACJ;AAAA,IAEA,MAAM,mBAAmB,SAAS,UAAU;AACxC,YAAM,MAAM,GAAG,OAAO,IAAI,YAAY,EAAE;AACxC,UAAI;AACA,cAAM,KAAK,MAAM,iCAAiC;AAAA,UAC9C,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,CAAC;AAAA,QAChC,CAAC;AAAA,MACL,SAAS,GAAG;AACR,gBAAQ,KAAK,oDAAoD,CAAC;AAAA,MACtE;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
@@ -214,7 +214,6 @@ function createDOCacheProvider(doStub, instanceName = "global") {
214
214
  const data = await response.json();
215
215
  return data.template || null;
216
216
  } catch (e) {
217
- console.warn("[DOCacheProvider] Failed to get template:", e);
218
217
  return null;
219
218
  }
220
219
  },
@@ -287,7 +286,6 @@ var EmailService = class {
287
286
  * @param {Object} [cacheProvider] - Optional cache interface (DO stub or KV wrapper)
288
287
  */
289
288
  constructor(env, config = {}, cacheProvider = null) {
290
- var _a, _b, _c;
291
289
  this.env = env;
292
290
  this.db = env.DB;
293
291
  this.config = {
@@ -305,9 +303,9 @@ var EmailService = class {
305
303
  settingsUpdater: config.settingsUpdater || null,
306
304
  // Branding configuration for email templates
307
305
  branding: {
308
- brandName: ((_a = config.branding) == null ? void 0 : _a.brandName) || "Your App",
309
- portalUrl: ((_b = config.branding) == null ? void 0 : _b.portalUrl) || "https://app.example.com",
310
- primaryColor: ((_c = config.branding) == null ? void 0 : _c.primaryColor) || "#667eea",
306
+ brandName: config.branding?.brandName || "Your App",
307
+ portalUrl: config.branding?.portalUrl || "https://app.example.com",
308
+ primaryColor: config.branding?.primaryColor || "#667eea",
311
309
  ...config.branding
312
310
  },
313
311
  ...config
@@ -482,8 +480,14 @@ var EmailService = class {
482
480
  const template = await this.getTemplate(templateId);
483
481
  if (!template) throw new Error(`Template not found: ${templateId}`);
484
482
  const subject = import_mustache.default.render(template.subject_template, data);
485
- const markdown = import_mustache.default.render(template.body_markdown, data);
486
- import_marked.marked.use({ mangle: false, headerIds: false });
483
+ let markdown = import_mustache.default.render(template.body_markdown, data);
484
+ markdown = markdown.replace(/\\n/g, "\n");
485
+ import_marked.marked.use({
486
+ mangle: false,
487
+ headerIds: false,
488
+ breaks: true
489
+ // Convert single line breaks to <br>
490
+ });
487
491
  const htmlContent = import_marked.marked.parse(markdown);
488
492
  const html = this.wrapInBaseTemplate(htmlContent, subject, data);
489
493
  const plainText = markdown.replace(/<[^>]*>/g, "");
@@ -582,7 +586,7 @@ var EmailService = class {
582
586
  return true;
583
587
  } else {
584
588
  const contentType = response.headers.get("content-type");
585
- const errorBody = (contentType == null ? void 0 : contentType.includes("application/json")) ? await response.json() : await response.text();
589
+ const errorBody = contentType?.includes("application/json") ? await response.json() : await response.text();
586
590
  console.error("[EmailService] MailChannels error:", response.status, errorBody);
587
591
  return false;
588
592
  }
@@ -696,7 +700,12 @@ var EmailService = class {
696
700
  const { access_token } = tokenData;
697
701
  const toBase64 = (str) => {
698
702
  if (!str) return "";
699
- return Buffer.from(String(str)).toString("base64");
703
+ try {
704
+ return btoa(unescape(encodeURIComponent(String(str))));
705
+ } catch (e) {
706
+ console.error("[EmailService] Base64 encoding failed:", e);
707
+ return "";
708
+ }
700
709
  };
701
710
  const htmlSafe = html || "";
702
711
  const textSafe = text || (htmlSafe ? htmlSafe.replace(/<[^>]*>/g, "") : "");