@happyvertical/smrt-messages 0.57.15 → 0.57.17
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/chunks/{AccountCollection--ruenWOa.js → AccountCollection-CDOBNWWG.js} +2 -2
- package/dist/chunks/{AccountCollection--ruenWOa.js.map → AccountCollection-CDOBNWWG.js.map} +1 -1
- package/dist/chunks/{Email-B2kXLj13.js → Email-DWC-RO7A.js} +3 -3
- package/dist/chunks/{Email-B2kXLj13.js.map → Email-DWC-RO7A.js.map} +1 -1
- package/dist/chunks/{EmailFolder-CeaKVXRZ.js → EmailFolder-BNX0j2B1.js} +2 -2
- package/dist/chunks/{EmailFolder-CeaKVXRZ.js.map → EmailFolder-BNX0j2B1.js.map} +1 -1
- package/dist/chunks/{Message-CqSUV46b.js → Message-HYEwxDiQ.js} +4 -4
- package/dist/chunks/{Message-CqSUV46b.js.map → Message-HYEwxDiQ.js.map} +1 -1
- package/dist/chunks/{MessageCollection-BJe8wdg7.js → MessageCollection-BaOC9VOR.js} +2 -2
- package/dist/chunks/{MessageCollection-BJe8wdg7.js.map → MessageCollection-BaOC9VOR.js.map} +1 -1
- package/dist/chunks/{__smrt-register__-fzR8GE5F.js → __smrt-register__-v_rnuvPG.js} +2 -2
- package/dist/chunks/{__smrt-register__-fzR8GE5F.js.map → __smrt-register__-v_rnuvPG.js.map} +1 -1
- package/dist/index.js +9 -9
- package/dist/manifest.json +1 -1
- package/dist/smrt-knowledge.json +4 -4
- package/package.json +10 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import "./__smrt-register__-
|
|
2
|
+
import "./__smrt-register__-v_rnuvPG.js";
|
|
3
3
|
import { SmrtCollection, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
|
|
4
4
|
import { TenantScoped, getCurrentTenant, queryGlobal, queryWithGlobals, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
|
|
5
5
|
//#region src/models/Account.ts
|
|
@@ -255,4 +255,4 @@ var AccountCollection = class extends SmrtCollection {
|
|
|
255
255
|
//#endregion
|
|
256
256
|
export { AccountCollection_exports as n, Account as r, AccountCollection as t };
|
|
257
257
|
|
|
258
|
-
//# sourceMappingURL=AccountCollection
|
|
258
|
+
//# sourceMappingURL=AccountCollection-CDOBNWWG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountCollection--ruenWOa.js","names":["tenantId"],"sources":["../../src/models/Account.ts","../../src/collections/AccountCollection.ts"],"sourcesContent":["/**\n * Account model - Base class for all messaging account types (STI)\n *\n * Common fields shared across email, Slack, Twitter accounts, etc.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { AccountOptions, MessageSenderInterface } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class Account extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({\n description: 'The display name for this connected messaging account.',\n })\n name = '';\n @field({\n description: 'The service that connects this account to your application.',\n })\n providerType = '';\n @field({\n description:\n 'The kind of messaging this account supports, such as email or chat.',\n })\n channelType = '';\n // credentialSecretId stores the Secret's NAME (keyed by name + tenant\n // context in smrt-secrets), NOT its primary-key id — see setCredentials()/\n // getCredentials() which call tenant-bound SecretService operations by name.\n // So it is deliberately NOT a @crossPackageRef id FK.\n @field({ sensitive: true, readonly: true })\n credentialSecretId: string | null = null;\n @field({ description: 'Whether this account is enabled for messaging.' })\n isActive = true;\n @field({ description: 'When this account last checked for new messages.' })\n lastSyncAt: Date | null = null;\n // JSON. Sensitive (#1540): may hold a plaintext credential fallback, so it is\n // excluded from generated API/MCP responses and rejected as a `where` filter\n // key.\n @field({ sensitive: true, readonly: true })\n settings = ''; // JSON\n /** Non-secret provider configuration such as a Zulip site URL. */\n @field({\n description:\n 'The public connection settings for this account; credentials are stored separately.',\n })\n configuration = '{}';\n\n // Timestamps\n @field({ description: 'When this account was added.' })\n createdAt = new Date();\n @field({ description: 'When this account was last changed.' })\n updatedAt = new Date();\n\n constructor(options: AccountOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.providerType !== undefined)\n this.providerType = options.providerType;\n if (options.channelType !== undefined)\n this.channelType = options.channelType;\n if (options.credentialSecretId !== undefined)\n this.credentialSecretId = options.credentialSecretId || null;\n if (options.isActive !== undefined) this.isActive = options.isActive;\n if (options.lastSyncAt !== undefined)\n this.lastSyncAt = options.lastSyncAt || null;\n if (options.settings !== undefined) this.settings = options.settings;\n if (options.configuration !== undefined)\n this.configuration = options.configuration;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n /**\n * Get settings as parsed object\n */\n getSettings(): Record<string, unknown> {\n if (!this.settings) return {};\n try {\n return JSON.parse(this.settings);\n } catch {\n return {};\n }\n }\n\n /**\n * Set settings from object\n */\n setSettings(settings: Record<string, unknown>): void {\n this.settings = JSON.stringify(settings);\n }\n\n getConfiguration(): Record<string, unknown> {\n try {\n return JSON.parse(this.configuration || '{}');\n } catch {\n return {};\n }\n }\n\n setConfiguration(configuration: Record<string, unknown>): void {\n this.configuration = JSON.stringify(configuration);\n }\n\n get hasCredentials(): boolean {\n return Boolean(this.credentialSecretId || this.settings);\n }\n\n /**\n * Activate account\n */\n async activate(): Promise<void> {\n this.isActive = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Deactivate account\n */\n async deactivate(): Promise<void> {\n this.isActive = false;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Store credentials securely using smrt-secrets\n */\n async setCredentials(\n credentials: Record<string, unknown>,\n options: {\n description?: string;\n category?: string;\n } = {},\n ): Promise<void> {\n if (!this.id) {\n throw new Error('Account must be persisted before storing credentials.');\n }\n const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n const secretName = `${this.channelType || 'messaging'}-account-${this.id}`;\n const secretValue = JSON.stringify(credentials);\n const tenantId = this.requireCredentialTenantId('store credentials');\n\n await secretService.storeForTenant(\n tenantId,\n this.credentialSecretId || secretName,\n secretValue,\n {\n description: options.description || `Credentials for ${this.name}`,\n category: options.category || 'messaging',\n },\n );\n\n this.credentialSecretId ||= secretName;\n this.updatedAt = new Date();\n await this.withCredentialTenantContext(() => this.save());\n }\n\n /**\n * Create a sender for this account.\n * Subclasses must override to return a concrete sender.\n */\n async createSender(): Promise<MessageSenderInterface> {\n const { getMessagingProvider } = await import('../providers.js');\n const provider = getMessagingProvider(this.providerType);\n if (!provider?.createSender) {\n throw new Error(\n `createSender() not implemented for account type '${this.providerType}'`,\n );\n }\n return provider.createSender(this);\n }\n\n /**\n * Retrieve stored credentials\n */\n async getCredentials(): Promise<Record<string, unknown> | null> {\n if (!this.credentialSecretId) {\n return this.getSettings();\n }\n\n const tenantId = this.requireCredentialTenantId('retrieve credentials');\n const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n try {\n const secret = await secretService.retrieveForTenant(\n tenantId,\n this.credentialSecretId,\n );\n return JSON.parse(secret.value);\n } catch {\n return null;\n }\n }\n\n private getCredentialTenantId(): string | null {\n return this.tenantId || getCurrentTenant()?.tenantId || null;\n }\n\n private requireCredentialTenantId(action: string): string {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId) {\n throw new Error(`Tenant context required to ${action}.`);\n }\n return tenantId;\n }\n\n private async withCredentialTenantContext<T>(\n fn: () => Promise<T>,\n ): Promise<T> {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId || getCurrentTenant()?.tenantId === tenantId) return fn();\n return withTenant({ tenantId }, fn);\n }\n}\n","/**\n * AccountCollection - Unified collection for polymorphic account queries\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Account } from '../models/Account';\nimport type { AccountSearchFilters } from '../types';\n\nexport class AccountCollection extends SmrtCollection<Account> {\n static readonly _itemClass = Account;\n\n /**\n * Get active accounts\n */\n async getActive(): Promise<Account[]> {\n return await this.list({ where: { isActive: true } });\n }\n\n /**\n * Get inactive accounts\n */\n async getInactive(): Promise<Account[]> {\n return await this.list({ where: { isActive: false } });\n }\n\n /**\n * Get accounts by provider type\n */\n async getByProviderType(providerType: string): Promise<Account[]> {\n return await this.list({ where: { providerType } });\n }\n\n /**\n * Get accounts by STI type.\n *\n * Accepts either the full discriminator (e.g. \"@happyvertical/smrt-messages:EmailAccount\")\n * or the short type name (e.g. \"EmailAccount\").\n */\n async getByType(accountType: string): Promise<Account[]> {\n if (accountType.includes(':') || accountType.startsWith('@')) {\n return await this.list({ where: { _meta_type: accountType } });\n }\n const allAccounts = await this.list({});\n return allAccounts.filter((a) => {\n const metaType = (a as { _meta_type?: string })._meta_type || '';\n return metaType.endsWith(`:${accountType}`);\n });\n }\n\n /**\n * Search accounts with filters\n */\n async search(\n query: string,\n filters?: AccountSearchFilters,\n ): Promise<Account[]> {\n let accounts = await this.list({});\n\n if (query) {\n const lowerQuery = query.toLowerCase();\n accounts = accounts.filter((a) =>\n a.name?.toLowerCase().includes(lowerQuery),\n );\n }\n\n if (filters) {\n if (filters.providerType) {\n accounts = accounts.filter(\n (a) => a.providerType === filters.providerType,\n );\n }\n if (filters.isActive !== undefined) {\n accounts = accounts.filter((a) => a.isActive === filters.isActive);\n }\n if (filters.accountType) {\n accounts = accounts.filter((a) => {\n const metaType = (a as { _meta_type?: string })._meta_type || '';\n return metaType.includes(filters.accountType as string);\n });\n }\n }\n\n return accounts;\n }\n\n /**\n * Get account statistics\n */\n async getStats(): Promise<{\n total: number;\n active: number;\n inactive: number;\n byType: Record<string, number>;\n }> {\n const accounts = await this.list({});\n const byType: Record<string, number> = {};\n\n for (const account of accounts) {\n const metaType =\n (account as { _meta_type?: string })._meta_type || 'Unknown';\n const shortType = metaType.split(':').pop() || metaType;\n byType[shortType] = (byType[shortType] || 0) + 1;\n }\n\n return {\n total: accounts.length,\n active: accounts.filter((a) => a.isActive).length,\n inactive: accounts.filter((a) => !a.isActive).length,\n byType,\n };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Tenant Helper Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n async findByTenant(tenantId: string): Promise<Account[]> {\n return this.list({ where: { tenantId } });\n }\n\n // Account is the @TenantScoped STI base — see MessageCollection. Route through\n // the raw helpers; no `_meta_type` so the base returns ALL account subtypes.\n async findGlobal(): Promise<Account[]> {\n return queryGlobal<Account>(this);\n }\n\n async findWithGlobals(tenantId: string): Promise<Account[]> {\n return queryWithGlobals<Account>(this, tenantId, 'Account.findWithGlobals');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AccountCollection-CDOBNWWG.js","names":["tenantId"],"sources":["../../src/models/Account.ts","../../src/collections/AccountCollection.ts"],"sourcesContent":["/**\n * Account model - Base class for all messaging account types (STI)\n *\n * Common fields shared across email, Slack, Twitter accounts, etc.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { AccountOptions, MessageSenderInterface } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class Account extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({\n description: 'The display name for this connected messaging account.',\n })\n name = '';\n @field({\n description: 'The service that connects this account to your application.',\n })\n providerType = '';\n @field({\n description:\n 'The kind of messaging this account supports, such as email or chat.',\n })\n channelType = '';\n // credentialSecretId stores the Secret's NAME (keyed by name + tenant\n // context in smrt-secrets), NOT its primary-key id — see setCredentials()/\n // getCredentials() which call tenant-bound SecretService operations by name.\n // So it is deliberately NOT a @crossPackageRef id FK.\n @field({ sensitive: true, readonly: true })\n credentialSecretId: string | null = null;\n @field({ description: 'Whether this account is enabled for messaging.' })\n isActive = true;\n @field({ description: 'When this account last checked for new messages.' })\n lastSyncAt: Date | null = null;\n // JSON. Sensitive (#1540): may hold a plaintext credential fallback, so it is\n // excluded from generated API/MCP responses and rejected as a `where` filter\n // key.\n @field({ sensitive: true, readonly: true })\n settings = ''; // JSON\n /** Non-secret provider configuration such as a Zulip site URL. */\n @field({\n description:\n 'The public connection settings for this account; credentials are stored separately.',\n })\n configuration = '{}';\n\n // Timestamps\n @field({ description: 'When this account was added.' })\n createdAt = new Date();\n @field({ description: 'When this account was last changed.' })\n updatedAt = new Date();\n\n constructor(options: AccountOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.providerType !== undefined)\n this.providerType = options.providerType;\n if (options.channelType !== undefined)\n this.channelType = options.channelType;\n if (options.credentialSecretId !== undefined)\n this.credentialSecretId = options.credentialSecretId || null;\n if (options.isActive !== undefined) this.isActive = options.isActive;\n if (options.lastSyncAt !== undefined)\n this.lastSyncAt = options.lastSyncAt || null;\n if (options.settings !== undefined) this.settings = options.settings;\n if (options.configuration !== undefined)\n this.configuration = options.configuration;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n /**\n * Get settings as parsed object\n */\n getSettings(): Record<string, unknown> {\n if (!this.settings) return {};\n try {\n return JSON.parse(this.settings);\n } catch {\n return {};\n }\n }\n\n /**\n * Set settings from object\n */\n setSettings(settings: Record<string, unknown>): void {\n this.settings = JSON.stringify(settings);\n }\n\n getConfiguration(): Record<string, unknown> {\n try {\n return JSON.parse(this.configuration || '{}');\n } catch {\n return {};\n }\n }\n\n setConfiguration(configuration: Record<string, unknown>): void {\n this.configuration = JSON.stringify(configuration);\n }\n\n get hasCredentials(): boolean {\n return Boolean(this.credentialSecretId || this.settings);\n }\n\n /**\n * Activate account\n */\n async activate(): Promise<void> {\n this.isActive = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Deactivate account\n */\n async deactivate(): Promise<void> {\n this.isActive = false;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Store credentials securely using smrt-secrets\n */\n async setCredentials(\n credentials: Record<string, unknown>,\n options: {\n description?: string;\n category?: string;\n } = {},\n ): Promise<void> {\n if (!this.id) {\n throw new Error('Account must be persisted before storing credentials.');\n }\n const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n const secretName = `${this.channelType || 'messaging'}-account-${this.id}`;\n const secretValue = JSON.stringify(credentials);\n const tenantId = this.requireCredentialTenantId('store credentials');\n\n await secretService.storeForTenant(\n tenantId,\n this.credentialSecretId || secretName,\n secretValue,\n {\n description: options.description || `Credentials for ${this.name}`,\n category: options.category || 'messaging',\n },\n );\n\n this.credentialSecretId ||= secretName;\n this.updatedAt = new Date();\n await this.withCredentialTenantContext(() => this.save());\n }\n\n /**\n * Create a sender for this account.\n * Subclasses must override to return a concrete sender.\n */\n async createSender(): Promise<MessageSenderInterface> {\n const { getMessagingProvider } = await import('../providers.js');\n const provider = getMessagingProvider(this.providerType);\n if (!provider?.createSender) {\n throw new Error(\n `createSender() not implemented for account type '${this.providerType}'`,\n );\n }\n return provider.createSender(this);\n }\n\n /**\n * Retrieve stored credentials\n */\n async getCredentials(): Promise<Record<string, unknown> | null> {\n if (!this.credentialSecretId) {\n return this.getSettings();\n }\n\n const tenantId = this.requireCredentialTenantId('retrieve credentials');\n const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n try {\n const secret = await secretService.retrieveForTenant(\n tenantId,\n this.credentialSecretId,\n );\n return JSON.parse(secret.value);\n } catch {\n return null;\n }\n }\n\n private getCredentialTenantId(): string | null {\n return this.tenantId || getCurrentTenant()?.tenantId || null;\n }\n\n private requireCredentialTenantId(action: string): string {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId) {\n throw new Error(`Tenant context required to ${action}.`);\n }\n return tenantId;\n }\n\n private async withCredentialTenantContext<T>(\n fn: () => Promise<T>,\n ): Promise<T> {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId || getCurrentTenant()?.tenantId === tenantId) return fn();\n return withTenant({ tenantId }, fn);\n }\n}\n","/**\n * AccountCollection - Unified collection for polymorphic account queries\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Account } from '../models/Account';\nimport type { AccountSearchFilters } from '../types';\n\nexport class AccountCollection extends SmrtCollection<Account> {\n static readonly _itemClass = Account;\n\n /**\n * Get active accounts\n */\n async getActive(): Promise<Account[]> {\n return await this.list({ where: { isActive: true } });\n }\n\n /**\n * Get inactive accounts\n */\n async getInactive(): Promise<Account[]> {\n return await this.list({ where: { isActive: false } });\n }\n\n /**\n * Get accounts by provider type\n */\n async getByProviderType(providerType: string): Promise<Account[]> {\n return await this.list({ where: { providerType } });\n }\n\n /**\n * Get accounts by STI type.\n *\n * Accepts either the full discriminator (e.g. \"@happyvertical/smrt-messages:EmailAccount\")\n * or the short type name (e.g. \"EmailAccount\").\n */\n async getByType(accountType: string): Promise<Account[]> {\n if (accountType.includes(':') || accountType.startsWith('@')) {\n return await this.list({ where: { _meta_type: accountType } });\n }\n const allAccounts = await this.list({});\n return allAccounts.filter((a) => {\n const metaType = (a as { _meta_type?: string })._meta_type || '';\n return metaType.endsWith(`:${accountType}`);\n });\n }\n\n /**\n * Search accounts with filters\n */\n async search(\n query: string,\n filters?: AccountSearchFilters,\n ): Promise<Account[]> {\n let accounts = await this.list({});\n\n if (query) {\n const lowerQuery = query.toLowerCase();\n accounts = accounts.filter((a) =>\n a.name?.toLowerCase().includes(lowerQuery),\n );\n }\n\n if (filters) {\n if (filters.providerType) {\n accounts = accounts.filter(\n (a) => a.providerType === filters.providerType,\n );\n }\n if (filters.isActive !== undefined) {\n accounts = accounts.filter((a) => a.isActive === filters.isActive);\n }\n if (filters.accountType) {\n accounts = accounts.filter((a) => {\n const metaType = (a as { _meta_type?: string })._meta_type || '';\n return metaType.includes(filters.accountType as string);\n });\n }\n }\n\n return accounts;\n }\n\n /**\n * Get account statistics\n */\n async getStats(): Promise<{\n total: number;\n active: number;\n inactive: number;\n byType: Record<string, number>;\n }> {\n const accounts = await this.list({});\n const byType: Record<string, number> = {};\n\n for (const account of accounts) {\n const metaType =\n (account as { _meta_type?: string })._meta_type || 'Unknown';\n const shortType = metaType.split(':').pop() || metaType;\n byType[shortType] = (byType[shortType] || 0) + 1;\n }\n\n return {\n total: accounts.length,\n active: accounts.filter((a) => a.isActive).length,\n inactive: accounts.filter((a) => !a.isActive).length,\n byType,\n };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Tenant Helper Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n async findByTenant(tenantId: string): Promise<Account[]> {\n return this.list({ where: { tenantId } });\n }\n\n // Account is the @TenantScoped STI base — see MessageCollection. Route through\n // the raw helpers; no `_meta_type` so the base returns ALL account subtypes.\n async findGlobal(): Promise<Account[]> {\n return queryGlobal<Account>(this);\n }\n\n async findWithGlobals(tenantId: string): Promise<Account[]> {\n return queryWithGlobals<Account>(this, tenantId, 'Account.findWithGlobals');\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import "./__smrt-register__-
|
|
3
|
-
import { t as Message } from "./Message-
|
|
2
|
+
import "./__smrt-register__-v_rnuvPG.js";
|
|
3
|
+
import { t as Message } from "./Message-HYEwxDiQ.js";
|
|
4
4
|
import { field, smrt } from "@happyvertical/smrt-core";
|
|
5
5
|
//#region src/models/Email.ts
|
|
6
6
|
var Email_exports = /* @__PURE__ */ __exportAll({ Email: () => Email });
|
|
@@ -334,4 +334,4 @@ Email = __decorateClass([smrt({
|
|
|
334
334
|
//#endregion
|
|
335
335
|
export { Email_exports as n, Email as t };
|
|
336
336
|
|
|
337
|
-
//# sourceMappingURL=Email-
|
|
337
|
+
//# sourceMappingURL=Email-DWC-RO7A.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Email-B2kXLj13.js","names":[],"sources":["../../src/models/Email.ts"],"sourcesContent":["/**\n * Email model - Email message extending the Message STI base\n *\n * Retains all email-specific fields (RFC 822 compliance, folders, etc.)\n * while inheriting common message fields from Message.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { field, smrt } from '@happyvertical/smrt-core';\nimport type {\n EmailOptions,\n MessageSendResult,\n SendEmailOptions,\n} from '../types';\nimport { Message } from './Message';\n\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n // Message is read-only over REST; this provider type is writable there.\n widenParentExposure: { api: ['create', 'update', 'delete'] },\n})\nexport class Email extends Message { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n // RFC 822 fields\n @field({\n description:\n 'The email reference assigned by the sender for tracking replies.',\n })\n messageId = ''; // RFC 822 Message-ID header\n @field({\n description: 'The sender-assigned reference of the email being replied to.',\n })\n inReplyTo = '';\n\n // Additional recipients\n @field({ description: 'Recipients receiving a visible copy of this email.' })\n ccAddresses = '';\n @field({\n description:\n 'Recipients receiving a copy without appearing in the visible recipient list.',\n })\n bccAddresses = '';\n @field({ description: 'The address replies should be sent to.' })\n replyToAddress = '';\n @field({ description: 'The name associated with the reply address.' })\n replyToName = '';\n\n // Email-specific content\n @field({ description: 'The plain-text version of the email.' })\n textBody = '';\n @field({ description: 'The formatted version of the email.' })\n htmlBody = '';\n\n // Location\n @field({\n description:\n 'The mailbox folder reference supplied by the email connection.',\n })\n folderId = '';\n @field({\n description: 'The location of this email within the mailbox folders.',\n })\n folderPath = '';\n @field({\n description: 'Labels applied to this email by the mailbox provider.',\n })\n labels = ''; // JSON array (Gmail)\n @field({ description: 'Mailbox markers such as read, answered, or flagged.' })\n flags = ''; // JSON array (IMAP)\n\n // Email-specific status flags\n @field({ description: 'Whether this email has been replied to.' })\n isAnswered = false;\n @field({ description: 'Whether this email is saved as an unfinished draft.' })\n isDraft = false;\n\n // Raw data\n @field({\n description: 'The original email content as received from the mail server.',\n })\n rawMessage = '';\n @field({\n description: 'Delivery and routing details included with the email.',\n })\n headers = ''; // JSON object\n\n constructor(options: EmailOptions = {}) {\n super(options);\n\n if (options.messageId !== undefined) this.messageId = options.messageId;\n if (options.inReplyTo !== undefined) this.inReplyTo = options.inReplyTo;\n if (options.ccAddresses !== undefined)\n this.ccAddresses = options.ccAddresses;\n if (options.bccAddresses !== undefined)\n this.bccAddresses = options.bccAddresses;\n if (options.replyToAddress !== undefined)\n this.replyToAddress = options.replyToAddress;\n if (options.replyToName !== undefined)\n this.replyToName = options.replyToName;\n if (options.textBody !== undefined) this.textBody = options.textBody;\n if (options.htmlBody !== undefined) this.htmlBody = options.htmlBody;\n if (options.folderId !== undefined) this.folderId = options.folderId;\n if (options.folderPath !== undefined) this.folderPath = options.folderPath;\n if (options.labels !== undefined) this.labels = options.labels;\n if (options.flags !== undefined) this.flags = options.flags;\n if (options.isAnswered !== undefined) this.isAnswered = options.isAnswered;\n if (options.isDraft !== undefined) this.isDraft = options.isDraft;\n if (options.rawMessage !== undefined) this.rawMessage = options.rawMessage;\n if (options.headers !== undefined) this.headers = options.headers;\n\n // Populate base body from textBody for unified access\n if (options.textBody && !options.body) {\n this.body = options.textBody;\n }\n }\n\n /**\n * Get CC addresses as parsed array\n */\n getCcAddresses(): Array<{ address: string; name?: string }> {\n if (!this.ccAddresses) return [];\n try {\n return JSON.parse(this.ccAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Get BCC addresses as parsed array\n */\n getBccAddresses(): Array<{ address: string; name?: string }> {\n if (!this.bccAddresses) return [];\n try {\n return JSON.parse(this.bccAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Get labels as parsed array\n */\n getLabels(): string[] {\n if (!this.labels) return [];\n try {\n return JSON.parse(this.labels);\n } catch {\n return [];\n }\n }\n\n /**\n * Set labels from array\n */\n setLabels(labels: string[]): void {\n this.labels = JSON.stringify(labels);\n }\n\n /**\n * Get flags as parsed array\n */\n getFlags(): string[] {\n if (!this.flags) return [];\n try {\n return JSON.parse(this.flags);\n } catch {\n return [];\n }\n }\n\n /**\n * Set flags from array\n */\n setFlags(flags: string[]): void {\n this.flags = JSON.stringify(flags);\n }\n\n /**\n * Get headers as parsed object\n */\n getHeaders(): Record<string, string | string[]> {\n if (!this.headers) return {};\n try {\n return JSON.parse(this.headers);\n } catch {\n return {};\n }\n }\n\n /**\n * Set headers from object\n */\n setHeaders(headers: Record<string, string | string[]>): void {\n this.headers = JSON.stringify(headers);\n }\n\n /**\n * Get the email account (typed as EmailAccount)\n */\n override async getAccount() {\n if (!this.accountId) return null;\n\n const { EmailAccountCollection } = await import(\n '../collections/EmailAccountCollection'\n );\n const collection = await EmailAccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n /**\n * Get the folder\n */\n async getFolder() {\n if (!this.folderId) return null;\n\n const { EmailFolderCollection } = await import(\n '../collections/EmailFolderCollection'\n );\n const collection = await EmailFolderCollection.create(this.options);\n\n return await collection.get({ id: this.folderId });\n }\n\n /**\n * Get emails in the same thread\n */\n async getThreadEmails(): Promise<Email[]> {\n if (!this.threadId) return [this];\n\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n return await collection.getByThread(this.threadId);\n }\n\n /**\n * Get a short preview of the email body\n */\n override getPreview(maxLength = 200): string {\n const body = this.textBody || this.htmlBody?.replace(/<[^>]*>/g, '') || '';\n if (body.length <= maxLength) return body;\n return `${body.slice(0, maxLength)}...`;\n }\n\n /**\n * Get References header values as array\n */\n getReferences(): string[] {\n const headers = this.getHeaders();\n const refs = headers.references;\n if (!refs) return [];\n if (Array.isArray(refs)) return refs;\n return refs.split(/\\s+/).filter(Boolean);\n }\n\n /**\n * Create a reply to this email with RFC 822 threading\n */\n override createReply(options?: { replyAll?: boolean }): Email {\n const reply = new Email({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: this.threadId || this.id || undefined,\n subject: this.subject.startsWith('Re:')\n ? this.subject\n : `Re: ${this.subject}`,\n fromAddress: '',\n fromName: '',\n inReplyToMessageId: this.id || undefined,\n sendStatus: 'draft',\n isRead: true,\n isDraft: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n\n // RFC 822 threading\n inReplyTo: this.messageId,\n\n // To: original sender\n toAddresses: JSON.stringify([\n { address: this.fromAddress, name: this.fromName },\n ]),\n });\n\n // RFC 822 References: original references + original Message-ID\n const refs = [...this.getReferences()];\n if (this.messageId && !refs.includes(this.messageId)) {\n refs.push(this.messageId);\n }\n reply.setHeaders({ references: refs.join(' ') });\n\n // Reply-All: add original To/CC as CC (excluding self)\n if (options?.replyAll) {\n const allRecipients = [\n ...this.getToAddresses(),\n ...this.getCcAddresses(),\n ];\n // Remove original sender (already in To:) and any duplicates\n const seen = new Set([this.fromAddress.toLowerCase()]);\n const ccAddresses = allRecipients.filter((r) => {\n const addr = r.address.toLowerCase();\n if (seen.has(addr)) return false;\n seen.add(addr);\n return true;\n });\n reply.ccAddresses = JSON.stringify(ccAddresses);\n }\n\n reply.body = this.buildQuotedBody();\n reply.textBody = reply.body;\n\n return reply;\n }\n\n /**\n * Create a forward of this email\n */\n override createForward(): Email {\n const forwardBody = this.buildForwardBody();\n\n const forward = new Email({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: '',\n subject: this.subject.startsWith('Fwd:')\n ? this.subject\n : `Fwd: ${this.subject}`,\n toAddresses: '[]',\n ccAddresses: '[]',\n bccAddresses: '[]',\n fromAddress: '',\n fromName: '',\n body: forwardBody,\n textBody: forwardBody,\n hasAttachments: this.hasAttachments,\n inReplyToMessageId: '',\n inReplyTo: '',\n sendStatus: 'draft',\n isDraft: true,\n isRead: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n });\n\n return forward;\n }\n\n /**\n * Build email-specific quoted body for replies\n */\n protected override buildQuotedBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const bodyText = this.textBody || this.body || '';\n const quotedLines = bodyText\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n');\n\n return `\\n\\nOn ${dateStr}, ${from} wrote:\\n${quotedLines}`;\n }\n\n /**\n * Build forwarded message body\n */\n private buildForwardBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const toStr = this.getToAddresses()\n .map((r) => (r.name ? `${r.name} <${r.address}>` : r.address))\n .join(', ');\n\n const bodyText = this.textBody || this.body || '';\n\n return [\n '',\n '',\n '---------- Forwarded message ----------',\n `From: ${from}`,\n `Date: ${dateStr}`,\n `Subject: ${this.subject}`,\n `To: ${toStr}`,\n '',\n bodyText,\n ].join('\\n');\n }\n\n /**\n * Send this email. Accepts {@link SendEmailOptions}, including send-time\n * `attachments`, which `EmailSender` merges with the stored rows (#3808).\n */\n override async send(options?: SendEmailOptions): Promise<MessageSendResult> {\n const result = await super.send(options);\n await this.markHasAttachments(options, result);\n return result;\n }\n\n /** Retry a failed send; takes the same options as {@link Email.send}. */\n override async retrySend(\n options?: SendEmailOptions,\n ): Promise<MessageSendResult> {\n const result = await super.retrySend(options);\n await this.markHasAttachments(options, result);\n return result;\n }\n\n /**\n * Record `hasAttachments` once send-time bytes were actually delivered, so a\n * rejected or failed send never claims attachments that did not go out.\n */\n private async markHasAttachments(\n options: SendEmailOptions | undefined,\n result: MessageSendResult,\n ): Promise<void> {\n if (!result.success || !options?.attachments?.length) return;\n if (this.hasAttachments) return;\n this.hasAttachments = true;\n if (this.isPersisted) await this.save();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Email-DWC-RO7A.js","names":[],"sources":["../../src/models/Email.ts"],"sourcesContent":["/**\n * Email model - Email message extending the Message STI base\n *\n * Retains all email-specific fields (RFC 822 compliance, folders, etc.)\n * while inheriting common message fields from Message.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { field, smrt } from '@happyvertical/smrt-core';\nimport type {\n EmailOptions,\n MessageSendResult,\n SendEmailOptions,\n} from '../types';\nimport { Message } from './Message';\n\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n // Message is read-only over REST; this provider type is writable there.\n widenParentExposure: { api: ['create', 'update', 'delete'] },\n})\nexport class Email extends Message { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n // RFC 822 fields\n @field({\n description:\n 'The email reference assigned by the sender for tracking replies.',\n })\n messageId = ''; // RFC 822 Message-ID header\n @field({\n description: 'The sender-assigned reference of the email being replied to.',\n })\n inReplyTo = '';\n\n // Additional recipients\n @field({ description: 'Recipients receiving a visible copy of this email.' })\n ccAddresses = '';\n @field({\n description:\n 'Recipients receiving a copy without appearing in the visible recipient list.',\n })\n bccAddresses = '';\n @field({ description: 'The address replies should be sent to.' })\n replyToAddress = '';\n @field({ description: 'The name associated with the reply address.' })\n replyToName = '';\n\n // Email-specific content\n @field({ description: 'The plain-text version of the email.' })\n textBody = '';\n @field({ description: 'The formatted version of the email.' })\n htmlBody = '';\n\n // Location\n @field({\n description:\n 'The mailbox folder reference supplied by the email connection.',\n })\n folderId = '';\n @field({\n description: 'The location of this email within the mailbox folders.',\n })\n folderPath = '';\n @field({\n description: 'Labels applied to this email by the mailbox provider.',\n })\n labels = ''; // JSON array (Gmail)\n @field({ description: 'Mailbox markers such as read, answered, or flagged.' })\n flags = ''; // JSON array (IMAP)\n\n // Email-specific status flags\n @field({ description: 'Whether this email has been replied to.' })\n isAnswered = false;\n @field({ description: 'Whether this email is saved as an unfinished draft.' })\n isDraft = false;\n\n // Raw data\n @field({\n description: 'The original email content as received from the mail server.',\n })\n rawMessage = '';\n @field({\n description: 'Delivery and routing details included with the email.',\n })\n headers = ''; // JSON object\n\n constructor(options: EmailOptions = {}) {\n super(options);\n\n if (options.messageId !== undefined) this.messageId = options.messageId;\n if (options.inReplyTo !== undefined) this.inReplyTo = options.inReplyTo;\n if (options.ccAddresses !== undefined)\n this.ccAddresses = options.ccAddresses;\n if (options.bccAddresses !== undefined)\n this.bccAddresses = options.bccAddresses;\n if (options.replyToAddress !== undefined)\n this.replyToAddress = options.replyToAddress;\n if (options.replyToName !== undefined)\n this.replyToName = options.replyToName;\n if (options.textBody !== undefined) this.textBody = options.textBody;\n if (options.htmlBody !== undefined) this.htmlBody = options.htmlBody;\n if (options.folderId !== undefined) this.folderId = options.folderId;\n if (options.folderPath !== undefined) this.folderPath = options.folderPath;\n if (options.labels !== undefined) this.labels = options.labels;\n if (options.flags !== undefined) this.flags = options.flags;\n if (options.isAnswered !== undefined) this.isAnswered = options.isAnswered;\n if (options.isDraft !== undefined) this.isDraft = options.isDraft;\n if (options.rawMessage !== undefined) this.rawMessage = options.rawMessage;\n if (options.headers !== undefined) this.headers = options.headers;\n\n // Populate base body from textBody for unified access\n if (options.textBody && !options.body) {\n this.body = options.textBody;\n }\n }\n\n /**\n * Get CC addresses as parsed array\n */\n getCcAddresses(): Array<{ address: string; name?: string }> {\n if (!this.ccAddresses) return [];\n try {\n return JSON.parse(this.ccAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Get BCC addresses as parsed array\n */\n getBccAddresses(): Array<{ address: string; name?: string }> {\n if (!this.bccAddresses) return [];\n try {\n return JSON.parse(this.bccAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Get labels as parsed array\n */\n getLabels(): string[] {\n if (!this.labels) return [];\n try {\n return JSON.parse(this.labels);\n } catch {\n return [];\n }\n }\n\n /**\n * Set labels from array\n */\n setLabels(labels: string[]): void {\n this.labels = JSON.stringify(labels);\n }\n\n /**\n * Get flags as parsed array\n */\n getFlags(): string[] {\n if (!this.flags) return [];\n try {\n return JSON.parse(this.flags);\n } catch {\n return [];\n }\n }\n\n /**\n * Set flags from array\n */\n setFlags(flags: string[]): void {\n this.flags = JSON.stringify(flags);\n }\n\n /**\n * Get headers as parsed object\n */\n getHeaders(): Record<string, string | string[]> {\n if (!this.headers) return {};\n try {\n return JSON.parse(this.headers);\n } catch {\n return {};\n }\n }\n\n /**\n * Set headers from object\n */\n setHeaders(headers: Record<string, string | string[]>): void {\n this.headers = JSON.stringify(headers);\n }\n\n /**\n * Get the email account (typed as EmailAccount)\n */\n override async getAccount() {\n if (!this.accountId) return null;\n\n const { EmailAccountCollection } = await import(\n '../collections/EmailAccountCollection'\n );\n const collection = await EmailAccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n /**\n * Get the folder\n */\n async getFolder() {\n if (!this.folderId) return null;\n\n const { EmailFolderCollection } = await import(\n '../collections/EmailFolderCollection'\n );\n const collection = await EmailFolderCollection.create(this.options);\n\n return await collection.get({ id: this.folderId });\n }\n\n /**\n * Get emails in the same thread\n */\n async getThreadEmails(): Promise<Email[]> {\n if (!this.threadId) return [this];\n\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n return await collection.getByThread(this.threadId);\n }\n\n /**\n * Get a short preview of the email body\n */\n override getPreview(maxLength = 200): string {\n const body = this.textBody || this.htmlBody?.replace(/<[^>]*>/g, '') || '';\n if (body.length <= maxLength) return body;\n return `${body.slice(0, maxLength)}...`;\n }\n\n /**\n * Get References header values as array\n */\n getReferences(): string[] {\n const headers = this.getHeaders();\n const refs = headers.references;\n if (!refs) return [];\n if (Array.isArray(refs)) return refs;\n return refs.split(/\\s+/).filter(Boolean);\n }\n\n /**\n * Create a reply to this email with RFC 822 threading\n */\n override createReply(options?: { replyAll?: boolean }): Email {\n const reply = new Email({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: this.threadId || this.id || undefined,\n subject: this.subject.startsWith('Re:')\n ? this.subject\n : `Re: ${this.subject}`,\n fromAddress: '',\n fromName: '',\n inReplyToMessageId: this.id || undefined,\n sendStatus: 'draft',\n isRead: true,\n isDraft: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n\n // RFC 822 threading\n inReplyTo: this.messageId,\n\n // To: original sender\n toAddresses: JSON.stringify([\n { address: this.fromAddress, name: this.fromName },\n ]),\n });\n\n // RFC 822 References: original references + original Message-ID\n const refs = [...this.getReferences()];\n if (this.messageId && !refs.includes(this.messageId)) {\n refs.push(this.messageId);\n }\n reply.setHeaders({ references: refs.join(' ') });\n\n // Reply-All: add original To/CC as CC (excluding self)\n if (options?.replyAll) {\n const allRecipients = [\n ...this.getToAddresses(),\n ...this.getCcAddresses(),\n ];\n // Remove original sender (already in To:) and any duplicates\n const seen = new Set([this.fromAddress.toLowerCase()]);\n const ccAddresses = allRecipients.filter((r) => {\n const addr = r.address.toLowerCase();\n if (seen.has(addr)) return false;\n seen.add(addr);\n return true;\n });\n reply.ccAddresses = JSON.stringify(ccAddresses);\n }\n\n reply.body = this.buildQuotedBody();\n reply.textBody = reply.body;\n\n return reply;\n }\n\n /**\n * Create a forward of this email\n */\n override createForward(): Email {\n const forwardBody = this.buildForwardBody();\n\n const forward = new Email({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: '',\n subject: this.subject.startsWith('Fwd:')\n ? this.subject\n : `Fwd: ${this.subject}`,\n toAddresses: '[]',\n ccAddresses: '[]',\n bccAddresses: '[]',\n fromAddress: '',\n fromName: '',\n body: forwardBody,\n textBody: forwardBody,\n hasAttachments: this.hasAttachments,\n inReplyToMessageId: '',\n inReplyTo: '',\n sendStatus: 'draft',\n isDraft: true,\n isRead: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n });\n\n return forward;\n }\n\n /**\n * Build email-specific quoted body for replies\n */\n protected override buildQuotedBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const bodyText = this.textBody || this.body || '';\n const quotedLines = bodyText\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n');\n\n return `\\n\\nOn ${dateStr}, ${from} wrote:\\n${quotedLines}`;\n }\n\n /**\n * Build forwarded message body\n */\n private buildForwardBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const toStr = this.getToAddresses()\n .map((r) => (r.name ? `${r.name} <${r.address}>` : r.address))\n .join(', ');\n\n const bodyText = this.textBody || this.body || '';\n\n return [\n '',\n '',\n '---------- Forwarded message ----------',\n `From: ${from}`,\n `Date: ${dateStr}`,\n `Subject: ${this.subject}`,\n `To: ${toStr}`,\n '',\n bodyText,\n ].join('\\n');\n }\n\n /**\n * Send this email. Accepts {@link SendEmailOptions}, including send-time\n * `attachments`, which `EmailSender` merges with the stored rows (#3808).\n */\n override async send(options?: SendEmailOptions): Promise<MessageSendResult> {\n const result = await super.send(options);\n await this.markHasAttachments(options, result);\n return result;\n }\n\n /** Retry a failed send; takes the same options as {@link Email.send}. */\n override async retrySend(\n options?: SendEmailOptions,\n ): Promise<MessageSendResult> {\n const result = await super.retrySend(options);\n await this.markHasAttachments(options, result);\n return result;\n }\n\n /**\n * Record `hasAttachments` once send-time bytes were actually delivered, so a\n * rejected or failed send never claims attachments that did not go out.\n */\n private async markHasAttachments(\n options: SendEmailOptions | undefined,\n result: MessageSendResult,\n ): Promise<void> {\n if (!result.success || !options?.attachments?.length) return;\n if (this.hasAttachments) return;\n this.hasAttachments = true;\n if (this.isPersisted) await this.save();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import "./__smrt-register__-
|
|
2
|
+
import "./__smrt-register__-v_rnuvPG.js";
|
|
3
3
|
import { SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
4
4
|
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
5
5
|
//#region src/models/EmailFolder.ts
|
|
@@ -159,4 +159,4 @@ EmailFolder = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
|
159
159
|
//#endregion
|
|
160
160
|
export { EmailFolder_exports as n, EmailFolder as t };
|
|
161
161
|
|
|
162
|
-
//# sourceMappingURL=EmailFolder-
|
|
162
|
+
//# sourceMappingURL=EmailFolder-BNX0j2B1.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmailFolder-
|
|
1
|
+
{"version":3,"file":"EmailFolder-BNX0j2B1.js","names":[],"sources":["../../src/models/EmailFolder.ts"],"sourcesContent":["/**\n * EmailFolder model - Folder/label tracking\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { EmailFolderOptions } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class EmailFolder extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Account')\n accountId = '';\n name = '';\n path = '';\n delimiter = '/';\n specialUse = ''; // '\\\\Inbox', '\\\\Sent', '\\\\Drafts', etc.\n messageCount = 0;\n unreadCount = 0;\n subscribed = true;\n\n // Timestamps\n createdAt = new Date();\n updatedAt = new Date();\n\n constructor(options: EmailFolderOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.accountId !== undefined) this.accountId = options.accountId;\n if (options.name !== undefined) this.name = options.name;\n if (options.path !== undefined) this.path = options.path;\n if (options.delimiter !== undefined) this.delimiter = options.delimiter;\n if (options.specialUse !== undefined) this.specialUse = options.specialUse;\n if (options.messageCount !== undefined)\n this.messageCount = options.messageCount;\n if (options.unreadCount !== undefined)\n this.unreadCount = options.unreadCount;\n if (options.subscribed !== undefined) this.subscribed = options.subscribed;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n /**\n * Get the email account\n */\n async getAccount() {\n if (!this.accountId) return null;\n\n const { EmailAccountCollection } = await import(\n '../collections/EmailAccountCollection'\n );\n const collection = await EmailAccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n /**\n * Get all emails in this folder\n */\n async getEmails(limit?: number) {\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n const options: { where: Record<string, unknown>; limit?: number } = {\n where: { folderId: this.id },\n };\n if (limit) {\n options.limit = limit;\n }\n\n return await collection.list(options);\n }\n\n /**\n * Get unread emails in this folder\n */\n async getUnreadEmails(limit?: number) {\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n const options: { where: Record<string, unknown>; limit?: number } = {\n where: { folderId: this.id, isRead: false },\n };\n if (limit) {\n options.limit = limit;\n }\n\n return await collection.list(options);\n }\n\n /**\n * Update message counts from database\n */\n async refreshCounts(): Promise<void> {\n if (!this.id) {\n throw new Error(\n 'EmailFolder.refreshCounts requires a persisted folder (missing id)',\n );\n }\n const folderId = this.id;\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n this.messageCount = await collection.countByFolder(folderId);\n this.unreadCount = await collection.countUnreadByFolder(folderId);\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Check if this is the inbox folder\n */\n isInbox(): boolean {\n return this.specialUse === '\\\\Inbox' || this.path.toLowerCase() === 'inbox';\n }\n\n /**\n * Check if this is the sent folder\n */\n isSent(): boolean {\n return this.specialUse === '\\\\Sent' || this.path.toLowerCase() === 'sent';\n }\n\n /**\n * Check if this is the drafts folder\n */\n isDrafts(): boolean {\n return (\n this.specialUse === '\\\\Drafts' || this.path.toLowerCase() === 'drafts'\n );\n }\n\n /**\n * Check if this is the trash folder\n */\n isTrash(): boolean {\n return this.specialUse === '\\\\Trash' || this.path.toLowerCase() === 'trash';\n }\n\n /**\n * Check if this is the spam/junk folder\n */\n isSpam(): boolean {\n return (\n this.specialUse === '\\\\Junk' ||\n this.path.toLowerCase() === 'spam' ||\n this.path.toLowerCase() === 'junk'\n );\n }\n\n /**\n * Check if this is a system folder\n */\n isSystemFolder(): boolean {\n return !!this.specialUse;\n }\n\n /**\n * Subscribe to folder\n */\n async subscribe(): Promise<void> {\n this.subscribed = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Unsubscribe from folder\n */\n async unsubscribe(): Promise<void> {\n this.subscribed = false;\n this.updatedAt = new Date();\n await this.save();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./__smrt-register__-
|
|
1
|
+
import "./__smrt-register__-v_rnuvPG.js";
|
|
2
2
|
import { SmrtObject, crossPackageRef, field, foreignKey, smrt, usesEmbeddedRevisionFallback, withEmbeddedWriteQueue } from "@happyvertical/smrt-core";
|
|
3
3
|
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
4
4
|
//#region src/models/Message.ts
|
|
@@ -191,7 +191,7 @@ var Message = class extends SmrtObject {
|
|
|
191
191
|
*/
|
|
192
192
|
async getAccount() {
|
|
193
193
|
if (!this.accountId) return null;
|
|
194
|
-
const { AccountCollection } = await import("./AccountCollection
|
|
194
|
+
const { AccountCollection } = await import("./AccountCollection-CDOBNWWG.js").then((n) => n.n);
|
|
195
195
|
return await (await AccountCollection.create(this.options)).get({ id: this.accountId });
|
|
196
196
|
}
|
|
197
197
|
async getEndpoint() {
|
|
@@ -204,7 +204,7 @@ var Message = class extends SmrtObject {
|
|
|
204
204
|
*/
|
|
205
205
|
async getThreadMessages() {
|
|
206
206
|
if (!this.threadId) return [this];
|
|
207
|
-
const { MessageCollection } = await import("./MessageCollection-
|
|
207
|
+
const { MessageCollection } = await import("./MessageCollection-BaOC9VOR.js").then((n) => n.n);
|
|
208
208
|
return await (await MessageCollection.create(this.options)).list({ where: { threadId: this.threadId } });
|
|
209
209
|
}
|
|
210
210
|
/**
|
|
@@ -453,4 +453,4 @@ Message = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
|
453
453
|
//#endregion
|
|
454
454
|
export { Message as t };
|
|
455
455
|
|
|
456
|
-
//# sourceMappingURL=Message-
|
|
456
|
+
//# sourceMappingURL=Message-HYEwxDiQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Message-CqSUV46b.js","names":["result"],"sources":["../../src/models/Message.ts"],"sourcesContent":["/**\n * Message model - Base class for all message types (STI)\n *\n * Common fields shared across email, tweets, slack messages, etc.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n usesEmbeddedRevisionFallback,\n withEmbeddedWriteQueue,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n MessageOptions,\n MessageSendResult,\n SendMessageOptions,\n SendStatus,\n} from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Message extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Account')\n @field({\n description: 'The connected account used to receive or send this message.',\n })\n accountId = '';\n @crossPackageRef('@happyvertical/smrt-personas:AgentPersona', {\n nullable: true,\n })\n @field({\n description:\n 'The assistant identity associated with this message, when one was used.',\n })\n personaId: string | null = null;\n @foreignKey('MessagingEndpoint')\n @field({ description: 'The saved destination used to deliver this message.' })\n endpointId: string | null = null;\n @field({\n description:\n 'A tracking reference that connects this message to related activity.',\n })\n correlationId = '';\n @field({ description: 'The conversation this message belongs to.' })\n threadId = '';\n @field({ description: 'The subject shown in the message list.' })\n subject = '';\n @field({ description: 'The message content in plain text.' })\n body = ''; // Normalized plain text\n @field({ description: 'The address the message was sent from.' })\n fromAddress = '';\n @field({ description: 'The sender name shown beside their address.' })\n fromName = '';\n @field({ description: 'The people or addresses receiving this message.' })\n toAddresses = ''; // JSON array of {address, name}\n @field({ description: 'When the message was sent or received.' })\n date: Date | null = null;\n @field({ description: 'Whether this message has been marked as read.' })\n isRead = false;\n @field({ description: 'Whether this message is flagged for follow-up.' })\n isFlagged = false;\n @field({ description: 'Whether files are attached to this message.' })\n hasAttachments = false;\n @field({ description: 'The size of the message in bytes.' })\n size = 0;\n @field({\n description: 'Additional details supplied by the messaging connection.',\n })\n metadata = ''; // JSON extension bag\n\n // Send lifecycle fields\n @field({\n description:\n 'The latest sending state: draft, sending, sent, or failed. Received mail may also show draft here.',\n })\n sendStatus: SendStatus = 'draft';\n @field({ description: 'When the message was successfully sent.' })\n sentAt: Date | null = null;\n @field({ description: 'The reason the latest send attempt failed.' })\n sendError = '';\n @field({ description: 'How many times delivery has been retried.' })\n retryCount = 0;\n @field({ description: 'The maximum number of delivery retries allowed.' })\n maxRetries = 3;\n @field({ description: 'When this message is scheduled to be sent.' })\n scheduledSendAt: Date | null = null;\n @foreignKey('Message')\n @field({ description: 'The earlier message this one replies to.' })\n inReplyToMessageId = '';\n\n // Timestamps\n @field({ description: 'When this message was recorded.' })\n createdAt = new Date();\n @field({ description: 'When this message was last changed.' })\n updatedAt = new Date();\n\n constructor(options: MessageOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.accountId !== undefined) this.accountId = options.accountId;\n if (options.personaId !== undefined) this.personaId = options.personaId;\n if (options.endpointId !== undefined) this.endpointId = options.endpointId;\n if (options.correlationId !== undefined)\n this.correlationId = options.correlationId;\n if (options.threadId !== undefined) this.threadId = options.threadId;\n if (options.subject !== undefined) this.subject = options.subject;\n if (options.body !== undefined) this.body = options.body;\n if (options.fromAddress !== undefined)\n this.fromAddress = options.fromAddress;\n if (options.fromName !== undefined) this.fromName = options.fromName;\n if (options.toAddresses !== undefined)\n this.toAddresses = options.toAddresses;\n if (options.date !== undefined) this.date = options.date || null;\n if (options.isRead !== undefined) this.isRead = options.isRead;\n if (options.isFlagged !== undefined) this.isFlagged = options.isFlagged;\n if (options.hasAttachments !== undefined)\n this.hasAttachments = options.hasAttachments;\n if (options.size !== undefined) this.size = options.size;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n if (options.sendStatus !== undefined) this.sendStatus = options.sendStatus;\n if (options.sentAt !== undefined) this.sentAt = options.sentAt || null;\n if (options.sendError !== undefined) this.sendError = options.sendError;\n if (options.retryCount !== undefined) this.retryCount = options.retryCount;\n if (options.maxRetries !== undefined) this.maxRetries = options.maxRetries;\n if (options.scheduledSendAt !== undefined)\n this.scheduledSendAt = options.scheduledSendAt || null;\n if (options.inReplyToMessageId !== undefined)\n this.inReplyToMessageId = options.inReplyToMessageId;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n private async finalizeSendLifecycle(\n result: MessageSendResult,\n ): Promise<void> {\n if (!this.id) throw new Error('Cannot finalize an unsaved message send');\n\n const sendStatus: SendStatus = result.success ? 'sent' : 'failed';\n const sendError = result.success ? '' : (result.error ?? 'Send failed');\n const useEmbeddedFallback = usesEmbeddedRevisionFallback(this.db);\n for (let attempt = 0; attempt < 8; attempt += 1) {\n let nextRevision: Date | undefined;\n await withEmbeddedWriteQueue(this.db, useEmbeddedFallback, async () => {\n const current = await this.getCanonicalPersistedRow({ id: this.id });\n if (current?.send_status !== 'sending') return;\n const currentRevision = current.updated_at;\n const currentRevisionMs = new Date(String(currentRevision)).getTime();\n if (!Number.isFinite(currentRevisionMs)) return;\n nextRevision = new Date(Math.max(Date.now(), currentRevisionMs + 1));\n const values = {\n send_status: sendStatus,\n sent_at: result.success ? result.sentAt : null,\n send_error: sendError,\n updated_at: nextRevision,\n };\n if (useEmbeddedFallback) {\n await this.db.upsert(this.tableName, ['id'], {\n ...current,\n ...values,\n });\n return;\n }\n await this.db.update(\n this.tableName,\n {\n id: this.id,\n send_status: 'sending',\n updated_at:\n currentRevision instanceof Date\n ? currentRevision.toISOString()\n : currentRevision,\n },\n values,\n );\n });\n // DuckDB's generic update result reports one affected row even when the\n // predicate matched none. Verify the durable lifecycle value instead of\n // trusting adapter row-count metadata before declaring finalization.\n const verified = await this.getCanonicalPersistedRow({ id: this.id });\n if (verified?.send_status === sendStatus) {\n await this.completePersistedUpdate(verified);\n return;\n }\n }\n throw new Error('Cannot finalize: message revision kept changing');\n }\n\n /**\n * Get to addresses as parsed array\n */\n getToAddresses(): Array<{ address: string; name?: string }> {\n if (!this.toAddresses) return [];\n try {\n return JSON.parse(this.toAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Set to addresses from array\n */\n setToAddresses(addresses: Array<{ address: string; name?: string }>): void {\n this.toAddresses = JSON.stringify(addresses);\n }\n\n /**\n * Get metadata as parsed object\n */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n return JSON.parse(this.metadata);\n } catch {\n return {};\n }\n }\n\n /**\n * Set metadata from object\n */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data);\n }\n\n /**\n * Mark message as read\n */\n async markRead(): Promise<void> {\n this.isRead = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Mark message as unread\n */\n async markUnread(): Promise<void> {\n this.isRead = false;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Toggle flagged status\n */\n async toggleFlagged(): Promise<void> {\n this.isFlagged = !this.isFlagged;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Check if message is unread\n */\n isUnread(): boolean {\n return !this.isRead;\n }\n\n /**\n * Get a short preview of the message body\n */\n getPreview(maxLength = 200): string {\n const text = this.body || '';\n if (text.length <= maxLength) return text;\n return `${text.slice(0, maxLength)}...`;\n }\n\n /**\n * Get the account for this message\n */\n async getAccount() {\n if (!this.accountId) return null;\n\n const { AccountCollection } = await import(\n '../collections/AccountCollection'\n );\n const collection = await AccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n async getEndpoint() {\n if (!this.endpointId) return null;\n const { MessagingEndpointCollection } = await import(\n '../collections/MessagingEndpointCollection.js'\n );\n const collection = await MessagingEndpointCollection.create(this.options);\n return collection.get({ id: this.endpointId });\n }\n\n /**\n * Get messages in the same thread\n */\n async getThreadMessages(): Promise<Message[]> {\n if (!this.threadId) return [this];\n\n const { MessageCollection } = await import(\n '../collections/MessageCollection'\n );\n const collection = await MessageCollection.create(this.options);\n\n return await collection.list({ where: { threadId: this.threadId } });\n }\n\n /**\n * Get attachments for this message\n */\n async getAttachments() {\n const { AttachmentCollection } = await import(\n '../collections/AttachmentCollection'\n );\n const collection = await AttachmentCollection.create(this.options);\n\n return await collection.list({ where: { messageId: this.id } });\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Send Lifecycle\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Send this message via its account's sender\n */\n async send(options?: SendMessageOptions): Promise<MessageSendResult> {\n // Entry guard: a message that is already in flight or delivered must not be\n // sent again. Catches same-instance double-clicks before any work happens.\n if (this.sendStatus === 'sending' || this.sendStatus === 'sent') {\n return {\n success: false,\n error: `Cannot send: message is already '${this.sendStatus}'`,\n sentAt: new Date(),\n };\n }\n\n // Resolve account\n const account = await this.getAccount();\n if (!account) {\n const result: MessageSendResult = {\n success: false,\n error: 'No account associated with this message',\n sentAt: new Date(),\n };\n this.sendStatus = 'failed';\n this.sendError = result.error ?? '';\n this.updatedAt = new Date();\n await this.save();\n return result;\n }\n\n if (!account.isActive) {\n const result: MessageSendResult = {\n success: false,\n error: 'Messaging account is inactive',\n sentAt: new Date(),\n };\n this.sendStatus = 'failed';\n this.sendError = result.error ?? '';\n this.updatedAt = new Date();\n await this.save();\n return result;\n }\n\n // Get sender from account\n let sender: Awaited<ReturnType<typeof account.createSender>>;\n try {\n sender = await account.createSender();\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n this.sendStatus = 'failed';\n this.sendError = errorMessage;\n this.updatedAt = new Date();\n await this.save();\n return {\n success: false,\n error: errorMessage,\n sentAt: new Date(),\n };\n }\n\n // Claim the send. For a persisted row, do a compare-and-set so only one\n // concurrent sender wins: flip send_status to 'sending' atomically, gated on\n // the status we observed. If no row matched, another sender already claimed\n // it — abort without delivering (prevents duplicate sends). For an unsaved\n // draft there is no row yet, so the in-memory transition + save() (INSERT)\n // serves as the claim.\n const claimFromStatus = this.sendStatus;\n if (this.isPersisted && this.id) {\n const loadedRevision = this.updated_at;\n if (!loadedRevision) {\n return {\n success: false,\n error: 'Cannot send: persisted message has no loaded revision',\n sentAt: new Date(),\n };\n }\n const claimedRevision = new Date(\n Math.max(Date.now(), loadedRevision.getTime() + 1),\n );\n const useEmbeddedFallback = usesEmbeddedRevisionFallback(this.db);\n let claimed = false;\n await withEmbeddedWriteQueue(this.db, useEmbeddedFallback, async () => {\n if (useEmbeddedFallback) {\n const current = await this.getCanonicalPersistedRow({ id: this.id });\n const storedRevision = current?.updated_at;\n const currentRevision =\n storedRevision instanceof Date\n ? storedRevision.getTime()\n : typeof storedRevision === 'string'\n ? Date.parse(storedRevision)\n : Number.NaN;\n if (\n !current ||\n current.send_status !== claimFromStatus ||\n !Number.isFinite(currentRevision) ||\n currentRevision !== loadedRevision.getTime()\n )\n return;\n await this.db.upsert(this.tableName, ['id'], {\n ...current,\n send_status: 'sending',\n updated_at: claimedRevision,\n });\n claimed = true;\n return;\n }\n const claim = await this.db.update(\n this.tableName,\n {\n id: this.id,\n send_status: claimFromStatus,\n updated_at: loadedRevision.toISOString(),\n },\n { send_status: 'sending', updated_at: claimedRevision },\n );\n claimed = claim?.affected === 1;\n });\n if (!claimed) {\n return {\n success: false,\n error: 'Cannot send: message is already being sent',\n sentAt: new Date(),\n };\n }\n this.sendStatus = 'sending';\n this.updated_at = claimedRevision;\n this.updatedAt = new Date();\n } else {\n this.sendStatus = 'sending';\n this.updatedAt = new Date();\n await this.save();\n }\n\n let result: MessageSendResult;\n try {\n result = await sender.send(this, options);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n result = {\n success: false,\n error: errorMessage,\n sentAt: new Date(),\n };\n }\n await this.finalizeSendLifecycle(result);\n return result;\n }\n\n /**\n * Retry sending a failed message\n */\n async retrySend(options?: SendMessageOptions): Promise<MessageSendResult> {\n if (this.sendStatus !== 'failed') {\n return {\n success: false,\n error: `Cannot retry: message status is '${this.sendStatus}', expected 'failed'`,\n sentAt: new Date(),\n };\n }\n\n if (this.retryCount >= this.maxRetries) {\n return {\n success: false,\n error: `Retry budget exhausted (${this.retryCount}/${this.maxRetries})`,\n sentAt: new Date(),\n };\n }\n\n this.retryCount++;\n this.updatedAt = new Date();\n await this.save();\n\n return this.send(options);\n }\n\n /**\n * Options for a derived draft (reply/forward) built from this message. Carries\n * the DB connection + tenant context from `this.options`, but strips this\n * message's own identity fields. When this message was hydrated from the DB,\n * `this.options` holds the row's `id`/`slug`/`context`/`_skipLoad`; spreading\n * those into a new draft would make `draft.save()` upsert onto the natural-key\n * conflict columns (`slug`/`context`/`_meta_type`) and overwrite the ORIGINAL\n * message instead of inserting a new row. See EmailAccount.childOptions().\n */\n protected draftOptions(): Record<string, unknown> {\n const rest = { ...(this.options as Record<string, unknown>) };\n delete rest.id;\n delete rest.slug;\n delete rest.context;\n delete rest._skipLoad;\n return rest;\n }\n\n /**\n * Create a reply to this message (returns unsaved draft)\n */\n createReply(_options?: { replyAll?: boolean }): Message {\n const reply = new (this.constructor as typeof Message)({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: this.threadId || this.id || '',\n subject: this.subject.startsWith('Re:')\n ? this.subject\n : `Re: ${this.subject}`,\n toAddresses: JSON.stringify([\n { address: this.fromAddress, name: this.fromName },\n ]),\n fromAddress: '',\n fromName: '',\n body: this.buildQuotedBody(),\n inReplyToMessageId: this.id || '',\n sendStatus: 'draft',\n isRead: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n });\n\n return reply;\n }\n\n /**\n * Create a forward of this message (returns unsaved draft)\n */\n createForward(): Message {\n const forward = new (this.constructor as typeof Message)({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: '',\n subject: this.subject.startsWith('Fwd:')\n ? this.subject\n : `Fwd: ${this.subject}`,\n toAddresses: '[]',\n fromAddress: '',\n fromName: '',\n body: this.buildQuotedBody(),\n hasAttachments: this.hasAttachments,\n inReplyToMessageId: '',\n sendStatus: 'draft',\n isRead: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n });\n\n return forward;\n }\n\n /**\n * Build quoted body for reply/forward\n */\n protected buildQuotedBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const quotedLines = (this.body || '')\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n');\n\n return `\\n\\nOn ${dateStr}, ${from} wrote:\\n${quotedLines}`;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Message-HYEwxDiQ.js","names":["result"],"sources":["../../src/models/Message.ts"],"sourcesContent":["/**\n * Message model - Base class for all message types (STI)\n *\n * Common fields shared across email, tweets, slack messages, etc.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n usesEmbeddedRevisionFallback,\n withEmbeddedWriteQueue,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n MessageOptions,\n MessageSendResult,\n SendMessageOptions,\n SendStatus,\n} from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Message extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Account')\n @field({\n description: 'The connected account used to receive or send this message.',\n })\n accountId = '';\n @crossPackageRef('@happyvertical/smrt-personas:AgentPersona', {\n nullable: true,\n })\n @field({\n description:\n 'The assistant identity associated with this message, when one was used.',\n })\n personaId: string | null = null;\n @foreignKey('MessagingEndpoint')\n @field({ description: 'The saved destination used to deliver this message.' })\n endpointId: string | null = null;\n @field({\n description:\n 'A tracking reference that connects this message to related activity.',\n })\n correlationId = '';\n @field({ description: 'The conversation this message belongs to.' })\n threadId = '';\n @field({ description: 'The subject shown in the message list.' })\n subject = '';\n @field({ description: 'The message content in plain text.' })\n body = ''; // Normalized plain text\n @field({ description: 'The address the message was sent from.' })\n fromAddress = '';\n @field({ description: 'The sender name shown beside their address.' })\n fromName = '';\n @field({ description: 'The people or addresses receiving this message.' })\n toAddresses = ''; // JSON array of {address, name}\n @field({ description: 'When the message was sent or received.' })\n date: Date | null = null;\n @field({ description: 'Whether this message has been marked as read.' })\n isRead = false;\n @field({ description: 'Whether this message is flagged for follow-up.' })\n isFlagged = false;\n @field({ description: 'Whether files are attached to this message.' })\n hasAttachments = false;\n @field({ description: 'The size of the message in bytes.' })\n size = 0;\n @field({\n description: 'Additional details supplied by the messaging connection.',\n })\n metadata = ''; // JSON extension bag\n\n // Send lifecycle fields\n @field({\n description:\n 'The latest sending state: draft, sending, sent, or failed. Received mail may also show draft here.',\n })\n sendStatus: SendStatus = 'draft';\n @field({ description: 'When the message was successfully sent.' })\n sentAt: Date | null = null;\n @field({ description: 'The reason the latest send attempt failed.' })\n sendError = '';\n @field({ description: 'How many times delivery has been retried.' })\n retryCount = 0;\n @field({ description: 'The maximum number of delivery retries allowed.' })\n maxRetries = 3;\n @field({ description: 'When this message is scheduled to be sent.' })\n scheduledSendAt: Date | null = null;\n @foreignKey('Message')\n @field({ description: 'The earlier message this one replies to.' })\n inReplyToMessageId = '';\n\n // Timestamps\n @field({ description: 'When this message was recorded.' })\n createdAt = new Date();\n @field({ description: 'When this message was last changed.' })\n updatedAt = new Date();\n\n constructor(options: MessageOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.accountId !== undefined) this.accountId = options.accountId;\n if (options.personaId !== undefined) this.personaId = options.personaId;\n if (options.endpointId !== undefined) this.endpointId = options.endpointId;\n if (options.correlationId !== undefined)\n this.correlationId = options.correlationId;\n if (options.threadId !== undefined) this.threadId = options.threadId;\n if (options.subject !== undefined) this.subject = options.subject;\n if (options.body !== undefined) this.body = options.body;\n if (options.fromAddress !== undefined)\n this.fromAddress = options.fromAddress;\n if (options.fromName !== undefined) this.fromName = options.fromName;\n if (options.toAddresses !== undefined)\n this.toAddresses = options.toAddresses;\n if (options.date !== undefined) this.date = options.date || null;\n if (options.isRead !== undefined) this.isRead = options.isRead;\n if (options.isFlagged !== undefined) this.isFlagged = options.isFlagged;\n if (options.hasAttachments !== undefined)\n this.hasAttachments = options.hasAttachments;\n if (options.size !== undefined) this.size = options.size;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n if (options.sendStatus !== undefined) this.sendStatus = options.sendStatus;\n if (options.sentAt !== undefined) this.sentAt = options.sentAt || null;\n if (options.sendError !== undefined) this.sendError = options.sendError;\n if (options.retryCount !== undefined) this.retryCount = options.retryCount;\n if (options.maxRetries !== undefined) this.maxRetries = options.maxRetries;\n if (options.scheduledSendAt !== undefined)\n this.scheduledSendAt = options.scheduledSendAt || null;\n if (options.inReplyToMessageId !== undefined)\n this.inReplyToMessageId = options.inReplyToMessageId;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n private async finalizeSendLifecycle(\n result: MessageSendResult,\n ): Promise<void> {\n if (!this.id) throw new Error('Cannot finalize an unsaved message send');\n\n const sendStatus: SendStatus = result.success ? 'sent' : 'failed';\n const sendError = result.success ? '' : (result.error ?? 'Send failed');\n const useEmbeddedFallback = usesEmbeddedRevisionFallback(this.db);\n for (let attempt = 0; attempt < 8; attempt += 1) {\n let nextRevision: Date | undefined;\n await withEmbeddedWriteQueue(this.db, useEmbeddedFallback, async () => {\n const current = await this.getCanonicalPersistedRow({ id: this.id });\n if (current?.send_status !== 'sending') return;\n const currentRevision = current.updated_at;\n const currentRevisionMs = new Date(String(currentRevision)).getTime();\n if (!Number.isFinite(currentRevisionMs)) return;\n nextRevision = new Date(Math.max(Date.now(), currentRevisionMs + 1));\n const values = {\n send_status: sendStatus,\n sent_at: result.success ? result.sentAt : null,\n send_error: sendError,\n updated_at: nextRevision,\n };\n if (useEmbeddedFallback) {\n await this.db.upsert(this.tableName, ['id'], {\n ...current,\n ...values,\n });\n return;\n }\n await this.db.update(\n this.tableName,\n {\n id: this.id,\n send_status: 'sending',\n updated_at:\n currentRevision instanceof Date\n ? currentRevision.toISOString()\n : currentRevision,\n },\n values,\n );\n });\n // DuckDB's generic update result reports one affected row even when the\n // predicate matched none. Verify the durable lifecycle value instead of\n // trusting adapter row-count metadata before declaring finalization.\n const verified = await this.getCanonicalPersistedRow({ id: this.id });\n if (verified?.send_status === sendStatus) {\n await this.completePersistedUpdate(verified);\n return;\n }\n }\n throw new Error('Cannot finalize: message revision kept changing');\n }\n\n /**\n * Get to addresses as parsed array\n */\n getToAddresses(): Array<{ address: string; name?: string }> {\n if (!this.toAddresses) return [];\n try {\n return JSON.parse(this.toAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Set to addresses from array\n */\n setToAddresses(addresses: Array<{ address: string; name?: string }>): void {\n this.toAddresses = JSON.stringify(addresses);\n }\n\n /**\n * Get metadata as parsed object\n */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n return JSON.parse(this.metadata);\n } catch {\n return {};\n }\n }\n\n /**\n * Set metadata from object\n */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data);\n }\n\n /**\n * Mark message as read\n */\n async markRead(): Promise<void> {\n this.isRead = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Mark message as unread\n */\n async markUnread(): Promise<void> {\n this.isRead = false;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Toggle flagged status\n */\n async toggleFlagged(): Promise<void> {\n this.isFlagged = !this.isFlagged;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Check if message is unread\n */\n isUnread(): boolean {\n return !this.isRead;\n }\n\n /**\n * Get a short preview of the message body\n */\n getPreview(maxLength = 200): string {\n const text = this.body || '';\n if (text.length <= maxLength) return text;\n return `${text.slice(0, maxLength)}...`;\n }\n\n /**\n * Get the account for this message\n */\n async getAccount() {\n if (!this.accountId) return null;\n\n const { AccountCollection } = await import(\n '../collections/AccountCollection'\n );\n const collection = await AccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n async getEndpoint() {\n if (!this.endpointId) return null;\n const { MessagingEndpointCollection } = await import(\n '../collections/MessagingEndpointCollection.js'\n );\n const collection = await MessagingEndpointCollection.create(this.options);\n return collection.get({ id: this.endpointId });\n }\n\n /**\n * Get messages in the same thread\n */\n async getThreadMessages(): Promise<Message[]> {\n if (!this.threadId) return [this];\n\n const { MessageCollection } = await import(\n '../collections/MessageCollection'\n );\n const collection = await MessageCollection.create(this.options);\n\n return await collection.list({ where: { threadId: this.threadId } });\n }\n\n /**\n * Get attachments for this message\n */\n async getAttachments() {\n const { AttachmentCollection } = await import(\n '../collections/AttachmentCollection'\n );\n const collection = await AttachmentCollection.create(this.options);\n\n return await collection.list({ where: { messageId: this.id } });\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Send Lifecycle\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Send this message via its account's sender\n */\n async send(options?: SendMessageOptions): Promise<MessageSendResult> {\n // Entry guard: a message that is already in flight or delivered must not be\n // sent again. Catches same-instance double-clicks before any work happens.\n if (this.sendStatus === 'sending' || this.sendStatus === 'sent') {\n return {\n success: false,\n error: `Cannot send: message is already '${this.sendStatus}'`,\n sentAt: new Date(),\n };\n }\n\n // Resolve account\n const account = await this.getAccount();\n if (!account) {\n const result: MessageSendResult = {\n success: false,\n error: 'No account associated with this message',\n sentAt: new Date(),\n };\n this.sendStatus = 'failed';\n this.sendError = result.error ?? '';\n this.updatedAt = new Date();\n await this.save();\n return result;\n }\n\n if (!account.isActive) {\n const result: MessageSendResult = {\n success: false,\n error: 'Messaging account is inactive',\n sentAt: new Date(),\n };\n this.sendStatus = 'failed';\n this.sendError = result.error ?? '';\n this.updatedAt = new Date();\n await this.save();\n return result;\n }\n\n // Get sender from account\n let sender: Awaited<ReturnType<typeof account.createSender>>;\n try {\n sender = await account.createSender();\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n this.sendStatus = 'failed';\n this.sendError = errorMessage;\n this.updatedAt = new Date();\n await this.save();\n return {\n success: false,\n error: errorMessage,\n sentAt: new Date(),\n };\n }\n\n // Claim the send. For a persisted row, do a compare-and-set so only one\n // concurrent sender wins: flip send_status to 'sending' atomically, gated on\n // the status we observed. If no row matched, another sender already claimed\n // it — abort without delivering (prevents duplicate sends). For an unsaved\n // draft there is no row yet, so the in-memory transition + save() (INSERT)\n // serves as the claim.\n const claimFromStatus = this.sendStatus;\n if (this.isPersisted && this.id) {\n const loadedRevision = this.updated_at;\n if (!loadedRevision) {\n return {\n success: false,\n error: 'Cannot send: persisted message has no loaded revision',\n sentAt: new Date(),\n };\n }\n const claimedRevision = new Date(\n Math.max(Date.now(), loadedRevision.getTime() + 1),\n );\n const useEmbeddedFallback = usesEmbeddedRevisionFallback(this.db);\n let claimed = false;\n await withEmbeddedWriteQueue(this.db, useEmbeddedFallback, async () => {\n if (useEmbeddedFallback) {\n const current = await this.getCanonicalPersistedRow({ id: this.id });\n const storedRevision = current?.updated_at;\n const currentRevision =\n storedRevision instanceof Date\n ? storedRevision.getTime()\n : typeof storedRevision === 'string'\n ? Date.parse(storedRevision)\n : Number.NaN;\n if (\n !current ||\n current.send_status !== claimFromStatus ||\n !Number.isFinite(currentRevision) ||\n currentRevision !== loadedRevision.getTime()\n )\n return;\n await this.db.upsert(this.tableName, ['id'], {\n ...current,\n send_status: 'sending',\n updated_at: claimedRevision,\n });\n claimed = true;\n return;\n }\n const claim = await this.db.update(\n this.tableName,\n {\n id: this.id,\n send_status: claimFromStatus,\n updated_at: loadedRevision.toISOString(),\n },\n { send_status: 'sending', updated_at: claimedRevision },\n );\n claimed = claim?.affected === 1;\n });\n if (!claimed) {\n return {\n success: false,\n error: 'Cannot send: message is already being sent',\n sentAt: new Date(),\n };\n }\n this.sendStatus = 'sending';\n this.updated_at = claimedRevision;\n this.updatedAt = new Date();\n } else {\n this.sendStatus = 'sending';\n this.updatedAt = new Date();\n await this.save();\n }\n\n let result: MessageSendResult;\n try {\n result = await sender.send(this, options);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n result = {\n success: false,\n error: errorMessage,\n sentAt: new Date(),\n };\n }\n await this.finalizeSendLifecycle(result);\n return result;\n }\n\n /**\n * Retry sending a failed message\n */\n async retrySend(options?: SendMessageOptions): Promise<MessageSendResult> {\n if (this.sendStatus !== 'failed') {\n return {\n success: false,\n error: `Cannot retry: message status is '${this.sendStatus}', expected 'failed'`,\n sentAt: new Date(),\n };\n }\n\n if (this.retryCount >= this.maxRetries) {\n return {\n success: false,\n error: `Retry budget exhausted (${this.retryCount}/${this.maxRetries})`,\n sentAt: new Date(),\n };\n }\n\n this.retryCount++;\n this.updatedAt = new Date();\n await this.save();\n\n return this.send(options);\n }\n\n /**\n * Options for a derived draft (reply/forward) built from this message. Carries\n * the DB connection + tenant context from `this.options`, but strips this\n * message's own identity fields. When this message was hydrated from the DB,\n * `this.options` holds the row's `id`/`slug`/`context`/`_skipLoad`; spreading\n * those into a new draft would make `draft.save()` upsert onto the natural-key\n * conflict columns (`slug`/`context`/`_meta_type`) and overwrite the ORIGINAL\n * message instead of inserting a new row. See EmailAccount.childOptions().\n */\n protected draftOptions(): Record<string, unknown> {\n const rest = { ...(this.options as Record<string, unknown>) };\n delete rest.id;\n delete rest.slug;\n delete rest.context;\n delete rest._skipLoad;\n return rest;\n }\n\n /**\n * Create a reply to this message (returns unsaved draft)\n */\n createReply(_options?: { replyAll?: boolean }): Message {\n const reply = new (this.constructor as typeof Message)({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: this.threadId || this.id || '',\n subject: this.subject.startsWith('Re:')\n ? this.subject\n : `Re: ${this.subject}`,\n toAddresses: JSON.stringify([\n { address: this.fromAddress, name: this.fromName },\n ]),\n fromAddress: '',\n fromName: '',\n body: this.buildQuotedBody(),\n inReplyToMessageId: this.id || '',\n sendStatus: 'draft',\n isRead: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n });\n\n return reply;\n }\n\n /**\n * Create a forward of this message (returns unsaved draft)\n */\n createForward(): Message {\n const forward = new (this.constructor as typeof Message)({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: '',\n subject: this.subject.startsWith('Fwd:')\n ? this.subject\n : `Fwd: ${this.subject}`,\n toAddresses: '[]',\n fromAddress: '',\n fromName: '',\n body: this.buildQuotedBody(),\n hasAttachments: this.hasAttachments,\n inReplyToMessageId: '',\n sendStatus: 'draft',\n isRead: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n });\n\n return forward;\n }\n\n /**\n * Build quoted body for reply/forward\n */\n protected buildQuotedBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const quotedLines = (this.body || '')\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n');\n\n return `\\n\\nOn ${dateStr}, ${from} wrote:\\n${quotedLines}`;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { t as Message } from "./Message-
|
|
2
|
+
import { t as Message } from "./Message-HYEwxDiQ.js";
|
|
3
3
|
import { SmrtCollection } from "@happyvertical/smrt-core";
|
|
4
4
|
import { queryGlobal, queryWithGlobals } from "@happyvertical/smrt-tenancy";
|
|
5
5
|
//#region src/collections/MessageCollection.ts
|
|
@@ -166,4 +166,4 @@ var MessageCollection = class extends SmrtCollection {
|
|
|
166
166
|
//#endregion
|
|
167
167
|
export { MessageCollection_exports as n, MessageCollection as t };
|
|
168
168
|
|
|
169
|
-
//# sourceMappingURL=MessageCollection-
|
|
169
|
+
//# sourceMappingURL=MessageCollection-BaOC9VOR.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageCollection-BJe8wdg7.js","names":[],"sources":["../../src/collections/MessageCollection.ts"],"sourcesContent":["/**\n * MessageCollection - Unified collection for polymorphic message queries\n *\n * Queries the messages table with STI, returning correct subclass instances.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Message } from '../models/Message';\nimport type { MessageSearchFilters } from '../types';\n\nexport class MessageCollection extends SmrtCollection<Message> {\n static readonly _itemClass = Message;\n\n /**\n * Search messages with filters\n */\n async search(\n query: string,\n filters?: MessageSearchFilters,\n ): Promise<Message[]> {\n let messages = await this.list({});\n\n // Filter by query\n if (query) {\n const lowerQuery = query.toLowerCase();\n messages = messages.filter(\n (m) =>\n m.subject?.toLowerCase().includes(lowerQuery) ||\n m.body?.toLowerCase().includes(lowerQuery) ||\n m.fromAddress?.toLowerCase().includes(lowerQuery) ||\n m.fromName?.toLowerCase().includes(lowerQuery),\n );\n }\n\n // Apply filters\n if (filters) {\n if (filters.accountIds && filters.accountIds.length > 0) {\n messages = messages.filter((m) =>\n filters.accountIds?.includes(m.accountId),\n );\n }\n if (filters.messageType) {\n messages = messages.filter((m) => {\n const metaType = (m as { _meta_type?: string })._meta_type || '';\n return metaType.includes(filters.messageType as string);\n });\n }\n if (filters.from) {\n const fromLower = filters.from.toLowerCase();\n messages = messages.filter(\n (m) =>\n m.fromAddress?.toLowerCase().includes(fromLower) ||\n m.fromName?.toLowerCase().includes(fromLower),\n );\n }\n if (filters.to) {\n const toLower = filters.to.toLowerCase();\n messages = messages.filter((m) =>\n m.toAddresses?.toLowerCase().includes(toLower),\n );\n }\n if (filters.isRead !== undefined) {\n messages = messages.filter((m) => m.isRead === filters.isRead);\n }\n if (filters.isFlagged !== undefined) {\n messages = messages.filter((m) => m.isFlagged === filters.isFlagged);\n }\n if (filters.sinceDate) {\n messages = messages.filter(\n (m) => m.date && m.date >= (filters.sinceDate as Date),\n );\n }\n if (filters.beforeDate) {\n messages = messages.filter(\n (m) => m.date && m.date < (filters.beforeDate as Date),\n );\n }\n if (filters.query) {\n const q = filters.query.toLowerCase();\n messages = messages.filter(\n (m) =>\n m.subject?.toLowerCase().includes(q) ||\n m.body?.toLowerCase().includes(q),\n );\n }\n }\n\n return messages;\n }\n\n /**\n * Get messages by multiple accounts\n */\n async getByAccounts(accountIds: string[]): Promise<Message[]> {\n const allMessages = await this.list({});\n return allMessages.filter((m) => accountIds.includes(m.accountId));\n }\n\n /**\n * Get messages by STI type.\n *\n * Accepts either the full discriminator (e.g. \"@happyvertical/smrt-messages:Email\")\n * or the short type name (e.g. \"Email\").\n */\n async getByType(messageType: string): Promise<Message[]> {\n if (messageType.includes(':') || messageType.startsWith('@')) {\n return await this.list({ where: { _meta_type: messageType } });\n }\n const allMessages = await this.list({});\n return allMessages.filter((m) => {\n const metaType = (m as { _meta_type?: string })._meta_type || '';\n return metaType.endsWith(`:${messageType}`);\n });\n }\n\n /**\n * Get unread messages\n */\n async getUnread(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { isRead: false };\n if (accountId) {\n where.accountId = accountId;\n }\n return await this.list({ where });\n }\n\n /**\n * Get flagged messages\n */\n async getFlagged(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { isFlagged: true };\n if (accountId) {\n where.accountId = accountId;\n }\n return await this.list({ where });\n }\n\n /**\n * Get recent messages\n */\n async getRecent(limit = 20, accountId?: string): Promise<Message[]> {\n const allMessages = await this.list({\n where: accountId ? { accountId } : undefined,\n });\n\n return allMessages\n .sort((a, b) => {\n const dateA = a.date?.getTime() || 0;\n const dateB = b.date?.getTime() || 0;\n return dateB - dateA;\n })\n .slice(0, limit);\n }\n\n /**\n * Get messages by thread\n */\n async getByThread(threadId: string): Promise<Message[]> {\n return await this.list({ where: { threadId } });\n }\n\n /**\n * Mark multiple messages as read\n */\n async markAllRead(messageIds: string[]): Promise<void> {\n for (const id of messageIds) {\n const message = await this.get({ id });\n if (message) {\n await message.markRead();\n }\n }\n }\n\n /**\n * Get message statistics for an account\n */\n async getAccountStats(accountId: string): Promise<{\n total: number;\n unread: number;\n flagged: number;\n byType: Record<string, number>;\n }> {\n const messages = await this.list({ where: { accountId } });\n const byType: Record<string, number> = {};\n\n for (const msg of messages) {\n const metaType = (msg as { _meta_type?: string })._meta_type || 'Unknown';\n const shortType = metaType.split(':').pop() || metaType;\n byType[shortType] = (byType[shortType] || 0) + 1;\n }\n\n return {\n total: messages.length,\n unread: messages.filter((m) => !m.isRead).length,\n flagged: messages.filter((m) => m.isFlagged).length,\n byType,\n };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Send / Draft Queries\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Get draft messages\n */\n async getDrafts(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { sendStatus: 'draft' };\n if (accountId) where.accountId = accountId;\n return await this.list({ where });\n }\n\n /**\n * Get sent messages\n */\n async getSent(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { sendStatus: 'sent' };\n if (accountId) where.accountId = accountId;\n return await this.list({ where });\n }\n\n /**\n * Get scheduled messages\n */\n async getScheduled(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { sendStatus: 'scheduled' };\n if (accountId) where.accountId = accountId;\n return await this.list({ where });\n }\n\n /**\n * Get messages that failed to send\n */\n async getFailedSends(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { sendStatus: 'failed' };\n if (accountId) where.accountId = accountId;\n return await this.list({ where });\n }\n\n /**\n * Get outbox (pending + sending + scheduled)\n */\n async getOutbox(accountId?: string): Promise<Message[]> {\n const allMessages = await this.list({\n where: accountId ? { accountId } : undefined,\n });\n return allMessages.filter(\n (m) =>\n m.sendStatus === 'pending' ||\n m.sendStatus === 'sending' ||\n m.sendStatus === 'scheduled',\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Tenant Helper Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n async findByTenant(tenantId: string): Promise<Message[]> {\n return this.list({ where: { tenantId } });\n }\n\n // Message is the @TenantScoped STI base, so an explicit `tenant_id IS NULL`\n // filter via list() throws and unflagged raw SQL is blocked under an active\n // tenant context (#1596). Route through the raw helpers — no `_meta_type`\n // scope so the base collection still returns ALL message subtypes.\n async findGlobal(): Promise<Message[]> {\n return queryGlobal<Message>(this);\n }\n\n async findWithGlobals(tenantId: string): Promise<Message[]> {\n return queryWithGlobals<Message>(this, tenantId, 'Message.findWithGlobals');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MessageCollection-BaOC9VOR.js","names":[],"sources":["../../src/collections/MessageCollection.ts"],"sourcesContent":["/**\n * MessageCollection - Unified collection for polymorphic message queries\n *\n * Queries the messages table with STI, returning correct subclass instances.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Message } from '../models/Message';\nimport type { MessageSearchFilters } from '../types';\n\nexport class MessageCollection extends SmrtCollection<Message> {\n static readonly _itemClass = Message;\n\n /**\n * Search messages with filters\n */\n async search(\n query: string,\n filters?: MessageSearchFilters,\n ): Promise<Message[]> {\n let messages = await this.list({});\n\n // Filter by query\n if (query) {\n const lowerQuery = query.toLowerCase();\n messages = messages.filter(\n (m) =>\n m.subject?.toLowerCase().includes(lowerQuery) ||\n m.body?.toLowerCase().includes(lowerQuery) ||\n m.fromAddress?.toLowerCase().includes(lowerQuery) ||\n m.fromName?.toLowerCase().includes(lowerQuery),\n );\n }\n\n // Apply filters\n if (filters) {\n if (filters.accountIds && filters.accountIds.length > 0) {\n messages = messages.filter((m) =>\n filters.accountIds?.includes(m.accountId),\n );\n }\n if (filters.messageType) {\n messages = messages.filter((m) => {\n const metaType = (m as { _meta_type?: string })._meta_type || '';\n return metaType.includes(filters.messageType as string);\n });\n }\n if (filters.from) {\n const fromLower = filters.from.toLowerCase();\n messages = messages.filter(\n (m) =>\n m.fromAddress?.toLowerCase().includes(fromLower) ||\n m.fromName?.toLowerCase().includes(fromLower),\n );\n }\n if (filters.to) {\n const toLower = filters.to.toLowerCase();\n messages = messages.filter((m) =>\n m.toAddresses?.toLowerCase().includes(toLower),\n );\n }\n if (filters.isRead !== undefined) {\n messages = messages.filter((m) => m.isRead === filters.isRead);\n }\n if (filters.isFlagged !== undefined) {\n messages = messages.filter((m) => m.isFlagged === filters.isFlagged);\n }\n if (filters.sinceDate) {\n messages = messages.filter(\n (m) => m.date && m.date >= (filters.sinceDate as Date),\n );\n }\n if (filters.beforeDate) {\n messages = messages.filter(\n (m) => m.date && m.date < (filters.beforeDate as Date),\n );\n }\n if (filters.query) {\n const q = filters.query.toLowerCase();\n messages = messages.filter(\n (m) =>\n m.subject?.toLowerCase().includes(q) ||\n m.body?.toLowerCase().includes(q),\n );\n }\n }\n\n return messages;\n }\n\n /**\n * Get messages by multiple accounts\n */\n async getByAccounts(accountIds: string[]): Promise<Message[]> {\n const allMessages = await this.list({});\n return allMessages.filter((m) => accountIds.includes(m.accountId));\n }\n\n /**\n * Get messages by STI type.\n *\n * Accepts either the full discriminator (e.g. \"@happyvertical/smrt-messages:Email\")\n * or the short type name (e.g. \"Email\").\n */\n async getByType(messageType: string): Promise<Message[]> {\n if (messageType.includes(':') || messageType.startsWith('@')) {\n return await this.list({ where: { _meta_type: messageType } });\n }\n const allMessages = await this.list({});\n return allMessages.filter((m) => {\n const metaType = (m as { _meta_type?: string })._meta_type || '';\n return metaType.endsWith(`:${messageType}`);\n });\n }\n\n /**\n * Get unread messages\n */\n async getUnread(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { isRead: false };\n if (accountId) {\n where.accountId = accountId;\n }\n return await this.list({ where });\n }\n\n /**\n * Get flagged messages\n */\n async getFlagged(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { isFlagged: true };\n if (accountId) {\n where.accountId = accountId;\n }\n return await this.list({ where });\n }\n\n /**\n * Get recent messages\n */\n async getRecent(limit = 20, accountId?: string): Promise<Message[]> {\n const allMessages = await this.list({\n where: accountId ? { accountId } : undefined,\n });\n\n return allMessages\n .sort((a, b) => {\n const dateA = a.date?.getTime() || 0;\n const dateB = b.date?.getTime() || 0;\n return dateB - dateA;\n })\n .slice(0, limit);\n }\n\n /**\n * Get messages by thread\n */\n async getByThread(threadId: string): Promise<Message[]> {\n return await this.list({ where: { threadId } });\n }\n\n /**\n * Mark multiple messages as read\n */\n async markAllRead(messageIds: string[]): Promise<void> {\n for (const id of messageIds) {\n const message = await this.get({ id });\n if (message) {\n await message.markRead();\n }\n }\n }\n\n /**\n * Get message statistics for an account\n */\n async getAccountStats(accountId: string): Promise<{\n total: number;\n unread: number;\n flagged: number;\n byType: Record<string, number>;\n }> {\n const messages = await this.list({ where: { accountId } });\n const byType: Record<string, number> = {};\n\n for (const msg of messages) {\n const metaType = (msg as { _meta_type?: string })._meta_type || 'Unknown';\n const shortType = metaType.split(':').pop() || metaType;\n byType[shortType] = (byType[shortType] || 0) + 1;\n }\n\n return {\n total: messages.length,\n unread: messages.filter((m) => !m.isRead).length,\n flagged: messages.filter((m) => m.isFlagged).length,\n byType,\n };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Send / Draft Queries\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Get draft messages\n */\n async getDrafts(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { sendStatus: 'draft' };\n if (accountId) where.accountId = accountId;\n return await this.list({ where });\n }\n\n /**\n * Get sent messages\n */\n async getSent(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { sendStatus: 'sent' };\n if (accountId) where.accountId = accountId;\n return await this.list({ where });\n }\n\n /**\n * Get scheduled messages\n */\n async getScheduled(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { sendStatus: 'scheduled' };\n if (accountId) where.accountId = accountId;\n return await this.list({ where });\n }\n\n /**\n * Get messages that failed to send\n */\n async getFailedSends(accountId?: string): Promise<Message[]> {\n const where: Record<string, unknown> = { sendStatus: 'failed' };\n if (accountId) where.accountId = accountId;\n return await this.list({ where });\n }\n\n /**\n * Get outbox (pending + sending + scheduled)\n */\n async getOutbox(accountId?: string): Promise<Message[]> {\n const allMessages = await this.list({\n where: accountId ? { accountId } : undefined,\n });\n return allMessages.filter(\n (m) =>\n m.sendStatus === 'pending' ||\n m.sendStatus === 'sending' ||\n m.sendStatus === 'scheduled',\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Tenant Helper Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n async findByTenant(tenantId: string): Promise<Message[]> {\n return this.list({ where: { tenantId } });\n }\n\n // Message is the @TenantScoped STI base, so an explicit `tenant_id IS NULL`\n // filter via list() throws and unflagged raw SQL is blocked under an active\n // tenant context (#1596). Route through the raw helpers — no `_meta_type`\n // scope so the base collection still returns ALL message subtypes.\n async findGlobal(): Promise<Message[]> {\n return queryGlobal<Message>(this);\n }\n\n async findWithGlobals(tenantId: string): Promise<Message[]> {\n return queryWithGlobals<Message>(this, tenantId, 'Message.findWithGlobals');\n }\n}\n"],"mappings":"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"}
|