@holo-js/mail 0.2.5 → 0.3.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/{chunk-BQCGZOVF.mjs → chunk-7CZAQPEH.mjs} +0 -2
- package/dist/chunk-YPNVXZL6.mjs +219 -0
- package/dist/config.d.ts +119 -0
- package/dist/config.mjs +20 -0
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.mjs +4 -2
- package/dist/index.d.ts +21 -17
- package/dist/index.mjs +205 -98
- package/package.json +10 -4
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/contracts-types.ts
|
|
2
|
-
import { defineMailConfig } from "@holo-js/config";
|
|
3
2
|
var HOLO_MAIL_DEFINITION_MARKER = /* @__PURE__ */ Symbol.for("holo-js.mail.definition");
|
|
4
3
|
var BUILT_IN_MAIL_DRIVERS = ["preview", "log", "fake", "smtp"];
|
|
5
4
|
var MAIL_PRIORITY_VALUES = ["high", "normal", "low"];
|
|
@@ -505,7 +504,6 @@ function attachContent(content, options) {
|
|
|
505
504
|
}
|
|
506
505
|
|
|
507
506
|
export {
|
|
508
|
-
defineMailConfig,
|
|
509
507
|
HOLO_MAIL_DEFINITION_MARKER,
|
|
510
508
|
BUILT_IN_MAIL_DRIVERS,
|
|
511
509
|
MAIL_PRIORITY_VALUES,
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { registerConfigNormalizer } from "@holo-js/config/registry";
|
|
3
|
+
function normalizeMailEnvironment(value, fallback = "development") {
|
|
4
|
+
return value === "development" || value === "production" || value === "test" ? value : fallback;
|
|
5
|
+
}
|
|
6
|
+
var DEFAULT_MAILER_NAME = "preview";
|
|
7
|
+
var DEFAULT_MAIL_PREVIEW_PATH = ".holo-js/runtime/mail-preview";
|
|
8
|
+
var DEFAULT_SMTP_HOST = "127.0.0.1";
|
|
9
|
+
var DEFAULT_SMTP_PORT = 1025;
|
|
10
|
+
var DEFAULT_MAIL_QUEUE_CONFIG = Object.freeze({
|
|
11
|
+
queued: false,
|
|
12
|
+
connection: void 0,
|
|
13
|
+
queue: void 0,
|
|
14
|
+
afterCommit: false
|
|
15
|
+
});
|
|
16
|
+
var holoMailDefaults = Object.freeze({
|
|
17
|
+
default: DEFAULT_MAILER_NAME,
|
|
18
|
+
from: void 0,
|
|
19
|
+
replyTo: void 0,
|
|
20
|
+
queue: DEFAULT_MAIL_QUEUE_CONFIG,
|
|
21
|
+
preview: Object.freeze({
|
|
22
|
+
allowedEnvironments: Object.freeze(["development"])
|
|
23
|
+
}),
|
|
24
|
+
markdown: Object.freeze({
|
|
25
|
+
wrapper: void 0
|
|
26
|
+
}),
|
|
27
|
+
mailers: Object.freeze({
|
|
28
|
+
preview: Object.freeze({
|
|
29
|
+
name: "preview",
|
|
30
|
+
driver: "preview",
|
|
31
|
+
from: void 0,
|
|
32
|
+
replyTo: void 0,
|
|
33
|
+
queue: DEFAULT_MAIL_QUEUE_CONFIG,
|
|
34
|
+
path: DEFAULT_MAIL_PREVIEW_PATH
|
|
35
|
+
}),
|
|
36
|
+
log: Object.freeze({
|
|
37
|
+
name: "log",
|
|
38
|
+
driver: "log",
|
|
39
|
+
from: void 0,
|
|
40
|
+
replyTo: void 0,
|
|
41
|
+
queue: DEFAULT_MAIL_QUEUE_CONFIG,
|
|
42
|
+
logBodies: false
|
|
43
|
+
}),
|
|
44
|
+
fake: Object.freeze({
|
|
45
|
+
name: "fake",
|
|
46
|
+
driver: "fake",
|
|
47
|
+
from: void 0,
|
|
48
|
+
replyTo: void 0,
|
|
49
|
+
queue: DEFAULT_MAIL_QUEUE_CONFIG
|
|
50
|
+
}),
|
|
51
|
+
smtp: Object.freeze({
|
|
52
|
+
name: "smtp",
|
|
53
|
+
driver: "smtp",
|
|
54
|
+
from: void 0,
|
|
55
|
+
replyTo: void 0,
|
|
56
|
+
queue: DEFAULT_MAIL_QUEUE_CONFIG,
|
|
57
|
+
host: DEFAULT_SMTP_HOST,
|
|
58
|
+
port: DEFAULT_SMTP_PORT,
|
|
59
|
+
secure: false
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
function normalizeOptionalMailString(value, label) {
|
|
64
|
+
if (typeof value === "undefined") {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
const normalized = value.trim();
|
|
68
|
+
if (!normalized) {
|
|
69
|
+
throw new Error(`[Holo Mail] ${label} must be a non-empty string when provided.`);
|
|
70
|
+
}
|
|
71
|
+
return normalized;
|
|
72
|
+
}
|
|
73
|
+
function isValidMailAddress(email) {
|
|
74
|
+
if (email.includes(" ")) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const parts = email.split("@");
|
|
78
|
+
return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
|
|
79
|
+
}
|
|
80
|
+
function normalizeMailAddress(address, label) {
|
|
81
|
+
if (!address) {
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
const email = normalizeOptionalMailString(address.email, `${label} email`)?.toLowerCase();
|
|
85
|
+
if (!email || !isValidMailAddress(email)) {
|
|
86
|
+
throw new Error(`[Holo Mail] ${label} email must be a valid email address.`);
|
|
87
|
+
}
|
|
88
|
+
const name = normalizeOptionalMailString(address.name, `${label} name`);
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
email,
|
|
91
|
+
...name ? { name } : {}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function normalizeMailQueueConfig(queue, fallback = holoMailDefaults.queue) {
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
queued: queue?.queued ?? fallback.queued,
|
|
97
|
+
connection: normalizeOptionalMailString(queue?.connection, "Mail queue connection") ?? fallback.connection,
|
|
98
|
+
queue: normalizeOptionalMailString(queue?.queue, "Mail queue name") ?? fallback.queue,
|
|
99
|
+
afterCommit: queue?.afterCommit ?? fallback.afterCommit
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function normalizeMailPreviewEnvironments(environments) {
|
|
103
|
+
if (typeof environments === "undefined") {
|
|
104
|
+
return holoMailDefaults.preview.allowedEnvironments;
|
|
105
|
+
}
|
|
106
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
107
|
+
for (const environment of environments) {
|
|
108
|
+
if (environment !== "development" && environment !== "production" && environment !== "test") {
|
|
109
|
+
throw new Error("[Holo Mail] Mail preview environments must be development, production, or test.");
|
|
110
|
+
}
|
|
111
|
+
normalized.add(environment);
|
|
112
|
+
}
|
|
113
|
+
return Object.freeze([...normalized]);
|
|
114
|
+
}
|
|
115
|
+
function normalizeMailMailerConfig(name, config, fallback) {
|
|
116
|
+
const normalizedName = normalizeOptionalMailString(name, "Mail mailer name");
|
|
117
|
+
const driver = normalizeOptionalMailString(config.driver, `Mail mailer "${name}" driver`);
|
|
118
|
+
if (!normalizedName || !driver) {
|
|
119
|
+
throw new Error("[Holo Mail] Mailers must define a name and driver.");
|
|
120
|
+
}
|
|
121
|
+
const base = {
|
|
122
|
+
name: normalizedName,
|
|
123
|
+
driver,
|
|
124
|
+
from: normalizeMailAddress(config.from, `Mail mailer "${name}" from`) ?? fallback?.from,
|
|
125
|
+
replyTo: normalizeMailAddress(config.replyTo, `Mail mailer "${name}" replyTo`) ?? fallback?.replyTo,
|
|
126
|
+
queue: normalizeMailQueueConfig(config.queue, fallback?.queue ?? holoMailDefaults.queue)
|
|
127
|
+
};
|
|
128
|
+
if (driver === "preview") {
|
|
129
|
+
const previewFallback = fallback?.driver === "preview" ? fallback : void 0;
|
|
130
|
+
return Object.freeze({
|
|
131
|
+
...base,
|
|
132
|
+
driver: "preview",
|
|
133
|
+
path: normalizeOptionalMailString(config.path, `Mail mailer "${name}" preview path`) ?? previewFallback?.path ?? DEFAULT_MAIL_PREVIEW_PATH
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (driver === "log") {
|
|
137
|
+
const logFallback = fallback?.driver === "log" ? fallback : void 0;
|
|
138
|
+
return Object.freeze({
|
|
139
|
+
...base,
|
|
140
|
+
driver: "log",
|
|
141
|
+
logBodies: config.logBodies ?? logFallback?.logBodies ?? false
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (driver === "fake") {
|
|
145
|
+
return Object.freeze({
|
|
146
|
+
...base,
|
|
147
|
+
driver: "fake"
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (driver === "smtp") {
|
|
151
|
+
const smtpFallback = fallback?.driver === "smtp" ? fallback : void 0;
|
|
152
|
+
const rawPort = config.port;
|
|
153
|
+
const normalizedPort = typeof rawPort === "number" ? rawPort : typeof rawPort === "string" && rawPort.trim() ? Number(rawPort.trim()) : smtpFallback?.port ?? DEFAULT_SMTP_PORT;
|
|
154
|
+
if (!Number.isFinite(normalizedPort) || normalizedPort <= 0) {
|
|
155
|
+
throw new Error(`[Holo Mail] Mail mailer "${name}" SMTP port must be a positive number.`);
|
|
156
|
+
}
|
|
157
|
+
return Object.freeze({
|
|
158
|
+
...base,
|
|
159
|
+
driver: "smtp",
|
|
160
|
+
host: normalizeOptionalMailString(config.host, `Mail mailer "${name}" SMTP host`) ?? smtpFallback?.host ?? DEFAULT_SMTP_HOST,
|
|
161
|
+
port: normalizedPort,
|
|
162
|
+
secure: config.secure ?? smtpFallback?.secure ?? false,
|
|
163
|
+
user: normalizeOptionalMailString(config.user, `Mail mailer "${name}" SMTP user`) ?? smtpFallback?.user ?? void 0,
|
|
164
|
+
password: normalizeOptionalMailString(config.password, `Mail mailer "${name}" SMTP password`) ?? smtpFallback?.password ?? void 0
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const customFields = Object.fromEntries(
|
|
168
|
+
Object.entries(config).filter(([key]) => key !== "driver" && key !== "from" && key !== "replyTo" && key !== "queue")
|
|
169
|
+
);
|
|
170
|
+
return Object.freeze({
|
|
171
|
+
...base,
|
|
172
|
+
...customFields
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function normalizeMailConfig(config = {}) {
|
|
176
|
+
const mergedMailers = {
|
|
177
|
+
...holoMailDefaults.mailers
|
|
178
|
+
};
|
|
179
|
+
for (const [name, mailer] of Object.entries(config.mailers ?? {})) {
|
|
180
|
+
mergedMailers[name] = normalizeMailMailerConfig(name, mailer, mergedMailers[name]);
|
|
181
|
+
}
|
|
182
|
+
const defaultMailer = normalizeOptionalMailString(config.default, "Default mailer") ?? holoMailDefaults.default;
|
|
183
|
+
if (!mergedMailers[defaultMailer]) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`[Holo Mail] default mailer "${defaultMailer}" is not configured. Available mailers: ${Object.keys(mergedMailers).join(", ")}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return Object.freeze({
|
|
189
|
+
default: defaultMailer,
|
|
190
|
+
from: normalizeMailAddress(config.from, "Mail from") ?? holoMailDefaults.from,
|
|
191
|
+
replyTo: normalizeMailAddress(config.replyTo, "Mail replyTo") ?? holoMailDefaults.replyTo,
|
|
192
|
+
queue: normalizeMailQueueConfig(config.queue),
|
|
193
|
+
preview: Object.freeze({
|
|
194
|
+
allowedEnvironments: normalizeMailPreviewEnvironments(config.preview?.allowedEnvironments)
|
|
195
|
+
}),
|
|
196
|
+
markdown: Object.freeze({
|
|
197
|
+
wrapper: normalizeOptionalMailString(config.markdown?.wrapper, "Mail markdown wrapper") ?? holoMailDefaults.markdown.wrapper
|
|
198
|
+
}),
|
|
199
|
+
mailers: Object.freeze(mergedMailers)
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function defineMailConfig(config) {
|
|
203
|
+
return Object.freeze({ ...config });
|
|
204
|
+
}
|
|
205
|
+
registerConfigNormalizer({
|
|
206
|
+
name: "mail",
|
|
207
|
+
normalize: normalizeMailConfig
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
export {
|
|
211
|
+
normalizeMailEnvironment,
|
|
212
|
+
DEFAULT_MAILER_NAME,
|
|
213
|
+
DEFAULT_MAIL_PREVIEW_PATH,
|
|
214
|
+
DEFAULT_SMTP_HOST,
|
|
215
|
+
DEFAULT_SMTP_PORT,
|
|
216
|
+
holoMailDefaults,
|
|
217
|
+
normalizeMailConfig,
|
|
218
|
+
defineMailConfig
|
|
219
|
+
};
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
type HoloAppEnv = 'development' | 'production' | 'test';
|
|
2
|
+
declare function normalizeMailEnvironment(value: string | undefined, fallback?: HoloAppEnv): HoloAppEnv;
|
|
3
|
+
interface HoloMailAddressConfig {
|
|
4
|
+
readonly email: string;
|
|
5
|
+
readonly name?: string;
|
|
6
|
+
}
|
|
7
|
+
interface HoloMailQueueConfig {
|
|
8
|
+
readonly queued?: boolean;
|
|
9
|
+
readonly connection?: string;
|
|
10
|
+
readonly queue?: string;
|
|
11
|
+
readonly afterCommit?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface HoloMailPreviewConfig {
|
|
14
|
+
readonly allowedEnvironments?: readonly HoloAppEnv[];
|
|
15
|
+
}
|
|
16
|
+
interface HoloMailMarkdownConfig {
|
|
17
|
+
readonly wrapper?: string;
|
|
18
|
+
}
|
|
19
|
+
interface BaseMailDriverConfig {
|
|
20
|
+
readonly driver: string;
|
|
21
|
+
readonly from?: HoloMailAddressConfig;
|
|
22
|
+
readonly replyTo?: HoloMailAddressConfig;
|
|
23
|
+
readonly queue?: HoloMailQueueConfig;
|
|
24
|
+
}
|
|
25
|
+
interface PreviewMailDriverConfig extends BaseMailDriverConfig {
|
|
26
|
+
readonly driver: 'preview';
|
|
27
|
+
readonly path?: string;
|
|
28
|
+
}
|
|
29
|
+
interface LogMailDriverConfig extends BaseMailDriverConfig {
|
|
30
|
+
readonly driver: 'log';
|
|
31
|
+
readonly logBodies?: boolean;
|
|
32
|
+
}
|
|
33
|
+
interface FakeMailDriverConfig extends BaseMailDriverConfig {
|
|
34
|
+
readonly driver: 'fake';
|
|
35
|
+
}
|
|
36
|
+
interface SmtpMailDriverConfig extends BaseMailDriverConfig {
|
|
37
|
+
readonly driver: 'smtp';
|
|
38
|
+
readonly host?: string;
|
|
39
|
+
readonly port?: number | string;
|
|
40
|
+
readonly secure?: boolean;
|
|
41
|
+
readonly user?: string;
|
|
42
|
+
readonly password?: string;
|
|
43
|
+
}
|
|
44
|
+
type HoloMailMailerConfig = PreviewMailDriverConfig | LogMailDriverConfig | FakeMailDriverConfig | SmtpMailDriverConfig | (BaseMailDriverConfig & Record<string, unknown>);
|
|
45
|
+
interface HoloMailConfig {
|
|
46
|
+
readonly default?: string;
|
|
47
|
+
readonly from?: HoloMailAddressConfig;
|
|
48
|
+
readonly replyTo?: HoloMailAddressConfig;
|
|
49
|
+
readonly queue?: HoloMailQueueConfig;
|
|
50
|
+
readonly preview?: HoloMailPreviewConfig;
|
|
51
|
+
readonly markdown?: HoloMailMarkdownConfig;
|
|
52
|
+
readonly mailers?: Readonly<Record<string, HoloMailMailerConfig>>;
|
|
53
|
+
}
|
|
54
|
+
interface NormalizedHoloMailAddressConfig {
|
|
55
|
+
readonly email: string;
|
|
56
|
+
readonly name?: string;
|
|
57
|
+
}
|
|
58
|
+
interface NormalizedHoloMailQueueConfig {
|
|
59
|
+
readonly queued: boolean;
|
|
60
|
+
readonly connection?: string;
|
|
61
|
+
readonly queue?: string;
|
|
62
|
+
readonly afterCommit: boolean;
|
|
63
|
+
}
|
|
64
|
+
interface NormalizedHoloMailPreviewConfig {
|
|
65
|
+
readonly allowedEnvironments: readonly HoloAppEnv[];
|
|
66
|
+
}
|
|
67
|
+
interface NormalizedHoloMailMarkdownConfig {
|
|
68
|
+
readonly wrapper?: string;
|
|
69
|
+
}
|
|
70
|
+
interface NormalizedBaseMailDriverConfig {
|
|
71
|
+
readonly name: string;
|
|
72
|
+
readonly driver: string;
|
|
73
|
+
readonly from?: NormalizedHoloMailAddressConfig;
|
|
74
|
+
readonly replyTo?: NormalizedHoloMailAddressConfig;
|
|
75
|
+
readonly queue: NormalizedHoloMailQueueConfig;
|
|
76
|
+
}
|
|
77
|
+
interface NormalizedPreviewMailDriverConfig extends NormalizedBaseMailDriverConfig {
|
|
78
|
+
readonly driver: 'preview';
|
|
79
|
+
readonly path: string;
|
|
80
|
+
}
|
|
81
|
+
interface NormalizedLogMailDriverConfig extends NormalizedBaseMailDriverConfig {
|
|
82
|
+
readonly driver: 'log';
|
|
83
|
+
readonly logBodies: boolean;
|
|
84
|
+
}
|
|
85
|
+
interface NormalizedFakeMailDriverConfig extends NormalizedBaseMailDriverConfig {
|
|
86
|
+
readonly driver: 'fake';
|
|
87
|
+
}
|
|
88
|
+
interface NormalizedSmtpMailDriverConfig extends NormalizedBaseMailDriverConfig {
|
|
89
|
+
readonly driver: 'smtp';
|
|
90
|
+
readonly host: string;
|
|
91
|
+
readonly port: number;
|
|
92
|
+
readonly secure: boolean;
|
|
93
|
+
readonly user?: string;
|
|
94
|
+
readonly password?: string;
|
|
95
|
+
}
|
|
96
|
+
type NormalizedHoloMailMailerConfig = NormalizedPreviewMailDriverConfig | NormalizedLogMailDriverConfig | NormalizedFakeMailDriverConfig | NormalizedSmtpMailDriverConfig | NormalizedBaseMailDriverConfig;
|
|
97
|
+
interface NormalizedHoloMailConfig {
|
|
98
|
+
readonly default: string;
|
|
99
|
+
readonly from?: NormalizedHoloMailAddressConfig;
|
|
100
|
+
readonly replyTo?: NormalizedHoloMailAddressConfig;
|
|
101
|
+
readonly queue: NormalizedHoloMailQueueConfig;
|
|
102
|
+
readonly preview: NormalizedHoloMailPreviewConfig;
|
|
103
|
+
readonly markdown: NormalizedHoloMailMarkdownConfig;
|
|
104
|
+
readonly mailers: Readonly<Record<string, NormalizedHoloMailMailerConfig>>;
|
|
105
|
+
}
|
|
106
|
+
declare const DEFAULT_MAILER_NAME = "preview";
|
|
107
|
+
declare const DEFAULT_MAIL_PREVIEW_PATH = ".holo-js/runtime/mail-preview";
|
|
108
|
+
declare const DEFAULT_SMTP_HOST = "127.0.0.1";
|
|
109
|
+
declare const DEFAULT_SMTP_PORT = 1025;
|
|
110
|
+
declare const holoMailDefaults: Readonly<NormalizedHoloMailConfig>;
|
|
111
|
+
declare function normalizeMailConfig(config?: HoloMailConfig): NormalizedHoloMailConfig;
|
|
112
|
+
declare function defineMailConfig<TConfig extends HoloMailConfig>(config: TConfig): Readonly<TConfig>;
|
|
113
|
+
declare module '@holo-js/config' {
|
|
114
|
+
interface HoloConfigRegistry {
|
|
115
|
+
mail: NormalizedHoloMailConfig;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { type BaseMailDriverConfig, DEFAULT_MAILER_NAME, DEFAULT_MAIL_PREVIEW_PATH, DEFAULT_SMTP_HOST, DEFAULT_SMTP_PORT, type FakeMailDriverConfig, type HoloAppEnv, type HoloMailAddressConfig, type HoloMailConfig, type HoloMailMailerConfig, type HoloMailMarkdownConfig, type HoloMailPreviewConfig, type HoloMailQueueConfig, type LogMailDriverConfig, type NormalizedBaseMailDriverConfig, type NormalizedFakeMailDriverConfig, type NormalizedHoloMailAddressConfig, type NormalizedHoloMailConfig, type NormalizedHoloMailMailerConfig, type NormalizedHoloMailMarkdownConfig, type NormalizedHoloMailPreviewConfig, type NormalizedHoloMailQueueConfig, type NormalizedLogMailDriverConfig, type NormalizedPreviewMailDriverConfig, type NormalizedSmtpMailDriverConfig, type PreviewMailDriverConfig, type SmtpMailDriverConfig, defineMailConfig, holoMailDefaults, normalizeMailConfig, normalizeMailEnvironment };
|
package/dist/config.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MAILER_NAME,
|
|
3
|
+
DEFAULT_MAIL_PREVIEW_PATH,
|
|
4
|
+
DEFAULT_SMTP_HOST,
|
|
5
|
+
DEFAULT_SMTP_PORT,
|
|
6
|
+
defineMailConfig,
|
|
7
|
+
holoMailDefaults,
|
|
8
|
+
normalizeMailConfig,
|
|
9
|
+
normalizeMailEnvironment
|
|
10
|
+
} from "./chunk-YPNVXZL6.mjs";
|
|
11
|
+
export {
|
|
12
|
+
DEFAULT_MAILER_NAME,
|
|
13
|
+
DEFAULT_MAIL_PREVIEW_PATH,
|
|
14
|
+
DEFAULT_SMTP_HOST,
|
|
15
|
+
DEFAULT_SMTP_PORT,
|
|
16
|
+
defineMailConfig,
|
|
17
|
+
holoMailDefaults,
|
|
18
|
+
normalizeMailConfig,
|
|
19
|
+
normalizeMailEnvironment
|
|
20
|
+
};
|
package/dist/contracts.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HoloAppEnv, NormalizedHoloMailConfig } from '
|
|
2
|
-
export { defineMailConfig } from '
|
|
1
|
+
import { HoloAppEnv, NormalizedHoloMailConfig } from './config.js';
|
|
2
|
+
export { defineMailConfig } from './config.js';
|
|
3
3
|
|
|
4
4
|
declare const HOLO_MAIL_DEFINITION_MARKER: unique symbol;
|
|
5
5
|
declare const BUILT_IN_MAIL_DRIVERS: readonly ["preview", "log", "fake", "smtp"];
|
package/dist/contracts.mjs
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
createAttachmentResolutionPlan,
|
|
11
11
|
createAttachmentResolutionPlans,
|
|
12
12
|
defineMail,
|
|
13
|
-
defineMailConfig,
|
|
14
13
|
inferMimeTypeFromName,
|
|
15
14
|
isAttachmentQueueSafe,
|
|
16
15
|
isMailDefinition,
|
|
@@ -18,7 +17,10 @@ import {
|
|
|
18
17
|
normalizeMailDefinition,
|
|
19
18
|
resolveAttachmentDefinition,
|
|
20
19
|
resolveNormalizedAttachment
|
|
21
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-7CZAQPEH.mjs";
|
|
21
|
+
import {
|
|
22
|
+
defineMailConfig
|
|
23
|
+
} from "./chunk-YPNVXZL6.mjs";
|
|
22
24
|
export {
|
|
23
25
|
BUILT_IN_MAIL_DRIVERS,
|
|
24
26
|
HOLO_MAIL_DEFINITION_MARKER,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
export { HoloMailConfig, NormalizedHoloMailConfig, defineMailConfig, holoMailDefaults, normalizeMailConfig } from './config.js';
|
|
2
|
+
import { MailPreviewPolicy, MailPreviewFormat, ResolvedMail, MailDriverExecutionContext, MailSendResult, MailRuntimeBindings, MailDefinition, MailDefinitionInput, MailOverrideInput, MailPreviewResult, PendingMailSend, RegisteredMailDriver, MailDriver, RegisterMailDriverOptions } from './contracts.js';
|
|
2
3
|
export { BuiltInMailDriverRegistry, HoloMailDriverRegistry, MailAddress, MailAddressInput, MailAttachmentBase, MailAttachmentContent, MailAttachmentDisposition, MailAttachmentHelperOptions, MailAttachmentInput, MailAttachmentMetadata, MailAttachmentResolutionContext, MailAttachmentResolutionPlan, MailContentAttachment, MailContentAttachmentHelperOptions, MailContentSourceKind, MailDelayValue, MailDriverName, MailJsonObject, MailJsonValue, MailPathAttachment, MailPreviewInput, MailPriority, MailQueueOptions, MailRecipientsInput, MailRenderPreviewInput, MailRenderSource, MailResolvableAttachment, MailResolvedAttachmentPayload, MailSendInput, MailSendOptions, MailStorageAttachment, MailStorageAttachmentHelperOptions, MailViewRenderInput, MailViewRenderer, ResolvedMailAttachment, attachContent, attachFromPath, attachFromStorage, createAttachmentMetadata, createAttachmentResolutionPlan, createAttachmentResolutionPlans, defineMail, inferMimeTypeFromName, isAttachmentQueueSafe, isMailDefinition, mergeMailDefinitionInputs, normalizeMailDefinition, resolveAttachmentDefinition, resolveNormalizedAttachment } from './contracts.js';
|
|
3
|
-
export { HoloMailConfig, NormalizedHoloMailConfig, defineMailConfig } from '@holo-js/config';
|
|
4
4
|
|
|
5
|
-
interface FakeSentMail {
|
|
6
|
-
readonly messageId: string;
|
|
7
|
-
readonly createdAt: Date;
|
|
8
|
-
readonly mail: Readonly<ResolvedMail>;
|
|
9
|
-
readonly context: Readonly<MailDriverExecutionContext>;
|
|
10
|
-
readonly result: Readonly<MailSendResult>;
|
|
11
|
-
}
|
|
12
|
-
interface MailPreviewArtifact {
|
|
13
|
-
readonly messageId: string;
|
|
14
|
-
readonly createdAt: Date;
|
|
15
|
-
readonly mail: Readonly<ResolvedMail>;
|
|
16
|
-
readonly context: Readonly<MailDriverExecutionContext>;
|
|
17
|
-
readonly result: Readonly<MailSendResult>;
|
|
18
|
-
}
|
|
19
5
|
declare class MailError extends Error {
|
|
20
6
|
readonly code: string;
|
|
21
7
|
constructor(message: string, code?: string, options?: ErrorOptions);
|
|
@@ -39,6 +25,21 @@ declare class MailSendError extends MailError {
|
|
|
39
25
|
readonly message?: string;
|
|
40
26
|
}, options?: ErrorOptions);
|
|
41
27
|
}
|
|
28
|
+
|
|
29
|
+
interface FakeSentMail {
|
|
30
|
+
readonly messageId: string;
|
|
31
|
+
readonly createdAt: Date;
|
|
32
|
+
readonly mail: Readonly<ResolvedMail>;
|
|
33
|
+
readonly context: Readonly<MailDriverExecutionContext>;
|
|
34
|
+
readonly result: Readonly<MailSendResult>;
|
|
35
|
+
}
|
|
36
|
+
interface MailPreviewArtifact {
|
|
37
|
+
readonly messageId: string;
|
|
38
|
+
readonly createdAt: Date;
|
|
39
|
+
readonly mail: Readonly<ResolvedMail>;
|
|
40
|
+
readonly context: Readonly<MailDriverExecutionContext>;
|
|
41
|
+
readonly result: Readonly<MailSendResult>;
|
|
42
|
+
}
|
|
42
43
|
interface MailRuntimeFacade {
|
|
43
44
|
sendMail(mail: MailDefinition | MailDefinitionInput, overrides?: MailOverrideInput): PendingMailSend<MailSendResult>;
|
|
44
45
|
previewMail(mail: MailDefinition | MailDefinitionInput, overrides?: MailOverrideInput): Promise<MailPreviewResult>;
|
|
@@ -67,10 +68,13 @@ declare const mailRegistryInternals: {
|
|
|
67
68
|
normalizeDriverName: typeof normalizeDriverName;
|
|
68
69
|
};
|
|
69
70
|
|
|
71
|
+
declare function loadMailPluginDrivers(projectRoot?: string, pluginNames?: readonly string[]): Promise<void>;
|
|
72
|
+
declare function resetMailPluginDrivers(): void;
|
|
73
|
+
|
|
70
74
|
declare const mail: Readonly<{
|
|
71
75
|
previewMail: typeof previewMail;
|
|
72
76
|
renderMailPreview: typeof renderMailPreview;
|
|
73
77
|
sendMail: typeof sendMail;
|
|
74
78
|
}>;
|
|
75
79
|
|
|
76
|
-
export { type FakeSentMail, MailDefinition, MailDefinitionInput, MailDriver, MailDriverExecutionContext, MailError, MailOverrideInput, type MailPreviewArtifact, MailPreviewDisabledError, MailPreviewFormat, MailPreviewFormatUnavailableError, MailPreviewPolicy, MailPreviewResult, MailRuntimeBindings, MailSendError, MailSendResult, PendingMailSend, RegisterMailDriverOptions, RegisteredMailDriver, ResolvedMail, configureMailRuntime, mail as default, getMailRuntime, getMailRuntimeBindings, getRegisteredMailDriver, listFakeSentMails, listPreviewMailArtifacts, listRegisteredMailDrivers, mailRegistryInternals, previewMail, registerMailDriver, renderMailPreview, resetFakeSentMails, resetMailDriverRegistry, resetMailRuntime, resetPreviewMailArtifacts, sendMail };
|
|
80
|
+
export { type FakeSentMail, MailDefinition, MailDefinitionInput, MailDriver, MailDriverExecutionContext, MailError, MailOverrideInput, type MailPreviewArtifact, MailPreviewDisabledError, MailPreviewFormat, MailPreviewFormatUnavailableError, MailPreviewPolicy, MailPreviewResult, MailRuntimeBindings, MailSendError, MailSendResult, PendingMailSend, RegisterMailDriverOptions, RegisteredMailDriver, ResolvedMail, configureMailRuntime, mail as default, getMailRuntime, getMailRuntimeBindings, getRegisteredMailDriver, listFakeSentMails, listPreviewMailArtifacts, listRegisteredMailDrivers, loadMailPluginDrivers, mailRegistryInternals, previewMail, registerMailDriver, renderMailPreview, resetFakeSentMails, resetMailDriverRegistry, resetMailPluginDrivers, resetMailRuntime, resetPreviewMailArtifacts, sendMail };
|
package/dist/index.mjs
CHANGED
|
@@ -13,17 +13,58 @@ import {
|
|
|
13
13
|
normalizeMailDefinition,
|
|
14
14
|
resolveAttachmentDefinition,
|
|
15
15
|
resolveNormalizedAttachment
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-7CZAQPEH.mjs";
|
|
17
|
+
import {
|
|
18
|
+
defineMailConfig,
|
|
19
|
+
holoMailDefaults,
|
|
20
|
+
normalizeMailConfig,
|
|
21
|
+
normalizeMailEnvironment
|
|
22
|
+
} from "./chunk-YPNVXZL6.mjs";
|
|
17
23
|
|
|
18
24
|
// src/runtime.ts
|
|
19
25
|
import { randomUUID } from "crypto";
|
|
20
26
|
import { mkdir, realpath, writeFile } from "fs/promises";
|
|
21
27
|
import { tmpdir } from "os";
|
|
22
28
|
import { join, resolve, sep } from "path";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
|
|
30
|
+
// src/markdown.ts
|
|
31
|
+
function escapeHtml(value) {
|
|
32
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
33
|
+
}
|
|
34
|
+
function isSafeMailHref(href) {
|
|
35
|
+
const trimmed = href.trim();
|
|
36
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) return true;
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(trimmed);
|
|
39
|
+
return url.protocol === "https:" || url.protocol === "http:" || url.protocol === "mailto:";
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function renderMarkdownInline(markdown) {
|
|
45
|
+
return escapeHtml(markdown).replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
|
|
46
|
+
return isSafeMailHref(href) ? `<a href="${escapeHtml(href)}">${label}</a>` : label;
|
|
47
|
+
}).replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
48
|
+
}
|
|
49
|
+
function stripMarkdownSyntax(markdown) {
|
|
50
|
+
return markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^[-*]\s+/gm, "").trim();
|
|
51
|
+
}
|
|
52
|
+
function renderMarkdown(markdown) {
|
|
53
|
+
const blocks = markdown.trim().split(/\n\s*\n/g).map((block) => block.trim()).filter(Boolean);
|
|
54
|
+
return blocks.map((block) => {
|
|
55
|
+
const lines = block.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
56
|
+
if (lines.every((line) => /^[-*]\s+/.test(line))) {
|
|
57
|
+
return `<ul>${lines.map((line) => `<li>${renderMarkdownInline(line.replace(/^[-*]\s+/, ""))}</li>`).join("")}</ul>`;
|
|
58
|
+
}
|
|
59
|
+
const heading = lines.length === 1 ? lines[0].match(/^(#{1,6})\s+(.+)$/) : null;
|
|
60
|
+
if (heading) {
|
|
61
|
+
const [, markers, title] = heading;
|
|
62
|
+
const level = markers.length;
|
|
63
|
+
return `<h${level}>${renderMarkdownInline(title)}</h${level}>`;
|
|
64
|
+
}
|
|
65
|
+
return `<p>${lines.map(renderMarkdownInline).join("<br />")}</p>`;
|
|
66
|
+
}).join("\n");
|
|
67
|
+
}
|
|
27
68
|
|
|
28
69
|
// src/registry.ts
|
|
29
70
|
function normalizeDriverName(value) {
|
|
@@ -55,6 +96,9 @@ function registerMailDriver(name, driver, options = {}) {
|
|
|
55
96
|
function getRegisteredMailDriver(name) {
|
|
56
97
|
return getRegistry().get(normalizeDriverName(name));
|
|
57
98
|
}
|
|
99
|
+
function unregisterMailDriver(name) {
|
|
100
|
+
return getRegistry().delete(normalizeDriverName(name));
|
|
101
|
+
}
|
|
58
102
|
function listRegisteredMailDrivers() {
|
|
59
103
|
return Object.freeze([...getRegistry().values()]);
|
|
60
104
|
}
|
|
@@ -66,12 +110,66 @@ var mailRegistryInternals = {
|
|
|
66
110
|
normalizeDriverName
|
|
67
111
|
};
|
|
68
112
|
|
|
69
|
-
// src/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
113
|
+
// src/plugins.ts
|
|
114
|
+
import {
|
|
115
|
+
loadHoloPluginContributionModules,
|
|
116
|
+
loadHoloPluginDefinitions
|
|
117
|
+
} from "@holo-js/kernel";
|
|
118
|
+
var loadedProjectRoots = /* @__PURE__ */ new Set();
|
|
119
|
+
var registeredPluginDrivers = /* @__PURE__ */ new Map();
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
function resolveMailDriver(moduleValue, packageName, driverName) {
|
|
124
|
+
const candidate = isRecord(moduleValue) && typeof moduleValue.default !== "undefined" ? moduleValue.default : isRecord(moduleValue) && typeof moduleValue.driver !== "undefined" ? moduleValue.driver : moduleValue;
|
|
125
|
+
if (!isRecord(candidate) || typeof candidate.send !== "function") {
|
|
126
|
+
throw new Error(`[@holo-js/mail] Plugin ${packageName} mail driver "${driverName}" must export send().`);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
send: candidate.send
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function loadMailPluginDrivers(projectRoot = process.cwd(), pluginNames = []) {
|
|
133
|
+
const loadKey = `${projectRoot}\0${[...pluginNames].sort().join("\0")}`;
|
|
134
|
+
if (loadedProjectRoots.has(loadKey)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const plugins = await loadHoloPluginDefinitions(projectRoot, pluginNames);
|
|
138
|
+
const contributions = await loadHoloPluginContributionModules(projectRoot, plugins, "mail", "drivers");
|
|
139
|
+
for (const contribution of contributions) {
|
|
140
|
+
const previous = getRegisteredMailDriver(contribution.name);
|
|
141
|
+
const driver = resolveMailDriver(contribution.module, contribution.plugin.packageName, contribution.name);
|
|
142
|
+
registerMailDriver(
|
|
143
|
+
contribution.name,
|
|
144
|
+
driver,
|
|
145
|
+
{ replaceExisting: true }
|
|
146
|
+
);
|
|
147
|
+
const registered = getRegisteredMailDriver(contribution.name);
|
|
148
|
+
if (registered) {
|
|
149
|
+
const existingPluginDriver = registeredPluginDrivers.get(registered.name);
|
|
150
|
+
registeredPluginDrivers.set(registered.name, {
|
|
151
|
+
driver,
|
|
152
|
+
previous: existingPluginDriver ? existingPluginDriver.previous : previous?.driver
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
loadedProjectRoots.add(loadKey);
|
|
157
|
+
}
|
|
158
|
+
function resetMailPluginDrivers() {
|
|
159
|
+
for (const [driverName, registered] of registeredPluginDrivers) {
|
|
160
|
+
if (getRegisteredMailDriver(driverName)?.driver !== registered.driver) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
unregisterMailDriver(driverName);
|
|
164
|
+
if (registered.previous) {
|
|
165
|
+
registerMailDriver(driverName, registered.previous, { replaceExisting: true });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
loadedProjectRoots.clear();
|
|
169
|
+
registeredPluginDrivers.clear();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/errors.ts
|
|
75
173
|
var MailError = class extends Error {
|
|
76
174
|
code;
|
|
77
175
|
constructor(message, code = "MAIL_ERROR", options) {
|
|
@@ -118,6 +216,52 @@ var MailSendError = class extends MailError {
|
|
|
118
216
|
this.driver = details.driver;
|
|
119
217
|
}
|
|
120
218
|
};
|
|
219
|
+
|
|
220
|
+
// src/preview.ts
|
|
221
|
+
function escapePreviewHtml(value) {
|
|
222
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
223
|
+
}
|
|
224
|
+
function formatPreviewAddress(address) {
|
|
225
|
+
return address.name ? `${escapePreviewHtml(address.name)} <${escapePreviewHtml(address.email)}>` : escapePreviewHtml(address.email);
|
|
226
|
+
}
|
|
227
|
+
function createMailPreviewHtml(preview) {
|
|
228
|
+
const header = [
|
|
229
|
+
`<h1>${escapePreviewHtml(preview.subject)}</h1>`,
|
|
230
|
+
`<p><strong>From:</strong> ${formatPreviewAddress(preview.from)}</p>`,
|
|
231
|
+
`<p><strong>Reply-To:</strong> ${formatPreviewAddress(preview.replyTo)}</p>`,
|
|
232
|
+
`<p><strong>To:</strong> ${preview.to.map(formatPreviewAddress).join(", ")}</p>`,
|
|
233
|
+
preview.cc.length > 0 ? `<p><strong>Cc:</strong> ${preview.cc.map(formatPreviewAddress).join(", ")}</p>` : "",
|
|
234
|
+
preview.bcc.length > 0 ? `<p><strong>Bcc:</strong> ${preview.bcc.map(formatPreviewAddress).join(", ")}</p>` : ""
|
|
235
|
+
].filter(Boolean).join("");
|
|
236
|
+
const body = preview.html ? `<pre>${escapePreviewHtml(preview.html)}</pre>` : preview.text ? `<pre>${escapePreviewHtml(preview.text)}</pre>` : "<p>No rendered content is available.</p>";
|
|
237
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapePreviewHtml(preview.subject)}</title></head><body>${header}${body}</body></html>`;
|
|
238
|
+
}
|
|
239
|
+
function createMailPreviewResponse(preview, format) {
|
|
240
|
+
if (format === "json") {
|
|
241
|
+
return new Response(JSON.stringify(preview, null, 2), {
|
|
242
|
+
status: 200,
|
|
243
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (format === "text") {
|
|
247
|
+
if (typeof preview.text !== "string") throw new MailPreviewFormatUnavailableError(format);
|
|
248
|
+
return new Response(preview.text, {
|
|
249
|
+
status: 200,
|
|
250
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return new Response(createMailPreviewHtml(preview), {
|
|
254
|
+
status: 200,
|
|
255
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/runtime.ts
|
|
260
|
+
var HOLO_MAIL_DELIVER_JOB = "holo.mail.deliver";
|
|
261
|
+
var LOCAL_ATTACHMENT_ROOTS = Object.freeze([
|
|
262
|
+
tmpdir(),
|
|
263
|
+
resolve(process.cwd(), "storage")
|
|
264
|
+
]);
|
|
121
265
|
function getRuntimeState() {
|
|
122
266
|
const runtime = globalThis;
|
|
123
267
|
runtime.__holoMailRuntime__ ??= {};
|
|
@@ -249,7 +393,7 @@ function getResolvedConfig() {
|
|
|
249
393
|
return getRuntimeBindings().config ?? holoMailDefaults;
|
|
250
394
|
}
|
|
251
395
|
function resolveCurrentEnvironment() {
|
|
252
|
-
return
|
|
396
|
+
return normalizeMailEnvironment(process.env.APP_ENV ?? process.env.NODE_ENV);
|
|
253
397
|
}
|
|
254
398
|
function createPreviewPolicy(config = getResolvedConfig()) {
|
|
255
399
|
return Object.freeze({
|
|
@@ -263,50 +407,8 @@ function assertPreviewEnabled(config = getResolvedConfig()) {
|
|
|
263
407
|
throw new MailPreviewDisabledError(policy);
|
|
264
408
|
}
|
|
265
409
|
}
|
|
266
|
-
function createPreviewHtml(preview) {
|
|
267
|
-
const header = [
|
|
268
|
-
`<h1>${escapeHtml(preview.subject)}</h1>`,
|
|
269
|
-
`<p><strong>From:</strong> ${formatAddress(preview.from)}</p>`,
|
|
270
|
-
`<p><strong>Reply-To:</strong> ${formatAddress(preview.replyTo)}</p>`,
|
|
271
|
-
`<p><strong>To:</strong> ${preview.to.map(formatAddress).join(", ")}</p>`,
|
|
272
|
-
preview.cc.length > 0 ? `<p><strong>Cc:</strong> ${preview.cc.map(formatAddress).join(", ")}</p>` : "",
|
|
273
|
-
preview.bcc.length > 0 ? `<p><strong>Bcc:</strong> ${preview.bcc.map(formatAddress).join(", ")}</p>` : ""
|
|
274
|
-
].filter(Boolean).join("");
|
|
275
|
-
const body = preview.html ? `<pre>${escapeHtml(preview.html)}</pre>` : preview.text ? `<pre>${escapeHtml(preview.text)}</pre>` : "<p>No rendered content is available.</p>";
|
|
276
|
-
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(preview.subject)}</title></head><body>${header}${body}</body></html>`;
|
|
277
|
-
}
|
|
278
|
-
function escapeHtml(value) {
|
|
279
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
280
|
-
}
|
|
281
|
-
function formatAddress(address) {
|
|
282
|
-
return address.name ? `${escapeHtml(address.name)} <${escapeHtml(address.email)}>` : escapeHtml(address.email);
|
|
283
|
-
}
|
|
284
410
|
function renderPreviewResponse(preview, format) {
|
|
285
|
-
|
|
286
|
-
return new Response(JSON.stringify(preview, null, 2), {
|
|
287
|
-
status: 200,
|
|
288
|
-
headers: {
|
|
289
|
-
"content-type": "application/json; charset=utf-8"
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
if (format === "text") {
|
|
294
|
-
if (typeof preview.text !== "string") {
|
|
295
|
-
throw new MailPreviewFormatUnavailableError(format);
|
|
296
|
-
}
|
|
297
|
-
return new Response(preview.text, {
|
|
298
|
-
status: 200,
|
|
299
|
-
headers: {
|
|
300
|
-
"content-type": "text/plain; charset=utf-8"
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
return new Response(createPreviewHtml(preview), {
|
|
305
|
-
status: 200,
|
|
306
|
-
headers: {
|
|
307
|
-
"content-type": "text/html; charset=utf-8"
|
|
308
|
-
}
|
|
309
|
-
});
|
|
411
|
+
return createMailPreviewResponse(preview, format);
|
|
310
412
|
}
|
|
311
413
|
function getMailerConfig(mailer, config = getResolvedConfig()) {
|
|
312
414
|
const mailerConfig = config.mailers[mailer];
|
|
@@ -641,6 +743,21 @@ function resolveDriver(mail2, options, config) {
|
|
|
641
743
|
implementation: registered.driver
|
|
642
744
|
});
|
|
643
745
|
}
|
|
746
|
+
function isMailDriverNotRegisteredError(error) {
|
|
747
|
+
return error instanceof MailError && error.code === "MAIL_DRIVER_NOT_REGISTERED";
|
|
748
|
+
}
|
|
749
|
+
async function resolveDriverWithPluginFallback(mail2, options, config) {
|
|
750
|
+
try {
|
|
751
|
+
return resolveDriver(mail2, options, config);
|
|
752
|
+
} catch (error) {
|
|
753
|
+
if (!isMailDriverNotRegisteredError(error)) {
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const state = getRuntimeState();
|
|
758
|
+
await loadMailPluginDrivers(state.projectRoot, state.pluginNames);
|
|
759
|
+
return resolveDriver(mail2, options, config);
|
|
760
|
+
}
|
|
644
761
|
function resolveDriverByName(mailer, driver) {
|
|
645
762
|
const builtIn = builtInDrivers[driver];
|
|
646
763
|
if (builtIn) {
|
|
@@ -663,6 +780,18 @@ function resolveDriverByName(mailer, driver) {
|
|
|
663
780
|
implementation: registered.driver
|
|
664
781
|
});
|
|
665
782
|
}
|
|
783
|
+
async function resolveDriverByNameWithPluginFallback(mailer, driver) {
|
|
784
|
+
try {
|
|
785
|
+
return resolveDriverByName(mailer, driver);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
if (!isMailDriverNotRegisteredError(error)) {
|
|
788
|
+
throw error;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const state = getRuntimeState();
|
|
792
|
+
await loadMailPluginDrivers(state.projectRoot, state.pluginNames);
|
|
793
|
+
return resolveDriverByName(mailer, driver);
|
|
794
|
+
}
|
|
666
795
|
function serializeQueuedAttachment(attachment) {
|
|
667
796
|
return Object.freeze({
|
|
668
797
|
source: attachment.source,
|
|
@@ -748,7 +877,7 @@ async function deliverResolvedMail(mail2, resolvedDriver, context) {
|
|
|
748
877
|
}
|
|
749
878
|
}
|
|
750
879
|
async function runQueuedMailDelivery(payload) {
|
|
751
|
-
const resolvedDriver =
|
|
880
|
+
const resolvedDriver = await resolveDriverByNameWithPluginFallback(payload.mailer, payload.driver);
|
|
752
881
|
const context = Object.freeze({
|
|
753
882
|
messageId: payload.messageId,
|
|
754
883
|
mailer: payload.mailer,
|
|
@@ -817,14 +946,6 @@ async function renderView(input) {
|
|
|
817
946
|
}
|
|
818
947
|
return html;
|
|
819
948
|
}
|
|
820
|
-
function stripMarkdownSyntax(markdown) {
|
|
821
|
-
return markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^[-*]\s+/gm, "").trim();
|
|
822
|
-
}
|
|
823
|
-
function renderMarkdownInline(markdown) {
|
|
824
|
-
return escapeHtml(markdown).replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
|
|
825
|
-
return isSafeMailHref(href) ? `<a href="${escapeHtml(href)}">${label}</a>` : label;
|
|
826
|
-
}).replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
827
|
-
}
|
|
828
949
|
async function resolveLocalAttachmentPath(path) {
|
|
829
950
|
const resolvedPath = await realpath(path);
|
|
830
951
|
const allowedRoots = await Promise.all(LOCAL_ATTACHMENT_ROOTS.map(async (root) => await realpath(root).catch(() => null)));
|
|
@@ -833,37 +954,6 @@ async function resolveLocalAttachmentPath(path) {
|
|
|
833
954
|
}
|
|
834
955
|
throw new MailError("[@holo-js/mail] Path attachments must resolve inside an allowed attachment directory.", "MAIL_ATTACHMENT_UNSAFE_PATH");
|
|
835
956
|
}
|
|
836
|
-
function isSafeMailHref(href) {
|
|
837
|
-
const trimmed = href.trim();
|
|
838
|
-
if (trimmed.startsWith("#") || trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
|
|
839
|
-
return true;
|
|
840
|
-
}
|
|
841
|
-
try {
|
|
842
|
-
const url = new URL(trimmed);
|
|
843
|
-
return url.protocol === "https:" || url.protocol === "http:" || url.protocol === "mailto:";
|
|
844
|
-
} catch {
|
|
845
|
-
return false;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
function renderMarkdown(markdown) {
|
|
849
|
-
const blocks = markdown.trim().split(/\n\s*\n/g).map((block) => block.trim()).filter(Boolean);
|
|
850
|
-
return blocks.map((block) => {
|
|
851
|
-
const lines = block.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
852
|
-
if (lines.length === 0) {
|
|
853
|
-
return "";
|
|
854
|
-
}
|
|
855
|
-
if (lines.every((line) => /^[-*]\s+/.test(line))) {
|
|
856
|
-
return `<ul>${lines.map((line) => `<li>${renderMarkdownInline(line.replace(/^[-*]\s+/, ""))}</li>`).join("")}</ul>`;
|
|
857
|
-
}
|
|
858
|
-
const heading = lines.length === 1 ? lines[0].match(/^(#{1,6})\s+(.+)$/) : null;
|
|
859
|
-
if (heading) {
|
|
860
|
-
const [, markers, title] = heading;
|
|
861
|
-
const level = markers.length;
|
|
862
|
-
return `<h${level}>${renderMarkdownInline(title)}</h${level}>`;
|
|
863
|
-
}
|
|
864
|
-
return `<p>${lines.map(renderMarkdownInline).join("<br />")}</p>`;
|
|
865
|
-
}).join("\n");
|
|
866
|
-
}
|
|
867
957
|
function resolveMailerName(mail2, config) {
|
|
868
958
|
return mail2.mailer ?? config.default;
|
|
869
959
|
}
|
|
@@ -1027,7 +1117,7 @@ var PendingSend = class {
|
|
|
1027
1117
|
async #execute(execution = {}) {
|
|
1028
1118
|
const options = Object.freeze({ ...this.#options });
|
|
1029
1119
|
const config = getResolvedConfig();
|
|
1030
|
-
const resolvedDriver =
|
|
1120
|
+
const resolvedDriver = await resolveDriverWithPluginFallback(this.#mail, options, config);
|
|
1031
1121
|
const plan = resolveQueuePlan(this.#mail, options, resolvedDriver.mailer, config);
|
|
1032
1122
|
const messageId = randomUUID();
|
|
1033
1123
|
const prepared = await prepareMailSend(this.#mail, plan.queued);
|
|
@@ -1093,7 +1183,18 @@ function resolveMailInput(mail2, overrides) {
|
|
|
1093
1183
|
return normalizeMailDefinition(mergeMailDefinitionInputs(mail2, overrides));
|
|
1094
1184
|
}
|
|
1095
1185
|
function configureMailRuntime(bindings) {
|
|
1096
|
-
getRuntimeState()
|
|
1186
|
+
const state = getRuntimeState();
|
|
1187
|
+
if (!bindings) {
|
|
1188
|
+
state.bindings = void 0;
|
|
1189
|
+
state.projectRoot = void 0;
|
|
1190
|
+
state.pluginNames = void 0;
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const { projectRoot, plugins, ...runtimeBindings } = bindings;
|
|
1194
|
+
state.bindings = runtimeBindings;
|
|
1195
|
+
const normalizedProjectRoot = typeof projectRoot === "string" ? projectRoot.trim() : "";
|
|
1196
|
+
state.projectRoot = normalizedProjectRoot ? normalizedProjectRoot : void 0;
|
|
1197
|
+
state.pluginNames = Object.freeze([...new Set((plugins ?? []).map((plugin) => plugin.trim()).filter((plugin) => plugin.length > 0))]);
|
|
1097
1198
|
}
|
|
1098
1199
|
function getMailRuntimeBindings() {
|
|
1099
1200
|
return getRuntimeBindings();
|
|
@@ -1101,12 +1202,15 @@ function getMailRuntimeBindings() {
|
|
|
1101
1202
|
function resetMailRuntime() {
|
|
1102
1203
|
const state = getRuntimeState();
|
|
1103
1204
|
state.bindings = void 0;
|
|
1205
|
+
state.projectRoot = void 0;
|
|
1206
|
+
state.pluginNames = void 0;
|
|
1104
1207
|
state.fakeSent = void 0;
|
|
1105
1208
|
state.previewArtifacts = void 0;
|
|
1106
1209
|
state.loadQueueModule = void 0;
|
|
1107
1210
|
state.loadDbModule = void 0;
|
|
1108
1211
|
state.loadNodemailerModule = void 0;
|
|
1109
1212
|
state.loadStorageModule = void 0;
|
|
1213
|
+
resetMailPluginDrivers();
|
|
1110
1214
|
}
|
|
1111
1215
|
function sendMail(mail2, overrides) {
|
|
1112
1216
|
return new PendingSend(resolveMailInput(mail2, overrides));
|
|
@@ -1163,7 +1267,6 @@ function getMailRuntime() {
|
|
|
1163
1267
|
}
|
|
1164
1268
|
|
|
1165
1269
|
// src/index.ts
|
|
1166
|
-
import { defineMailConfig } from "@holo-js/config";
|
|
1167
1270
|
var mail = Object.freeze({
|
|
1168
1271
|
previewMail,
|
|
1169
1272
|
renderMailPreview,
|
|
@@ -1188,20 +1291,24 @@ export {
|
|
|
1188
1291
|
getMailRuntime,
|
|
1189
1292
|
getMailRuntimeBindings,
|
|
1190
1293
|
getRegisteredMailDriver,
|
|
1294
|
+
holoMailDefaults,
|
|
1191
1295
|
inferMimeTypeFromName,
|
|
1192
1296
|
isAttachmentQueueSafe,
|
|
1193
1297
|
isMailDefinition,
|
|
1194
1298
|
listFakeSentMails,
|
|
1195
1299
|
listPreviewMailArtifacts,
|
|
1196
1300
|
listRegisteredMailDrivers,
|
|
1301
|
+
loadMailPluginDrivers,
|
|
1197
1302
|
mailRegistryInternals,
|
|
1198
1303
|
mergeMailDefinitionInputs,
|
|
1304
|
+
normalizeMailConfig,
|
|
1199
1305
|
normalizeMailDefinition,
|
|
1200
1306
|
previewMail,
|
|
1201
1307
|
registerMailDriver,
|
|
1202
1308
|
renderMailPreview,
|
|
1203
1309
|
resetFakeSentMails,
|
|
1204
1310
|
resetMailDriverRegistry,
|
|
1311
|
+
resetMailPluginDrivers,
|
|
1205
1312
|
resetMailRuntime,
|
|
1206
1313
|
resetPreviewMailArtifacts,
|
|
1207
1314
|
resolveAttachmentDefinition,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/mail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Holo-JS Framework - mail contracts, preview helpers, and fluent send helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"import": "./dist/index.mjs",
|
|
11
11
|
"default": "./dist/index.mjs"
|
|
12
12
|
},
|
|
13
|
+
"./config": {
|
|
14
|
+
"types": "./dist/config.d.ts",
|
|
15
|
+
"import": "./dist/config.mjs",
|
|
16
|
+
"default": "./dist/config.mjs"
|
|
17
|
+
},
|
|
13
18
|
"./contracts": {
|
|
14
19
|
"types": "./dist/contracts.d.ts",
|
|
15
20
|
"import": "./dist/contracts.mjs",
|
|
@@ -28,12 +33,13 @@
|
|
|
28
33
|
"test": "vitest --run"
|
|
29
34
|
},
|
|
30
35
|
"dependencies": {
|
|
31
|
-
"@holo-js/config": "^0.
|
|
36
|
+
"@holo-js/config": "^0.3.0",
|
|
37
|
+
"@holo-js/kernel": "^0.3.0",
|
|
32
38
|
"nodemailer": "^6.10.1"
|
|
33
39
|
},
|
|
34
40
|
"peerDependencies": {
|
|
35
|
-
"@holo-js/queue": "^0.
|
|
36
|
-
"@holo-js/storage": "^0.
|
|
41
|
+
"@holo-js/queue": "^0.3.0",
|
|
42
|
+
"@holo-js/storage": "^0.3.0"
|
|
37
43
|
},
|
|
38
44
|
"peerDependenciesMeta": {
|
|
39
45
|
"@holo-js/queue": {
|