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.
- package/dist/api/auth.d.mts +3 -3
- package/dist/api/cache.d.mts +2 -2
- package/dist/api/cli.d.mts +5 -5
- package/dist/api/cli.mjs +35 -14
- package/dist/api/container.d.mts +5 -5
- package/dist/api/database.d.mts +2 -2
- package/dist/api/database.mjs +2 -2
- package/dist/api/event.d.mts +5 -5
- package/dist/api/event.mjs +2 -2
- package/dist/api/instrumentation.d.mts +1 -1
- package/dist/api/mailer.d.mts +6 -6
- package/dist/api/mailer.mjs +537 -7
- package/dist/api/observability.d.mts +3 -3
- package/dist/api/openapi.d.mts +5 -5
- package/dist/api/openapi.mjs +2 -2
- package/dist/api/server.d.mts +5 -5
- package/dist/api/server.mjs +3 -3
- package/dist/api/ssr.d.mts +5 -5
- package/dist/api/storage.d.mts +5 -5
- package/dist/api/storage.mjs +3 -3
- package/dist/api/workflow.d.mts +5 -5
- package/dist/api/workflow.mjs +3 -3
- package/dist/{auth-D56zivoE.d.ts → auth-1uQuSjl_.d.ts} +631 -563
- package/dist/{auth-BzQ2oAZ8.d.mts → auth-COwDukbw.d.mts} +311 -567
- package/dist/{brand-DhfRxUs9.d.ts → brand-CIdvO6yY.d.ts} +4 -4
- package/dist/{cache-B-TvutDL.d.mts → cache-D1ATIulZ.d.mts} +1 -1
- package/dist/colors-AFh2mCN3.mjs +144 -0
- package/dist/colors-CtIXNATb.mjs +3 -0
- package/dist/{constants-BLEvvEa7.mjs → constants-Cn8qeyGD.mjs} +52 -1
- package/dist/{container-C00pnItg.d.mts → container-qBBwSMpE.d.mts} +700 -110
- package/dist/{database-ltnXGvKZ.d.mts → database-Cch2A-2M.d.mts} +1 -1
- package/dist/{database-A6tffVCD.mjs → database-s-rZuCOz.mjs} +1 -1
- package/dist/devtools/test.d.mts +7 -7
- package/dist/devtools/test.mjs +4 -4
- package/dist/devtools/vite.d.mts +60 -5
- package/dist/devtools/vite.mjs +139 -3
- package/dist/{event-DeqWH7f1.mjs → event-BFYD8Mmy.mjs} +1 -1
- package/dist/{observability-DvkWyfwr.d.mts → observability-D9z-cqNJ.d.mts} +2 -2
- package/dist/{openapi-C2GOpT-O.mjs → openapi-kCcmkZ30.mjs} +1 -1
- package/dist/panel-C2TDalNf.mjs +5 -0
- package/dist/panel-CpZWj60p.mjs +57 -0
- package/dist/routes-B9VlLVFz.mjs +1123 -0
- package/dist/routes-Bkg2Mqxd.mjs +4 -0
- package/dist/{server-D_7v-BCM.mjs → server-BNk1lm5r.mjs} +355 -32
- package/dist/web/auth.d.ts +2 -2
- package/dist/web/i18n.js +1 -0
- package/dist/web/ui/accordion.d.ts +5 -5
- package/dist/web/ui/alert-dialog.d.ts +13 -13
- package/dist/web/ui/alert.d.ts +5 -5
- package/dist/web/ui/animated-check.d.ts +2 -2
- package/dist/web/ui/aspect-ratio.d.ts +2 -2
- package/dist/web/ui/auth.d.ts +2 -2
- package/dist/web/ui/avatar.d.ts +7 -7
- package/dist/web/ui/badge.d.ts +1 -1
- package/dist/web/ui/brand.d.ts +1 -1
- package/dist/web/ui/breadcrumb.d.ts +7 -7
- package/dist/web/ui/button-group.d.ts +5 -5
- package/dist/web/ui/button.d.ts +5 -5
- package/dist/web/ui/calendar.d.ts +3 -3
- package/dist/web/ui/card.d.ts +8 -8
- package/dist/web/ui/carousel.d.ts +6 -6
- package/dist/web/ui/chart.d.ts +5 -5
- package/dist/web/ui/checkbox.d.ts +2 -2
- package/dist/web/ui/collapsible.d.ts +4 -4
- package/dist/web/ui/command.d.ts +10 -10
- package/dist/web/ui/context-menu.d.ts +16 -16
- package/dist/web/ui/dialog.d.ts +11 -11
- package/dist/web/ui/drawer.d.ts +11 -11
- package/dist/web/ui/dropdown-menu.d.ts +16 -16
- package/dist/web/ui/empty.d.ts +9 -9
- package/dist/web/ui/error-page.d.ts +2 -2
- package/dist/web/ui/field.d.ts +13 -13
- package/dist/web/ui/hover-card.d.ts +4 -4
- package/dist/web/ui/input-group.d.ts +10 -10
- package/dist/web/ui/input-otp.d.ts +5 -5
- package/dist/web/ui/input.d.ts +2 -2
- package/dist/web/ui/item.d.ts +13 -13
- package/dist/web/ui/kbd.d.ts +3 -3
- package/dist/web/ui/label.d.ts +2 -2
- package/dist/web/ui/menubar.d.ts +17 -17
- package/dist/web/ui/navigation-menu.d.ts +11 -11
- package/dist/web/ui/pagination.d.ts +8 -8
- package/dist/web/ui/popover.d.ts +7 -7
- package/dist/web/ui/progress.d.ts +6 -6
- package/dist/web/ui/radio-group.d.ts +3 -3
- package/dist/web/ui/resizable.d.ts +4 -4
- package/dist/web/ui/scroll-area.d.ts +3 -3
- package/dist/web/ui/select.d.ts +10 -10
- package/dist/web/ui/separator.d.ts +2 -2
- package/dist/web/ui/sheet.d.ts +9 -9
- package/dist/web/ui/sidebar.d.ts +22 -22
- package/dist/web/ui/sidebar.js +1 -1
- package/dist/web/ui/skeleton.d.ts +2 -2
- package/dist/web/ui/slider.d.ts +2 -2
- package/dist/web/ui/sonner.d.ts +2 -2
- package/dist/web/ui/spinner.d.ts +2 -2
- package/dist/web/ui/switch.d.ts +2 -2
- package/dist/web/ui/table.d.ts +9 -9
- package/dist/web/ui/tabs.d.ts +7 -7
- package/dist/web/ui/textarea.d.ts +2 -2
- package/dist/web/ui/toggle-group.d.ts +3 -3
- package/dist/web/ui/toggle.d.ts +4 -4
- package/dist/web/ui/tooltip.d.ts +5 -5
- package/dist/{workflow-MLL2Z1Jv.mjs → workflow-BN-NfNzZ.mjs} +2 -2
- package/package.json +28 -13
- package/src/devtools/panel/index.ts +114 -0
- package/src/devtools/panel/modules/emails/compatibility.ts +289 -0
- package/src/devtools/panel/modules/emails/discovery.ts +243 -0
- package/src/devtools/panel/modules/emails/linter.ts +501 -0
- package/src/devtools/panel/modules/emails/render.ts +155 -0
- package/src/devtools/panel/modules/emails/routes.ts +502 -0
- package/src/devtools/panel/modules/emails/schema.ts +82 -0
- package/src/devtools/panel/modules/emails/spam-checker.ts +231 -0
- package/src/devtools/storybook/.storybook/main.ts +9 -1
- package/src/devtools/storybook/.storybook/manager.ts +1 -1
- package/src/devtools/storybook/.storybook/preview.tsx +6 -0
- package/src/web/client/i18n.d.ts +2 -0
- package/src/web/client/i18n.ts +6 -1
- package/src/web/devtools/index.ts +1 -0
- package/src/web/devtools/modules/dashboard.tsx +151 -0
- package/src/web/devtools/modules/emails.tsx +1342 -0
- package/src/web/devtools/overlay.tsx +109 -0
- package/src/web/devtools/panel.tsx +353 -0
- package/src/web/index.ts +1 -0
- package/src/web/ui/globals.css +2 -1
- package/src/web/ui/locales/en/devtools.json +102 -0
- package/src/web/ui/locales/zh-CN/devtools.json +102 -0
- package/src/web/ui/locales/zh-TW/devtools.json +102 -0
- package/src/web/ui/sidebar.tsx +1 -1
- /package/dist/{instrumentation-B8fU6rcD.d.mts → instrumentation-DL2LQsyM.d.mts} +0 -0
- /package/dist/{logger-BJ2PCtBR.d.mts → logger-C8av5E6b.d.mts} +0 -0
- /package/dist/{vite-compat-gu8Mi4cu.mjs → vite-compat-DaoxY3IL.mjs} +0 -0
- /package/dist/{youch-handler-DeoaVZPR.mjs → youch-handler-DfBgXQra.mjs} +0 -0
- /package/src/{web/lib → universal}/colors.ts +0 -0
package/dist/api/mailer.mjs
CHANGED
|
@@ -1,12 +1,542 @@
|
|
|
1
1
|
import "../auth-schema-OUnjUQlj.mjs";
|
|
2
2
|
import "../redis-a5sw5J3L.mjs";
|
|
3
|
-
import "../constants-
|
|
4
|
-
import "../event-
|
|
5
|
-
import { a as sendEmail } from "../workflow-
|
|
6
|
-
import {
|
|
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-
|
|
2
|
-
import "../instrumentation-
|
|
3
|
-
import { n as withOtelSpan } from "../observability-
|
|
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 };
|
package/dist/api/openapi.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "../logger-
|
|
2
|
-
import "../database-
|
|
3
|
-
import "../auth-
|
|
4
|
-
import "../cache-
|
|
5
|
-
import { $ as
|
|
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 };
|
package/dist/api/openapi.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "../constants-
|
|
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-
|
|
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 };
|
package/dist/api/server.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "../logger-
|
|
2
|
-
import "../database-
|
|
3
|
-
import "../auth-
|
|
4
|
-
import "../cache-
|
|
5
|
-
import {
|
|
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 };
|
package/dist/api/server.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "../constants-
|
|
2
|
-
import { a as loadMiddleware, i as defineMiddleware, n as defaultI18nConfig, r as defineI18n, t as defineServer } from "../server-
|
|
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-
|
|
5
|
+
import "../openapi-kCcmkZ30.mjs";
|
|
6
6
|
import "../context-9Yd81kh5.mjs";
|
|
7
7
|
|
|
8
8
|
export { defaultI18nConfig, defineI18n, defineMiddleware, defineServer, loadMiddleware };
|
package/dist/api/ssr.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "../logger-
|
|
2
|
-
import "../database-
|
|
3
|
-
import { m as AuthPaths, o as AuthConfig } from "../auth-
|
|
4
|
-
import "../cache-
|
|
5
|
-
import {
|
|
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";
|
package/dist/api/storage.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "../logger-
|
|
2
|
-
import "../database-
|
|
3
|
-
import "../auth-
|
|
4
|
-
import "../cache-
|
|
5
|
-
import {
|
|
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 };
|
package/dist/api/storage.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "../auth-schema-OUnjUQlj.mjs";
|
|
2
2
|
import "../redis-a5sw5J3L.mjs";
|
|
3
|
-
import "../constants-
|
|
4
|
-
import "../event-
|
|
5
|
-
import { c as generatePreview, l as generateImageVariant, o as purgeAttachment, u as extractBlobMetadata } from "../workflow-
|
|
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";
|