@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.
- package/dist/TemplateManager-Db41KyPN.d.cts +77 -0
- package/dist/TemplateManager-Db41KyPN.d.ts +77 -0
- package/dist/backend/EmailService.cjs +18 -9
- package/dist/backend/EmailService.cjs.map +1 -1
- package/dist/backend/EmailService.d.cts +101 -0
- package/dist/backend/EmailService.d.ts +101 -0
- package/dist/backend/EmailService.js +18 -9
- package/dist/backend/EmailService.js.map +1 -1
- package/dist/backend/EmailingCacheDO.cjs +0 -1
- package/dist/backend/EmailingCacheDO.cjs.map +1 -1
- package/dist/backend/EmailingCacheDO.d.cts +66 -0
- package/dist/backend/EmailingCacheDO.d.ts +66 -0
- package/dist/backend/EmailingCacheDO.js +0 -1
- package/dist/backend/EmailingCacheDO.js.map +1 -1
- package/dist/backend/routes/index.cjs +18 -9
- package/dist/backend/routes/index.cjs.map +1 -1
- package/dist/backend/routes/index.d.cts +32 -0
- package/dist/backend/routes/index.d.ts +32 -0
- package/dist/backend/routes/index.js +18 -9
- package/dist/backend/routes/index.js.map +1 -1
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/common/index.d.cts +46 -0
- package/dist/common/index.d.ts +46 -0
- package/dist/frontend/index.cjs +665 -0
- package/dist/frontend/index.cjs.map +1 -0
- package/dist/frontend/index.d.cts +32 -0
- package/dist/frontend/index.d.ts +32 -0
- package/dist/frontend/index.js +626 -0
- package/dist/frontend/index.js.map +1 -0
- package/dist/index.cjs +413 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +414 -109
- package/dist/index.js.map +1 -1
- 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 };
|
|
@@ -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":"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","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:
|
|
309
|
-
portalUrl:
|
|
310
|
-
primaryColor:
|
|
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
|
-
|
|
486
|
-
|
|
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 =
|
|
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
|
-
|
|
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, "") : "");
|