@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.
@@ -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;AAUxC,eAAsB,UAAU,EAAE,IAAI,SAAS,OAAO,MAAM,QAAQ,GAAqB;AACvF,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,EACzC;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;",
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
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.4.5-develop-754ef4d2f0";
1
+ const APP_VERSION = "0.4.5-develop-9f9549ebc8";
2
2
  const appVersion = APP_VERSION;
3
3
  export {
4
4
  APP_VERSION,
@@ -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-754ef4d2f0'\nexport const appVersion = APP_VERSION\n"],
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
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/shared",
3
- "version": "0.4.5-develop-754ef4d2f0",
3
+ "version": "0.4.5-develop-9f9549ebc8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -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
 
@@ -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
+ }