@happyvertical/smrt-messages 0.37.2 → 0.37.4
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,153 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
3
|
+
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
4
|
+
//#region src/models/EmailFolder.ts
|
|
5
|
+
var EmailFolder_exports = /* @__PURE__ */ __exportAll({ EmailFolder: () => EmailFolder });
|
|
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 EmailFolder = class extends SmrtObject {
|
|
15
|
+
tenantId = null;
|
|
16
|
+
accountId = "";
|
|
17
|
+
name = "";
|
|
18
|
+
path = "";
|
|
19
|
+
delimiter = "/";
|
|
20
|
+
specialUse = "";
|
|
21
|
+
messageCount = 0;
|
|
22
|
+
unreadCount = 0;
|
|
23
|
+
subscribed = true;
|
|
24
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
25
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
super(options);
|
|
28
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
29
|
+
if (options.accountId !== void 0) this.accountId = options.accountId;
|
|
30
|
+
if (options.name !== void 0) this.name = options.name;
|
|
31
|
+
if (options.path !== void 0) this.path = options.path;
|
|
32
|
+
if (options.delimiter !== void 0) this.delimiter = options.delimiter;
|
|
33
|
+
if (options.specialUse !== void 0) this.specialUse = options.specialUse;
|
|
34
|
+
if (options.messageCount !== void 0) this.messageCount = options.messageCount;
|
|
35
|
+
if (options.unreadCount !== void 0) this.unreadCount = options.unreadCount;
|
|
36
|
+
if (options.subscribed !== void 0) this.subscribed = options.subscribed;
|
|
37
|
+
if (options.createdAt) this.createdAt = options.createdAt;
|
|
38
|
+
if (options.updatedAt) this.updatedAt = options.updatedAt;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get the email account
|
|
42
|
+
*/
|
|
43
|
+
async getAccount() {
|
|
44
|
+
if (!this.accountId) return null;
|
|
45
|
+
const { EmailAccountCollection } = await import("../index.js").then((n) => n.r);
|
|
46
|
+
return await (await EmailAccountCollection.create(this.options)).get({ id: this.accountId });
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get all emails in this folder
|
|
50
|
+
*/
|
|
51
|
+
async getEmails(limit) {
|
|
52
|
+
const { EmailCollection } = await import("../index.js").then((n) => n.n);
|
|
53
|
+
const collection = await EmailCollection.create(this.options);
|
|
54
|
+
const options = { where: { folderId: this.id } };
|
|
55
|
+
if (limit) options.limit = limit;
|
|
56
|
+
return await collection.list(options);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get unread emails in this folder
|
|
60
|
+
*/
|
|
61
|
+
async getUnreadEmails(limit) {
|
|
62
|
+
const { EmailCollection } = await import("../index.js").then((n) => n.n);
|
|
63
|
+
const collection = await EmailCollection.create(this.options);
|
|
64
|
+
const options = { where: {
|
|
65
|
+
folderId: this.id,
|
|
66
|
+
isRead: false
|
|
67
|
+
} };
|
|
68
|
+
if (limit) options.limit = limit;
|
|
69
|
+
return await collection.list(options);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Update message counts from database
|
|
73
|
+
*/
|
|
74
|
+
async refreshCounts() {
|
|
75
|
+
if (!this.id) throw new Error("EmailFolder.refreshCounts requires a persisted folder (missing id)");
|
|
76
|
+
const folderId = this.id;
|
|
77
|
+
const { EmailCollection } = await import("../index.js").then((n) => n.n);
|
|
78
|
+
const collection = await EmailCollection.create(this.options);
|
|
79
|
+
this.messageCount = await collection.countByFolder(folderId);
|
|
80
|
+
this.unreadCount = await collection.countUnreadByFolder(folderId);
|
|
81
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
82
|
+
await this.save();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if this is the inbox folder
|
|
86
|
+
*/
|
|
87
|
+
isInbox() {
|
|
88
|
+
return this.specialUse === "\\Inbox" || this.path.toLowerCase() === "inbox";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if this is the sent folder
|
|
92
|
+
*/
|
|
93
|
+
isSent() {
|
|
94
|
+
return this.specialUse === "\\Sent" || this.path.toLowerCase() === "sent";
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if this is the drafts folder
|
|
98
|
+
*/
|
|
99
|
+
isDrafts() {
|
|
100
|
+
return this.specialUse === "\\Drafts" || this.path.toLowerCase() === "drafts";
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if this is the trash folder
|
|
104
|
+
*/
|
|
105
|
+
isTrash() {
|
|
106
|
+
return this.specialUse === "\\Trash" || this.path.toLowerCase() === "trash";
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if this is the spam/junk folder
|
|
110
|
+
*/
|
|
111
|
+
isSpam() {
|
|
112
|
+
return this.specialUse === "\\Junk" || this.path.toLowerCase() === "spam" || this.path.toLowerCase() === "junk";
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if this is a system folder
|
|
116
|
+
*/
|
|
117
|
+
isSystemFolder() {
|
|
118
|
+
return !!this.specialUse;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Subscribe to folder
|
|
122
|
+
*/
|
|
123
|
+
async subscribe() {
|
|
124
|
+
this.subscribed = true;
|
|
125
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
126
|
+
await this.save();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Unsubscribe from folder
|
|
130
|
+
*/
|
|
131
|
+
async unsubscribe() {
|
|
132
|
+
this.subscribed = false;
|
|
133
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
134
|
+
await this.save();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
__decorateClass([tenantId({ nullable: true })], EmailFolder.prototype, "tenantId", 2);
|
|
138
|
+
__decorateClass([foreignKey("Account")], EmailFolder.prototype, "accountId", 2);
|
|
139
|
+
EmailFolder = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
140
|
+
api: { include: [
|
|
141
|
+
"list",
|
|
142
|
+
"get",
|
|
143
|
+
"create",
|
|
144
|
+
"update",
|
|
145
|
+
"delete"
|
|
146
|
+
] },
|
|
147
|
+
mcp: { include: ["list", "get"] },
|
|
148
|
+
cli: true
|
|
149
|
+
})], EmailFolder);
|
|
150
|
+
//#endregion
|
|
151
|
+
export { EmailFolder_exports as n, EmailFolder as t };
|
|
152
|
+
|
|
153
|
+
//# sourceMappingURL=EmailFolder-DvZODbnW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmailFolder-DvZODbnW.js","names":[],"sources":["../../src/models/EmailFolder.ts"],"sourcesContent":["/**\n * EmailFolder model - Folder/label tracking\n */\n\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: true,\n})\nexport class EmailFolder extends SmrtObject {\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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"}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
2
|
+
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
3
|
+
//#region src/models/Message.ts
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
7
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
8
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
9
|
+
if (kind && result) __defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
var Message = class extends SmrtObject {
|
|
13
|
+
tenantId = null;
|
|
14
|
+
accountId = "";
|
|
15
|
+
threadId = "";
|
|
16
|
+
subject = "";
|
|
17
|
+
body = "";
|
|
18
|
+
fromAddress = "";
|
|
19
|
+
fromName = "";
|
|
20
|
+
toAddresses = "";
|
|
21
|
+
date = null;
|
|
22
|
+
isRead = false;
|
|
23
|
+
isFlagged = false;
|
|
24
|
+
hasAttachments = false;
|
|
25
|
+
size = 0;
|
|
26
|
+
metadata = "";
|
|
27
|
+
sendStatus = "draft";
|
|
28
|
+
sentAt = null;
|
|
29
|
+
sendError = "";
|
|
30
|
+
retryCount = 0;
|
|
31
|
+
maxRetries = 3;
|
|
32
|
+
scheduledSendAt = null;
|
|
33
|
+
inReplyToMessageId = "";
|
|
34
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
35
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
super(options);
|
|
38
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
39
|
+
if (options.accountId !== void 0) this.accountId = options.accountId;
|
|
40
|
+
if (options.threadId !== void 0) this.threadId = options.threadId;
|
|
41
|
+
if (options.subject !== void 0) this.subject = options.subject;
|
|
42
|
+
if (options.body !== void 0) this.body = options.body;
|
|
43
|
+
if (options.fromAddress !== void 0) this.fromAddress = options.fromAddress;
|
|
44
|
+
if (options.fromName !== void 0) this.fromName = options.fromName;
|
|
45
|
+
if (options.toAddresses !== void 0) this.toAddresses = options.toAddresses;
|
|
46
|
+
if (options.date !== void 0) this.date = options.date || null;
|
|
47
|
+
if (options.isRead !== void 0) this.isRead = options.isRead;
|
|
48
|
+
if (options.isFlagged !== void 0) this.isFlagged = options.isFlagged;
|
|
49
|
+
if (options.hasAttachments !== void 0) this.hasAttachments = options.hasAttachments;
|
|
50
|
+
if (options.size !== void 0) this.size = options.size;
|
|
51
|
+
if (options.metadata !== void 0) this.metadata = options.metadata;
|
|
52
|
+
if (options.sendStatus !== void 0) this.sendStatus = options.sendStatus;
|
|
53
|
+
if (options.sentAt !== void 0) this.sentAt = options.sentAt || null;
|
|
54
|
+
if (options.sendError !== void 0) this.sendError = options.sendError;
|
|
55
|
+
if (options.retryCount !== void 0) this.retryCount = options.retryCount;
|
|
56
|
+
if (options.maxRetries !== void 0) this.maxRetries = options.maxRetries;
|
|
57
|
+
if (options.scheduledSendAt !== void 0) this.scheduledSendAt = options.scheduledSendAt || null;
|
|
58
|
+
if (options.inReplyToMessageId !== void 0) this.inReplyToMessageId = options.inReplyToMessageId;
|
|
59
|
+
if (options.createdAt) this.createdAt = options.createdAt;
|
|
60
|
+
if (options.updatedAt) this.updatedAt = options.updatedAt;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get to addresses as parsed array
|
|
64
|
+
*/
|
|
65
|
+
getToAddresses() {
|
|
66
|
+
if (!this.toAddresses) return [];
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(this.toAddresses);
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Set to addresses from array
|
|
75
|
+
*/
|
|
76
|
+
setToAddresses(addresses) {
|
|
77
|
+
this.toAddresses = JSON.stringify(addresses);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get metadata as parsed object
|
|
81
|
+
*/
|
|
82
|
+
getMetadata() {
|
|
83
|
+
if (!this.metadata) return {};
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(this.metadata);
|
|
86
|
+
} catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Set metadata from object
|
|
92
|
+
*/
|
|
93
|
+
setMetadata(data) {
|
|
94
|
+
this.metadata = JSON.stringify(data);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Mark message as read
|
|
98
|
+
*/
|
|
99
|
+
async markRead() {
|
|
100
|
+
this.isRead = true;
|
|
101
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
102
|
+
await this.save();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Mark message as unread
|
|
106
|
+
*/
|
|
107
|
+
async markUnread() {
|
|
108
|
+
this.isRead = false;
|
|
109
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
110
|
+
await this.save();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Toggle flagged status
|
|
114
|
+
*/
|
|
115
|
+
async toggleFlagged() {
|
|
116
|
+
this.isFlagged = !this.isFlagged;
|
|
117
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
118
|
+
await this.save();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if message is unread
|
|
122
|
+
*/
|
|
123
|
+
isUnread() {
|
|
124
|
+
return !this.isRead;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get a short preview of the message body
|
|
128
|
+
*/
|
|
129
|
+
getPreview(maxLength = 200) {
|
|
130
|
+
const text = this.body || "";
|
|
131
|
+
if (text.length <= maxLength) return text;
|
|
132
|
+
return `${text.slice(0, maxLength)}...`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get the account for this message
|
|
136
|
+
*/
|
|
137
|
+
async getAccount() {
|
|
138
|
+
if (!this.accountId) return null;
|
|
139
|
+
const { AccountCollection } = await import("./AccountCollection-BekDBDAm.js").then((n) => n.n);
|
|
140
|
+
return await (await AccountCollection.create(this.options)).get({ id: this.accountId });
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get messages in the same thread
|
|
144
|
+
*/
|
|
145
|
+
async getThreadMessages() {
|
|
146
|
+
if (!this.threadId) return [this];
|
|
147
|
+
const { MessageCollection } = await import("./MessageCollection-DvG0YCfd.js").then((n) => n.n);
|
|
148
|
+
return await (await MessageCollection.create(this.options)).list({ where: { threadId: this.threadId } });
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get attachments for this message
|
|
152
|
+
*/
|
|
153
|
+
async getAttachments() {
|
|
154
|
+
const { AttachmentCollection } = await import("../index.js").then((n) => n.i);
|
|
155
|
+
return await (await AttachmentCollection.create(this.options)).list({ where: { messageId: this.id } });
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Send this message via its account's sender
|
|
159
|
+
*/
|
|
160
|
+
async send(options) {
|
|
161
|
+
if (this.sendStatus === "sending" || this.sendStatus === "sent") return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: `Cannot send: message is already '${this.sendStatus}'`,
|
|
164
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
165
|
+
};
|
|
166
|
+
const account = await this.getAccount();
|
|
167
|
+
if (!account) {
|
|
168
|
+
const result = {
|
|
169
|
+
success: false,
|
|
170
|
+
error: "No account associated with this message",
|
|
171
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
172
|
+
};
|
|
173
|
+
this.sendStatus = "failed";
|
|
174
|
+
this.sendError = result.error ?? "";
|
|
175
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
176
|
+
await this.save();
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
const sender = await account.createSender();
|
|
180
|
+
const claimFromStatus = this.sendStatus;
|
|
181
|
+
if (this.isPersisted && this.id) {
|
|
182
|
+
const claim = await this.db.update(this.tableName, {
|
|
183
|
+
id: this.id,
|
|
184
|
+
send_status: claimFromStatus
|
|
185
|
+
}, {
|
|
186
|
+
send_status: "sending",
|
|
187
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
188
|
+
});
|
|
189
|
+
if (!claim || claim.affected < 1) return {
|
|
190
|
+
success: false,
|
|
191
|
+
error: "Cannot send: message is already being sent",
|
|
192
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
193
|
+
};
|
|
194
|
+
this.sendStatus = "sending";
|
|
195
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
196
|
+
} else {
|
|
197
|
+
this.sendStatus = "sending";
|
|
198
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
199
|
+
await this.save();
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const result = await sender.send(this, options);
|
|
203
|
+
if (result.success) {
|
|
204
|
+
this.sendStatus = "sent";
|
|
205
|
+
this.sentAt = result.sentAt;
|
|
206
|
+
this.sendError = "";
|
|
207
|
+
} else {
|
|
208
|
+
this.sendStatus = "failed";
|
|
209
|
+
this.sendError = result.error ?? "Send failed";
|
|
210
|
+
}
|
|
211
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
212
|
+
await this.save();
|
|
213
|
+
return result;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
216
|
+
this.sendStatus = "failed";
|
|
217
|
+
this.sendError = errorMessage;
|
|
218
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
219
|
+
await this.save();
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: errorMessage,
|
|
223
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Retry sending a failed message
|
|
229
|
+
*/
|
|
230
|
+
async retrySend(options) {
|
|
231
|
+
if (this.sendStatus !== "failed") return {
|
|
232
|
+
success: false,
|
|
233
|
+
error: `Cannot retry: message status is '${this.sendStatus}', expected 'failed'`,
|
|
234
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
235
|
+
};
|
|
236
|
+
if (this.retryCount >= this.maxRetries) return {
|
|
237
|
+
success: false,
|
|
238
|
+
error: `Retry budget exhausted (${this.retryCount}/${this.maxRetries})`,
|
|
239
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
240
|
+
};
|
|
241
|
+
this.retryCount++;
|
|
242
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
243
|
+
await this.save();
|
|
244
|
+
return this.send(options);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Options for a derived draft (reply/forward) built from this message. Carries
|
|
248
|
+
* the DB connection + tenant context from `this.options`, but strips this
|
|
249
|
+
* message's own identity fields. When this message was hydrated from the DB,
|
|
250
|
+
* `this.options` holds the row's `id`/`slug`/`context`/`_skipLoad`; spreading
|
|
251
|
+
* those into a new draft would make `draft.save()` upsert onto the natural-key
|
|
252
|
+
* conflict columns (`slug`/`context`/`_meta_type`) and overwrite the ORIGINAL
|
|
253
|
+
* message instead of inserting a new row. See EmailAccount.childOptions().
|
|
254
|
+
*/
|
|
255
|
+
draftOptions() {
|
|
256
|
+
const rest = { ...this.options };
|
|
257
|
+
delete rest.id;
|
|
258
|
+
delete rest.slug;
|
|
259
|
+
delete rest.context;
|
|
260
|
+
delete rest._skipLoad;
|
|
261
|
+
return rest;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create a reply to this message (returns unsaved draft)
|
|
265
|
+
*/
|
|
266
|
+
createReply(_options) {
|
|
267
|
+
return new this.constructor({
|
|
268
|
+
...this.draftOptions(),
|
|
269
|
+
id: void 0,
|
|
270
|
+
accountId: this.accountId,
|
|
271
|
+
threadId: this.threadId || this.id || "",
|
|
272
|
+
subject: this.subject.startsWith("Re:") ? this.subject : `Re: ${this.subject}`,
|
|
273
|
+
toAddresses: JSON.stringify([{
|
|
274
|
+
address: this.fromAddress,
|
|
275
|
+
name: this.fromName
|
|
276
|
+
}]),
|
|
277
|
+
fromAddress: "",
|
|
278
|
+
fromName: "",
|
|
279
|
+
body: this.buildQuotedBody(),
|
|
280
|
+
inReplyToMessageId: this.id || "",
|
|
281
|
+
sendStatus: "draft",
|
|
282
|
+
isRead: true,
|
|
283
|
+
date: null,
|
|
284
|
+
createdAt: void 0,
|
|
285
|
+
updatedAt: void 0
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Create a forward of this message (returns unsaved draft)
|
|
290
|
+
*/
|
|
291
|
+
createForward() {
|
|
292
|
+
return new this.constructor({
|
|
293
|
+
...this.draftOptions(),
|
|
294
|
+
id: void 0,
|
|
295
|
+
accountId: this.accountId,
|
|
296
|
+
threadId: "",
|
|
297
|
+
subject: this.subject.startsWith("Fwd:") ? this.subject : `Fwd: ${this.subject}`,
|
|
298
|
+
toAddresses: "[]",
|
|
299
|
+
fromAddress: "",
|
|
300
|
+
fromName: "",
|
|
301
|
+
body: this.buildQuotedBody(),
|
|
302
|
+
hasAttachments: this.hasAttachments,
|
|
303
|
+
inReplyToMessageId: "",
|
|
304
|
+
sendStatus: "draft",
|
|
305
|
+
isRead: true,
|
|
306
|
+
date: null,
|
|
307
|
+
createdAt: void 0,
|
|
308
|
+
updatedAt: void 0
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Build quoted body for reply/forward
|
|
313
|
+
*/
|
|
314
|
+
buildQuotedBody() {
|
|
315
|
+
return `
|
|
316
|
+
|
|
317
|
+
On ${this.date ? this.date.toLocaleString() : "unknown date"}, ${this.fromName ? `${this.fromName} <${this.fromAddress}>` : this.fromAddress} wrote:
|
|
318
|
+
${(this.body || "").split("\n").map((line) => `> ${line}`).join("\n")}`;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
__decorateClass([tenantId({ nullable: true })], Message.prototype, "tenantId", 2);
|
|
322
|
+
__decorateClass([foreignKey("Account")], Message.prototype, "accountId", 2);
|
|
323
|
+
__decorateClass([foreignKey("Message")], Message.prototype, "inReplyToMessageId", 2);
|
|
324
|
+
Message = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
325
|
+
tableStrategy: "sti",
|
|
326
|
+
api: { include: ["list", "get"] },
|
|
327
|
+
mcp: { include: ["list", "get"] },
|
|
328
|
+
cli: true
|
|
329
|
+
})], Message);
|
|
330
|
+
//#endregion
|
|
331
|
+
export { Message as t };
|
|
332
|
+
|
|
333
|
+
//# sourceMappingURL=Message-CHTC1RuY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Message-CHTC1RuY.js","names":[],"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\nimport { foreignKey, SmrtObject, smrt } 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: true,\n})\nexport class Message extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Account')\n accountId = '';\n threadId = '';\n subject = '';\n body = ''; // Normalized plain text\n fromAddress = '';\n fromName = '';\n toAddresses = ''; // JSON array of {address, name}\n date: Date | null = null;\n isRead = false;\n isFlagged = false;\n hasAttachments = false;\n size = 0;\n metadata = ''; // JSON extension bag\n\n // Send lifecycle fields\n sendStatus: SendStatus = 'draft';\n sentAt: Date | null = null;\n sendError = '';\n retryCount = 0;\n maxRetries = 3;\n scheduledSendAt: Date | null = null;\n @foreignKey('Message')\n inReplyToMessageId = '';\n\n // Timestamps\n createdAt = new Date();\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.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 /**\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 /**\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 // Get sender from account\n const sender = await account.createSender();\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 claim = await this.db.update(\n this.tableName,\n { id: this.id, send_status: claimFromStatus },\n { send_status: 'sending', updated_at: new Date() },\n );\n if (!claim || claim.affected < 1) {\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.updatedAt = new Date();\n } else {\n this.sendStatus = 'sending';\n this.updatedAt = new Date();\n await this.save();\n }\n\n try {\n const result = await sender.send(this, options);\n\n if (result.success) {\n this.sendStatus = 'sent';\n this.sentAt = result.sentAt;\n this.sendError = '';\n } else {\n this.sendStatus = 'failed';\n this.sendError = result.error ?? 'Send failed';\n }\n\n this.updatedAt = new Date();\n await this.save();\n return result;\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\n return {\n success: false,\n error: errorMessage,\n sentAt: new Date(),\n };\n }\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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"}
|