@open-mercato/shared 0.4.5-develop-754ef4d2f0 → 0.4.5-develop-9f9549ebc8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/email/send.js +3 -2
- package/dist/lib/email/send.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/messages/types.js +1 -0
- package/dist/modules/messages/types.js.map +7 -0
- package/package.json +1 -1
- package/src/lib/email/__tests__/send.test.ts +27 -0
- package/src/lib/email/send.ts +7 -1
- package/src/modules/messages/types.ts +169 -0
package/dist/lib/email/send.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Resend } from "resend";
|
|
2
2
|
import { parseBooleanWithDefault } from "../boolean.js";
|
|
3
|
-
async function sendEmail({ to, subject, react, from, replyTo }) {
|
|
3
|
+
async function sendEmail({ to, subject, react, from, replyTo, attachments }) {
|
|
4
4
|
const emailDisabled = parseBooleanWithDefault(process.env.OM_DISABLE_EMAIL_DELIVERY, false) || parseBooleanWithDefault(process.env.OM_TEST_MODE, false);
|
|
5
5
|
if (emailDisabled) return;
|
|
6
6
|
const apiKey = process.env.RESEND_API_KEY;
|
|
@@ -12,7 +12,8 @@ async function sendEmail({ to, subject, react, from, replyTo }) {
|
|
|
12
12
|
subject,
|
|
13
13
|
from: fromAddr,
|
|
14
14
|
react,
|
|
15
|
-
...replyTo ? { reply_to: replyTo } : {}
|
|
15
|
+
...replyTo ? { reply_to: replyTo } : {},
|
|
16
|
+
...attachments?.length ? { attachments } : {}
|
|
16
17
|
};
|
|
17
18
|
const result = await resend.emails.send(payload);
|
|
18
19
|
const errorMessage = typeof result?.error === "string" ? result.error : typeof result?.error?.message === "string" ? result.error.message : null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/email/send.ts"],
|
|
4
|
-
"sourcesContent": ["import { Resend } from 'resend'\nimport React from 'react'\nimport { parseBooleanWithDefault } from '../boolean'\n\nexport type SendEmailOptions = {\n to: string\n subject: string\n react: React.ReactElement\n from?: string\n replyTo?: string\n}\n\nexport async function sendEmail({ to, subject, react, from, replyTo }: SendEmailOptions) {\n const emailDisabled =\n parseBooleanWithDefault(process.env.OM_DISABLE_EMAIL_DELIVERY, false) ||\n parseBooleanWithDefault(process.env.OM_TEST_MODE, false)\n if (emailDisabled) return\n\n const apiKey = process.env.RESEND_API_KEY\n if (!apiKey) throw new Error('RESEND_API_KEY is not set')\n const resend = new Resend(apiKey)\n const fromAddr = from || process.env.EMAIL_FROM || 'no-reply@localhost'\n const payload = {\n to,\n subject,\n from: fromAddr,\n react,\n ...(replyTo ? { reply_to: replyTo } : {}),\n }\n const result = await resend.emails.send(payload)\n const errorMessage =\n typeof (result as any)?.error === 'string'\n ? (result as any).error\n : typeof (result as any)?.error?.message === 'string'\n ? (result as any).error.message\n : null\n if (errorMessage) {\n throw new Error(`RESEND_SEND_FAILED: ${errorMessage}`)\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,cAAc;AAEvB,SAAS,+BAA+B;
|
|
4
|
+
"sourcesContent": ["import { Resend } from 'resend'\nimport React from 'react'\nimport { parseBooleanWithDefault } from '../boolean'\n\nexport type SendEmailOptions = {\n to: string\n subject: string\n react: React.ReactElement\n from?: string\n replyTo?: string\n attachments?: Array<{\n filename: string\n content: string\n contentType?: string\n }>\n}\n\nexport async function sendEmail({ to, subject, react, from, replyTo, attachments }: SendEmailOptions) {\n const emailDisabled =\n parseBooleanWithDefault(process.env.OM_DISABLE_EMAIL_DELIVERY, false) ||\n parseBooleanWithDefault(process.env.OM_TEST_MODE, false)\n if (emailDisabled) return\n\n const apiKey = process.env.RESEND_API_KEY\n if (!apiKey) throw new Error('RESEND_API_KEY is not set')\n const resend = new Resend(apiKey)\n const fromAddr = from || process.env.EMAIL_FROM || 'no-reply@localhost'\n const payload = {\n to,\n subject,\n from: fromAddr,\n react,\n ...(replyTo ? { reply_to: replyTo } : {}),\n ...(attachments?.length ? { attachments } : {}),\n }\n const result = await resend.emails.send(payload)\n const errorMessage =\n typeof (result as any)?.error === 'string'\n ? (result as any).error\n : typeof (result as any)?.error?.message === 'string'\n ? (result as any).error.message\n : null\n if (errorMessage) {\n throw new Error(`RESEND_SEND_FAILED: ${errorMessage}`)\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAc;AAEvB,SAAS,+BAA+B;AAexC,eAAsB,UAAU,EAAE,IAAI,SAAS,OAAO,MAAM,SAAS,YAAY,GAAqB;AACpG,QAAM,gBACJ,wBAAwB,QAAQ,IAAI,2BAA2B,KAAK,KACpE,wBAAwB,QAAQ,IAAI,cAAc,KAAK;AACzD,MAAI,cAAe;AAEnB,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AACxD,QAAM,SAAS,IAAI,OAAO,MAAM;AAChC,QAAM,WAAW,QAAQ,QAAQ,IAAI,cAAc;AACnD,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA,GAAI,UAAU,EAAE,UAAU,QAAQ,IAAI,CAAC;AAAA,IACvC,GAAI,aAAa,SAAS,EAAE,YAAY,IAAI,CAAC;AAAA,EAC/C;AACA,QAAM,SAAS,MAAM,OAAO,OAAO,KAAK,OAAO;AAC/C,QAAM,eACJ,OAAQ,QAAgB,UAAU,WAC7B,OAAe,QAChB,OAAQ,QAAgB,OAAO,YAAY,WACxC,OAAe,MAAM,UACtB;AACR,MAAI,cAAc;AAChB,UAAM,IAAI,MAAM,uBAAuB,YAAY,EAAE;AAAA,EACvD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.5-develop-
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.5-develop-9f9549ebc8'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
package/package.json
CHANGED
|
@@ -61,6 +61,33 @@ describe('sendEmail', () => {
|
|
|
61
61
|
expect(payload.reply_to).toBeUndefined()
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
it('passes attachments to Resend payload when provided', async () => {
|
|
65
|
+
await sendEmail({
|
|
66
|
+
to: 'user@example.com',
|
|
67
|
+
subject: 'Hello',
|
|
68
|
+
react: React.createElement('div', null, 'Hi'),
|
|
69
|
+
attachments: [
|
|
70
|
+
{
|
|
71
|
+
filename: 'invoice.pdf',
|
|
72
|
+
content: 'dGVzdA==',
|
|
73
|
+
contentType: 'application/pdf',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
expect(sendMock).toHaveBeenCalledWith(
|
|
79
|
+
expect.objectContaining({
|
|
80
|
+
attachments: [
|
|
81
|
+
{
|
|
82
|
+
filename: 'invoice.pdf',
|
|
83
|
+
content: 'dGVzdA==',
|
|
84
|
+
contentType: 'application/pdf',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
64
91
|
it('throws when Resend returns an error', async () => {
|
|
65
92
|
sendMock.mockResolvedValueOnce({ error: { message: 'invalid domain' } })
|
|
66
93
|
|
package/src/lib/email/send.ts
CHANGED
|
@@ -8,9 +8,14 @@ export type SendEmailOptions = {
|
|
|
8
8
|
react: React.ReactElement
|
|
9
9
|
from?: string
|
|
10
10
|
replyTo?: string
|
|
11
|
+
attachments?: Array<{
|
|
12
|
+
filename: string
|
|
13
|
+
content: string
|
|
14
|
+
contentType?: string
|
|
15
|
+
}>
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
export async function sendEmail({ to, subject, react, from, replyTo }: SendEmailOptions) {
|
|
18
|
+
export async function sendEmail({ to, subject, react, from, replyTo, attachments }: SendEmailOptions) {
|
|
14
19
|
const emailDisabled =
|
|
15
20
|
parseBooleanWithDefault(process.env.OM_DISABLE_EMAIL_DELIVERY, false) ||
|
|
16
21
|
parseBooleanWithDefault(process.env.OM_TEST_MODE, false)
|
|
@@ -26,6 +31,7 @@ export async function sendEmail({ to, subject, react, from, replyTo }: SendEmail
|
|
|
26
31
|
from: fromAddr,
|
|
27
32
|
react,
|
|
28
33
|
...(replyTo ? { reply_to: replyTo } : {}),
|
|
34
|
+
...(attachments?.length ? { attachments } : {}),
|
|
29
35
|
}
|
|
30
36
|
const result = await resend.emails.send(payload)
|
|
31
37
|
const errorMessage =
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { ComponentType } from 'react'
|
|
2
|
+
|
|
3
|
+
export type MessageAction = {
|
|
4
|
+
id: string
|
|
5
|
+
label: string
|
|
6
|
+
labelKey?: string
|
|
7
|
+
variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost'
|
|
8
|
+
icon?: string
|
|
9
|
+
commandId?: string
|
|
10
|
+
href?: string
|
|
11
|
+
isTerminal?: boolean
|
|
12
|
+
confirmRequired?: boolean
|
|
13
|
+
confirmMessage?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type MessageActionData = {
|
|
17
|
+
actions: MessageAction[]
|
|
18
|
+
primaryActionId?: string
|
|
19
|
+
expiresAt?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type MessageListItemProps = {
|
|
23
|
+
message: {
|
|
24
|
+
id: string
|
|
25
|
+
type: string
|
|
26
|
+
typeLabel?: string
|
|
27
|
+
subject: string
|
|
28
|
+
body: string
|
|
29
|
+
bodyFormat: 'text' | 'markdown'
|
|
30
|
+
priority: 'low' | 'normal' | 'high' | 'urgent'
|
|
31
|
+
sentAt: Date | null
|
|
32
|
+
senderName?: string
|
|
33
|
+
senderAvatar?: string
|
|
34
|
+
hasObjects: boolean
|
|
35
|
+
objectCount?: number
|
|
36
|
+
hasAttachments: boolean
|
|
37
|
+
attachmentCount?: number
|
|
38
|
+
recipientCount?: number
|
|
39
|
+
hasActions: boolean
|
|
40
|
+
actionTaken?: string | null
|
|
41
|
+
unread: boolean
|
|
42
|
+
}
|
|
43
|
+
onClick: () => void
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type MessageContentProps = {
|
|
47
|
+
message: {
|
|
48
|
+
id: string
|
|
49
|
+
type: string
|
|
50
|
+
subject: string
|
|
51
|
+
body: string
|
|
52
|
+
bodyFormat: 'text' | 'markdown'
|
|
53
|
+
priority: 'low' | 'normal' | 'high' | 'urgent'
|
|
54
|
+
sentAt: Date | null
|
|
55
|
+
senderName?: string
|
|
56
|
+
senderAvatar?: string
|
|
57
|
+
senderUserId: string
|
|
58
|
+
actionData?: MessageActionData | null
|
|
59
|
+
actionTaken?: string | null
|
|
60
|
+
actionTakenAt?: Date | null
|
|
61
|
+
actionTakenByName?: string
|
|
62
|
+
}
|
|
63
|
+
objects: Array<{
|
|
64
|
+
id: string
|
|
65
|
+
entityModule: string
|
|
66
|
+
entityType: string
|
|
67
|
+
entityId: string
|
|
68
|
+
actionRequired: boolean
|
|
69
|
+
snapshot?: Record<string, unknown>
|
|
70
|
+
}>
|
|
71
|
+
attachments: Array<{
|
|
72
|
+
id: string
|
|
73
|
+
fileName: string
|
|
74
|
+
fileSize: number
|
|
75
|
+
mimeType: string
|
|
76
|
+
url: string
|
|
77
|
+
}>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type MessageActionsProps = {
|
|
81
|
+
message: {
|
|
82
|
+
id: string
|
|
83
|
+
type: string
|
|
84
|
+
actionData?: MessageActionData | null
|
|
85
|
+
actionTaken?: string | null
|
|
86
|
+
}
|
|
87
|
+
onExecuteAction: (actionId: string, payload?: Record<string, unknown>) => Promise<void>
|
|
88
|
+
isExecuting: boolean
|
|
89
|
+
executingActionId?: string | null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type MessageTypeDefinition = {
|
|
93
|
+
type: string
|
|
94
|
+
module: string
|
|
95
|
+
labelKey: string
|
|
96
|
+
icon: string
|
|
97
|
+
color?: string
|
|
98
|
+
ui?: {
|
|
99
|
+
listItemComponent?: string
|
|
100
|
+
contentComponent?: string
|
|
101
|
+
actionsComponent?: string
|
|
102
|
+
}
|
|
103
|
+
ListItemComponent?: ComponentType<MessageListItemProps>
|
|
104
|
+
ContentComponent?: ComponentType<MessageContentProps>
|
|
105
|
+
ActionsComponent?: ComponentType<MessageActionsProps>
|
|
106
|
+
defaultActions?: MessageAction[]
|
|
107
|
+
allowReply?: boolean
|
|
108
|
+
allowForward?: boolean
|
|
109
|
+
actionsExpireAfterHours?: number
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type MessageObjectAction = {
|
|
113
|
+
id: string
|
|
114
|
+
labelKey: string
|
|
115
|
+
variant?: 'default' | 'secondary' | 'destructive' | 'outline'
|
|
116
|
+
icon?: string
|
|
117
|
+
commandId?: string
|
|
118
|
+
href?: string
|
|
119
|
+
isTerminal?: boolean
|
|
120
|
+
confirmRequired?: boolean
|
|
121
|
+
confirmMessage?: string
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type ObjectPreviewProps = {
|
|
125
|
+
entityId: string
|
|
126
|
+
entityModule: string
|
|
127
|
+
entityType: string
|
|
128
|
+
snapshot?: Record<string, unknown>
|
|
129
|
+
previewData?: ObjectPreviewData
|
|
130
|
+
actionRequired?: boolean
|
|
131
|
+
actionType?: string
|
|
132
|
+
actionLabel?: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type ObjectDetailProps = ObjectPreviewProps & {
|
|
136
|
+
onAction: (actionId: string, payload?: Record<string, unknown>) => Promise<void>
|
|
137
|
+
actions: MessageObjectAction[]
|
|
138
|
+
actionTaken?: string | null
|
|
139
|
+
actionTakenAt?: Date | null
|
|
140
|
+
actionTakenByUserId?: string | null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type ObjectPreviewData = {
|
|
144
|
+
title: string
|
|
145
|
+
subtitle?: string
|
|
146
|
+
status?: string
|
|
147
|
+
statusColor?: string
|
|
148
|
+
metadata?: Record<string, string>
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type LoadContext = {
|
|
152
|
+
tenantId: string
|
|
153
|
+
organizationId?: string | null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type MessageObjectTypeDefinition = {
|
|
157
|
+
module: string
|
|
158
|
+
entityType: string
|
|
159
|
+
labelKey: string
|
|
160
|
+
icon: string
|
|
161
|
+
messageTypes?: string[]
|
|
162
|
+
entityId?: string
|
|
163
|
+
optionLabelField?: string
|
|
164
|
+
optionSubtitleField?: string
|
|
165
|
+
PreviewComponent?: ComponentType<ObjectPreviewProps>
|
|
166
|
+
DetailComponent?: ComponentType<ObjectDetailProps>
|
|
167
|
+
actions: MessageObjectAction[]
|
|
168
|
+
loadPreview?: (entityId: string, ctx: LoadContext) => Promise<ObjectPreviewData>
|
|
169
|
+
}
|