@happyvertical/smrt-messages 0.60.9 → 0.60.11

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.
Files changed (44) hide show
  1. package/AGENTS.md +27 -0
  2. package/README.md +74 -0
  3. package/agents/notification-outbox.md +162 -0
  4. package/dist/chunks/{AccountCollection-CPsjabD-.js → AccountCollection-BGzfQG2I.js} +2 -2
  5. package/dist/chunks/{AccountCollection-CPsjabD-.js.map → AccountCollection-BGzfQG2I.js.map} +1 -1
  6. package/dist/chunks/{Email-C85SNWH0.js → Email-CBIrfFLm.js} +3 -3
  7. package/dist/chunks/{Email-C85SNWH0.js.map → Email-CBIrfFLm.js.map} +1 -1
  8. package/dist/chunks/{EmailFolder-Ia8OFxRn.js → EmailFolder-Cz2JU5Z1.js} +2 -2
  9. package/dist/chunks/{EmailFolder-Ia8OFxRn.js.map → EmailFolder-Cz2JU5Z1.js.map} +1 -1
  10. package/dist/chunks/{Message-x-bj-WcS.js → Message-DiO_FvAR.js} +52 -5
  11. package/dist/chunks/Message-DiO_FvAR.js.map +1 -0
  12. package/dist/chunks/{MessageCollection-BM4y2_mB.js → MessageCollection-uui10uhg.js} +2 -2
  13. package/dist/chunks/{MessageCollection-BM4y2_mB.js.map → MessageCollection-uui10uhg.js.map} +1 -1
  14. package/dist/chunks/__smrt-register__-DyTpUDMS.js +6 -0
  15. package/dist/chunks/__smrt-register__-DyTpUDMS.js.map +1 -0
  16. package/dist/collections/NotificationDeliveryCollection.d.ts +6 -0
  17. package/dist/collections/NotificationDeliveryCollection.d.ts.map +1 -0
  18. package/dist/collections/NotificationOptOutCollection.d.ts +6 -0
  19. package/dist/collections/NotificationOptOutCollection.d.ts.map +1 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +989 -38
  23. package/dist/index.js.map +1 -1
  24. package/dist/manifest.json +3870 -2495
  25. package/dist/models/Message.d.ts +15 -0
  26. package/dist/models/Message.d.ts.map +1 -1
  27. package/dist/models/NotificationDelivery.d.ts +128 -0
  28. package/dist/models/NotificationDelivery.d.ts.map +1 -0
  29. package/dist/models/NotificationOptOut.d.ts +44 -0
  30. package/dist/models/NotificationOptOut.d.ts.map +1 -0
  31. package/dist/senders/EmailSender.d.ts +9 -0
  32. package/dist/senders/EmailSender.d.ts.map +1 -1
  33. package/dist/services/NotificationOptOutService.d.ts +80 -0
  34. package/dist/services/NotificationOptOutService.d.ts.map +1 -0
  35. package/dist/services/NotificationOutboxService.d.ts +226 -0
  36. package/dist/services/NotificationOutboxService.d.ts.map +1 -0
  37. package/dist/smrt-knowledge.json +564 -9
  38. package/dist/smrt-knowledge.okf.md +157 -0
  39. package/dist/types.d.ts +6 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +17 -16
  42. package/dist/chunks/Message-x-bj-WcS.js.map +0 -1
  43. package/dist/chunks/__smrt-register__-B7iWfX8p.js +0 -6
  44. package/dist/chunks/__smrt-register__-B7iWfX8p.js.map +0 -1
package/AGENTS.md CHANGED
@@ -54,6 +54,29 @@ The in-app channel: one stored row per person, with read state.
54
54
  - Hosts decide who is told (e.g. `SocialPost.createdByUserId` for a failed
55
55
  post). New table: consumers run `db:migrate`.
56
56
 
57
+ ## Notification email outbox (`NotificationDelivery`)
58
+
59
+ `NotificationOutboxService` is the external-channel counterpart of
60
+ `UserNotificationService`: `enqueueEmail()` stores one delivery per
61
+ `(tenant, recipient, channel, sourceRef)` after the host resolves the
62
+ recipient address and same-tenant sender account (callers never choose an
63
+ address); `dispatchDue()` leases due rows by compare-and-set
64
+ (`NotificationDelivery.transition()`), creates the single outbound `Email`
65
+ under the lease, and settles sent/retry/failed/uncertain. Never resend an
66
+ `uncertain` row automatically. Contract, retry, and reconciliation policy:
67
+ [agents/notification-outbox.md](agents/notification-outbox.md). New table:
68
+ consumers run `db:migrate`.
69
+
70
+ `NotificationOptOut` (`notification_opt_outs`, tenant-required, generated
71
+ surfaces off) is the per-recipient opt-out ledger for the outbox's
72
+ `isSuppressed` hook, keyed `(tenant, recipient, channel, scope)` with scope `*`
73
+ or `project:<uuid>`-style keys. `NotificationOptOutService`: idempotent
74
+ `optOut`/`optIn` (with a `principal` only `userId === recipientUserId` may
75
+ change a row, and only in the principal's own `tenantId`, else
76
+ `NotificationOptOutDeniedError`; no principal = trusted host code), trusted `isOptedOut` (a `*` row matches every scope), `listForRecipient`
77
+ (principal required). `optOutSuppression(service, scopeOf)` builds the
78
+ `isSuppressed` function. Details in the outbox doc; consumers run `db:migrate`.
79
+
57
80
  ## Credential Security
58
81
 
59
82
  Account credentials stored via tenant-bound `credentialSecretId` →
@@ -63,6 +86,10 @@ directly. Credential descriptor schemas contain field definitions, never values.
63
86
  ## Send Lifecycle
64
87
 
65
88
  `message.send()`: resolves account → creates provider sender → updates sendStatus. Retry support with `maxRetries` budget.
89
+ A failure the provider may still have accepted carries
90
+ `MessageSendResult.outcomeUnknown` (set by `EmailSender` unless
91
+ `isDefiniteSendRejection()` proves a refusal, and when a sender throws) and is
92
+ persisted as `metadata.sendOutcomeUnknown`; never auto-resend it.
66
93
  `EmailSender` attaches the message's stored `Attachment` rows (read through
67
94
  `Attachment.readContent()`, under the message's tenant context) plus
68
95
  `SendEmailOptions.attachments` bytes; an unreadable or file-less row fails the
package/README.md CHANGED
@@ -206,6 +206,80 @@ await notifications.markAllRead(userId, { tenantIds });
206
206
  The generated REST/MCP/CLI surfaces are off; expose host routes that pass the
207
207
  signed-in user's id. The table is new: run `db:migrate`.
208
208
 
209
+ ### Notification email outbox
210
+
211
+ `NotificationOutboxService` sends one email per event and recipient from a
212
+ durable, leased outbox. The host resolves recipients and the sender account;
213
+ callers pass a user id and a host-relative link path, never an address.
214
+
215
+ ```typescript
216
+ import { NotificationOutboxService } from '@happyvertical/smrt-messages';
217
+ import '@happyvertical/smrt-messages/providers/email'; // default transport
218
+
219
+ const outbox = new NotificationOutboxService({
220
+ db,
221
+ linkBaseUrl: 'https://app.example.com',
222
+ resolveRecipient: async ({ tenantId, recipientUserId }) =>
223
+ (await isMember(tenantId, recipientUserId))
224
+ ? { address: await emailOf(recipientUserId) }
225
+ : null, // denied
226
+ resolveSenderAccountId: ({ tenantId }) => senderAccountIdFor(tenantId),
227
+ });
228
+
229
+ // Idempotent per (tenant, recipient, email, sourceRef).
230
+ await outbox.enqueueEmail({
231
+ tenantId,
232
+ recipientUserId,
233
+ sourceRef: `project-question:${question.id}:asked`,
234
+ kind: 'project-question.asked',
235
+ subject: 'A question is waiting for you',
236
+ text: question.prompt,
237
+ path: `/portal/projects/${projectId}/questions#question-${question.id}`,
238
+ });
239
+
240
+ // From a scheduled job; safe to run concurrently and after a crash.
241
+ await outbox.dispatchDue({ limit: 25 });
242
+ ```
243
+
244
+ Outcomes the provider may have seen but did not confirm become `uncertain`
245
+ and wait for `reconcile()`. See
246
+ [agents/notification-outbox.md](agents/notification-outbox.md) for the lease,
247
+ retry, and reconciliation policy. The table is new: run `db:migrate`.
248
+
249
+ #### Per-recipient opt-out
250
+
251
+ `NotificationOptOutService` is the opt-out ledger (`notification_opt_outs`)
252
+ for the outbox's `isSuppressed` hook. A scope is `*` or a host key such as
253
+ `project:<uuid>`.
254
+
255
+ ```typescript
256
+ import {
257
+ NotificationOptOutService,
258
+ optOutSuppression,
259
+ } from '@happyvertical/smrt-messages';
260
+
261
+ const optOuts = new NotificationOptOutService({ db });
262
+ // Idempotent; with a principal only the recipient may change their own row,
263
+ // in the tenant the principal is authenticated in.
264
+ await optOuts.optOut({
265
+ tenantId,
266
+ recipientUserId,
267
+ channel: 'email',
268
+ scope: `project:${projectId}`,
269
+ principal: { userId: session.userId, tenantId },
270
+ });
271
+
272
+ // Pass to the outbox: enqueue and dispatch both honour opt-outs.
273
+ const isSuppressed = optOutSuppression(optOuts, async (request) => {
274
+ // The host maps its own event to a scope; null = not opt-out-able.
275
+ // Here a question's sourceRef is resolved to its project by a host lookup.
276
+ const projectId = await projectIdForSourceRef(request.tenantId, request.sourceRef);
277
+ return projectId ? `project:${projectId}` : null;
278
+ });
279
+ ```
280
+
281
+ The table is new: run `db:migrate`.
282
+
209
283
  ### Credential Security
210
284
 
211
285
  Account credentials are stored via `credentialSecretId` pointing to smrt-secrets envelope encryption. Use `account.setCredentials()` and `account.getCredentials()` -- never store passwords as plain fields.
@@ -0,0 +1,162 @@
1
+ # Notification email outbox (#4237)
2
+
3
+ `NotificationOutboxService` turns a host event into at most one outbound email
4
+ per recipient and delivers it from a crash-safe, leased outbox. It pairs with
5
+ `UserNotificationService`: hosts that tell someone in-app and by email pass the
6
+ same `sourceRef` to both.
7
+
8
+ Source: `src/models/NotificationDelivery.ts`,
9
+ `src/services/NotificationOutboxService.ts`,
10
+ tests `src/__tests__/notification-outbox*.test.ts`.
11
+
12
+ ## Enqueue
13
+
14
+ `enqueueEmail({ tenantId, recipientUserId, sourceRef, kind, subject, text, path, notBefore? })`
15
+ stores one `NotificationDelivery` (`notification_deliveries`) per
16
+ `(tenant_id, recipient_user_id, channel, source_ref)`, the conflict identity. A
17
+ repeat (or a concurrent duplicate that loses the insert) returns the stored row
18
+ untouched with `created: false`. Encode each distinct event in `sourceRef`, e.g.
19
+ `project-question:<id>:asked` and `project-question:<id>:reminder:<n>`.
20
+
21
+ Host authorization, all checked before anything is stored:
22
+
23
+ - `resolveRecipient({ tenantId, recipientUserId, channel })` returns the address
24
+ the host allows for that user in that tenant, or `null` (denied with
25
+ `NotificationOutboxDeniedError`, reason `recipient`). Callers never pass an
26
+ address; the resolved one is snapshotted on the row.
27
+ - `resolveSenderAccountId({ tenantId })` names an active `EmailAccount` owned by
28
+ the same tenant (reason `sender` otherwise). Configuring that account is
29
+ deployment setup; the outbox has no mail transport of its own.
30
+ - With `principal`, enqueue, `listUncertain`, and `reconcile` require
31
+ `messages.send` in the tenant (reason `permission`). Omit it only for trusted
32
+ system code that authorizes itself.
33
+ - `isSuppressed(...)` (optional) is the opt-out hook. A suppressed enqueue stores
34
+ a `cancelled` row (so the event stays deduplicated); suppression at dispatch
35
+ cancels before handoff. The package's own preference store is the
36
+ [opt-out ledger](#opt-out-ledger); wire it with `optOutSuppression`.
37
+
38
+ Deep links: `path` must be host-relative (`/portal/...`); it is joined to
39
+ `linkBaseUrl` (absolute http(s), no credentials/query/fragment, validated at
40
+ construction) and must stay on that origin — `//host`, backslashes, whitespace,
41
+ and control characters are refused (reason `link`). The link is stored on the
42
+ row, so a duplicate enqueue keeps the first link. Links carry no tokens: they
43
+ lead to the host's normal authenticated route. Subjects must be one line.
44
+
45
+ ## Dispatch
46
+
47
+ `dispatchDue({ tenantIds?, limit? })` (default 25, max 200) leases due rows —
48
+ `pending` with `nextAttemptAt <= now`, and `sending` rows whose lease lapsed —
49
+ for the given tenants, or every tenant (discovered under `withSystemContext`)
50
+ when `tenantIds` is omitted. Each row is processed under `withTenant(row.tenantId)`.
51
+ Run it from a scheduled job; several workers may run it concurrently.
52
+
53
+ Every state change is `NotificationDelivery.transition()`: a compare-and-set on
54
+ `stateVersion` (and the lease token for a lease holder) that writes a unique
55
+ `transitionId` and re-reads the row to prove the write won. PostgreSQL and
56
+ remote LibSQL use one conditional `UPDATE`; local embedded engines use the
57
+ process write queue, like `Message.send()`'s claim.
58
+
59
+ Per row:
60
+
61
+ 1. Acquire: `status = sending`, new `leaseToken`, `leaseExpiresAt = now + leaseMs`
62
+ (default 5 min; must exceed the slowest provider call), `attempts + 1`.
63
+ 2. Create the single outbound `Email` and record `messageId`. Its id is
64
+ `notificationDeliveryMessageId(deliveryId)` (a name-based uuid) and it is
65
+ inserted insert-only, so a crashed or stale dispatcher finds or collides
66
+ with the stored row instead of creating a second email; its correlation id
67
+ is `notification-delivery:<deliveryId>`.
68
+ 3. Record `handedOffAt`, then call `transport.send({ delivery, message, idempotencyKey })`.
69
+ 4. Settle with the lease token: `accepted` → `sent`; `rejected` + `retryable`
70
+ → `pending` after `retryDelayMs(attempts)` (default 1 min · 2ⁿ⁻¹, capped at
71
+ 1 h) until `maxAttempts` (default 5), then `failed`; non-retryable → `failed`;
72
+ `unknown` (including a thrown send) → `uncertain`. A holder whose lease was
73
+ taken over gets `lease-lost` and writes nothing.
74
+
75
+ ## Provider idempotency and reconciliation policy
76
+
77
+ Delivery is at-most-once once a provider may have seen the message.
78
+
79
+ - A lapsed lease with no `handedOffAt` never reached the provider: it is resent.
80
+ - Ambiguous provider failures are not rejections. `EmailSender` sets
81
+ `MessageSendResult.outcomeUnknown` unless `isDefiniteSendRejection()` proves
82
+ the send was refused (an SMTP reply code, an SDK auth/validation error, a
83
+ send error that accepted no recipient, or a connection that was never made);
84
+ `Message.send()` sets it when a sender throws. `Message` persists the marker
85
+ in `metadata.sendOutcomeUnknown` with the `failed` status
86
+ (`Message.isSendOutcomeUnknown()`), and `messageSendTransport` maps it to
87
+ `unknown`, so the delivery becomes `uncertain` instead of being retried.
88
+ - A lapsed lease after `handedOffAt` asks `transport.reconcile()`. The default
89
+ `messageSendTransport` reads the canonical `Message.sendStatus`: `sent` →
90
+ delivery `sent` (no resend), `failed` → normal retry (or `uncertain` when the
91
+ failure carries the outcome-unknown marker), `draft`/`pending` → resend,
92
+ anything else (e.g. still `sending`) → `uncertain`.
93
+ - `uncertain` rows are never resent automatically. Operators list them with
94
+ `listUncertain({ tenantId })` and settle them with
95
+ `reconcile({ tenantId, deliveryId, outcome: 'sent' | 'failed' | 'retry', note? })`
96
+ after checking the provider; `retry` accepts the risk of a duplicate. It
97
+ first releases a message a dead sender left `sending`
98
+ (`Message.abandonSendAttempt()`, a compare-and-set to `failed`) so the next
99
+ lease can send it, and refuses when the message is recorded as `sent`.
100
+ - A transport with `idempotent: true` declares that its provider deduplicates on
101
+ `idempotencyKey` (stable per delivery); unknown outcomes are then retried
102
+ instead of parked. SMTP and the current SDK email providers take no key, so
103
+ `messageSendTransport` is not idempotent.
104
+
105
+ ## Transport seam
106
+
107
+ `NotificationTransport { name; idempotent?; send(request); reconcile?(request) }`.
108
+ The default delivers through `Message.send()` → account → registered provider
109
+ sender (import `@happyvertical/smrt-messages/providers/email` at server
110
+ startup). Tests inject a fake; nothing in the outbox reaches a provider SDK.
111
+
112
+ ## Opt-out ledger
113
+
114
+ `NotificationOptOut` (`notification_opt_outs`, tenant-required, generated
115
+ REST/MCP/CLI off) records one row per `(tenant_id, recipient_user_id, channel,
116
+ scope)`: `channel` is `email`; `scope` is `*` (all notification email in the
117
+ tenant) or a host key such as `project:<uuid>` (lowercase prefix, `:`, then
118
+ `[A-Za-z0-9._:-]{1,200}`; malformed scopes throw). `optedOutAt` is set while
119
+ opted out and cleared by an opt-in (the row stays as history); `updatedBy` is
120
+ the person's user id, or `system` for host code. It exists because
121
+ `smrt-preferences` is owner-read only, while suppression runs where the recipient
122
+ is not the caller (a manager enqueues, a worker dispatches).
123
+
124
+ `NotificationOptOutService({ db })`:
125
+
126
+ - `optOut({ tenantId, recipientUserId, channel, scope, principal? })` and
127
+ `optIn(...)` are idempotent. With `principal: { userId, tenantId }` only
128
+ `principal.userId === recipientUserId` may change the row, and only when
129
+ `principal.tenantId` equals the request's tenant: a principal never acts in
130
+ another tenant (`NotificationOptOutDeniedError`, reason `principal` or
131
+ `tenant`). Without a principal the call is trusted host code that
132
+ authenticated the recipient itself (e.g. a signed unsubscribe link, or a host
133
+ route whose member belongs to a child tenant of the delivery tenant).
134
+ - `isOptedOut({ tenantId, recipientUserId, channel, scope })` is the trusted
135
+ suppression read, under `withTenant`; a `*` row matches every scope.
136
+ - `listForRecipient({ tenantId, recipientUserId, principal })` returns only the
137
+ recipient's own active opt-outs; any other principal is denied.
138
+
139
+ ```ts
140
+ const optOuts = new NotificationOptOutService({ db });
141
+ new NotificationOutboxService({
142
+ // ...
143
+ // `scopeOf` maps the host's own event to a scope; null = not opt-out-able.
144
+ // A question's sourceRef names the question, not its project, so the host
145
+ // resolves the project itself (scopeOf may be async).
146
+ isSuppressed: optOutSuppression(optOuts, async (request) => {
147
+ const projectId = await projectIdForSourceRef(request.tenantId, request.sourceRef);
148
+ return projectId ? `project:${projectId}` : null;
149
+ }),
150
+ });
151
+ ```
152
+
153
+ Because the outbox re-checks at dispatch, an opt-out recorded after enqueue
154
+ cancels the pending delivery before handoff. Tests:
155
+ `src/__tests__/notification-opt-out*.test.ts`.
156
+
157
+ ## Schema
158
+
159
+ New tables `notification_opt_outs` (same rules) and `notification_deliveries` (tenant-required, generated REST/MCP/CLI
160
+ off): consumers run `db:migrate`. `recipient_user_id` is a cross-package uuid
161
+ ref to `smrt-users:User`; `account_id`/`message_id` are FKs to `accounts` and
162
+ `messages`.
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import "./__smrt-register__-B7iWfX8p.js";
2
+ import "./__smrt-register__-DyTpUDMS.js";
3
3
  import { SmrtCollection, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
4
4
  import { TenantScoped, getCurrentTenant, queryGlobal, queryWithGlobals, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
5
5
  //#region src/models/Account.ts
@@ -255,4 +255,4 @@ var AccountCollection = class extends SmrtCollection {
255
255
  //#endregion
256
256
  export { AccountCollection_exports as n, Account as r, AccountCollection as t };
257
257
 
258
- //# sourceMappingURL=AccountCollection-CPsjabD-.js.map
258
+ //# sourceMappingURL=AccountCollection-BGzfQG2I.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AccountCollection-CPsjabD-.js","names":["tenantId"],"sources":["../../src/models/Account.ts","../../src/collections/AccountCollection.ts"],"sourcesContent":["/**\n * Account model - Base class for all messaging account types (STI)\n *\n * Common fields shared across email, Slack, Twitter accounts, etc.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { AccountOptions, MessageSenderInterface } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class Account extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({\n description: 'The display name for this connected messaging account.',\n })\n name = '';\n @field({\n description: 'The service that connects this account to your application.',\n })\n providerType = '';\n @field({\n description:\n 'The kind of messaging this account supports, such as email or chat.',\n })\n channelType = '';\n // credentialSecretId stores the Secret's NAME (keyed by name + tenant\n // context in smrt-secrets), NOT its primary-key id — see setCredentials()/\n // getCredentials() which call tenant-bound SecretService operations by name.\n // So it is deliberately NOT a @crossPackageRef id FK.\n @field({ sensitive: true, readonly: true })\n credentialSecretId: string | null = null;\n @field({ description: 'Whether this account is enabled for messaging.' })\n isActive = true;\n @field({ description: 'When this account last checked for new messages.' })\n lastSyncAt: Date | null = null;\n // JSON. Sensitive (#1540): may hold a plaintext credential fallback, so it is\n // excluded from generated API/MCP responses and rejected as a `where` filter\n // key.\n @field({ sensitive: true, readonly: true })\n settings = ''; // JSON\n /** Non-secret provider configuration such as a Zulip site URL. */\n @field({\n description:\n 'The public connection settings for this account; credentials are stored separately.',\n })\n configuration = '{}';\n\n // Timestamps\n @field({ description: 'When this account was added.' })\n createdAt = new Date();\n @field({ description: 'When this account was last changed.' })\n updatedAt = new Date();\n\n constructor(options: AccountOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.providerType !== undefined)\n this.providerType = options.providerType;\n if (options.channelType !== undefined)\n this.channelType = options.channelType;\n if (options.credentialSecretId !== undefined)\n this.credentialSecretId = options.credentialSecretId || null;\n if (options.isActive !== undefined) this.isActive = options.isActive;\n if (options.lastSyncAt !== undefined)\n this.lastSyncAt = options.lastSyncAt || null;\n if (options.settings !== undefined) this.settings = options.settings;\n if (options.configuration !== undefined)\n this.configuration = options.configuration;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n /**\n * Get settings as parsed object\n */\n getSettings(): Record<string, unknown> {\n if (!this.settings) return {};\n try {\n return JSON.parse(this.settings);\n } catch {\n return {};\n }\n }\n\n /**\n * Set settings from object\n */\n setSettings(settings: Record<string, unknown>): void {\n this.settings = JSON.stringify(settings);\n }\n\n getConfiguration(): Record<string, unknown> {\n try {\n return JSON.parse(this.configuration || '{}');\n } catch {\n return {};\n }\n }\n\n setConfiguration(configuration: Record<string, unknown>): void {\n this.configuration = JSON.stringify(configuration);\n }\n\n get hasCredentials(): boolean {\n return Boolean(this.credentialSecretId || this.settings);\n }\n\n /**\n * Activate account\n */\n async activate(): Promise<void> {\n this.isActive = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Deactivate account\n */\n async deactivate(): Promise<void> {\n this.isActive = false;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Store credentials securely using smrt-secrets\n */\n async setCredentials(\n credentials: Record<string, unknown>,\n options: {\n description?: string;\n category?: string;\n } = {},\n ): Promise<void> {\n if (!this.id) {\n throw new Error('Account must be persisted before storing credentials.');\n }\n const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n const secretName = `${this.channelType || 'messaging'}-account-${this.id}`;\n const secretValue = JSON.stringify(credentials);\n const tenantId = this.requireCredentialTenantId('store credentials');\n\n await secretService.storeForTenant(\n tenantId,\n this.credentialSecretId || secretName,\n secretValue,\n {\n description: options.description || `Credentials for ${this.name}`,\n category: options.category || 'messaging',\n },\n );\n\n this.credentialSecretId ||= secretName;\n this.updatedAt = new Date();\n await this.withCredentialTenantContext(() => this.save());\n }\n\n /**\n * Create a sender for this account.\n * Subclasses must override to return a concrete sender.\n */\n async createSender(): Promise<MessageSenderInterface> {\n const { getMessagingProvider } = await import('../providers.js');\n const provider = getMessagingProvider(this.providerType);\n if (!provider?.createSender) {\n throw new Error(\n `createSender() not implemented for account type '${this.providerType}'`,\n );\n }\n return provider.createSender(this);\n }\n\n /**\n * Retrieve stored credentials\n */\n async getCredentials(): Promise<Record<string, unknown> | null> {\n if (!this.credentialSecretId) {\n return this.getSettings();\n }\n\n const tenantId = this.requireCredentialTenantId('retrieve credentials');\n const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n try {\n const secret = await secretService.retrieveForTenant(\n tenantId,\n this.credentialSecretId,\n );\n return JSON.parse(secret.value);\n } catch {\n return null;\n }\n }\n\n private getCredentialTenantId(): string | null {\n return this.tenantId || getCurrentTenant()?.tenantId || null;\n }\n\n private requireCredentialTenantId(action: string): string {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId) {\n throw new Error(`Tenant context required to ${action}.`);\n }\n return tenantId;\n }\n\n private async withCredentialTenantContext<T>(\n fn: () => Promise<T>,\n ): Promise<T> {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId || getCurrentTenant()?.tenantId === tenantId) return fn();\n return withTenant({ tenantId }, fn);\n }\n}\n","/**\n * AccountCollection - Unified collection for polymorphic account queries\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Account } from '../models/Account';\nimport type { AccountSearchFilters } from '../types';\n\nexport class AccountCollection extends SmrtCollection<Account> {\n static readonly _itemClass = Account;\n\n /**\n * Get active accounts\n */\n async getActive(): Promise<Account[]> {\n return await this.list({ where: { isActive: true } });\n }\n\n /**\n * Get inactive accounts\n */\n async getInactive(): Promise<Account[]> {\n return await this.list({ where: { isActive: false } });\n }\n\n /**\n * Get accounts by provider type\n */\n async getByProviderType(providerType: string): Promise<Account[]> {\n return await this.list({ where: { providerType } });\n }\n\n /**\n * Get accounts by STI type.\n *\n * Accepts either the full discriminator (e.g. \"@happyvertical/smrt-messages:EmailAccount\")\n * or the short type name (e.g. \"EmailAccount\").\n */\n async getByType(accountType: string): Promise<Account[]> {\n if (accountType.includes(':') || accountType.startsWith('@')) {\n return await this.list({ where: { _meta_type: accountType } });\n }\n const allAccounts = await this.list({});\n return allAccounts.filter((a) => {\n const metaType = (a as { _meta_type?: string })._meta_type || '';\n return metaType.endsWith(`:${accountType}`);\n });\n }\n\n /**\n * Search accounts with filters\n */\n async search(\n query: string,\n filters?: AccountSearchFilters,\n ): Promise<Account[]> {\n let accounts = await this.list({});\n\n if (query) {\n const lowerQuery = query.toLowerCase();\n accounts = accounts.filter((a) =>\n a.name?.toLowerCase().includes(lowerQuery),\n );\n }\n\n if (filters) {\n if (filters.providerType) {\n accounts = accounts.filter(\n (a) => a.providerType === filters.providerType,\n );\n }\n if (filters.isActive !== undefined) {\n accounts = accounts.filter((a) => a.isActive === filters.isActive);\n }\n if (filters.accountType) {\n accounts = accounts.filter((a) => {\n const metaType = (a as { _meta_type?: string })._meta_type || '';\n return metaType.includes(filters.accountType as string);\n });\n }\n }\n\n return accounts;\n }\n\n /**\n * Get account statistics\n */\n async getStats(): Promise<{\n total: number;\n active: number;\n inactive: number;\n byType: Record<string, number>;\n }> {\n const accounts = await this.list({});\n const byType: Record<string, number> = {};\n\n for (const account of accounts) {\n const metaType =\n (account as { _meta_type?: string })._meta_type || 'Unknown';\n const shortType = metaType.split(':').pop() || metaType;\n byType[shortType] = (byType[shortType] || 0) + 1;\n }\n\n return {\n total: accounts.length,\n active: accounts.filter((a) => a.isActive).length,\n inactive: accounts.filter((a) => !a.isActive).length,\n byType,\n };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Tenant Helper Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n async findByTenant(tenantId: string): Promise<Account[]> {\n return this.list({ where: { tenantId } });\n }\n\n // Account is the @TenantScoped STI base — see MessageCollection. Route through\n // the raw helpers; no `_meta_type` so the base returns ALL account subtypes.\n async findGlobal(): Promise<Account[]> {\n return queryGlobal<Account>(this);\n }\n\n async findWithGlobals(tenantId: string): Promise<Account[]> {\n return queryWithGlobals<Account>(this, tenantId, 'Account.findWithGlobals');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AccountCollection-BGzfQG2I.js","names":["tenantId"],"sources":["../../src/models/Account.ts","../../src/collections/AccountCollection.ts"],"sourcesContent":["/**\n * Account model - Base class for all messaging account types (STI)\n *\n * Common fields shared across email, Slack, Twitter accounts, etc.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { AccountOptions, MessageSenderInterface } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class Account extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({\n description: 'The display name for this connected messaging account.',\n })\n name = '';\n @field({\n description: 'The service that connects this account to your application.',\n })\n providerType = '';\n @field({\n description:\n 'The kind of messaging this account supports, such as email or chat.',\n })\n channelType = '';\n // credentialSecretId stores the Secret's NAME (keyed by name + tenant\n // context in smrt-secrets), NOT its primary-key id — see setCredentials()/\n // getCredentials() which call tenant-bound SecretService operations by name.\n // So it is deliberately NOT a @crossPackageRef id FK.\n @field({ sensitive: true, readonly: true })\n credentialSecretId: string | null = null;\n @field({ description: 'Whether this account is enabled for messaging.' })\n isActive = true;\n @field({ description: 'When this account last checked for new messages.' })\n lastSyncAt: Date | null = null;\n // JSON. Sensitive (#1540): may hold a plaintext credential fallback, so it is\n // excluded from generated API/MCP responses and rejected as a `where` filter\n // key.\n @field({ sensitive: true, readonly: true })\n settings = ''; // JSON\n /** Non-secret provider configuration such as a Zulip site URL. */\n @field({\n description:\n 'The public connection settings for this account; credentials are stored separately.',\n })\n configuration = '{}';\n\n // Timestamps\n @field({ description: 'When this account was added.' })\n createdAt = new Date();\n @field({ description: 'When this account was last changed.' })\n updatedAt = new Date();\n\n constructor(options: AccountOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.providerType !== undefined)\n this.providerType = options.providerType;\n if (options.channelType !== undefined)\n this.channelType = options.channelType;\n if (options.credentialSecretId !== undefined)\n this.credentialSecretId = options.credentialSecretId || null;\n if (options.isActive !== undefined) this.isActive = options.isActive;\n if (options.lastSyncAt !== undefined)\n this.lastSyncAt = options.lastSyncAt || null;\n if (options.settings !== undefined) this.settings = options.settings;\n if (options.configuration !== undefined)\n this.configuration = options.configuration;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n /**\n * Get settings as parsed object\n */\n getSettings(): Record<string, unknown> {\n if (!this.settings) return {};\n try {\n return JSON.parse(this.settings);\n } catch {\n return {};\n }\n }\n\n /**\n * Set settings from object\n */\n setSettings(settings: Record<string, unknown>): void {\n this.settings = JSON.stringify(settings);\n }\n\n getConfiguration(): Record<string, unknown> {\n try {\n return JSON.parse(this.configuration || '{}');\n } catch {\n return {};\n }\n }\n\n setConfiguration(configuration: Record<string, unknown>): void {\n this.configuration = JSON.stringify(configuration);\n }\n\n get hasCredentials(): boolean {\n return Boolean(this.credentialSecretId || this.settings);\n }\n\n /**\n * Activate account\n */\n async activate(): Promise<void> {\n this.isActive = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Deactivate account\n */\n async deactivate(): Promise<void> {\n this.isActive = false;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Store credentials securely using smrt-secrets\n */\n async setCredentials(\n credentials: Record<string, unknown>,\n options: {\n description?: string;\n category?: string;\n } = {},\n ): Promise<void> {\n if (!this.id) {\n throw new Error('Account must be persisted before storing credentials.');\n }\n const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n const secretName = `${this.channelType || 'messaging'}-account-${this.id}`;\n const secretValue = JSON.stringify(credentials);\n const tenantId = this.requireCredentialTenantId('store credentials');\n\n await secretService.storeForTenant(\n tenantId,\n this.credentialSecretId || secretName,\n secretValue,\n {\n description: options.description || `Credentials for ${this.name}`,\n category: options.category || 'messaging',\n },\n );\n\n this.credentialSecretId ||= secretName;\n this.updatedAt = new Date();\n await this.withCredentialTenantContext(() => this.save());\n }\n\n /**\n * Create a sender for this account.\n * Subclasses must override to return a concrete sender.\n */\n async createSender(): Promise<MessageSenderInterface> {\n const { getMessagingProvider } = await import('../providers.js');\n const provider = getMessagingProvider(this.providerType);\n if (!provider?.createSender) {\n throw new Error(\n `createSender() not implemented for account type '${this.providerType}'`,\n );\n }\n return provider.createSender(this);\n }\n\n /**\n * Retrieve stored credentials\n */\n async getCredentials(): Promise<Record<string, unknown> | null> {\n if (!this.credentialSecretId) {\n return this.getSettings();\n }\n\n const tenantId = this.requireCredentialTenantId('retrieve credentials');\n const { SecretService } = await import('@happyvertical/smrt-secrets');\n const secretService = await SecretService.create({ db: this.db });\n\n try {\n const secret = await secretService.retrieveForTenant(\n tenantId,\n this.credentialSecretId,\n );\n return JSON.parse(secret.value);\n } catch {\n return null;\n }\n }\n\n private getCredentialTenantId(): string | null {\n return this.tenantId || getCurrentTenant()?.tenantId || null;\n }\n\n private requireCredentialTenantId(action: string): string {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId) {\n throw new Error(`Tenant context required to ${action}.`);\n }\n return tenantId;\n }\n\n private async withCredentialTenantContext<T>(\n fn: () => Promise<T>,\n ): Promise<T> {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId || getCurrentTenant()?.tenantId === tenantId) return fn();\n return withTenant({ tenantId }, fn);\n }\n}\n","/**\n * AccountCollection - Unified collection for polymorphic account queries\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Account } from '../models/Account';\nimport type { AccountSearchFilters } from '../types';\n\nexport class AccountCollection extends SmrtCollection<Account> {\n static readonly _itemClass = Account;\n\n /**\n * Get active accounts\n */\n async getActive(): Promise<Account[]> {\n return await this.list({ where: { isActive: true } });\n }\n\n /**\n * Get inactive accounts\n */\n async getInactive(): Promise<Account[]> {\n return await this.list({ where: { isActive: false } });\n }\n\n /**\n * Get accounts by provider type\n */\n async getByProviderType(providerType: string): Promise<Account[]> {\n return await this.list({ where: { providerType } });\n }\n\n /**\n * Get accounts by STI type.\n *\n * Accepts either the full discriminator (e.g. \"@happyvertical/smrt-messages:EmailAccount\")\n * or the short type name (e.g. \"EmailAccount\").\n */\n async getByType(accountType: string): Promise<Account[]> {\n if (accountType.includes(':') || accountType.startsWith('@')) {\n return await this.list({ where: { _meta_type: accountType } });\n }\n const allAccounts = await this.list({});\n return allAccounts.filter((a) => {\n const metaType = (a as { _meta_type?: string })._meta_type || '';\n return metaType.endsWith(`:${accountType}`);\n });\n }\n\n /**\n * Search accounts with filters\n */\n async search(\n query: string,\n filters?: AccountSearchFilters,\n ): Promise<Account[]> {\n let accounts = await this.list({});\n\n if (query) {\n const lowerQuery = query.toLowerCase();\n accounts = accounts.filter((a) =>\n a.name?.toLowerCase().includes(lowerQuery),\n );\n }\n\n if (filters) {\n if (filters.providerType) {\n accounts = accounts.filter(\n (a) => a.providerType === filters.providerType,\n );\n }\n if (filters.isActive !== undefined) {\n accounts = accounts.filter((a) => a.isActive === filters.isActive);\n }\n if (filters.accountType) {\n accounts = accounts.filter((a) => {\n const metaType = (a as { _meta_type?: string })._meta_type || '';\n return metaType.includes(filters.accountType as string);\n });\n }\n }\n\n return accounts;\n }\n\n /**\n * Get account statistics\n */\n async getStats(): Promise<{\n total: number;\n active: number;\n inactive: number;\n byType: Record<string, number>;\n }> {\n const accounts = await this.list({});\n const byType: Record<string, number> = {};\n\n for (const account of accounts) {\n const metaType =\n (account as { _meta_type?: string })._meta_type || 'Unknown';\n const shortType = metaType.split(':').pop() || metaType;\n byType[shortType] = (byType[shortType] || 0) + 1;\n }\n\n return {\n total: accounts.length,\n active: accounts.filter((a) => a.isActive).length,\n inactive: accounts.filter((a) => !a.isActive).length,\n byType,\n };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Tenant Helper Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n async findByTenant(tenantId: string): Promise<Account[]> {\n return this.list({ where: { tenantId } });\n }\n\n // Account is the @TenantScoped STI base — see MessageCollection. Route through\n // the raw helpers; no `_meta_type` so the base returns ALL account subtypes.\n async findGlobal(): Promise<Account[]> {\n return queryGlobal<Account>(this);\n }\n\n async findWithGlobals(tenantId: string): Promise<Account[]> {\n return queryWithGlobals<Account>(this, tenantId, 'Account.findWithGlobals');\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import "./__smrt-register__-B7iWfX8p.js";
3
- import { t as Message } from "./Message-x-bj-WcS.js";
2
+ import "./__smrt-register__-DyTpUDMS.js";
3
+ import { t as Message } from "./Message-DiO_FvAR.js";
4
4
  import { field, smrt } from "@happyvertical/smrt-core";
5
5
  //#region src/models/Email.ts
6
6
  var Email_exports = /* @__PURE__ */ __exportAll({ Email: () => Email });
@@ -334,4 +334,4 @@ Email = __decorateClass([smrt({
334
334
  //#endregion
335
335
  export { Email_exports as n, Email as t };
336
336
 
337
- //# sourceMappingURL=Email-C85SNWH0.js.map
337
+ //# sourceMappingURL=Email-CBIrfFLm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Email-C85SNWH0.js","names":[],"sources":["../../src/models/Email.ts"],"sourcesContent":["/**\n * Email model - Email message extending the Message STI base\n *\n * Retains all email-specific fields (RFC 822 compliance, folders, etc.)\n * while inheriting common message fields from Message.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { field, smrt } from '@happyvertical/smrt-core';\nimport type {\n EmailOptions,\n MessageSendResult,\n SendEmailOptions,\n} from '../types';\nimport { Message } from './Message';\n\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n // Message is read-only over REST; this provider type is writable there.\n widenParentExposure: { api: ['create', 'update', 'delete'] },\n})\nexport class Email extends Message { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n // RFC 822 fields\n @field({\n description:\n 'The email reference assigned by the sender for tracking replies.',\n })\n messageId = ''; // RFC 822 Message-ID header\n @field({\n description: 'The sender-assigned reference of the email being replied to.',\n })\n inReplyTo = '';\n\n // Additional recipients\n @field({ description: 'Recipients receiving a visible copy of this email.' })\n ccAddresses = '';\n @field({\n description:\n 'Recipients receiving a copy without appearing in the visible recipient list.',\n })\n bccAddresses = '';\n @field({ description: 'The address replies should be sent to.' })\n replyToAddress = '';\n @field({ description: 'The name associated with the reply address.' })\n replyToName = '';\n\n // Email-specific content\n @field({ description: 'The plain-text version of the email.' })\n textBody = '';\n @field({ description: 'The formatted version of the email.' })\n htmlBody = '';\n\n // Location\n @field({\n description:\n 'The mailbox folder reference supplied by the email connection.',\n })\n folderId = '';\n @field({\n description: 'The location of this email within the mailbox folders.',\n })\n folderPath = '';\n @field({\n description: 'Labels applied to this email by the mailbox provider.',\n })\n labels = ''; // JSON array (Gmail)\n @field({ description: 'Mailbox markers such as read, answered, or flagged.' })\n flags = ''; // JSON array (IMAP)\n\n // Email-specific status flags\n @field({ description: 'Whether this email has been replied to.' })\n isAnswered = false;\n @field({ description: 'Whether this email is saved as an unfinished draft.' })\n isDraft = false;\n\n // Raw data\n @field({\n description: 'The original email content as received from the mail server.',\n })\n rawMessage = '';\n @field({\n description: 'Delivery and routing details included with the email.',\n })\n headers = ''; // JSON object\n\n constructor(options: EmailOptions = {}) {\n super(options);\n\n if (options.messageId !== undefined) this.messageId = options.messageId;\n if (options.inReplyTo !== undefined) this.inReplyTo = options.inReplyTo;\n if (options.ccAddresses !== undefined)\n this.ccAddresses = options.ccAddresses;\n if (options.bccAddresses !== undefined)\n this.bccAddresses = options.bccAddresses;\n if (options.replyToAddress !== undefined)\n this.replyToAddress = options.replyToAddress;\n if (options.replyToName !== undefined)\n this.replyToName = options.replyToName;\n if (options.textBody !== undefined) this.textBody = options.textBody;\n if (options.htmlBody !== undefined) this.htmlBody = options.htmlBody;\n if (options.folderId !== undefined) this.folderId = options.folderId;\n if (options.folderPath !== undefined) this.folderPath = options.folderPath;\n if (options.labels !== undefined) this.labels = options.labels;\n if (options.flags !== undefined) this.flags = options.flags;\n if (options.isAnswered !== undefined) this.isAnswered = options.isAnswered;\n if (options.isDraft !== undefined) this.isDraft = options.isDraft;\n if (options.rawMessage !== undefined) this.rawMessage = options.rawMessage;\n if (options.headers !== undefined) this.headers = options.headers;\n\n // Populate base body from textBody for unified access\n if (options.textBody && !options.body) {\n this.body = options.textBody;\n }\n }\n\n /**\n * Get CC addresses as parsed array\n */\n getCcAddresses(): Array<{ address: string; name?: string }> {\n if (!this.ccAddresses) return [];\n try {\n return JSON.parse(this.ccAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Get BCC addresses as parsed array\n */\n getBccAddresses(): Array<{ address: string; name?: string }> {\n if (!this.bccAddresses) return [];\n try {\n return JSON.parse(this.bccAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Get labels as parsed array\n */\n getLabels(): string[] {\n if (!this.labels) return [];\n try {\n return JSON.parse(this.labels);\n } catch {\n return [];\n }\n }\n\n /**\n * Set labels from array\n */\n setLabels(labels: string[]): void {\n this.labels = JSON.stringify(labels);\n }\n\n /**\n * Get flags as parsed array\n */\n getFlags(): string[] {\n if (!this.flags) return [];\n try {\n return JSON.parse(this.flags);\n } catch {\n return [];\n }\n }\n\n /**\n * Set flags from array\n */\n setFlags(flags: string[]): void {\n this.flags = JSON.stringify(flags);\n }\n\n /**\n * Get headers as parsed object\n */\n getHeaders(): Record<string, string | string[]> {\n if (!this.headers) return {};\n try {\n return JSON.parse(this.headers);\n } catch {\n return {};\n }\n }\n\n /**\n * Set headers from object\n */\n setHeaders(headers: Record<string, string | string[]>): void {\n this.headers = JSON.stringify(headers);\n }\n\n /**\n * Get the email account (typed as EmailAccount)\n */\n override async getAccount() {\n if (!this.accountId) return null;\n\n const { EmailAccountCollection } = await import(\n '../collections/EmailAccountCollection'\n );\n const collection = await EmailAccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n /**\n * Get the folder\n */\n async getFolder() {\n if (!this.folderId) return null;\n\n const { EmailFolderCollection } = await import(\n '../collections/EmailFolderCollection'\n );\n const collection = await EmailFolderCollection.create(this.options);\n\n return await collection.get({ id: this.folderId });\n }\n\n /**\n * Get emails in the same thread\n */\n async getThreadEmails(): Promise<Email[]> {\n if (!this.threadId) return [this];\n\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n return await collection.getByThread(this.threadId);\n }\n\n /**\n * Get a short preview of the email body\n */\n override getPreview(maxLength = 200): string {\n const body = this.textBody || this.htmlBody?.replace(/<[^>]*>/g, '') || '';\n if (body.length <= maxLength) return body;\n return `${body.slice(0, maxLength)}...`;\n }\n\n /**\n * Get References header values as array\n */\n getReferences(): string[] {\n const headers = this.getHeaders();\n const refs = headers.references;\n if (!refs) return [];\n if (Array.isArray(refs)) return refs;\n return refs.split(/\\s+/).filter(Boolean);\n }\n\n /**\n * Create a reply to this email with RFC 822 threading\n */\n override createReply(options?: { replyAll?: boolean }): Email {\n const reply = new Email({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: this.threadId || this.id || undefined,\n subject: this.subject.startsWith('Re:')\n ? this.subject\n : `Re: ${this.subject}`,\n fromAddress: '',\n fromName: '',\n inReplyToMessageId: this.id || undefined,\n sendStatus: 'draft',\n isRead: true,\n isDraft: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n\n // RFC 822 threading\n inReplyTo: this.messageId,\n\n // To: original sender\n toAddresses: JSON.stringify([\n { address: this.fromAddress, name: this.fromName },\n ]),\n });\n\n // RFC 822 References: original references + original Message-ID\n const refs = [...this.getReferences()];\n if (this.messageId && !refs.includes(this.messageId)) {\n refs.push(this.messageId);\n }\n reply.setHeaders({ references: refs.join(' ') });\n\n // Reply-All: add original To/CC as CC (excluding self)\n if (options?.replyAll) {\n const allRecipients = [\n ...this.getToAddresses(),\n ...this.getCcAddresses(),\n ];\n // Remove original sender (already in To:) and any duplicates\n const seen = new Set([this.fromAddress.toLowerCase()]);\n const ccAddresses = allRecipients.filter((r) => {\n const addr = r.address.toLowerCase();\n if (seen.has(addr)) return false;\n seen.add(addr);\n return true;\n });\n reply.ccAddresses = JSON.stringify(ccAddresses);\n }\n\n reply.body = this.buildQuotedBody();\n reply.textBody = reply.body;\n\n return reply;\n }\n\n /**\n * Create a forward of this email\n */\n override createForward(): Email {\n const forwardBody = this.buildForwardBody();\n\n const forward = new Email({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: '',\n subject: this.subject.startsWith('Fwd:')\n ? this.subject\n : `Fwd: ${this.subject}`,\n toAddresses: '[]',\n ccAddresses: '[]',\n bccAddresses: '[]',\n fromAddress: '',\n fromName: '',\n body: forwardBody,\n textBody: forwardBody,\n hasAttachments: this.hasAttachments,\n inReplyToMessageId: '',\n inReplyTo: '',\n sendStatus: 'draft',\n isDraft: true,\n isRead: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n });\n\n return forward;\n }\n\n /**\n * Build email-specific quoted body for replies\n */\n protected override buildQuotedBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const bodyText = this.textBody || this.body || '';\n const quotedLines = bodyText\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n');\n\n return `\\n\\nOn ${dateStr}, ${from} wrote:\\n${quotedLines}`;\n }\n\n /**\n * Build forwarded message body\n */\n private buildForwardBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const toStr = this.getToAddresses()\n .map((r) => (r.name ? `${r.name} <${r.address}>` : r.address))\n .join(', ');\n\n const bodyText = this.textBody || this.body || '';\n\n return [\n '',\n '',\n '---------- Forwarded message ----------',\n `From: ${from}`,\n `Date: ${dateStr}`,\n `Subject: ${this.subject}`,\n `To: ${toStr}`,\n '',\n bodyText,\n ].join('\\n');\n }\n\n /**\n * Send this email. Accepts {@link SendEmailOptions}, including send-time\n * `attachments`, which `EmailSender` merges with the stored rows (#3808).\n */\n override async send(options?: SendEmailOptions): Promise<MessageSendResult> {\n const result = await super.send(options);\n await this.markHasAttachments(options, result);\n return result;\n }\n\n /** Retry a failed send; takes the same options as {@link Email.send}. */\n override async retrySend(\n options?: SendEmailOptions,\n ): Promise<MessageSendResult> {\n const result = await super.retrySend(options);\n await this.markHasAttachments(options, result);\n return result;\n }\n\n /**\n * Record `hasAttachments` once send-time bytes were actually delivered, so a\n * rejected or failed send never claims attachments that did not go out.\n */\n private async markHasAttachments(\n options: SendEmailOptions | undefined,\n result: MessageSendResult,\n ): Promise<void> {\n if (!result.success || !options?.attachments?.length) return;\n if (this.hasAttachments) return;\n this.hasAttachments = true;\n if (this.isPersisted) await this.save();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Email-CBIrfFLm.js","names":[],"sources":["../../src/models/Email.ts"],"sourcesContent":["/**\n * Email model - Email message extending the Message STI base\n *\n * Retains all email-specific fields (RFC 822 compliance, folders, etc.)\n * while inheriting common message fields from Message.\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { field, smrt } from '@happyvertical/smrt-core';\nimport type {\n EmailOptions,\n MessageSendResult,\n SendEmailOptions,\n} from '../types';\nimport { Message } from './Message';\n\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n // Message is read-only over REST; this provider type is writable there.\n widenParentExposure: { api: ['create', 'update', 'delete'] },\n})\nexport class Email extends Message { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n // RFC 822 fields\n @field({\n description:\n 'The email reference assigned by the sender for tracking replies.',\n })\n messageId = ''; // RFC 822 Message-ID header\n @field({\n description: 'The sender-assigned reference of the email being replied to.',\n })\n inReplyTo = '';\n\n // Additional recipients\n @field({ description: 'Recipients receiving a visible copy of this email.' })\n ccAddresses = '';\n @field({\n description:\n 'Recipients receiving a copy without appearing in the visible recipient list.',\n })\n bccAddresses = '';\n @field({ description: 'The address replies should be sent to.' })\n replyToAddress = '';\n @field({ description: 'The name associated with the reply address.' })\n replyToName = '';\n\n // Email-specific content\n @field({ description: 'The plain-text version of the email.' })\n textBody = '';\n @field({ description: 'The formatted version of the email.' })\n htmlBody = '';\n\n // Location\n @field({\n description:\n 'The mailbox folder reference supplied by the email connection.',\n })\n folderId = '';\n @field({\n description: 'The location of this email within the mailbox folders.',\n })\n folderPath = '';\n @field({\n description: 'Labels applied to this email by the mailbox provider.',\n })\n labels = ''; // JSON array (Gmail)\n @field({ description: 'Mailbox markers such as read, answered, or flagged.' })\n flags = ''; // JSON array (IMAP)\n\n // Email-specific status flags\n @field({ description: 'Whether this email has been replied to.' })\n isAnswered = false;\n @field({ description: 'Whether this email is saved as an unfinished draft.' })\n isDraft = false;\n\n // Raw data\n @field({\n description: 'The original email content as received from the mail server.',\n })\n rawMessage = '';\n @field({\n description: 'Delivery and routing details included with the email.',\n })\n headers = ''; // JSON object\n\n constructor(options: EmailOptions = {}) {\n super(options);\n\n if (options.messageId !== undefined) this.messageId = options.messageId;\n if (options.inReplyTo !== undefined) this.inReplyTo = options.inReplyTo;\n if (options.ccAddresses !== undefined)\n this.ccAddresses = options.ccAddresses;\n if (options.bccAddresses !== undefined)\n this.bccAddresses = options.bccAddresses;\n if (options.replyToAddress !== undefined)\n this.replyToAddress = options.replyToAddress;\n if (options.replyToName !== undefined)\n this.replyToName = options.replyToName;\n if (options.textBody !== undefined) this.textBody = options.textBody;\n if (options.htmlBody !== undefined) this.htmlBody = options.htmlBody;\n if (options.folderId !== undefined) this.folderId = options.folderId;\n if (options.folderPath !== undefined) this.folderPath = options.folderPath;\n if (options.labels !== undefined) this.labels = options.labels;\n if (options.flags !== undefined) this.flags = options.flags;\n if (options.isAnswered !== undefined) this.isAnswered = options.isAnswered;\n if (options.isDraft !== undefined) this.isDraft = options.isDraft;\n if (options.rawMessage !== undefined) this.rawMessage = options.rawMessage;\n if (options.headers !== undefined) this.headers = options.headers;\n\n // Populate base body from textBody for unified access\n if (options.textBody && !options.body) {\n this.body = options.textBody;\n }\n }\n\n /**\n * Get CC addresses as parsed array\n */\n getCcAddresses(): Array<{ address: string; name?: string }> {\n if (!this.ccAddresses) return [];\n try {\n return JSON.parse(this.ccAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Get BCC addresses as parsed array\n */\n getBccAddresses(): Array<{ address: string; name?: string }> {\n if (!this.bccAddresses) return [];\n try {\n return JSON.parse(this.bccAddresses);\n } catch {\n return [];\n }\n }\n\n /**\n * Get labels as parsed array\n */\n getLabels(): string[] {\n if (!this.labels) return [];\n try {\n return JSON.parse(this.labels);\n } catch {\n return [];\n }\n }\n\n /**\n * Set labels from array\n */\n setLabels(labels: string[]): void {\n this.labels = JSON.stringify(labels);\n }\n\n /**\n * Get flags as parsed array\n */\n getFlags(): string[] {\n if (!this.flags) return [];\n try {\n return JSON.parse(this.flags);\n } catch {\n return [];\n }\n }\n\n /**\n * Set flags from array\n */\n setFlags(flags: string[]): void {\n this.flags = JSON.stringify(flags);\n }\n\n /**\n * Get headers as parsed object\n */\n getHeaders(): Record<string, string | string[]> {\n if (!this.headers) return {};\n try {\n return JSON.parse(this.headers);\n } catch {\n return {};\n }\n }\n\n /**\n * Set headers from object\n */\n setHeaders(headers: Record<string, string | string[]>): void {\n this.headers = JSON.stringify(headers);\n }\n\n /**\n * Get the email account (typed as EmailAccount)\n */\n override async getAccount() {\n if (!this.accountId) return null;\n\n const { EmailAccountCollection } = await import(\n '../collections/EmailAccountCollection'\n );\n const collection = await EmailAccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n /**\n * Get the folder\n */\n async getFolder() {\n if (!this.folderId) return null;\n\n const { EmailFolderCollection } = await import(\n '../collections/EmailFolderCollection'\n );\n const collection = await EmailFolderCollection.create(this.options);\n\n return await collection.get({ id: this.folderId });\n }\n\n /**\n * Get emails in the same thread\n */\n async getThreadEmails(): Promise<Email[]> {\n if (!this.threadId) return [this];\n\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n return await collection.getByThread(this.threadId);\n }\n\n /**\n * Get a short preview of the email body\n */\n override getPreview(maxLength = 200): string {\n const body = this.textBody || this.htmlBody?.replace(/<[^>]*>/g, '') || '';\n if (body.length <= maxLength) return body;\n return `${body.slice(0, maxLength)}...`;\n }\n\n /**\n * Get References header values as array\n */\n getReferences(): string[] {\n const headers = this.getHeaders();\n const refs = headers.references;\n if (!refs) return [];\n if (Array.isArray(refs)) return refs;\n return refs.split(/\\s+/).filter(Boolean);\n }\n\n /**\n * Create a reply to this email with RFC 822 threading\n */\n override createReply(options?: { replyAll?: boolean }): Email {\n const reply = new Email({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: this.threadId || this.id || undefined,\n subject: this.subject.startsWith('Re:')\n ? this.subject\n : `Re: ${this.subject}`,\n fromAddress: '',\n fromName: '',\n inReplyToMessageId: this.id || undefined,\n sendStatus: 'draft',\n isRead: true,\n isDraft: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n\n // RFC 822 threading\n inReplyTo: this.messageId,\n\n // To: original sender\n toAddresses: JSON.stringify([\n { address: this.fromAddress, name: this.fromName },\n ]),\n });\n\n // RFC 822 References: original references + original Message-ID\n const refs = [...this.getReferences()];\n if (this.messageId && !refs.includes(this.messageId)) {\n refs.push(this.messageId);\n }\n reply.setHeaders({ references: refs.join(' ') });\n\n // Reply-All: add original To/CC as CC (excluding self)\n if (options?.replyAll) {\n const allRecipients = [\n ...this.getToAddresses(),\n ...this.getCcAddresses(),\n ];\n // Remove original sender (already in To:) and any duplicates\n const seen = new Set([this.fromAddress.toLowerCase()]);\n const ccAddresses = allRecipients.filter((r) => {\n const addr = r.address.toLowerCase();\n if (seen.has(addr)) return false;\n seen.add(addr);\n return true;\n });\n reply.ccAddresses = JSON.stringify(ccAddresses);\n }\n\n reply.body = this.buildQuotedBody();\n reply.textBody = reply.body;\n\n return reply;\n }\n\n /**\n * Create a forward of this email\n */\n override createForward(): Email {\n const forwardBody = this.buildForwardBody();\n\n const forward = new Email({\n ...this.draftOptions(),\n id: undefined,\n accountId: this.accountId,\n threadId: '',\n subject: this.subject.startsWith('Fwd:')\n ? this.subject\n : `Fwd: ${this.subject}`,\n toAddresses: '[]',\n ccAddresses: '[]',\n bccAddresses: '[]',\n fromAddress: '',\n fromName: '',\n body: forwardBody,\n textBody: forwardBody,\n hasAttachments: this.hasAttachments,\n inReplyToMessageId: '',\n inReplyTo: '',\n sendStatus: 'draft',\n isDraft: true,\n isRead: true,\n date: null,\n createdAt: undefined,\n updatedAt: undefined,\n });\n\n return forward;\n }\n\n /**\n * Build email-specific quoted body for replies\n */\n protected override buildQuotedBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const bodyText = this.textBody || this.body || '';\n const quotedLines = bodyText\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n');\n\n return `\\n\\nOn ${dateStr}, ${from} wrote:\\n${quotedLines}`;\n }\n\n /**\n * Build forwarded message body\n */\n private buildForwardBody(): string {\n const dateStr = this.date ? this.date.toLocaleString() : 'unknown date';\n const from = this.fromName\n ? `${this.fromName} <${this.fromAddress}>`\n : this.fromAddress;\n\n const toStr = this.getToAddresses()\n .map((r) => (r.name ? `${r.name} <${r.address}>` : r.address))\n .join(', ');\n\n const bodyText = this.textBody || this.body || '';\n\n return [\n '',\n '',\n '---------- Forwarded message ----------',\n `From: ${from}`,\n `Date: ${dateStr}`,\n `Subject: ${this.subject}`,\n `To: ${toStr}`,\n '',\n bodyText,\n ].join('\\n');\n }\n\n /**\n * Send this email. Accepts {@link SendEmailOptions}, including send-time\n * `attachments`, which `EmailSender` merges with the stored rows (#3808).\n */\n override async send(options?: SendEmailOptions): Promise<MessageSendResult> {\n const result = await super.send(options);\n await this.markHasAttachments(options, result);\n return result;\n }\n\n /** Retry a failed send; takes the same options as {@link Email.send}. */\n override async retrySend(\n options?: SendEmailOptions,\n ): Promise<MessageSendResult> {\n const result = await super.retrySend(options);\n await this.markHasAttachments(options, result);\n return result;\n }\n\n /**\n * Record `hasAttachments` once send-time bytes were actually delivered, so a\n * rejected or failed send never claims attachments that did not go out.\n */\n private async markHasAttachments(\n options: SendEmailOptions | undefined,\n result: MessageSendResult,\n ): Promise<void> {\n if (!result.success || !options?.attachments?.length) return;\n if (this.hasAttachments) return;\n this.hasAttachments = true;\n if (this.isPersisted) await this.save();\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import "./__smrt-register__-B7iWfX8p.js";
2
+ import "./__smrt-register__-DyTpUDMS.js";
3
3
  import { SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
4
4
  import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
5
5
  //#region src/models/EmailFolder.ts
@@ -159,4 +159,4 @@ EmailFolder = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
159
159
  //#endregion
160
160
  export { EmailFolder_exports as n, EmailFolder as t };
161
161
 
162
- //# sourceMappingURL=EmailFolder-Ia8OFxRn.js.map
162
+ //# sourceMappingURL=EmailFolder-Cz2JU5Z1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EmailFolder-Ia8OFxRn.js","names":[],"sources":["../../src/models/EmailFolder.ts"],"sourcesContent":["/**\n * EmailFolder model - Folder/label tracking\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { EmailFolderOptions } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class EmailFolder extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Account')\n accountId = '';\n name = '';\n path = '';\n delimiter = '/';\n specialUse = ''; // '\\\\Inbox', '\\\\Sent', '\\\\Drafts', etc.\n messageCount = 0;\n unreadCount = 0;\n subscribed = true;\n\n // Timestamps\n createdAt = new Date();\n updatedAt = new Date();\n\n constructor(options: EmailFolderOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.accountId !== undefined) this.accountId = options.accountId;\n if (options.name !== undefined) this.name = options.name;\n if (options.path !== undefined) this.path = options.path;\n if (options.delimiter !== undefined) this.delimiter = options.delimiter;\n if (options.specialUse !== undefined) this.specialUse = options.specialUse;\n if (options.messageCount !== undefined)\n this.messageCount = options.messageCount;\n if (options.unreadCount !== undefined)\n this.unreadCount = options.unreadCount;\n if (options.subscribed !== undefined) this.subscribed = options.subscribed;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n /**\n * Get the email account\n */\n async getAccount() {\n if (!this.accountId) return null;\n\n const { EmailAccountCollection } = await import(\n '../collections/EmailAccountCollection'\n );\n const collection = await EmailAccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n /**\n * Get all emails in this folder\n */\n async getEmails(limit?: number) {\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n const options: { where: Record<string, unknown>; limit?: number } = {\n where: { folderId: this.id },\n };\n if (limit) {\n options.limit = limit;\n }\n\n return await collection.list(options);\n }\n\n /**\n * Get unread emails in this folder\n */\n async getUnreadEmails(limit?: number) {\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n const options: { where: Record<string, unknown>; limit?: number } = {\n where: { folderId: this.id, isRead: false },\n };\n if (limit) {\n options.limit = limit;\n }\n\n return await collection.list(options);\n }\n\n /**\n * Update message counts from database\n */\n async refreshCounts(): Promise<void> {\n if (!this.id) {\n throw new Error(\n 'EmailFolder.refreshCounts requires a persisted folder (missing id)',\n );\n }\n const folderId = this.id;\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n this.messageCount = await collection.countByFolder(folderId);\n this.unreadCount = await collection.countUnreadByFolder(folderId);\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Check if this is the inbox folder\n */\n isInbox(): boolean {\n return this.specialUse === '\\\\Inbox' || this.path.toLowerCase() === 'inbox';\n }\n\n /**\n * Check if this is the sent folder\n */\n isSent(): boolean {\n return this.specialUse === '\\\\Sent' || this.path.toLowerCase() === 'sent';\n }\n\n /**\n * Check if this is the drafts folder\n */\n isDrafts(): boolean {\n return (\n this.specialUse === '\\\\Drafts' || this.path.toLowerCase() === 'drafts'\n );\n }\n\n /**\n * Check if this is the trash folder\n */\n isTrash(): boolean {\n return this.specialUse === '\\\\Trash' || this.path.toLowerCase() === 'trash';\n }\n\n /**\n * Check if this is the spam/junk folder\n */\n isSpam(): boolean {\n return (\n this.specialUse === '\\\\Junk' ||\n this.path.toLowerCase() === 'spam' ||\n this.path.toLowerCase() === 'junk'\n );\n }\n\n /**\n * Check if this is a system folder\n */\n isSystemFolder(): boolean {\n return !!this.specialUse;\n }\n\n /**\n * Subscribe to folder\n */\n async subscribe(): Promise<void> {\n this.subscribed = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Unsubscribe from folder\n */\n async unsubscribe(): Promise<void> {\n this.subscribed = false;\n this.updatedAt = new Date();\n await this.save();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EmailFolder-Cz2JU5Z1.js","names":[],"sources":["../../src/models/EmailFolder.ts"],"sourcesContent":["/**\n * EmailFolder model - Folder/label tracking\n */\n\n// Register the package manifest before this module's @smrt() decorator\n// runs, whichever chunk the library build places it in (#3098).\nimport '../__smrt-register__.js';\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { EmailFolderOptions } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class EmailFolder extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-messages\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Account')\n accountId = '';\n name = '';\n path = '';\n delimiter = '/';\n specialUse = ''; // '\\\\Inbox', '\\\\Sent', '\\\\Drafts', etc.\n messageCount = 0;\n unreadCount = 0;\n subscribed = true;\n\n // Timestamps\n createdAt = new Date();\n updatedAt = new Date();\n\n constructor(options: EmailFolderOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.accountId !== undefined) this.accountId = options.accountId;\n if (options.name !== undefined) this.name = options.name;\n if (options.path !== undefined) this.path = options.path;\n if (options.delimiter !== undefined) this.delimiter = options.delimiter;\n if (options.specialUse !== undefined) this.specialUse = options.specialUse;\n if (options.messageCount !== undefined)\n this.messageCount = options.messageCount;\n if (options.unreadCount !== undefined)\n this.unreadCount = options.unreadCount;\n if (options.subscribed !== undefined) this.subscribed = options.subscribed;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n /**\n * Get the email account\n */\n async getAccount() {\n if (!this.accountId) return null;\n\n const { EmailAccountCollection } = await import(\n '../collections/EmailAccountCollection'\n );\n const collection = await EmailAccountCollection.create(this.options);\n\n return await collection.get({ id: this.accountId });\n }\n\n /**\n * Get all emails in this folder\n */\n async getEmails(limit?: number) {\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n const options: { where: Record<string, unknown>; limit?: number } = {\n where: { folderId: this.id },\n };\n if (limit) {\n options.limit = limit;\n }\n\n return await collection.list(options);\n }\n\n /**\n * Get unread emails in this folder\n */\n async getUnreadEmails(limit?: number) {\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n const options: { where: Record<string, unknown>; limit?: number } = {\n where: { folderId: this.id, isRead: false },\n };\n if (limit) {\n options.limit = limit;\n }\n\n return await collection.list(options);\n }\n\n /**\n * Update message counts from database\n */\n async refreshCounts(): Promise<void> {\n if (!this.id) {\n throw new Error(\n 'EmailFolder.refreshCounts requires a persisted folder (missing id)',\n );\n }\n const folderId = this.id;\n const { EmailCollection } = await import('../collections/EmailCollection');\n const collection = await EmailCollection.create(this.options);\n\n this.messageCount = await collection.countByFolder(folderId);\n this.unreadCount = await collection.countUnreadByFolder(folderId);\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Check if this is the inbox folder\n */\n isInbox(): boolean {\n return this.specialUse === '\\\\Inbox' || this.path.toLowerCase() === 'inbox';\n }\n\n /**\n * Check if this is the sent folder\n */\n isSent(): boolean {\n return this.specialUse === '\\\\Sent' || this.path.toLowerCase() === 'sent';\n }\n\n /**\n * Check if this is the drafts folder\n */\n isDrafts(): boolean {\n return (\n this.specialUse === '\\\\Drafts' || this.path.toLowerCase() === 'drafts'\n );\n }\n\n /**\n * Check if this is the trash folder\n */\n isTrash(): boolean {\n return this.specialUse === '\\\\Trash' || this.path.toLowerCase() === 'trash';\n }\n\n /**\n * Check if this is the spam/junk folder\n */\n isSpam(): boolean {\n return (\n this.specialUse === '\\\\Junk' ||\n this.path.toLowerCase() === 'spam' ||\n this.path.toLowerCase() === 'junk'\n );\n }\n\n /**\n * Check if this is a system folder\n */\n isSystemFolder(): boolean {\n return !!this.specialUse;\n }\n\n /**\n * Subscribe to folder\n */\n async subscribe(): Promise<void> {\n this.subscribed = true;\n this.updatedAt = new Date();\n await this.save();\n }\n\n /**\n * Unsubscribe from folder\n */\n async unsubscribe(): Promise<void> {\n this.subscribed = false;\n this.updatedAt = new Date();\n await this.save();\n }\n}\n"],"mappings":"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"}