appos 0.6.1-0 → 0.7.1-0

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 (134) hide show
  1. package/dist/api/auth.d.mts +3 -3
  2. package/dist/api/cache.d.mts +2 -2
  3. package/dist/api/cli.d.mts +5 -5
  4. package/dist/api/cli.mjs +35 -14
  5. package/dist/api/container.d.mts +5 -5
  6. package/dist/api/database.d.mts +2 -2
  7. package/dist/api/database.mjs +2 -2
  8. package/dist/api/event.d.mts +5 -5
  9. package/dist/api/event.mjs +2 -2
  10. package/dist/api/instrumentation.d.mts +1 -1
  11. package/dist/api/mailer.d.mts +6 -6
  12. package/dist/api/mailer.mjs +537 -7
  13. package/dist/api/observability.d.mts +3 -3
  14. package/dist/api/openapi.d.mts +5 -5
  15. package/dist/api/openapi.mjs +2 -2
  16. package/dist/api/server.d.mts +5 -5
  17. package/dist/api/server.mjs +3 -3
  18. package/dist/api/ssr.d.mts +5 -5
  19. package/dist/api/storage.d.mts +5 -5
  20. package/dist/api/storage.mjs +3 -3
  21. package/dist/api/workflow.d.mts +5 -5
  22. package/dist/api/workflow.mjs +3 -3
  23. package/dist/{auth-D56zivoE.d.ts → auth-1uQuSjl_.d.ts} +631 -563
  24. package/dist/{auth-BzQ2oAZ8.d.mts → auth-COwDukbw.d.mts} +311 -567
  25. package/dist/{brand-DhfRxUs9.d.ts → brand-CIdvO6yY.d.ts} +4 -4
  26. package/dist/{cache-B-TvutDL.d.mts → cache-D1ATIulZ.d.mts} +1 -1
  27. package/dist/colors-AFh2mCN3.mjs +144 -0
  28. package/dist/colors-CtIXNATb.mjs +3 -0
  29. package/dist/{constants-BLEvvEa7.mjs → constants-Cn8qeyGD.mjs} +52 -1
  30. package/dist/{container-C00pnItg.d.mts → container-qBBwSMpE.d.mts} +700 -110
  31. package/dist/{database-ltnXGvKZ.d.mts → database-Cch2A-2M.d.mts} +1 -1
  32. package/dist/{database-A6tffVCD.mjs → database-s-rZuCOz.mjs} +1 -1
  33. package/dist/devtools/test.d.mts +7 -7
  34. package/dist/devtools/test.mjs +4 -4
  35. package/dist/devtools/vite.d.mts +60 -5
  36. package/dist/devtools/vite.mjs +139 -3
  37. package/dist/{event-DeqWH7f1.mjs → event-BFYD8Mmy.mjs} +1 -1
  38. package/dist/{observability-DvkWyfwr.d.mts → observability-D9z-cqNJ.d.mts} +2 -2
  39. package/dist/{openapi-C2GOpT-O.mjs → openapi-kCcmkZ30.mjs} +1 -1
  40. package/dist/panel-C2TDalNf.mjs +5 -0
  41. package/dist/panel-CpZWj60p.mjs +57 -0
  42. package/dist/routes-B9VlLVFz.mjs +1123 -0
  43. package/dist/routes-Bkg2Mqxd.mjs +4 -0
  44. package/dist/{server-D_7v-BCM.mjs → server-BNk1lm5r.mjs} +355 -32
  45. package/dist/web/auth.d.ts +2 -2
  46. package/dist/web/i18n.js +1 -0
  47. package/dist/web/ui/accordion.d.ts +5 -5
  48. package/dist/web/ui/alert-dialog.d.ts +13 -13
  49. package/dist/web/ui/alert.d.ts +5 -5
  50. package/dist/web/ui/animated-check.d.ts +2 -2
  51. package/dist/web/ui/aspect-ratio.d.ts +2 -2
  52. package/dist/web/ui/auth.d.ts +2 -2
  53. package/dist/web/ui/avatar.d.ts +7 -7
  54. package/dist/web/ui/badge.d.ts +1 -1
  55. package/dist/web/ui/brand.d.ts +1 -1
  56. package/dist/web/ui/breadcrumb.d.ts +7 -7
  57. package/dist/web/ui/button-group.d.ts +5 -5
  58. package/dist/web/ui/button.d.ts +5 -5
  59. package/dist/web/ui/calendar.d.ts +3 -3
  60. package/dist/web/ui/card.d.ts +8 -8
  61. package/dist/web/ui/carousel.d.ts +6 -6
  62. package/dist/web/ui/chart.d.ts +5 -5
  63. package/dist/web/ui/checkbox.d.ts +2 -2
  64. package/dist/web/ui/collapsible.d.ts +4 -4
  65. package/dist/web/ui/command.d.ts +10 -10
  66. package/dist/web/ui/context-menu.d.ts +16 -16
  67. package/dist/web/ui/dialog.d.ts +11 -11
  68. package/dist/web/ui/drawer.d.ts +11 -11
  69. package/dist/web/ui/dropdown-menu.d.ts +16 -16
  70. package/dist/web/ui/empty.d.ts +9 -9
  71. package/dist/web/ui/error-page.d.ts +2 -2
  72. package/dist/web/ui/field.d.ts +13 -13
  73. package/dist/web/ui/hover-card.d.ts +4 -4
  74. package/dist/web/ui/input-group.d.ts +10 -10
  75. package/dist/web/ui/input-otp.d.ts +5 -5
  76. package/dist/web/ui/input.d.ts +2 -2
  77. package/dist/web/ui/item.d.ts +13 -13
  78. package/dist/web/ui/kbd.d.ts +3 -3
  79. package/dist/web/ui/label.d.ts +2 -2
  80. package/dist/web/ui/menubar.d.ts +17 -17
  81. package/dist/web/ui/navigation-menu.d.ts +11 -11
  82. package/dist/web/ui/pagination.d.ts +8 -8
  83. package/dist/web/ui/popover.d.ts +7 -7
  84. package/dist/web/ui/progress.d.ts +6 -6
  85. package/dist/web/ui/radio-group.d.ts +3 -3
  86. package/dist/web/ui/resizable.d.ts +4 -4
  87. package/dist/web/ui/scroll-area.d.ts +3 -3
  88. package/dist/web/ui/select.d.ts +10 -10
  89. package/dist/web/ui/separator.d.ts +2 -2
  90. package/dist/web/ui/sheet.d.ts +9 -9
  91. package/dist/web/ui/sidebar.d.ts +22 -22
  92. package/dist/web/ui/sidebar.js +1 -1
  93. package/dist/web/ui/skeleton.d.ts +2 -2
  94. package/dist/web/ui/slider.d.ts +2 -2
  95. package/dist/web/ui/sonner.d.ts +2 -2
  96. package/dist/web/ui/spinner.d.ts +2 -2
  97. package/dist/web/ui/switch.d.ts +2 -2
  98. package/dist/web/ui/table.d.ts +9 -9
  99. package/dist/web/ui/tabs.d.ts +7 -7
  100. package/dist/web/ui/textarea.d.ts +2 -2
  101. package/dist/web/ui/toggle-group.d.ts +3 -3
  102. package/dist/web/ui/toggle.d.ts +4 -4
  103. package/dist/web/ui/tooltip.d.ts +5 -5
  104. package/dist/{workflow-MLL2Z1Jv.mjs → workflow-BN-NfNzZ.mjs} +2 -2
  105. package/package.json +28 -13
  106. package/src/devtools/panel/index.ts +114 -0
  107. package/src/devtools/panel/modules/emails/compatibility.ts +289 -0
  108. package/src/devtools/panel/modules/emails/discovery.ts +243 -0
  109. package/src/devtools/panel/modules/emails/linter.ts +501 -0
  110. package/src/devtools/panel/modules/emails/render.ts +155 -0
  111. package/src/devtools/panel/modules/emails/routes.ts +502 -0
  112. package/src/devtools/panel/modules/emails/schema.ts +82 -0
  113. package/src/devtools/panel/modules/emails/spam-checker.ts +231 -0
  114. package/src/devtools/storybook/.storybook/main.ts +9 -1
  115. package/src/devtools/storybook/.storybook/manager.ts +1 -1
  116. package/src/devtools/storybook/.storybook/preview.tsx +6 -0
  117. package/src/web/client/i18n.d.ts +2 -0
  118. package/src/web/client/i18n.ts +6 -1
  119. package/src/web/devtools/index.ts +1 -0
  120. package/src/web/devtools/modules/dashboard.tsx +151 -0
  121. package/src/web/devtools/modules/emails.tsx +1342 -0
  122. package/src/web/devtools/overlay.tsx +109 -0
  123. package/src/web/devtools/panel.tsx +353 -0
  124. package/src/web/index.ts +1 -0
  125. package/src/web/ui/globals.css +2 -1
  126. package/src/web/ui/locales/en/devtools.json +102 -0
  127. package/src/web/ui/locales/zh-CN/devtools.json +102 -0
  128. package/src/web/ui/locales/zh-TW/devtools.json +102 -0
  129. package/src/web/ui/sidebar.tsx +1 -1
  130. /package/dist/{instrumentation-B8fU6rcD.d.mts → instrumentation-DL2LQsyM.d.mts} +0 -0
  131. /package/dist/{logger-BJ2PCtBR.d.mts → logger-C8av5E6b.d.mts} +0 -0
  132. /package/dist/{vite-compat-gu8Mi4cu.mjs → vite-compat-DaoxY3IL.mjs} +0 -0
  133. /package/dist/{youch-handler-DeoaVZPR.mjs → youch-handler-DfBgXQra.mjs} +0 -0
  134. /package/src/{web/lib → universal}/colors.ts +0 -0
@@ -1,12 +1,542 @@
1
1
  import "../auth-schema-OUnjUQlj.mjs";
2
2
  import "../redis-a5sw5J3L.mjs";
3
- import "../constants-BLEvvEa7.mjs";
4
- import "../event-DeqWH7f1.mjs";
5
- import { a as sendEmail } from "../workflow-MLL2Z1Jv.mjs";
6
- import { render } from "@react-email/render";
3
+ import { i as BUILD_OUTPUT_DIR, u as EMAIL_THEME_FILE, v as ROOT_CSS_FILE, x as WEB_DIR } from "../constants-Cn8qeyGD.mjs";
4
+ import "../event-BFYD8Mmy.mjs";
5
+ import { a as sendEmail } from "../workflow-BN-NfNzZ.mjs";
6
+ import { n as parseThemeFromString } from "../colors-AFh2mCN3.mjs";
7
+ import { join } from "node:path";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { render as render$1 } from "@react-email/render";
7
10
  import { createTransport } from "nodemailer";
11
+ import { Body, Body as Body$1, Button as Button$1, CodeBlock as CodeBlock$1, CodeInline as CodeInline$1, Column as EmailColumn, Container as Container$1, Font, Head, Head as Head$1, Heading as Heading$1, Hr as Hr$1, Html, Html as Html$1, Img as Img$1, Link as Link$1, Markdown, Preview, Preview as Preview$1, Row, Section, Tailwind, Tailwind as Tailwind$1, Text as Text$1, plainTextSelectors, pretty, render, toPlainText } from "@react-email/components";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
8
13
 
14
+ //#region src/api/mailer/email-components.tsx
15
+ /**
16
+ * Utility for merging class names.
17
+ * Simple implementation to avoid importing clsx/tailwind-merge in server code.
18
+ */
19
+ function cn(...classes) {
20
+ return classes.filter(Boolean).join(" ");
21
+ }
22
+ /**
23
+ * Email button variant styles matching web/ui/button.
24
+ **/
25
+ const emailButtonVariants = {
26
+ default: "bg-primary text-primary-foreground",
27
+ secondary: "bg-secondary text-secondary-foreground",
28
+ destructive: "bg-destructive text-white",
29
+ outline: "bg-background text-foreground border-border border border-solid",
30
+ ghost: "bg-transparent text-foreground border-border border border-solid",
31
+ link: "text-primary underline bg-transparent"
32
+ };
33
+ /**
34
+ * Email button size styles matching web/ui/button.
35
+ *
36
+ * Uses padding + line-height instead of fixed heights because email clients
37
+ * render `<a>` tags with height differently, causing vertical alignment issues.
38
+ */
39
+ const emailButtonSizes = {
40
+ default: "px-4 py-2 text-sm leading-5",
41
+ xs: "px-2 py-1 text-xs leading-4",
42
+ sm: "px-3 py-1.5 text-sm leading-4",
43
+ lg: "px-5 py-3 text-base leading-5",
44
+ icon: "p-2 text-base leading-none",
45
+ "icon-xs": "p-1 text-sm leading-none",
46
+ "icon-sm": "p-1.5 text-sm leading-none",
47
+ "icon-lg": "p-2.5 text-base leading-none"
48
+ };
49
+ /**
50
+ * Available email button variant keys.
51
+ **/
52
+ const emailButtonVariantKeys = Object.keys(emailButtonVariants);
53
+ /**
54
+ * Available email button size keys.
55
+ **/
56
+ const emailButtonSizeKeys = Object.keys(emailButtonSizes);
57
+ /**
58
+ * Email button component with design system styling.
59
+ *
60
+ * Matches appos/web/ui/button variants and sizes for visual consistency
61
+ * between web UI and email templates.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * // Default button
66
+ * <Button href="https://example.com">Click me</Button>
67
+ *
68
+ * // With variant
69
+ * <Button href="/reset" variant="destructive">Reset Password</Button>
70
+ *
71
+ * // With size
72
+ * <Button href="/start" size="lg">Get Started</Button>
73
+ * ```
74
+ */
75
+ function Button({ variant = "default", size = "default", className, ...props }) {
76
+ return /* @__PURE__ */ jsx(Button$1, {
77
+ className: cn("rounded-md font-medium no-underline inline-block text-center", emailButtonVariants[variant], emailButtonSizes[size], className),
78
+ ...props
79
+ });
80
+ }
81
+ /**
82
+ * Email heading size styles.
83
+ **/
84
+ const emailHeadingSizes = {
85
+ default: "text-xl",
86
+ sm: "text-lg",
87
+ lg: "text-2xl",
88
+ xl: "text-3xl"
89
+ };
90
+ /**
91
+ * Available email heading size keys.
92
+ **/
93
+ const emailHeadingSizeKeys = Object.keys(emailHeadingSizes);
94
+ /**
95
+ * Email heading component with design system styling.
96
+ *
97
+ * Provides consistent heading styles with size variants and default margin.
98
+ *
99
+ * @example
100
+ * ```tsx
101
+ * // Default heading
102
+ * <Heading>Welcome to AppOS</Heading>
103
+ *
104
+ * // Large heading
105
+ * <Heading size="lg">Account Created</Heading>
106
+ *
107
+ * // Extra large heading
108
+ * <Heading size="xl">Important Notice</Heading>
109
+ * ```
110
+ */
111
+ function Heading({ size = "default", className, ...props }) {
112
+ return /* @__PURE__ */ jsx(Heading$1, {
113
+ className: cn("text-foreground font-semibold mt-0 mb-4", emailHeadingSizes[size], className),
114
+ ...props
115
+ });
116
+ }
117
+ /**
118
+ * Email text variant styles.
119
+ **/
120
+ const emailTextVariants = {
121
+ default: "text-muted-foreground",
122
+ foreground: "text-foreground"
123
+ };
124
+ /**
125
+ * Available email text variant keys.
126
+ **/
127
+ const emailTextVariantKeys = Object.keys(emailTextVariants);
128
+ /**
129
+ * Email text component with design system styling.
130
+ *
131
+ * Provides consistent body text styles with color variants.
132
+ *
133
+ * @example
134
+ * ```tsx
135
+ * // Default text (muted color)
136
+ * <Text>Thank you for signing up.</Text>
137
+ *
138
+ * // Emphasized text (foreground color)
139
+ * <Text variant="foreground">Your account is now active.</Text>
140
+ * ```
141
+ */
142
+ function Text({ variant = "default", className, ...props }) {
143
+ return /* @__PURE__ */ jsx(Text$1, {
144
+ className: cn("text-base leading-relaxed mt-0 mb-4", emailTextVariants[variant], className),
145
+ ...props
146
+ });
147
+ }
148
+ /**
149
+ * Email link component with design system styling.
150
+ *
151
+ * Provides consistent inline link styles.
152
+ *
153
+ * @example
154
+ * ```tsx
155
+ * <Text>
156
+ * Need help? Contact <Link href="mailto:support@example.com">support</Link>.
157
+ * </Text>
158
+ * ```
159
+ */
160
+ function Link({ className, ...props }) {
161
+ return /* @__PURE__ */ jsx(Link$1, {
162
+ className: cn("text-primary underline underline-offset-4", className),
163
+ ...props
164
+ });
165
+ }
166
+ /**
167
+ * Email horizontal rule component with design system styling.
168
+ *
169
+ * Provides consistent divider styles with default margin.
170
+ *
171
+ * @example
172
+ * ```tsx
173
+ * <Text>Section one content.</Text>
174
+ * <Hr />
175
+ * <Text>Section two content.</Text>
176
+ * ```
177
+ */
178
+ function Hr({ className, ...props }) {
179
+ return /* @__PURE__ */ jsx(Hr$1, {
180
+ className: cn("border-border border-t border-solid my-6 mx-0 w-full", className),
181
+ ...props
182
+ });
183
+ }
184
+ /**
185
+ * Email code block component with design system styling.
186
+ *
187
+ * Provides consistent code block styles for displaying code snippets.
188
+ *
189
+ * @example
190
+ * ```tsx
191
+ * <CodeBlock code="npm install appos" language="bash" />
192
+ * ```
193
+ */
194
+ function CodeBlock({ className, ...props }) {
195
+ return /* @__PURE__ */ jsx(CodeBlock$1, {
196
+ className: cn("bg-muted text-foreground rounded-md p-4 font-mono text-sm overflow-auto mb-4", className),
197
+ ...props
198
+ });
199
+ }
200
+ /**
201
+ * Email inline code component with design system styling.
202
+ *
203
+ * Provides consistent inline code styles for embedding code in text.
204
+ *
205
+ * @example
206
+ * ```tsx
207
+ * <Text>
208
+ * Run <CodeInline>bun app start</CodeInline> to start the server.
209
+ * </Text>
210
+ * ```
211
+ */
212
+ function CodeInline({ className, ...props }) {
213
+ return /* @__PURE__ */ jsx(CodeInline$1, {
214
+ className: cn("bg-muted text-muted-foreground rounded-sm px-1.5 py-0.5 font-mono text-sm", className),
215
+ ...props
216
+ });
217
+ }
218
+ /**
219
+ * Email image variant styles.
220
+ **/
221
+ const emailImgVariants = {
222
+ default: "rounded-md",
223
+ rounded: "rounded-full",
224
+ none: ""
225
+ };
226
+ /**
227
+ * Available email image variant keys.
228
+ **/
229
+ const emailImgVariantKeys = Object.keys(emailImgVariants);
230
+ /**
231
+ * Email image component with design system styling.
232
+ *
233
+ * Provides consistent image styles with border radius variants.
234
+ *
235
+ * @example
236
+ * ```tsx
237
+ * // Default rounded corners
238
+ * <Img src="https://example.com/photo.jpg" alt="Photo" />
239
+ *
240
+ * // Circular (for avatars)
241
+ * <Img src="https://example.com/avatar.jpg" alt="Avatar" variant="rounded" />
242
+ *
243
+ * // No rounding
244
+ * <Img src="https://example.com/banner.jpg" alt="Banner" variant="none" />
245
+ * ```
246
+ */
247
+ function Img({ variant = "default", className, ...props }) {
248
+ return /* @__PURE__ */ jsx(Img$1, {
249
+ className: cn(emailImgVariants[variant], className),
250
+ ...props
251
+ });
252
+ }
253
+ /**
254
+ * Email container variant styles.
255
+ **/
256
+ const emailContainerVariants = {
257
+ default: "",
258
+ card: "bg-card rounded-xl shadow-sm"
259
+ };
260
+ /**
261
+ * Available email container variant keys.
262
+ **/
263
+ const emailContainerVariantKeys = Object.keys(emailContainerVariants);
264
+ /**
265
+ * Email container component with design system styling.
266
+ *
267
+ * Provides layout container with optional card styling.
268
+ *
269
+ * @example
270
+ * ```tsx
271
+ * // Default passthrough container
272
+ * <Container>
273
+ * <Text>Content here</Text>
274
+ * </Container>
275
+ *
276
+ * // Card-styled container
277
+ * <Container variant="card">
278
+ * <Text>Card content</Text>
279
+ * </Container>
280
+ * ```
281
+ */
282
+ function Container({ variant = "default", className, ...props }) {
283
+ return /* @__PURE__ */ jsx(Container$1, {
284
+ className: cn(emailContainerVariants[variant], className),
285
+ ...props
286
+ });
287
+ }
288
+
289
+ //#endregion
290
+ //#region src/api/mailer/email-theme.ts
291
+ /**
292
+ * Email theme utilities for converting appos design system colors to email-safe hex format.
293
+ */
294
+ /**
295
+ * Theme keys used in email templates.
296
+ * Single source of truth for email-relevant colors.
297
+ */
298
+ const EMAIL_THEME_KEYS = [
299
+ "background",
300
+ "foreground",
301
+ "card",
302
+ "cardForeground",
303
+ "primary",
304
+ "primaryForeground",
305
+ "secondary",
306
+ "secondaryForeground",
307
+ "muted",
308
+ "mutedForeground",
309
+ "destructive",
310
+ "border",
311
+ "fontSans",
312
+ "fontMono",
313
+ "radius"
314
+ ];
315
+ /**
316
+ * Gets the default email theme from the app's web/root.css.
317
+ *
318
+ * In production, reads from pre-generated build/theme.json for efficiency.
319
+ * In development, parses web/root.css directly for hot-reload support.
320
+ *
321
+ * @returns Email theme with hex colors and pixel values.
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * const theme = getDefaultEmailTheme();
326
+ * // { background: "#ffffff", primary: "#1a1a1a", ... }
327
+ * ```
328
+ */
329
+ function getDefaultEmailTheme() {
330
+ const cwd = process.cwd();
331
+ const themeJsonPath = join(cwd, BUILD_OUTPUT_DIR, EMAIL_THEME_FILE);
332
+ if (existsSync(themeJsonPath)) return JSON.parse(readFileSync(themeJsonPath, "utf-8"));
333
+ return extractEmailTheme(parseThemeFromString(readFileSync(join(cwd, WEB_DIR, ROOT_CSS_FILE), "utf-8")));
334
+ }
335
+ /**
336
+ * Creates an email theme from custom CSS.
337
+ *
338
+ * Parses CSS containing oklch color variables and converts them
339
+ * to email-safe hex format.
340
+ *
341
+ * @param css - CSS string containing theme variables in oklch format.
342
+ * @returns Email theme with hex colors and pixel values.
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * import { readFileSync } from "node:fs";
347
+ *
348
+ * const customCss = readFileSync("./my-theme.css", "utf-8");
349
+ * const theme = defineEmailTheme(customCss);
350
+ * ```
351
+ */
352
+ function defineEmailTheme(css) {
353
+ return extractEmailTheme(parseThemeFromString(css));
354
+ }
355
+ /**
356
+ * Extracts email-relevant keys from a full ParsedTheme.
357
+ *
358
+ * @param parsed - Full parsed theme from CSS.
359
+ * @returns Email theme subset.
360
+ */
361
+ function extractEmailTheme(parsed) {
362
+ return {
363
+ background: parsed.background,
364
+ foreground: parsed.foreground,
365
+ card: parsed.card,
366
+ cardForeground: parsed.cardForeground,
367
+ primary: parsed.primary,
368
+ primaryForeground: parsed.primaryForeground,
369
+ secondary: parsed.secondary,
370
+ secondaryForeground: parsed.secondaryForeground,
371
+ muted: parsed.muted,
372
+ mutedForeground: parsed.mutedForeground,
373
+ destructive: parsed.destructive,
374
+ border: parsed.border,
375
+ fontSans: parsed.fontSans,
376
+ fontMono: parsed.fontMono,
377
+ radius: parsed.radius
378
+ };
379
+ }
380
+ /**
381
+ * Generates a Tailwind CSS configuration object for email templates.
382
+ *
383
+ * Creates a Tailwind config with design system colors converted to
384
+ * email-safe hex format for use with @react-email/tailwind.
385
+ *
386
+ * @param theme - Email theme with hex colors.
387
+ * @returns Tailwind configuration object.
388
+ *
389
+ * @example
390
+ * ```typescript
391
+ * const theme = getDefaultEmailTheme();
392
+ * const tailwindConfig = defineEmailTailwindConfig(theme);
393
+ *
394
+ * <Tailwind config={tailwindConfig}>
395
+ * <Button className="bg-primary text-primary-foreground">Click me</Button>
396
+ * </Tailwind>
397
+ * ```
398
+ */
399
+ function defineEmailTailwindConfig(theme) {
400
+ return { theme: { extend: {
401
+ colors: {
402
+ background: theme.background,
403
+ foreground: theme.foreground,
404
+ card: {
405
+ DEFAULT: theme.card,
406
+ foreground: theme.cardForeground
407
+ },
408
+ primary: {
409
+ DEFAULT: theme.primary,
410
+ foreground: theme.primaryForeground
411
+ },
412
+ secondary: {
413
+ DEFAULT: theme.secondary,
414
+ foreground: theme.secondaryForeground
415
+ },
416
+ muted: {
417
+ DEFAULT: theme.muted,
418
+ foreground: theme.mutedForeground
419
+ },
420
+ destructive: { DEFAULT: theme.destructive },
421
+ border: theme.border
422
+ },
423
+ fontFamily: {
424
+ sans: theme.fontSans,
425
+ mono: theme.fontMono
426
+ },
427
+ borderRadius: {
428
+ DEFAULT: `${theme.radius}px`,
429
+ sm: `${Math.max(0, theme.radius - 4)}px`,
430
+ md: `${Math.max(0, theme.radius - 2)}px`,
431
+ lg: `${theme.radius}px`,
432
+ xl: `${theme.radius + 4}px`
433
+ }
434
+ } } };
435
+ }
436
+
437
+ //#endregion
438
+ //#region src/api/mailer/email-layout.tsx
439
+ /**
440
+ * Email layout component that wraps content with Tailwind CSS and design system colors.
441
+ *
442
+ * Provides a consistent email structure with:
443
+ * - Tailwind CSS classes using design system colors (converted to email-safe hex)
444
+ * - Responsive container with configurable max width
445
+ * - Preview text for email client list view
446
+ * - Language attribute for accessibility
447
+ *
448
+ * @example
449
+ * ```tsx
450
+ * import { EmailLayout, Heading, Text, Button, Section } from "appos/api/mailer";
451
+ *
452
+ * export default defineEmail({
453
+ * schema: z.object({ name: z.string(), t: z.custom<TFunction>() }),
454
+ * render: ({ t, name }) => (
455
+ * <EmailLayout preview={t("email:welcome.preview")}>
456
+ * <Section className="px-12 py-10">
457
+ * <Heading size="lg">{t("email:welcome.greeting", { name })}</Heading>
458
+ * <Text>{t("email:welcome.body")}</Text>
459
+ * <Button href="https://example.com">Get Started</Button>
460
+ * </Section>
461
+ * </EmailLayout>
462
+ * ),
463
+ * });
464
+ * ```
465
+ */
466
+ function EmailLayout({ children, preview, lang = "en", theme, maxWidth = 600, bodyClassName, containerClassName }) {
467
+ const tailwindConfig = defineEmailTailwindConfig(theme ?? getDefaultEmailTheme());
468
+ return /* @__PURE__ */ jsxs(Html$1, {
469
+ lang,
470
+ children: [
471
+ /* @__PURE__ */ jsx(Head$1, {}),
472
+ preview && /* @__PURE__ */ jsx(Preview$1, { children: preview }),
473
+ /* @__PURE__ */ jsx(Tailwind$1, {
474
+ config: tailwindConfig,
475
+ children: /* @__PURE__ */ jsx(Body$1, {
476
+ className: `bg-muted font-sans m-0 p-0 ${bodyClassName ?? ""}`.trim(),
477
+ children: /* @__PURE__ */ jsx(Container$1, {
478
+ className: `bg-background mx-auto my-10 rounded-xl ${containerClassName ?? ""}`.trim(),
479
+ style: { maxWidth: `${maxWidth}px` },
480
+ children
481
+ })
482
+ })
483
+ })
484
+ ]
485
+ });
486
+ }
487
+
488
+ //#endregion
9
489
  //#region src/api/mailer.ts
490
+ /**
491
+ * Defines a type-safe email template with runtime validation.
492
+ *
493
+ * Provides:
494
+ * - Type-safe props inferred from Zod schema
495
+ * - Runtime validation with safeParse (applies defaults, catches errors)
496
+ * - Schema attached to component for devtools preview generation
497
+ *
498
+ * @template TSchema - Zod object schema defining the email props.
499
+ * @param options - Email template options with schema and render function.
500
+ * @returns Email component with attached schema.
501
+ *
502
+ * @example
503
+ * ```tsx
504
+ * import { defineEmail } from "appos/api/mailer";
505
+ * import type { TFunction } from "i18next";
506
+ * import { z } from "appos/universal/zod";
507
+ * import { Html, Text } from "@react-email/components";
508
+ *
509
+ * export default defineEmail({
510
+ * schema: z.object({
511
+ * t: z.custom<TFunction<"email">>(),
512
+ * name: z.string().default("there"),
513
+ * verificationUrl: z.url().optional(),
514
+ * }),
515
+ * render: ({ t, name, verificationUrl }) => (
516
+ * <Html>
517
+ * <Text>{t("email:welcome.greeting", { name })}</Text>
518
+ * </Html>
519
+ * ),
520
+ * });
521
+ *
522
+ * // Usage in route handler:
523
+ * const t = container.i18n.getFixedT(user.locale, ["email"]);
524
+ * await container.mailer.send({
525
+ * to: user.email,
526
+ * subject: t("email:welcome.subject"),
527
+ * react: <WelcomeEmail t={t} name={user.name} />,
528
+ * });
529
+ * ```
530
+ */
531
+ function defineEmail(options) {
532
+ const component = (props) => {
533
+ const result = options.schema.safeParse(props);
534
+ if (!result.success) throw new Error(`[defineEmail] Invalid props: ${result.error.message}`);
535
+ return options.render(result.data);
536
+ };
537
+ component.schema = options.schema;
538
+ return component;
539
+ }
10
540
  async function defineMailer(options) {
11
541
  const { from, smtpUrl } = options;
12
542
  const queue = "queue" in options ? options.queue : void 0;
@@ -17,7 +547,7 @@ async function defineMailer(options) {
17
547
  let text;
18
548
  let mailOptions;
19
549
  if ("react" in payload) {
20
- [html, text] = await Promise.all([render(payload.react), render(payload.react, { plainText: true })]);
550
+ [html, text] = await Promise.all([render$1(payload.react), render$1(payload.react, { plainText: true })]);
21
551
  const { react: _, ...rest } = payload;
22
552
  mailOptions = rest;
23
553
  } else {
@@ -38,7 +568,7 @@ async function defineMailer(options) {
38
568
  let text;
39
569
  let restPayload;
40
570
  if ("react" in payload) {
41
- [html, text] = await Promise.all([render(payload.react), render(payload.react, { plainText: true })]);
571
+ [html, text] = await Promise.all([render$1(payload.react), render$1(payload.react, { plainText: true })]);
42
572
  const { react: _, ...rest } = payload;
43
573
  restPayload = rest;
44
574
  } else {
@@ -61,4 +591,4 @@ async function defineMailer(options) {
61
591
  }
62
592
 
63
593
  //#endregion
64
- export { defineMailer };
594
+ export { Body, Button, CodeBlock, CodeInline, EMAIL_THEME_KEYS, EmailColumn, Container as EmailContainer, EmailLayout, Font, Head, Heading, Hr, Html, Img, Link, Markdown, Preview, Row, Section, Tailwind, Text, defineEmail, defineEmailTailwindConfig, defineEmailTheme, defineMailer, emailButtonSizeKeys, emailButtonVariantKeys, emailContainerVariantKeys, emailHeadingSizeKeys, emailImgVariantKeys, emailTextVariantKeys, getDefaultEmailTheme, plainTextSelectors, pretty, render, toPlainText };
@@ -1,4 +1,4 @@
1
- import { n as Logger, r as defineLogger, t as DefineLoggerOptions } from "../logger-BJ2PCtBR.mjs";
2
- import "../instrumentation-B8fU6rcD.mjs";
3
- import { n as withOtelSpan } from "../observability-DvkWyfwr.mjs";
1
+ import { n as Logger, r as defineLogger, t as DefineLoggerOptions } from "../logger-C8av5E6b.mjs";
2
+ import "../instrumentation-DL2LQsyM.mjs";
3
+ import { n as withOtelSpan } from "../observability-D9z-cqNJ.mjs";
4
4
  export { DefineLoggerOptions, Logger, defineLogger, withOtelSpan };
@@ -1,6 +1,6 @@
1
- import "../logger-BJ2PCtBR.mjs";
2
- import "../database-ltnXGvKZ.mjs";
3
- import "../auth-BzQ2oAZ8.mjs";
4
- import "../cache-B-TvutDL.mjs";
5
- import { $ as OpenAPIRegistration, J as DefineOpenAPIConfigInput, Q as OpenAPIObjectConfigV31, X as HandlerParams, Y as DefineOpenAPIReturn, Z as OpenAPIMethodSpec, at as defineTypedResponses, ct as registerRoutes, et as RouteModule, it as defineOpenAPIEndpoint, lt as scanAPIRoutes, nt as defineOpenAPI, ot as generateOpenAPIDocument, q as DefineOpenAPIConfig, rt as defineOpenAPIConfig, st as loadAndRegisterAPIRoutes, tt as ValidationErrorResponse, ut as writeOpenAPISpecs } from "../container-C00pnItg.mjs";
1
+ import "../logger-C8av5E6b.mjs";
2
+ import "../database-Cch2A-2M.mjs";
3
+ import "../auth-COwDukbw.mjs";
4
+ import "../cache-D1ATIulZ.mjs";
5
+ import { $t as DefineOpenAPIConfigInput, Qt as DefineOpenAPIConfig, an as RouteModule, cn as defineOpenAPIConfig, dn as generateOpenAPIDocument, en as DefineOpenAPIReturn, fn as loadAndRegisterAPIRoutes, hn as writeOpenAPISpecs, in as OpenAPIRegistration, ln as defineOpenAPIEndpoint, mn as scanAPIRoutes, nn as OpenAPIMethodSpec, on as ValidationErrorResponse, pn as registerRoutes, rn as OpenAPIObjectConfigV31, sn as defineOpenAPI, tn as HandlerParams, un as defineTypedResponses } from "../container-qBBwSMpE.mjs";
6
6
  export { DefineOpenAPIConfig, DefineOpenAPIConfigInput, DefineOpenAPIReturn, HandlerParams, OpenAPIMethodSpec, OpenAPIObjectConfigV31, OpenAPIRegistration, RouteModule, ValidationErrorResponse, defineOpenAPI, defineOpenAPIConfig, defineOpenAPIEndpoint, defineTypedResponses, generateOpenAPIDocument, loadAndRegisterAPIRoutes, registerRoutes, scanAPIRoutes, writeOpenAPISpecs };
@@ -1,5 +1,5 @@
1
- import "../constants-BLEvvEa7.mjs";
1
+ import "../constants-Cn8qeyGD.mjs";
2
2
  import "../app-context-CaxDuPj-.mjs";
3
- import { a as generateOpenAPIDocument, c as scanAPIRoutes, i as defineTypedResponses, l as writeOpenAPISpecs, n as defineOpenAPIConfig, o as loadAndRegisterAPIRoutes, r as defineOpenAPIEndpoint, s as registerRoutes, t as defineOpenAPI } from "../openapi-C2GOpT-O.mjs";
3
+ import { a as generateOpenAPIDocument, c as scanAPIRoutes, i as defineTypedResponses, l as writeOpenAPISpecs, n as defineOpenAPIConfig, o as loadAndRegisterAPIRoutes, r as defineOpenAPIEndpoint, s as registerRoutes, t as defineOpenAPI } from "../openapi-kCcmkZ30.mjs";
4
4
 
5
5
  export { defineOpenAPI, defineOpenAPIConfig, defineOpenAPIEndpoint, defineTypedResponses, generateOpenAPIDocument, loadAndRegisterAPIRoutes, registerRoutes, scanAPIRoutes, writeOpenAPISpecs };
@@ -1,6 +1,6 @@
1
- import "../logger-BJ2PCtBR.mjs";
2
- import "../database-ltnXGvKZ.mjs";
3
- import "../auth-BzQ2oAZ8.mjs";
4
- import "../cache-B-TvutDL.mjs";
5
- import { St as i18n, _t as ServerInstance, bt as defaultI18nConfig, dt as Middleware, ft as defineMiddleware, gt as Server, ht as DefineServerOptions, mt as App, pt as loadMiddleware, vt as defineServer, xt as defineI18n, yt as I18nInitOptions } from "../container-C00pnItg.mjs";
1
+ import "../logger-C8av5E6b.mjs";
2
+ import "../database-Cch2A-2M.mjs";
3
+ import "../auth-COwDukbw.mjs";
4
+ import "../cache-D1ATIulZ.mjs";
5
+ import { Cn as defineServer, Dn as i18n, En as defineI18n, Sn as ServerInstance, Tn as defaultI18nConfig, _n as defineMiddleware, bn as DefineServerOptions, gn as Middleware, vn as loadMiddleware, wn as I18nInitOptions, xn as Server, yn as App } from "../container-qBBwSMpE.mjs";
6
6
  export { App, DefineServerOptions, I18nInitOptions, Middleware, Server, ServerInstance, defaultI18nConfig, defineI18n, defineMiddleware, defineServer, i18n, loadMiddleware };
@@ -1,8 +1,8 @@
1
- import "../constants-BLEvvEa7.mjs";
2
- import { a as loadMiddleware, i as defineMiddleware, n as defaultI18nConfig, r as defineI18n, t as defineServer } from "../server-D_7v-BCM.mjs";
1
+ import "../constants-Cn8qeyGD.mjs";
2
+ import { a as loadMiddleware, i as defineMiddleware, n as defaultI18nConfig, r as defineI18n, t as defineServer } from "../server-BNk1lm5r.mjs";
3
3
  import "../instrumentation-DCouyaIn.mjs";
4
4
  import "../app-context-CaxDuPj-.mjs";
5
- import "../openapi-C2GOpT-O.mjs";
5
+ import "../openapi-kCcmkZ30.mjs";
6
6
  import "../context-9Yd81kh5.mjs";
7
7
 
8
8
  export { defaultI18nConfig, defineI18n, defineMiddleware, defineServer, loadMiddleware };
@@ -1,8 +1,8 @@
1
- import "../logger-BJ2PCtBR.mjs";
2
- import "../database-ltnXGvKZ.mjs";
3
- import { m as AuthPaths, o as AuthConfig } from "../auth-BzQ2oAZ8.mjs";
4
- import "../cache-B-TvutDL.mjs";
5
- import { wt as AppContext } from "../container-C00pnItg.mjs";
1
+ import "../logger-C8av5E6b.mjs";
2
+ import "../database-Cch2A-2M.mjs";
3
+ import { m as AuthPaths, o as AuthConfig } from "../auth-COwDukbw.mjs";
4
+ import "../cache-D1ATIulZ.mjs";
5
+ import { kn as AppContext } from "../container-qBBwSMpE.mjs";
6
6
  import * as react_router0 from "react-router";
7
7
  import { RouterContextProvider } from "react-router";
8
8
  import { GetLoadContextFunction } from "@react-router/express";
@@ -1,6 +1,6 @@
1
- import "../logger-BJ2PCtBR.mjs";
2
- import "../database-ltnXGvKZ.mjs";
3
- import "../auth-BzQ2oAZ8.mjs";
4
- import "../cache-B-TvutDL.mjs";
5
- import { A as DatabaseWithStorage, B as StorageAttachment, F as defineS3Disk, G as StorageVariantRecord, H as StorageRelations, I as defineStorage, K as defineStorageSchema, L as NewStorageAttachment, M as DefineStorageOptions, N as Storage, P as StorageService, R as NewStorageBlob, U as StorageRelationsConfig, V as StorageBlob, W as StorageTables, j as DefineS3DiskOptions, z as NewStorageVariantRecord } from "../container-C00pnItg.mjs";
1
+ import "../logger-C8av5E6b.mjs";
2
+ import "../database-Cch2A-2M.mjs";
3
+ import "../auth-COwDukbw.mjs";
4
+ import "../cache-D1ATIulZ.mjs";
5
+ import { Bt as defineS3Disk, Ft as DatabaseWithStorage, Gt as StorageAttachment, Ht as NewStorageAttachment, It as DefineS3DiskOptions, Jt as StorageRelationsConfig, Kt as StorageBlob, Lt as DefineStorageOptions, Rt as Storage, Ut as NewStorageBlob, Vt as defineStorage, Wt as NewStorageVariantRecord, Xt as StorageVariantRecord, Yt as StorageTables, Zt as defineStorageSchema, qt as StorageRelations, zt as StorageService } from "../container-qBBwSMpE.mjs";
6
6
  export { DatabaseWithStorage, DefineS3DiskOptions, DefineStorageOptions, NewStorageAttachment, NewStorageBlob, NewStorageVariantRecord, Storage, StorageAttachment, StorageBlob, StorageRelations, StorageRelationsConfig, StorageService, StorageTables, StorageVariantRecord, defineS3Disk, defineStorage, defineStorageSchema };
@@ -1,8 +1,8 @@
1
1
  import "../auth-schema-OUnjUQlj.mjs";
2
2
  import "../redis-a5sw5J3L.mjs";
3
- import "../constants-BLEvvEa7.mjs";
4
- import "../event-DeqWH7f1.mjs";
5
- import { c as generatePreview, l as generateImageVariant, o as purgeAttachment, u as extractBlobMetadata } from "../workflow-MLL2Z1Jv.mjs";
3
+ import "../constants-Cn8qeyGD.mjs";
4
+ import "../event-BFYD8Mmy.mjs";
5
+ import { c as generatePreview, l as generateImageVariant, o as purgeAttachment, u as extractBlobMetadata } from "../workflow-BN-NfNzZ.mjs";
6
6
  import { and, eq, isNull, lt, sql } from "drizzle-orm";
7
7
  import { index, pgTable, unique } from "drizzle-orm/pg-core";
8
8
  import { join } from "node:path";