@happyvertical/smrt-messages 0.37.1 → 0.37.3
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-BekDBDAm.js +210 -0
- package/dist/chunks/AccountCollection-BekDBDAm.js.map +1 -0
- package/dist/chunks/Email-Bv6cu8QX.js +282 -0
- package/dist/chunks/Email-Bv6cu8QX.js.map +1 -0
- package/dist/chunks/EmailFolder-DvZODbnW.js +153 -0
- package/dist/chunks/EmailFolder-DvZODbnW.js.map +1 -0
- package/dist/chunks/Message-CHTC1RuY.js +333 -0
- package/dist/chunks/Message-CHTC1RuY.js.map +1 -0
- package/dist/chunks/MessageCollection-DvG0YCfd.js +169 -0
- package/dist/chunks/MessageCollection-DvG0YCfd.js.map +1 -0
- package/dist/chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/index.js +1597 -3098
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/playground.js +154 -174
- package/dist/playground.js.map +1 -1
- package/dist/smrt-knowledge.json +11 -11
- package/dist/types.js +0 -2
- package/dist/ui.js +101 -101
- package/dist/ui.js.map +1 -1
- package/package.json +22 -22
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { SmrtCollection, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
|
|
3
|
+
import { TenantScoped, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
|
|
4
|
+
//#region src/models/Account.ts
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
8
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
9
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
10
|
+
if (kind && result) __defProp(target, key, result);
|
|
11
|
+
return result;
|
|
12
|
+
};
|
|
13
|
+
var Account = class extends SmrtObject {
|
|
14
|
+
tenantId = null;
|
|
15
|
+
name = "";
|
|
16
|
+
providerType = "";
|
|
17
|
+
credentialSecretId = null;
|
|
18
|
+
isActive = true;
|
|
19
|
+
lastSyncAt = null;
|
|
20
|
+
settings = "";
|
|
21
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
22
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
super(options);
|
|
25
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
26
|
+
if (options.name !== void 0) this.name = options.name;
|
|
27
|
+
if (options.providerType !== void 0) this.providerType = options.providerType;
|
|
28
|
+
if (options.credentialSecretId !== void 0) this.credentialSecretId = options.credentialSecretId || null;
|
|
29
|
+
if (options.isActive !== void 0) this.isActive = options.isActive;
|
|
30
|
+
if (options.lastSyncAt !== void 0) this.lastSyncAt = options.lastSyncAt || null;
|
|
31
|
+
if (options.settings !== void 0) this.settings = options.settings;
|
|
32
|
+
if (options.createdAt) this.createdAt = options.createdAt;
|
|
33
|
+
if (options.updatedAt) this.updatedAt = options.updatedAt;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get settings as parsed object
|
|
37
|
+
*/
|
|
38
|
+
getSettings() {
|
|
39
|
+
if (!this.settings) return {};
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(this.settings);
|
|
42
|
+
} catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Set settings from object
|
|
48
|
+
*/
|
|
49
|
+
setSettings(settings) {
|
|
50
|
+
this.settings = JSON.stringify(settings);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Activate account
|
|
54
|
+
*/
|
|
55
|
+
async activate() {
|
|
56
|
+
this.isActive = true;
|
|
57
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
58
|
+
await this.save();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Deactivate account
|
|
62
|
+
*/
|
|
63
|
+
async deactivate() {
|
|
64
|
+
this.isActive = false;
|
|
65
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
66
|
+
await this.save();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Store credentials securely using smrt-secrets
|
|
70
|
+
*/
|
|
71
|
+
async setCredentials(credentials, options = {}) {
|
|
72
|
+
const { SecretService } = await import("@happyvertical/smrt-secrets");
|
|
73
|
+
const secretService = await SecretService.create({ db: this.db });
|
|
74
|
+
const secretName = `account-${this.id}`;
|
|
75
|
+
const secretValue = JSON.stringify(credentials);
|
|
76
|
+
if (this.credentialSecretId) await secretService.store(this.credentialSecretId, secretValue, {
|
|
77
|
+
description: options.description || `Credentials for ${this.name}`,
|
|
78
|
+
category: options.category || "messaging"
|
|
79
|
+
});
|
|
80
|
+
else {
|
|
81
|
+
await secretService.store(secretName, secretValue, {
|
|
82
|
+
description: options.description || `Credentials for ${this.name}`,
|
|
83
|
+
category: options.category || "messaging"
|
|
84
|
+
});
|
|
85
|
+
this.credentialSecretId = secretName;
|
|
86
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
87
|
+
await this.save();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create a sender for this account.
|
|
92
|
+
* Subclasses must override to return a concrete sender.
|
|
93
|
+
*/
|
|
94
|
+
async createSender() {
|
|
95
|
+
throw new Error(`createSender() not implemented for account type '${this.providerType}'`);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Retrieve stored credentials
|
|
99
|
+
*/
|
|
100
|
+
async getCredentials() {
|
|
101
|
+
if (!this.credentialSecretId) return this.getSettings();
|
|
102
|
+
const { SecretService } = await import("@happyvertical/smrt-secrets");
|
|
103
|
+
const secretService = await SecretService.create({ db: this.db });
|
|
104
|
+
try {
|
|
105
|
+
const secret = await secretService.retrieve(this.credentialSecretId);
|
|
106
|
+
return JSON.parse(secret.value);
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
__decorateClass([tenantId({ nullable: true })], Account.prototype, "tenantId", 2);
|
|
113
|
+
__decorateClass([field({ sensitive: true })], Account.prototype, "settings", 2);
|
|
114
|
+
Account = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
115
|
+
tableStrategy: "sti",
|
|
116
|
+
api: { include: [
|
|
117
|
+
"list",
|
|
118
|
+
"get",
|
|
119
|
+
"create",
|
|
120
|
+
"update",
|
|
121
|
+
"delete"
|
|
122
|
+
] },
|
|
123
|
+
mcp: { include: ["list", "get"] },
|
|
124
|
+
cli: true
|
|
125
|
+
})], Account);
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/collections/AccountCollection.ts
|
|
128
|
+
var AccountCollection_exports = /* @__PURE__ */ __exportAll({ AccountCollection: () => AccountCollection });
|
|
129
|
+
var AccountCollection = class extends SmrtCollection {
|
|
130
|
+
static _itemClass = Account;
|
|
131
|
+
/**
|
|
132
|
+
* Get active accounts
|
|
133
|
+
*/
|
|
134
|
+
async getActive() {
|
|
135
|
+
return await this.list({ where: { isActive: true } });
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get inactive accounts
|
|
139
|
+
*/
|
|
140
|
+
async getInactive() {
|
|
141
|
+
return await this.list({ where: { isActive: false } });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get accounts by provider type
|
|
145
|
+
*/
|
|
146
|
+
async getByProviderType(providerType) {
|
|
147
|
+
return await this.list({ where: { providerType } });
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get accounts by STI type.
|
|
151
|
+
*
|
|
152
|
+
* Accepts either the full discriminator (e.g. "@happyvertical/smrt-messages:EmailAccount")
|
|
153
|
+
* or the short type name (e.g. "EmailAccount").
|
|
154
|
+
*/
|
|
155
|
+
async getByType(accountType) {
|
|
156
|
+
if (accountType.includes(":") || accountType.startsWith("@")) return await this.list({ where: { _meta_type: accountType } });
|
|
157
|
+
return (await this.list({})).filter((a) => {
|
|
158
|
+
return (a._meta_type || "").endsWith(`:${accountType}`);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Search accounts with filters
|
|
163
|
+
*/
|
|
164
|
+
async search(query, filters) {
|
|
165
|
+
let accounts = await this.list({});
|
|
166
|
+
if (query) {
|
|
167
|
+
const lowerQuery = query.toLowerCase();
|
|
168
|
+
accounts = accounts.filter((a) => a.name?.toLowerCase().includes(lowerQuery));
|
|
169
|
+
}
|
|
170
|
+
if (filters) {
|
|
171
|
+
if (filters.providerType) accounts = accounts.filter((a) => a.providerType === filters.providerType);
|
|
172
|
+
if (filters.isActive !== void 0) accounts = accounts.filter((a) => a.isActive === filters.isActive);
|
|
173
|
+
if (filters.accountType) accounts = accounts.filter((a) => {
|
|
174
|
+
return (a._meta_type || "").includes(filters.accountType);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return accounts;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get account statistics
|
|
181
|
+
*/
|
|
182
|
+
async getStats() {
|
|
183
|
+
const accounts = await this.list({});
|
|
184
|
+
const byType = {};
|
|
185
|
+
for (const account of accounts) {
|
|
186
|
+
const metaType = account._meta_type || "Unknown";
|
|
187
|
+
const shortType = metaType.split(":").pop() || metaType;
|
|
188
|
+
byType[shortType] = (byType[shortType] || 0) + 1;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
total: accounts.length,
|
|
192
|
+
active: accounts.filter((a) => a.isActive).length,
|
|
193
|
+
inactive: accounts.filter((a) => !a.isActive).length,
|
|
194
|
+
byType
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async findByTenant(tenantId) {
|
|
198
|
+
return this.list({ where: { tenantId } });
|
|
199
|
+
}
|
|
200
|
+
async findGlobal() {
|
|
201
|
+
return queryGlobal(this);
|
|
202
|
+
}
|
|
203
|
+
async findWithGlobals(tenantId) {
|
|
204
|
+
return queryWithGlobals(this, tenantId, "Account.findWithGlobals");
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
//#endregion
|
|
208
|
+
export { AccountCollection_exports as n, Account as r, AccountCollection as t };
|
|
209
|
+
|
|
210
|
+
//# sourceMappingURL=AccountCollection-BekDBDAm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccountCollection-BekDBDAm.js","names":[],"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\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } 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', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class Account extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n name = '';\n providerType = '';\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 secretService.store/retrieve by name. So it is\n // deliberately NOT a @crossPackageRef id FK.\n credentialSecretId: string | null = null;\n isActive = true;\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 })\n settings = ''; // JSON\n\n // Timestamps\n createdAt = new Date();\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.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.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 /**\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 const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n const secretName = `account-${this.id}`;\n const secretValue = JSON.stringify(credentials);\n\n if (this.credentialSecretId) {\n await secretService.store(this.credentialSecretId, secretValue, {\n description: options.description || `Credentials for ${this.name}`,\n category: options.category || 'messaging',\n });\n } else {\n await secretService.store(secretName, secretValue, {\n description: options.description || `Credentials for ${this.name}`,\n category: options.category || 'messaging',\n });\n\n this.credentialSecretId = secretName;\n this.updatedAt = new Date();\n await this.save();\n }\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 throw new Error(\n `createSender() not implemented for account type '${this.providerType}'`,\n );\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 { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n try {\n const secret = await secretService.retrieve(this.credentialSecretId);\n return JSON.parse(secret.value);\n } catch {\n return null;\n }\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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"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { t as Message } from "./Message-CHTC1RuY.js";
|
|
3
|
+
import { smrt } from "@happyvertical/smrt-core";
|
|
4
|
+
//#region src/models/Email.ts
|
|
5
|
+
var Email_exports = /* @__PURE__ */ __exportAll({ Email: () => Email });
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
9
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
10
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
11
|
+
if (kind && result) __defProp(target, key, result);
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
var Email = class extends Message {
|
|
15
|
+
messageId = "";
|
|
16
|
+
inReplyTo = "";
|
|
17
|
+
ccAddresses = "";
|
|
18
|
+
bccAddresses = "";
|
|
19
|
+
replyToAddress = "";
|
|
20
|
+
replyToName = "";
|
|
21
|
+
textBody = "";
|
|
22
|
+
htmlBody = "";
|
|
23
|
+
folderId = "";
|
|
24
|
+
folderPath = "";
|
|
25
|
+
labels = "";
|
|
26
|
+
flags = "";
|
|
27
|
+
isAnswered = false;
|
|
28
|
+
isDraft = false;
|
|
29
|
+
rawMessage = "";
|
|
30
|
+
headers = "";
|
|
31
|
+
constructor(options = {}) {
|
|
32
|
+
super(options);
|
|
33
|
+
if (options.messageId !== void 0) this.messageId = options.messageId;
|
|
34
|
+
if (options.inReplyTo !== void 0) this.inReplyTo = options.inReplyTo;
|
|
35
|
+
if (options.ccAddresses !== void 0) this.ccAddresses = options.ccAddresses;
|
|
36
|
+
if (options.bccAddresses !== void 0) this.bccAddresses = options.bccAddresses;
|
|
37
|
+
if (options.replyToAddress !== void 0) this.replyToAddress = options.replyToAddress;
|
|
38
|
+
if (options.replyToName !== void 0) this.replyToName = options.replyToName;
|
|
39
|
+
if (options.textBody !== void 0) this.textBody = options.textBody;
|
|
40
|
+
if (options.htmlBody !== void 0) this.htmlBody = options.htmlBody;
|
|
41
|
+
if (options.folderId !== void 0) this.folderId = options.folderId;
|
|
42
|
+
if (options.folderPath !== void 0) this.folderPath = options.folderPath;
|
|
43
|
+
if (options.labels !== void 0) this.labels = options.labels;
|
|
44
|
+
if (options.flags !== void 0) this.flags = options.flags;
|
|
45
|
+
if (options.isAnswered !== void 0) this.isAnswered = options.isAnswered;
|
|
46
|
+
if (options.isDraft !== void 0) this.isDraft = options.isDraft;
|
|
47
|
+
if (options.rawMessage !== void 0) this.rawMessage = options.rawMessage;
|
|
48
|
+
if (options.headers !== void 0) this.headers = options.headers;
|
|
49
|
+
if (options.textBody && !options.body) this.body = options.textBody;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get CC addresses as parsed array
|
|
53
|
+
*/
|
|
54
|
+
getCcAddresses() {
|
|
55
|
+
if (!this.ccAddresses) return [];
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(this.ccAddresses);
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get BCC addresses as parsed array
|
|
64
|
+
*/
|
|
65
|
+
getBccAddresses() {
|
|
66
|
+
if (!this.bccAddresses) return [];
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(this.bccAddresses);
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get labels as parsed array
|
|
75
|
+
*/
|
|
76
|
+
getLabels() {
|
|
77
|
+
if (!this.labels) return [];
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(this.labels);
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Set labels from array
|
|
86
|
+
*/
|
|
87
|
+
setLabels(labels) {
|
|
88
|
+
this.labels = JSON.stringify(labels);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get flags as parsed array
|
|
92
|
+
*/
|
|
93
|
+
getFlags() {
|
|
94
|
+
if (!this.flags) return [];
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(this.flags);
|
|
97
|
+
} catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Set flags from array
|
|
103
|
+
*/
|
|
104
|
+
setFlags(flags) {
|
|
105
|
+
this.flags = JSON.stringify(flags);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get headers as parsed object
|
|
109
|
+
*/
|
|
110
|
+
getHeaders() {
|
|
111
|
+
if (!this.headers) return {};
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(this.headers);
|
|
114
|
+
} catch {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Set headers from object
|
|
120
|
+
*/
|
|
121
|
+
setHeaders(headers) {
|
|
122
|
+
this.headers = JSON.stringify(headers);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the email account (typed as EmailAccount)
|
|
126
|
+
*/
|
|
127
|
+
async getAccount() {
|
|
128
|
+
if (!this.accountId) return null;
|
|
129
|
+
const { EmailAccountCollection } = await import("../index.js").then((n) => n.r);
|
|
130
|
+
return await (await EmailAccountCollection.create(this.options)).get({ id: this.accountId });
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get the folder
|
|
134
|
+
*/
|
|
135
|
+
async getFolder() {
|
|
136
|
+
if (!this.folderId) return null;
|
|
137
|
+
const { EmailFolderCollection } = await import("../index.js").then((n) => n.t);
|
|
138
|
+
return await (await EmailFolderCollection.create(this.options)).get({ id: this.folderId });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get emails in the same thread
|
|
142
|
+
*/
|
|
143
|
+
async getThreadEmails() {
|
|
144
|
+
if (!this.threadId) return [this];
|
|
145
|
+
const { EmailCollection } = await import("../index.js").then((n) => n.n);
|
|
146
|
+
return await (await EmailCollection.create(this.options)).getByThread(this.threadId);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get a short preview of the email body
|
|
150
|
+
*/
|
|
151
|
+
getPreview(maxLength = 200) {
|
|
152
|
+
const body = this.textBody || this.htmlBody?.replace(/<[^>]*>/g, "") || "";
|
|
153
|
+
if (body.length <= maxLength) return body;
|
|
154
|
+
return `${body.slice(0, maxLength)}...`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get References header values as array
|
|
158
|
+
*/
|
|
159
|
+
getReferences() {
|
|
160
|
+
const refs = this.getHeaders().references;
|
|
161
|
+
if (!refs) return [];
|
|
162
|
+
if (Array.isArray(refs)) return refs;
|
|
163
|
+
return refs.split(/\s+/).filter(Boolean);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Create a reply to this email with RFC 822 threading
|
|
167
|
+
*/
|
|
168
|
+
createReply(options) {
|
|
169
|
+
const reply = new Email({
|
|
170
|
+
...this.draftOptions(),
|
|
171
|
+
id: void 0,
|
|
172
|
+
accountId: this.accountId,
|
|
173
|
+
threadId: this.threadId || this.id || void 0,
|
|
174
|
+
subject: this.subject.startsWith("Re:") ? this.subject : `Re: ${this.subject}`,
|
|
175
|
+
fromAddress: "",
|
|
176
|
+
fromName: "",
|
|
177
|
+
inReplyToMessageId: this.id || void 0,
|
|
178
|
+
sendStatus: "draft",
|
|
179
|
+
isRead: true,
|
|
180
|
+
isDraft: true,
|
|
181
|
+
date: null,
|
|
182
|
+
createdAt: void 0,
|
|
183
|
+
updatedAt: void 0,
|
|
184
|
+
inReplyTo: this.messageId,
|
|
185
|
+
toAddresses: JSON.stringify([{
|
|
186
|
+
address: this.fromAddress,
|
|
187
|
+
name: this.fromName
|
|
188
|
+
}])
|
|
189
|
+
});
|
|
190
|
+
const refs = [...this.getReferences()];
|
|
191
|
+
if (this.messageId && !refs.includes(this.messageId)) refs.push(this.messageId);
|
|
192
|
+
reply.setHeaders({ references: refs.join(" ") });
|
|
193
|
+
if (options?.replyAll) {
|
|
194
|
+
const allRecipients = [...this.getToAddresses(), ...this.getCcAddresses()];
|
|
195
|
+
const seen = /* @__PURE__ */ new Set([this.fromAddress.toLowerCase()]);
|
|
196
|
+
const ccAddresses = allRecipients.filter((r) => {
|
|
197
|
+
const addr = r.address.toLowerCase();
|
|
198
|
+
if (seen.has(addr)) return false;
|
|
199
|
+
seen.add(addr);
|
|
200
|
+
return true;
|
|
201
|
+
});
|
|
202
|
+
reply.ccAddresses = JSON.stringify(ccAddresses);
|
|
203
|
+
}
|
|
204
|
+
reply.body = this.buildQuotedBody();
|
|
205
|
+
reply.textBody = reply.body;
|
|
206
|
+
return reply;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Create a forward of this email
|
|
210
|
+
*/
|
|
211
|
+
createForward() {
|
|
212
|
+
const forwardBody = this.buildForwardBody();
|
|
213
|
+
return new Email({
|
|
214
|
+
...this.draftOptions(),
|
|
215
|
+
id: void 0,
|
|
216
|
+
accountId: this.accountId,
|
|
217
|
+
threadId: "",
|
|
218
|
+
subject: this.subject.startsWith("Fwd:") ? this.subject : `Fwd: ${this.subject}`,
|
|
219
|
+
toAddresses: "[]",
|
|
220
|
+
ccAddresses: "[]",
|
|
221
|
+
bccAddresses: "[]",
|
|
222
|
+
fromAddress: "",
|
|
223
|
+
fromName: "",
|
|
224
|
+
body: forwardBody,
|
|
225
|
+
textBody: forwardBody,
|
|
226
|
+
hasAttachments: this.hasAttachments,
|
|
227
|
+
inReplyToMessageId: "",
|
|
228
|
+
inReplyTo: "",
|
|
229
|
+
sendStatus: "draft",
|
|
230
|
+
isDraft: true,
|
|
231
|
+
isRead: true,
|
|
232
|
+
date: null,
|
|
233
|
+
createdAt: void 0,
|
|
234
|
+
updatedAt: void 0
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Build email-specific quoted body for replies
|
|
239
|
+
*/
|
|
240
|
+
buildQuotedBody() {
|
|
241
|
+
return `
|
|
242
|
+
|
|
243
|
+
On ${this.date ? this.date.toLocaleString() : "unknown date"}, ${this.fromName ? `${this.fromName} <${this.fromAddress}>` : this.fromAddress} wrote:
|
|
244
|
+
${(this.textBody || this.body || "").split("\n").map((line) => `> ${line}`).join("\n")}`;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Build forwarded message body
|
|
248
|
+
*/
|
|
249
|
+
buildForwardBody() {
|
|
250
|
+
const dateStr = this.date ? this.date.toLocaleString() : "unknown date";
|
|
251
|
+
const from = this.fromName ? `${this.fromName} <${this.fromAddress}>` : this.fromAddress;
|
|
252
|
+
const toStr = this.getToAddresses().map((r) => r.name ? `${r.name} <${r.address}>` : r.address).join(", ");
|
|
253
|
+
const bodyText = this.textBody || this.body || "";
|
|
254
|
+
return [
|
|
255
|
+
"",
|
|
256
|
+
"",
|
|
257
|
+
"---------- Forwarded message ----------",
|
|
258
|
+
`From: ${from}`,
|
|
259
|
+
`Date: ${dateStr}`,
|
|
260
|
+
`Subject: ${this.subject}`,
|
|
261
|
+
`To: ${toStr}`,
|
|
262
|
+
"",
|
|
263
|
+
bodyText
|
|
264
|
+
].join("\n");
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
Email = __decorateClass([smrt({
|
|
268
|
+
tableStrategy: "sti",
|
|
269
|
+
api: { include: [
|
|
270
|
+
"list",
|
|
271
|
+
"get",
|
|
272
|
+
"create",
|
|
273
|
+
"update",
|
|
274
|
+
"delete"
|
|
275
|
+
] },
|
|
276
|
+
mcp: { include: ["list", "get"] },
|
|
277
|
+
cli: true
|
|
278
|
+
})], Email);
|
|
279
|
+
//#endregion
|
|
280
|
+
export { Email_exports as n, Email as t };
|
|
281
|
+
|
|
282
|
+
//# sourceMappingURL=Email-Bv6cu8QX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Email-Bv6cu8QX.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\nimport { smrt } from '@happyvertical/smrt-core';\nimport type { EmailOptions } 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: true,\n})\nexport class Email extends Message {\n // RFC 822 fields\n messageId = ''; // RFC 822 Message-ID header\n inReplyTo = '';\n\n // Additional recipients\n ccAddresses = '';\n bccAddresses = '';\n replyToAddress = '';\n replyToName = '';\n\n // Email-specific content\n textBody = '';\n htmlBody = '';\n\n // Location\n folderId = '';\n folderPath = '';\n labels = ''; // JSON array (Gmail)\n flags = ''; // JSON array (IMAP)\n\n // Email-specific status flags\n isAnswered = false;\n isDraft = false;\n\n // Raw data\n rawMessage = '';\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"],"mappings":"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"}
|