@happyvertical/smrt-messages 0.39.4 → 0.39.6
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/AGENTS.md +27 -4
- package/README.md +53 -1
- package/dist/chunks/{AccountCollection-BekDBDAm.js → AccountCollection-vN5XAyOY.js} +56 -24
- package/dist/chunks/AccountCollection-vN5XAyOY.js.map +1 -0
- package/dist/chunks/{Email-Bv6cu8QX.js → Email-DskdI-Mq.js} +2 -2
- package/dist/chunks/{Email-Bv6cu8QX.js.map → Email-DskdI-Mq.js.map} +1 -1
- package/dist/chunks/{Message-CHTC1RuY.js → Message-BGsB-xhM.js} +45 -6
- package/dist/chunks/Message-BGsB-xhM.js.map +1 -0
- package/dist/chunks/{MessageCollection-DvG0YCfd.js → MessageCollection-BwyTAGEd.js} +2 -2
- package/dist/chunks/{MessageCollection-DvG0YCfd.js.map → MessageCollection-BwyTAGEd.js.map} +1 -1
- package/dist/collections/MessagingEndpointCollection.d.ts +6 -0
- package/dist/collections/MessagingEndpointCollection.d.ts.map +1 -0
- package/dist/collections/PersonaMessageRouteCollection.d.ts +7 -0
- package/dist/collections/PersonaMessageRouteCollection.d.ts.map +1 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +929 -105
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +3331 -735
- package/dist/models/Account.d.ts +9 -0
- package/dist/models/Account.d.ts.map +1 -1
- package/dist/models/EmailAccount.d.ts.map +1 -1
- package/dist/models/Message.d.ts +4 -0
- package/dist/models/Message.d.ts.map +1 -1
- package/dist/models/MessagingEndpoint.d.ts +19 -0
- package/dist/models/MessagingEndpoint.d.ts.map +1 -0
- package/dist/models/PersonaMessageRoute.d.ts +15 -0
- package/dist/models/PersonaMessageRoute.d.ts.map +1 -0
- package/dist/models/SlackAccount.d.ts.map +1 -1
- package/dist/models/TelegramAccount.d.ts +6 -0
- package/dist/models/TelegramAccount.d.ts.map +1 -0
- package/dist/models/TwitterAccount.d.ts.map +1 -1
- package/dist/models/ZulipAccount.d.ts +6 -0
- package/dist/models/ZulipAccount.d.ts.map +1 -0
- package/dist/permissions.d.ts +17 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/providers.d.ts +33 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/senders/TelegramSender.d.ts +11 -0
- package/dist/senders/TelegramSender.d.ts.map +1 -0
- package/dist/senders/ZulipSender.d.ts +11 -0
- package/dist/senders/ZulipSender.d.ts.map +1 -0
- package/dist/services/MessagingSettingsService.d.ts +89 -0
- package/dist/services/MessagingSettingsService.d.ts.map +1 -0
- package/dist/services/PersonaMessagingService.d.ts +21 -0
- package/dist/services/PersonaMessagingService.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +1115 -410
- package/dist/svelte/components/MessagingSettingsPanel.svelte +398 -0
- package/dist/svelte/components/MessagingSettingsPanel.svelte.d.ts +21 -0
- package/dist/svelte/components/MessagingSettingsPanel.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.messages.d.ts +41 -0
- package/dist/svelte/i18n.messages.d.ts.map +1 -1
- package/dist/svelte/i18n.messages.js +42 -0
- package/dist/svelte/index.d.ts +3 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +3 -1
- package/dist/svelte/messaging-settings.d.ts +3 -0
- package/dist/svelte/messaging-settings.d.ts.map +1 -0
- package/dist/svelte/messaging-settings.js +15 -0
- package/dist/svelte/messaging-settings.test.js +16 -0
- package/dist/types.d.ts +43 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +16 -1
- package/dist/ui.js.map +1 -1
- package/package.json +8 -7
- package/dist/chunks/AccountCollection-BekDBDAm.js.map +0 -1
- package/dist/chunks/Message-CHTC1RuY.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -5,18 +5,39 @@ Multi-channel messaging with STI hierarchies for both messages and accounts. Cre
|
|
|
5
5
|
## STI Hierarchies
|
|
6
6
|
|
|
7
7
|
**Messages** (share `messages` table):
|
|
8
|
-
- `Message` (base): accountId, threadId, subject, body, fromAddress, toAddresses (JSON), sendStatus (draft/sending/sent/failed), retryCount
|
|
8
|
+
- `Message` (base): accountId, personaId, endpointId, correlationId, threadId, subject, body, fromAddress, toAddresses (JSON), sendStatus (draft/sending/sent/failed), retryCount
|
|
9
9
|
- `Email`: messageId (RFC 822), inReplyTo, ccAddresses, bccAddresses, htmlBody, textBody, folderId, labels, headers
|
|
10
10
|
- `Tweet`: tweetId, retweetCount, likeCount, mediaUrls, hashtags, mentions
|
|
11
11
|
- `SlackMessage`: channelId, slackTs, slackThreadTs, reactions, blocks
|
|
12
12
|
|
|
13
13
|
**Accounts** (share `accounts` table):
|
|
14
|
-
- `Account` (base):
|
|
15
|
-
|
|
14
|
+
- `Account` (base): channelType, providerType, public configuration,
|
|
15
|
+
credentialSecretId, isActive, lastSyncAt, deprecated sensitive settings
|
|
16
|
+
- `EmailAccount`, `TwitterAccount`, `SlackAccount`, `ZulipAccount`,
|
|
17
|
+
`TelegramAccount`: provider-specific identity/sender behavior
|
|
18
|
+
|
|
19
|
+
## Persona messaging
|
|
20
|
+
|
|
21
|
+
- `MessagingEndpoint`: tenant-owned, write-only destination JSON with masked
|
|
22
|
+
display helper.
|
|
23
|
+
- `PersonaMessageRoute`: persona → account + endpoint, selected by purpose and
|
|
24
|
+
descending priority.
|
|
25
|
+
- `PersonaMessagingService`: persists and sends through an enabled route.
|
|
26
|
+
- Provider registry: public setup descriptors + optional sender factory. Keep
|
|
27
|
+
channel (`sms`) separate from provider (`twilio`).
|
|
28
|
+
- Permissions: `messages.send`, `messages.manage-routes`,
|
|
29
|
+
`messages.manage-credentials`.
|
|
30
|
+
- `MessagingSettingsService` is the permissioned server boundary;
|
|
31
|
+
its host-supplied `resolvePersonaTenantId` proves cross-package ownership.
|
|
32
|
+
`MessagingSettingsPanel` is the callback-driven Svelte surface.
|
|
33
|
+
- Account, endpoint, and route generated surfaces are read-only. Do not add
|
|
34
|
+
generated mutations around the settings service or make secret fields writable.
|
|
16
35
|
|
|
17
36
|
## Credential Security
|
|
18
37
|
|
|
19
|
-
Account credentials stored via `credentialSecretId` →
|
|
38
|
+
Account credentials stored via tenant-bound `credentialSecretId` →
|
|
39
|
+
smrt-secrets. Use `setCredentials()`/`getCredentials()` — never store passwords
|
|
40
|
+
directly. Credential descriptor schemas contain field definitions, never values.
|
|
20
41
|
|
|
21
42
|
## Send Lifecycle
|
|
22
43
|
|
|
@@ -29,3 +50,5 @@ Account credentials stored via `credentialSecretId` → smrt-secrets. Use `setCr
|
|
|
29
50
|
- **RFC 822 threading**: Email uses `inReplyTo`/`messageId`/`references` — manual management required
|
|
30
51
|
- **Attachment.messageId**: use this field (not `emailId`); old getter/setter mapped via deprecation wrapper
|
|
31
52
|
- **Optional tenancy**: `@TenantScoped({ mode: 'optional' })` on Message, Account, Attachment
|
|
53
|
+
- **Required tenancy**: endpoints and persona routes are always tenant scoped
|
|
54
|
+
- **Secrets are write-only**: never serialize credentials or endpoint.address to an app
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @happyvertical/smrt-messages
|
|
2
2
|
|
|
3
|
-
Unified multi-channel messaging with
|
|
3
|
+
Unified multi-channel messaging with persona routes, encrypted credentials, and
|
|
4
|
+
provider adapters. Outbound delivery supports email, Zulip, and Telegram; SMS is
|
|
5
|
+
a reserved channel ready for a provider adapter.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -84,6 +86,56 @@ Each channel has a dedicated sender implementing `MessageSenderInterface`.
|
|
|
84
86
|
| `EmailSender` | Send emails via `@happyvertical/email` client |
|
|
85
87
|
| `SlackSender` | Send Slack messages via API |
|
|
86
88
|
| `TweetSender` | Post tweets via Twitter API |
|
|
89
|
+
| `ZulipSender` | Send stream or private Zulip messages via bot credentials |
|
|
90
|
+
| `TelegramSender` | Send Telegram bot messages to a chat/topic |
|
|
91
|
+
|
|
92
|
+
### Persona-scoped outbound messaging
|
|
93
|
+
|
|
94
|
+
`MessagingSettingsService` is the permission-gated server boundary for saving
|
|
95
|
+
accounts, write-only credentials, destinations, and persona routes.
|
|
96
|
+
`PersonaMessagingService` selects the highest-priority matching route, persists
|
|
97
|
+
the message, then runs the normal send lifecycle.
|
|
98
|
+
|
|
99
|
+
Schema changes are manifest-driven. After upgrading an application, run
|
|
100
|
+
`smrt db:diff` and `smrt db:migrate` to add the account/message columns and the
|
|
101
|
+
`messaging_endpoints` / `persona_message_routes` tables.
|
|
102
|
+
|
|
103
|
+
When constructing `MessagingSettingsService`, provide
|
|
104
|
+
`resolvePersonaTenantId(personaId)` from the host's persona collection. Route
|
|
105
|
+
writes fail closed unless it proves the persona belongs to the same tenant.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import {
|
|
109
|
+
messagingPrincipalFromPermissions,
|
|
110
|
+
PersonaMessagingService,
|
|
111
|
+
} from '@happyvertical/smrt-messages';
|
|
112
|
+
|
|
113
|
+
const messaging = new PersonaMessagingService({
|
|
114
|
+
db,
|
|
115
|
+
tenantId,
|
|
116
|
+
principal: messagingPrincipalFromPermissions(['messages.send'], { tenantId }),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await messaging.send({
|
|
120
|
+
personaId,
|
|
121
|
+
channel: 'telegram', // optional; default route otherwise
|
|
122
|
+
body: 'The scheduled report is ready.',
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Agents receive the persona-fixed tool from `@happyvertical/smrt-personas`:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const tool = createPersonaMessagingTool({ db, tenantId, personaId });
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The tool only accepts message content and route intent. Persona, account,
|
|
133
|
+
endpoint, and credential ids remain trusted server state.
|
|
134
|
+
|
|
135
|
+
Apps can render `MessagingSettingsPanel` from
|
|
136
|
+
`@happyvertical/smrt-messages/svelte` using the sanitized view returned by
|
|
137
|
+
`MessagingSettingsService.list()` and wiring its save callbacks back to the
|
|
138
|
+
service.
|
|
87
139
|
|
|
88
140
|
### Credential Security
|
|
89
141
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { SmrtCollection, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
|
|
3
|
-
import { TenantScoped, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
|
|
3
|
+
import { TenantScoped, getCurrentTenant, queryGlobal, queryWithGlobals, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
|
|
4
4
|
//#region src/models/Account.ts
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -14,10 +14,13 @@ var Account = class extends SmrtObject {
|
|
|
14
14
|
tenantId = null;
|
|
15
15
|
name = "";
|
|
16
16
|
providerType = "";
|
|
17
|
+
channelType = "";
|
|
17
18
|
credentialSecretId = null;
|
|
18
19
|
isActive = true;
|
|
19
20
|
lastSyncAt = null;
|
|
20
21
|
settings = "";
|
|
22
|
+
/** Non-secret provider configuration such as a Zulip site URL. */
|
|
23
|
+
configuration = "{}";
|
|
21
24
|
createdAt = /* @__PURE__ */ new Date();
|
|
22
25
|
updatedAt = /* @__PURE__ */ new Date();
|
|
23
26
|
constructor(options = {}) {
|
|
@@ -25,10 +28,12 @@ var Account = class extends SmrtObject {
|
|
|
25
28
|
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
26
29
|
if (options.name !== void 0) this.name = options.name;
|
|
27
30
|
if (options.providerType !== void 0) this.providerType = options.providerType;
|
|
31
|
+
if (options.channelType !== void 0) this.channelType = options.channelType;
|
|
28
32
|
if (options.credentialSecretId !== void 0) this.credentialSecretId = options.credentialSecretId || null;
|
|
29
33
|
if (options.isActive !== void 0) this.isActive = options.isActive;
|
|
30
34
|
if (options.lastSyncAt !== void 0) this.lastSyncAt = options.lastSyncAt || null;
|
|
31
35
|
if (options.settings !== void 0) this.settings = options.settings;
|
|
36
|
+
if (options.configuration !== void 0) this.configuration = options.configuration;
|
|
32
37
|
if (options.createdAt) this.createdAt = options.createdAt;
|
|
33
38
|
if (options.updatedAt) this.updatedAt = options.updatedAt;
|
|
34
39
|
}
|
|
@@ -49,6 +54,19 @@ var Account = class extends SmrtObject {
|
|
|
49
54
|
setSettings(settings) {
|
|
50
55
|
this.settings = JSON.stringify(settings);
|
|
51
56
|
}
|
|
57
|
+
getConfiguration() {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(this.configuration || "{}");
|
|
60
|
+
} catch {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
setConfiguration(configuration) {
|
|
65
|
+
this.configuration = JSON.stringify(configuration);
|
|
66
|
+
}
|
|
67
|
+
get hasCredentials() {
|
|
68
|
+
return Boolean(this.credentialSecretId || this.settings);
|
|
69
|
+
}
|
|
52
70
|
/**
|
|
53
71
|
* Activate account
|
|
54
72
|
*/
|
|
@@ -69,59 +87,73 @@ var Account = class extends SmrtObject {
|
|
|
69
87
|
* Store credentials securely using smrt-secrets
|
|
70
88
|
*/
|
|
71
89
|
async setCredentials(credentials, options = {}) {
|
|
90
|
+
if (!this.id) throw new Error("Account must be persisted before storing credentials.");
|
|
72
91
|
const { SecretService } = await import("@happyvertical/smrt-secrets");
|
|
73
92
|
const secretService = await SecretService.create({ db: this.db });
|
|
74
|
-
const secretName =
|
|
93
|
+
const secretName = `${this.channelType || "messaging"}-account-${this.id}`;
|
|
75
94
|
const secretValue = JSON.stringify(credentials);
|
|
76
|
-
|
|
95
|
+
const tenantId2 = this.requireCredentialTenantId("store credentials");
|
|
96
|
+
await secretService.storeForTenant(tenantId2, this.credentialSecretId || secretName, secretValue, {
|
|
77
97
|
description: options.description || `Credentials for ${this.name}`,
|
|
78
98
|
category: options.category || "messaging"
|
|
79
99
|
});
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
category: options.category || "messaging"
|
|
84
|
-
});
|
|
85
|
-
this.credentialSecretId = secretName;
|
|
86
|
-
this.updatedAt = /* @__PURE__ */ new Date();
|
|
87
|
-
await this.save();
|
|
88
|
-
}
|
|
100
|
+
this.credentialSecretId ||= secretName;
|
|
101
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
102
|
+
await this.withCredentialTenantContext(() => this.save());
|
|
89
103
|
}
|
|
90
104
|
/**
|
|
91
105
|
* Create a sender for this account.
|
|
92
106
|
* Subclasses must override to return a concrete sender.
|
|
93
107
|
*/
|
|
94
108
|
async createSender() {
|
|
95
|
-
|
|
109
|
+
const { getMessagingProvider } = await import("../index.js").then((n) => n.o);
|
|
110
|
+
const provider = getMessagingProvider(this.providerType);
|
|
111
|
+
if (!provider?.createSender) throw new Error(`createSender() not implemented for account type '${this.providerType}'`);
|
|
112
|
+
return provider.createSender(this);
|
|
96
113
|
}
|
|
97
114
|
/**
|
|
98
115
|
* Retrieve stored credentials
|
|
99
116
|
*/
|
|
100
117
|
async getCredentials() {
|
|
101
118
|
if (!this.credentialSecretId) return this.getSettings();
|
|
119
|
+
const tenantId2 = this.requireCredentialTenantId("retrieve credentials");
|
|
102
120
|
const { SecretService } = await import("@happyvertical/smrt-secrets");
|
|
103
121
|
const secretService = await SecretService.create({ db: this.db });
|
|
104
122
|
try {
|
|
105
|
-
const secret = await secretService.
|
|
123
|
+
const secret = await secretService.retrieveForTenant(tenantId2, this.credentialSecretId);
|
|
106
124
|
return JSON.parse(secret.value);
|
|
107
125
|
} catch {
|
|
108
126
|
return null;
|
|
109
127
|
}
|
|
110
128
|
}
|
|
129
|
+
getCredentialTenantId() {
|
|
130
|
+
return this.tenantId || getCurrentTenant()?.tenantId || null;
|
|
131
|
+
}
|
|
132
|
+
requireCredentialTenantId(action) {
|
|
133
|
+
const tenantId2 = this.getCredentialTenantId();
|
|
134
|
+
if (!tenantId2) throw new Error(`Tenant context required to ${action}.`);
|
|
135
|
+
return tenantId2;
|
|
136
|
+
}
|
|
137
|
+
async withCredentialTenantContext(fn) {
|
|
138
|
+
const tenantId2 = this.getCredentialTenantId();
|
|
139
|
+
if (!tenantId2 || getCurrentTenant()?.tenantId === tenantId2) return fn();
|
|
140
|
+
return withTenant({ tenantId: tenantId2 }, fn);
|
|
141
|
+
}
|
|
111
142
|
};
|
|
112
143
|
__decorateClass([tenantId({ nullable: true })], Account.prototype, "tenantId", 2);
|
|
113
|
-
__decorateClass([field({
|
|
144
|
+
__decorateClass([field({
|
|
145
|
+
sensitive: true,
|
|
146
|
+
readonly: true
|
|
147
|
+
})], Account.prototype, "credentialSecretId", 2);
|
|
148
|
+
__decorateClass([field({
|
|
149
|
+
sensitive: true,
|
|
150
|
+
readonly: true
|
|
151
|
+
})], Account.prototype, "settings", 2);
|
|
114
152
|
Account = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
115
153
|
tableStrategy: "sti",
|
|
116
|
-
api: { include: [
|
|
117
|
-
"list",
|
|
118
|
-
"get",
|
|
119
|
-
"create",
|
|
120
|
-
"update",
|
|
121
|
-
"delete"
|
|
122
|
-
] },
|
|
154
|
+
api: { include: ["list", "get"] },
|
|
123
155
|
mcp: { include: ["list", "get"] },
|
|
124
|
-
cli:
|
|
156
|
+
cli: { include: ["list", "get"] }
|
|
125
157
|
})], Account);
|
|
126
158
|
//#endregion
|
|
127
159
|
//#region src/collections/AccountCollection.ts
|
|
@@ -207,4 +239,4 @@ var AccountCollection = class extends SmrtCollection {
|
|
|
207
239
|
//#endregion
|
|
208
240
|
export { AccountCollection_exports as n, Account as r, AccountCollection as t };
|
|
209
241
|
|
|
210
|
-
//# sourceMappingURL=AccountCollection-
|
|
242
|
+
//# sourceMappingURL=AccountCollection-vN5XAyOY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccountCollection-vN5XAyOY.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\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 {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n name = '';\n providerType = '';\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 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, readonly: true })\n settings = ''; // JSON\n /** Non-secret provider configuration such as a Zulip site URL. */\n configuration = '{}';\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.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,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-BGsB-xhM.js";
|
|
3
3
|
import { smrt } from "@happyvertical/smrt-core";
|
|
4
4
|
//#region src/models/Email.ts
|
|
5
5
|
var Email_exports = /* @__PURE__ */ __exportAll({ Email: () => Email });
|
|
@@ -279,4 +279,4 @@ Email = __decorateClass([smrt({
|
|
|
279
279
|
//#endregion
|
|
280
280
|
export { Email_exports as n, Email as t };
|
|
281
281
|
|
|
282
|
-
//# sourceMappingURL=Email-
|
|
282
|
+
//# sourceMappingURL=Email-DskdI-Mq.js.map
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"Email-DskdI-Mq.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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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
1
|
+
import { SmrtObject, crossPackageRef, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
2
2
|
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
3
3
|
//#region src/models/Message.ts
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -12,6 +12,9 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
12
12
|
var Message = class extends SmrtObject {
|
|
13
13
|
tenantId = null;
|
|
14
14
|
accountId = "";
|
|
15
|
+
personaId = null;
|
|
16
|
+
endpointId = null;
|
|
17
|
+
correlationId = "";
|
|
15
18
|
threadId = "";
|
|
16
19
|
subject = "";
|
|
17
20
|
body = "";
|
|
@@ -37,6 +40,9 @@ var Message = class extends SmrtObject {
|
|
|
37
40
|
super(options);
|
|
38
41
|
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
39
42
|
if (options.accountId !== void 0) this.accountId = options.accountId;
|
|
43
|
+
if (options.personaId !== void 0) this.personaId = options.personaId;
|
|
44
|
+
if (options.endpointId !== void 0) this.endpointId = options.endpointId;
|
|
45
|
+
if (options.correlationId !== void 0) this.correlationId = options.correlationId;
|
|
40
46
|
if (options.threadId !== void 0) this.threadId = options.threadId;
|
|
41
47
|
if (options.subject !== void 0) this.subject = options.subject;
|
|
42
48
|
if (options.body !== void 0) this.body = options.body;
|
|
@@ -136,22 +142,27 @@ var Message = class extends SmrtObject {
|
|
|
136
142
|
*/
|
|
137
143
|
async getAccount() {
|
|
138
144
|
if (!this.accountId) return null;
|
|
139
|
-
const { AccountCollection } = await import("./AccountCollection-
|
|
145
|
+
const { AccountCollection } = await import("./AccountCollection-vN5XAyOY.js").then((n) => n.n);
|
|
140
146
|
return await (await AccountCollection.create(this.options)).get({ id: this.accountId });
|
|
141
147
|
}
|
|
148
|
+
async getEndpoint() {
|
|
149
|
+
if (!this.endpointId) return null;
|
|
150
|
+
const { MessagingEndpointCollection } = await import("../index.js").then((n) => n.i);
|
|
151
|
+
return (await MessagingEndpointCollection.create(this.options)).get({ id: this.endpointId });
|
|
152
|
+
}
|
|
142
153
|
/**
|
|
143
154
|
* Get messages in the same thread
|
|
144
155
|
*/
|
|
145
156
|
async getThreadMessages() {
|
|
146
157
|
if (!this.threadId) return [this];
|
|
147
|
-
const { MessageCollection } = await import("./MessageCollection-
|
|
158
|
+
const { MessageCollection } = await import("./MessageCollection-BwyTAGEd.js").then((n) => n.n);
|
|
148
159
|
return await (await MessageCollection.create(this.options)).list({ where: { threadId: this.threadId } });
|
|
149
160
|
}
|
|
150
161
|
/**
|
|
151
162
|
* Get attachments for this message
|
|
152
163
|
*/
|
|
153
164
|
async getAttachments() {
|
|
154
|
-
const { AttachmentCollection } = await import("../index.js").then((n) => n.
|
|
165
|
+
const { AttachmentCollection } = await import("../index.js").then((n) => n.a);
|
|
155
166
|
return await (await AttachmentCollection.create(this.options)).list({ where: { messageId: this.id } });
|
|
156
167
|
}
|
|
157
168
|
/**
|
|
@@ -176,7 +187,33 @@ var Message = class extends SmrtObject {
|
|
|
176
187
|
await this.save();
|
|
177
188
|
return result;
|
|
178
189
|
}
|
|
179
|
-
|
|
190
|
+
if (!account.isActive) {
|
|
191
|
+
const result = {
|
|
192
|
+
success: false,
|
|
193
|
+
error: "Messaging account is inactive",
|
|
194
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
195
|
+
};
|
|
196
|
+
this.sendStatus = "failed";
|
|
197
|
+
this.sendError = result.error ?? "";
|
|
198
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
199
|
+
await this.save();
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
let sender;
|
|
203
|
+
try {
|
|
204
|
+
sender = await account.createSender();
|
|
205
|
+
} catch (error) {
|
|
206
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
207
|
+
this.sendStatus = "failed";
|
|
208
|
+
this.sendError = errorMessage;
|
|
209
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
210
|
+
await this.save();
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
error: errorMessage,
|
|
214
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
215
|
+
};
|
|
216
|
+
}
|
|
180
217
|
const claimFromStatus = this.sendStatus;
|
|
181
218
|
if (this.isPersisted && this.id) {
|
|
182
219
|
const claim = await this.db.update(this.tableName, {
|
|
@@ -320,6 +357,8 @@ ${(this.body || "").split("\n").map((line) => `> ${line}`).join("\n")}`;
|
|
|
320
357
|
};
|
|
321
358
|
__decorateClass([tenantId({ nullable: true })], Message.prototype, "tenantId", 2);
|
|
322
359
|
__decorateClass([foreignKey("Account")], Message.prototype, "accountId", 2);
|
|
360
|
+
__decorateClass([crossPackageRef("@happyvertical/smrt-personas:AgentPersona", { nullable: true })], Message.prototype, "personaId", 2);
|
|
361
|
+
__decorateClass([foreignKey("MessagingEndpoint")], Message.prototype, "endpointId", 2);
|
|
323
362
|
__decorateClass([foreignKey("Message")], Message.prototype, "inReplyToMessageId", 2);
|
|
324
363
|
Message = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
325
364
|
tableStrategy: "sti",
|
|
@@ -330,4 +369,4 @@ Message = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
|
330
369
|
//#endregion
|
|
331
370
|
export { Message as t };
|
|
332
371
|
|
|
333
|
-
//# sourceMappingURL=Message-
|
|
372
|
+
//# sourceMappingURL=Message-BGsB-xhM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Message-BGsB-xhM.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 {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\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: true,\n})\nexport class Message extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Account')\n accountId = '';\n @crossPackageRef('@happyvertical/smrt-personas:AgentPersona', {\n nullable: true,\n })\n personaId: string | null = null;\n @foreignKey('MessagingEndpoint')\n endpointId: string | null = null;\n correlationId = '';\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.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 /**\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 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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"}
|
|
@@ -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-BGsB-xhM.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-BwyTAGEd.js.map
|