@gravito/signal 1.0.0-alpha.6 → 1.0.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/CHANGELOG.md +10 -0
- package/dist/ReactRenderer-CMCAOEPH.mjs +28 -0
- package/dist/VueRenderer-DWTCD2RF.mjs +31 -0
- package/dist/index.d.mts +24 -26
- package/dist/index.d.ts +24 -26
- package/dist/index.js +474 -501
- package/dist/index.mjs +468 -15
- package/package.json +17 -16
- package/src/Mailable.ts +44 -10
- package/src/OrbitSignal.ts +28 -64
- package/src/augmentation.ts +1 -1
- package/src/dev/DevServer.ts +132 -73
- package/src/renderers/ReactRenderer.ts +8 -3
- package/src/renderers/VueRenderer.ts +10 -3
- package/src/types.ts +11 -0
- package/tests/dev-server.test.ts +64 -0
- package/tests/log-transport.test.ts +21 -0
- package/tests/mailable-extra.test.ts +61 -0
- package/tests/mailable.test.ts +2 -2
- package/tests/orbit-signal.test.ts +43 -0
- package/tests/renderers.test.ts +14 -12
- package/tests/template-renderer.test.ts +24 -0
- package/tests/ui.test.ts +37 -0
- package/tsconfig.json +11 -4
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DevMailbox,
|
|
3
|
-
LogTransport,
|
|
4
|
-
MemoryTransport,
|
|
5
|
-
OrbitSignal
|
|
6
|
-
} from "./chunk-YLVDJSED.mjs";
|
|
7
1
|
import "./chunk-EBO3CZXG.mjs";
|
|
8
2
|
|
|
3
|
+
// src/dev/DevMailbox.ts
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
var DevMailbox = class {
|
|
6
|
+
entries = [];
|
|
7
|
+
maxEntries = 50;
|
|
8
|
+
add(message) {
|
|
9
|
+
const entry = {
|
|
10
|
+
id: randomUUID(),
|
|
11
|
+
envelope: {
|
|
12
|
+
from: message.from,
|
|
13
|
+
to: message.to,
|
|
14
|
+
...message.cc ? { cc: message.cc } : {},
|
|
15
|
+
...message.bcc ? { bcc: message.bcc } : {},
|
|
16
|
+
...message.replyTo ? { replyTo: message.replyTo } : {},
|
|
17
|
+
subject: message.subject,
|
|
18
|
+
...message.priority ? { priority: message.priority } : {},
|
|
19
|
+
...message.attachments ? { attachments: message.attachments } : {}
|
|
20
|
+
},
|
|
21
|
+
html: message.html,
|
|
22
|
+
...message.text ? { text: message.text } : {},
|
|
23
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
24
|
+
};
|
|
25
|
+
this.entries.unshift(entry);
|
|
26
|
+
if (this.entries.length > this.maxEntries) {
|
|
27
|
+
this.entries = this.entries.slice(0, this.maxEntries);
|
|
28
|
+
}
|
|
29
|
+
return entry;
|
|
30
|
+
}
|
|
31
|
+
list() {
|
|
32
|
+
return this.entries;
|
|
33
|
+
}
|
|
34
|
+
get(id) {
|
|
35
|
+
return this.entries.find((e) => e.id === id);
|
|
36
|
+
}
|
|
37
|
+
delete(id) {
|
|
38
|
+
const index = this.entries.findIndex((e) => e.id === id);
|
|
39
|
+
if (index !== -1) {
|
|
40
|
+
this.entries.splice(index, 1);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
clear() {
|
|
46
|
+
this.entries = [];
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
9
50
|
// src/renderers/HtmlRenderer.ts
|
|
10
51
|
var HtmlRenderer = class {
|
|
11
52
|
constructor(content) {
|
|
@@ -50,6 +91,7 @@ var Mailable = class {
|
|
|
50
91
|
renderer;
|
|
51
92
|
rendererResolver;
|
|
52
93
|
renderData = {};
|
|
94
|
+
config;
|
|
53
95
|
// ===== Fluent API (Envelope Construction) =====
|
|
54
96
|
from(address) {
|
|
55
97
|
this.envelope.from = typeof address === "string" ? { address } : address;
|
|
@@ -75,7 +117,7 @@ var Mailable = class {
|
|
|
75
117
|
this.envelope.subject = subject;
|
|
76
118
|
return this;
|
|
77
119
|
}
|
|
78
|
-
|
|
120
|
+
emailPriority(level) {
|
|
79
121
|
this.envelope.priority = level;
|
|
80
122
|
return this;
|
|
81
123
|
}
|
|
@@ -106,10 +148,10 @@ var Mailable = class {
|
|
|
106
148
|
* Set the content using a React component.
|
|
107
149
|
* Dynamically imports ReactRenderer to avoid hard dependency errors if React is not installed.
|
|
108
150
|
*/
|
|
109
|
-
react(component, props) {
|
|
151
|
+
react(component, props, deps) {
|
|
110
152
|
this.rendererResolver = async () => {
|
|
111
|
-
const { ReactRenderer } = await import("./ReactRenderer-
|
|
112
|
-
return new ReactRenderer(component, props);
|
|
153
|
+
const { ReactRenderer } = await import("./ReactRenderer-CMCAOEPH.mjs");
|
|
154
|
+
return new ReactRenderer(component, props, deps);
|
|
113
155
|
};
|
|
114
156
|
return this;
|
|
115
157
|
}
|
|
@@ -117,10 +159,10 @@ var Mailable = class {
|
|
|
117
159
|
* Set the content using a Vue component.
|
|
118
160
|
* Dynamically imports VueRenderer to avoid hard dependency errors if Vue is not installed.
|
|
119
161
|
*/
|
|
120
|
-
vue(component, props) {
|
|
162
|
+
vue(component, props, deps) {
|
|
121
163
|
this.rendererResolver = async () => {
|
|
122
|
-
const { VueRenderer } = await import("./VueRenderer-
|
|
123
|
-
return new VueRenderer(component, props);
|
|
164
|
+
const { VueRenderer } = await import("./VueRenderer-DWTCD2RF.mjs");
|
|
165
|
+
return new VueRenderer(component, props, deps);
|
|
124
166
|
};
|
|
125
167
|
return this;
|
|
126
168
|
}
|
|
@@ -128,6 +170,7 @@ var Mailable = class {
|
|
|
128
170
|
queueName;
|
|
129
171
|
connectionName;
|
|
130
172
|
delaySeconds;
|
|
173
|
+
priority;
|
|
131
174
|
onQueue(queue) {
|
|
132
175
|
this.queueName = queue;
|
|
133
176
|
return this;
|
|
@@ -140,12 +183,23 @@ var Mailable = class {
|
|
|
140
183
|
this.delaySeconds = seconds;
|
|
141
184
|
return this;
|
|
142
185
|
}
|
|
186
|
+
withPriority(priority) {
|
|
187
|
+
this.priority = priority;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
143
190
|
/**
|
|
144
191
|
* Queue the mailable for sending.
|
|
145
192
|
*/
|
|
146
193
|
async queue() {
|
|
147
|
-
|
|
148
|
-
|
|
194
|
+
try {
|
|
195
|
+
const { app } = await import("@gravito/core");
|
|
196
|
+
const mail = app().container.make("mail");
|
|
197
|
+
if (mail) {
|
|
198
|
+
return mail.queue(this);
|
|
199
|
+
}
|
|
200
|
+
} catch (_e) {
|
|
201
|
+
console.warn("[Mailable] Could not auto-resolve mail service for queuing.");
|
|
202
|
+
}
|
|
149
203
|
}
|
|
150
204
|
// ===== I18n Support =====
|
|
151
205
|
currentLocale;
|
|
@@ -178,11 +232,13 @@ var Mailable = class {
|
|
|
178
232
|
*/
|
|
179
233
|
async buildEnvelope(configPromise) {
|
|
180
234
|
const config = await Promise.resolve(configPromise);
|
|
235
|
+
this.config = config;
|
|
181
236
|
if (config.translator) {
|
|
182
237
|
this.setTranslator(config.translator);
|
|
183
238
|
}
|
|
184
239
|
this.build();
|
|
185
240
|
if (this.renderer instanceof TemplateRenderer && config.viewsDir) {
|
|
241
|
+
this.renderer = new TemplateRenderer(this.renderer.template, config.viewsDir);
|
|
186
242
|
}
|
|
187
243
|
const envelope = {
|
|
188
244
|
from: this.envelope.from || config.from,
|
|
@@ -227,6 +283,403 @@ var Mailable = class {
|
|
|
227
283
|
}
|
|
228
284
|
};
|
|
229
285
|
|
|
286
|
+
// src/dev/ui/shared.ts
|
|
287
|
+
var styles = `
|
|
288
|
+
:root {
|
|
289
|
+
--primary: #6366f1;
|
|
290
|
+
--primary-dark: #4f46e5;
|
|
291
|
+
--bg-dark: #0f172a;
|
|
292
|
+
--bg-card: #1e293b;
|
|
293
|
+
--text: #f1f5f9;
|
|
294
|
+
--text-muted: #94a3b8;
|
|
295
|
+
--border: #334155;
|
|
296
|
+
--danger: #ef4444;
|
|
297
|
+
}
|
|
298
|
+
body { background: var(--bg-dark); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 20px; }
|
|
299
|
+
.container { max-width: 1000px; margin: 0 auto; }
|
|
300
|
+
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
|
301
|
+
.title { font-size: 24px; font-weight: bold; display: flex; align-items: center; gap: 10px; }
|
|
302
|
+
.card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
303
|
+
.btn { background: var(--border); color: var(--text); border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; text-decoration: none; font-size: 14px; transition: 0.2s; }
|
|
304
|
+
.btn:hover { background: var(--bg-card-hover); }
|
|
305
|
+
.btn-primary { background: var(--primary); color: white; }
|
|
306
|
+
.btn-primary:hover { background: var(--primary-dark); }
|
|
307
|
+
.btn-danger { background: var(--danger); color: white; }
|
|
308
|
+
.list-item { padding: 16px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 4px; text-decoration: none; color: inherit; transition: background 0.2s; }
|
|
309
|
+
.list-item:last-child { border-bottom: none; }
|
|
310
|
+
.list-item:hover { background: #334155; }
|
|
311
|
+
.meta { display: flex; justify-content: space-between; font-size: 14px; color: var(--text-muted); }
|
|
312
|
+
.subject { font-weight: 600; font-size: 16px; }
|
|
313
|
+
.from { color: #cbd5e1; }
|
|
314
|
+
.badge { background: #475569; padding: 2px 6px; border-radius: 4px; font-size: 12px; }
|
|
315
|
+
.badge-high { background: #dc2626; color: white; }
|
|
316
|
+
.empty { padding: 40px; text-align: center; color: var(--text-muted); }
|
|
317
|
+
`;
|
|
318
|
+
var layout = (title, content) => `
|
|
319
|
+
<!DOCTYPE html>
|
|
320
|
+
<html>
|
|
321
|
+
<head>
|
|
322
|
+
<title>${title} - Gravito Mailbox</title>
|
|
323
|
+
<style>${styles}</style>
|
|
324
|
+
</head>
|
|
325
|
+
<body>
|
|
326
|
+
<div class="container">
|
|
327
|
+
${content}
|
|
328
|
+
</div>
|
|
329
|
+
</body>
|
|
330
|
+
</html>
|
|
331
|
+
`;
|
|
332
|
+
|
|
333
|
+
// src/dev/ui/mailbox.ts
|
|
334
|
+
function formatAddress(addr) {
|
|
335
|
+
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
336
|
+
}
|
|
337
|
+
function timeAgo(date) {
|
|
338
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
339
|
+
if (seconds < 60) {
|
|
340
|
+
return "Just now";
|
|
341
|
+
}
|
|
342
|
+
const minutes = Math.floor(seconds / 60);
|
|
343
|
+
if (minutes < 60) {
|
|
344
|
+
return `${minutes}m ago`;
|
|
345
|
+
}
|
|
346
|
+
const hours = Math.floor(minutes / 60);
|
|
347
|
+
if (hours < 24) {
|
|
348
|
+
return `${hours}h ago`;
|
|
349
|
+
}
|
|
350
|
+
return date.toLocaleDateString();
|
|
351
|
+
}
|
|
352
|
+
function getMailboxHtml(entries, prefix) {
|
|
353
|
+
const list = entries.length === 0 ? '<div class="empty">No emails found in mailbox.</div>' : entries.map(
|
|
354
|
+
(entry) => `
|
|
355
|
+
<a href="${prefix}/${entry.id}" class="list-item">
|
|
356
|
+
<div class="meta">
|
|
357
|
+
<span class="from">${formatAddress(entry.envelope.from || { address: "Unknown" })}</span>
|
|
358
|
+
<span>${timeAgo(entry.sentAt)}</span>
|
|
359
|
+
</div>
|
|
360
|
+
<div class="subject">
|
|
361
|
+
${entry.envelope.priority === "high" ? '<span class="badge badge-high">High</span> ' : ""}
|
|
362
|
+
${entry.envelope.subject || "(No Subject)"}
|
|
363
|
+
</div>
|
|
364
|
+
<div class="meta" style="margin-top: 4px;">
|
|
365
|
+
To: ${entry.envelope.to?.map((t) => t.address).join(", ")}
|
|
366
|
+
</div>
|
|
367
|
+
</a>
|
|
368
|
+
`
|
|
369
|
+
).join("");
|
|
370
|
+
const content = `
|
|
371
|
+
<div class="header">
|
|
372
|
+
<div class="title">\u{1F4EC} Gravito Mailbox</div>
|
|
373
|
+
${entries.length > 0 ? `<button onclick="clearAll()" class="btn btn-danger">Clear All</button>` : ""}
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
<div class="card">
|
|
377
|
+
${list}
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<script>
|
|
381
|
+
async function clearAll() {
|
|
382
|
+
if (!confirm('Clear all emails?')) return;
|
|
383
|
+
await fetch('${prefix}', { method: 'DELETE' });
|
|
384
|
+
window.location.reload();
|
|
385
|
+
}
|
|
386
|
+
</script>
|
|
387
|
+
`;
|
|
388
|
+
return layout("Inbox", content);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/dev/ui/preview.ts
|
|
392
|
+
function getPreviewHtml(entry, prefix) {
|
|
393
|
+
const from = entry.envelope.from ? `${entry.envelope.from.name || ""} <${entry.envelope.from.address}>` : "Unknown";
|
|
394
|
+
const to = entry.envelope.to?.map((t) => t.address).join(", ") || "Unknown";
|
|
395
|
+
const content = `
|
|
396
|
+
<div class="header">
|
|
397
|
+
<div class="title">
|
|
398
|
+
<a href="${prefix}" class="btn">\u2190 Back</a>
|
|
399
|
+
<span style="margin-left: 10px">Email Preview</span>
|
|
400
|
+
</div>
|
|
401
|
+
<button onclick="deleteEmail('${entry.id}')" class="btn btn-danger">Delete</button>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<div class="card" style="margin-bottom: 20px; padding: 20px;">
|
|
405
|
+
<div style="font-size: 18px; font-weight: bold; margin-bottom: 10px;">${entry.envelope.subject || "(No Subject)"}</div>
|
|
406
|
+
<div class="meta" style="margin-bottom: 5px;">From: ${from}</div>
|
|
407
|
+
<div class="meta" style="margin-bottom: 5px;">To: ${to}</div>
|
|
408
|
+
<div class="meta">Date: ${entry.sentAt.toLocaleString()}</div>
|
|
409
|
+
</div>
|
|
410
|
+
|
|
411
|
+
<div style="margin-bottom: 10px;">
|
|
412
|
+
<button onclick="setView('html')" id="btn-html" class="btn btn-primary">HTML</button>
|
|
413
|
+
<button onclick="setView('text')" id="btn-text" class="btn">Text</button>
|
|
414
|
+
<button onclick="setView('raw')" id="btn-raw" class="btn">Raw JSON</button>
|
|
415
|
+
<a href="${prefix}/${entry.id}/html" target="_blank" class="btn">Open HTML \u2197</a>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
<div class="card" style="height: 600px; display: flex;">
|
|
419
|
+
<iframe id="preview-frame" src="${prefix}/${entry.id}/html" style="width: 100%; height: 100%; border: none;"></iframe>
|
|
420
|
+
<pre id="raw-view" style="display: none; padding: 20px; overflow: auto; width: 100%;">${JSON.stringify(entry, null, 2)}</pre>
|
|
421
|
+
<pre id="text-view" style="display: none; padding: 20px; overflow: auto; width: 100%; white-space: pre-wrap; font-family: monospace;">${entry.text || "No text content"}</pre>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
<script>
|
|
425
|
+
function setView(mode) {
|
|
426
|
+
document.getElementById('preview-frame').style.display = mode === 'html' ? 'block' : 'none';
|
|
427
|
+
document.getElementById('raw-view').style.display = mode === 'raw' ? 'block' : 'none';
|
|
428
|
+
document.getElementById('text-view').style.display = mode === 'text' ? 'block' : 'none';
|
|
429
|
+
|
|
430
|
+
document.getElementById('btn-html').className = mode === 'html' ? 'btn btn-primary' : 'btn';
|
|
431
|
+
document.getElementById('btn-text').className = mode === 'text' ? 'btn btn-primary' : 'btn';
|
|
432
|
+
document.getElementById('btn-raw').className = mode === 'raw' ? 'btn btn-primary' : 'btn';
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function deleteEmail(id) {
|
|
436
|
+
if (!confirm('Delete this email?')) return;
|
|
437
|
+
await fetch('${prefix}/' + id, { method: 'DELETE' });
|
|
438
|
+
window.location.href = '${prefix}';
|
|
439
|
+
}
|
|
440
|
+
</script>
|
|
441
|
+
`;
|
|
442
|
+
return layout(entry.envelope.subject || "Preview", content);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/dev/DevServer.ts
|
|
446
|
+
var DevServer = class {
|
|
447
|
+
constructor(mailbox, base = "/__mail", options) {
|
|
448
|
+
this.mailbox = mailbox;
|
|
449
|
+
this.base = base;
|
|
450
|
+
this.options = options;
|
|
451
|
+
}
|
|
452
|
+
async canAccess(ctx) {
|
|
453
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
454
|
+
if (isProduction && !this.options?.allowInProduction && !this.options?.gate) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
if (this.options?.gate) {
|
|
458
|
+
return await this.options.gate(ctx);
|
|
459
|
+
}
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
register(core) {
|
|
463
|
+
const router = core.router;
|
|
464
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
465
|
+
if (isProduction && !this.options?.allowInProduction && !this.options?.gate) {
|
|
466
|
+
core.logger.warn(
|
|
467
|
+
"[OrbitSignal] Dev Mailbox disabled in production. Configure a gate to allow access."
|
|
468
|
+
);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const prefix = this.base.replace(/\/$/, "");
|
|
472
|
+
const wrap = (handler) => {
|
|
473
|
+
return async (ctx) => {
|
|
474
|
+
const allowed = await this.canAccess(ctx);
|
|
475
|
+
if (!allowed) {
|
|
476
|
+
return ctx.text("Unauthorized", 403);
|
|
477
|
+
}
|
|
478
|
+
return await handler(ctx);
|
|
479
|
+
};
|
|
480
|
+
};
|
|
481
|
+
router.get(
|
|
482
|
+
prefix,
|
|
483
|
+
wrap((ctx) => {
|
|
484
|
+
const entries = this.mailbox.list();
|
|
485
|
+
return ctx.html(getMailboxHtml(entries, prefix));
|
|
486
|
+
})
|
|
487
|
+
);
|
|
488
|
+
router.get(
|
|
489
|
+
`${prefix}/:id`,
|
|
490
|
+
wrap((ctx) => {
|
|
491
|
+
const id = ctx.req.param("id");
|
|
492
|
+
if (!id) {
|
|
493
|
+
return ctx.text("Bad Request", 400);
|
|
494
|
+
}
|
|
495
|
+
const entry = this.mailbox.get(id);
|
|
496
|
+
if (!entry) {
|
|
497
|
+
return ctx.text("Email not found", 404);
|
|
498
|
+
}
|
|
499
|
+
return ctx.html(getPreviewHtml(entry, prefix));
|
|
500
|
+
})
|
|
501
|
+
);
|
|
502
|
+
router.get(
|
|
503
|
+
`${prefix}/:id/html`,
|
|
504
|
+
wrap((ctx) => {
|
|
505
|
+
const id = ctx.req.param("id");
|
|
506
|
+
if (!id) {
|
|
507
|
+
return ctx.text("Bad Request", 400);
|
|
508
|
+
}
|
|
509
|
+
const entry = this.mailbox.get(id);
|
|
510
|
+
if (!entry) {
|
|
511
|
+
return ctx.text("Not found", 404);
|
|
512
|
+
}
|
|
513
|
+
return ctx.html(entry.html);
|
|
514
|
+
})
|
|
515
|
+
);
|
|
516
|
+
router.get(
|
|
517
|
+
`${prefix}/:id/text`,
|
|
518
|
+
wrap((ctx) => {
|
|
519
|
+
const id = ctx.req.param("id");
|
|
520
|
+
if (!id) {
|
|
521
|
+
return ctx.text("Bad Request", 400);
|
|
522
|
+
}
|
|
523
|
+
const entry = this.mailbox.get(id);
|
|
524
|
+
if (!entry) {
|
|
525
|
+
return ctx.text("Not found", 404);
|
|
526
|
+
}
|
|
527
|
+
ctx.header("Content-Type", "text/plain; charset=utf-8");
|
|
528
|
+
return ctx.text(entry.text || "No text content", 200);
|
|
529
|
+
})
|
|
530
|
+
);
|
|
531
|
+
router.get(
|
|
532
|
+
`${prefix}/:id/raw`,
|
|
533
|
+
wrap((ctx) => {
|
|
534
|
+
const id = ctx.req.param("id");
|
|
535
|
+
if (!id) {
|
|
536
|
+
return ctx.json({ error: "Bad Request" }, 400);
|
|
537
|
+
}
|
|
538
|
+
const entry = this.mailbox.get(id);
|
|
539
|
+
if (!entry) {
|
|
540
|
+
return ctx.json({ error: "Not found" }, 404);
|
|
541
|
+
}
|
|
542
|
+
return ctx.json(entry);
|
|
543
|
+
})
|
|
544
|
+
);
|
|
545
|
+
router.get(
|
|
546
|
+
`${prefix}/:id/delete`,
|
|
547
|
+
wrap((ctx) => {
|
|
548
|
+
return ctx.text("Method not allowed", 405);
|
|
549
|
+
})
|
|
550
|
+
);
|
|
551
|
+
router.delete(
|
|
552
|
+
`${prefix}/:id`,
|
|
553
|
+
wrap((ctx) => {
|
|
554
|
+
const id = ctx.req.param("id");
|
|
555
|
+
if (!id) {
|
|
556
|
+
return ctx.json({ success: false, error: "Bad Request" }, 400);
|
|
557
|
+
}
|
|
558
|
+
const success = this.mailbox.delete(id);
|
|
559
|
+
return ctx.json({ success });
|
|
560
|
+
})
|
|
561
|
+
);
|
|
562
|
+
router.delete(
|
|
563
|
+
prefix,
|
|
564
|
+
wrap((ctx) => {
|
|
565
|
+
this.mailbox.clear();
|
|
566
|
+
return ctx.json({ success: true });
|
|
567
|
+
})
|
|
568
|
+
);
|
|
569
|
+
core.logger.info(`[OrbitSignal] Dev Mailbox available at ${prefix}`);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/transports/LogTransport.ts
|
|
574
|
+
var LogTransport = class {
|
|
575
|
+
async send(message) {
|
|
576
|
+
console.log("\n\u{1F4E7} [OrbitSignal] Email Sent (Simulated):");
|
|
577
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
578
|
+
console.log(
|
|
579
|
+
`From: ${message.from.name ? `${message.from.name} <${message.from.address}>` : message.from.address}`
|
|
580
|
+
);
|
|
581
|
+
console.log(`To: ${message.to.map((t) => t.address).join(", ")}`);
|
|
582
|
+
console.log(`Subject: ${message.subject}`);
|
|
583
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
584
|
+
console.log(`[Content Size]: ${message.html.length} chars (HTML)`);
|
|
585
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// src/transports/MemoryTransport.ts
|
|
590
|
+
var MemoryTransport = class {
|
|
591
|
+
constructor(mailbox) {
|
|
592
|
+
this.mailbox = mailbox;
|
|
593
|
+
}
|
|
594
|
+
async send(message) {
|
|
595
|
+
this.mailbox.add(message);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/OrbitSignal.ts
|
|
600
|
+
var OrbitSignal = class {
|
|
601
|
+
config;
|
|
602
|
+
devMailbox;
|
|
603
|
+
core;
|
|
604
|
+
constructor(config = {}) {
|
|
605
|
+
this.config = config;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Install the orbit into PlanetCore
|
|
609
|
+
*
|
|
610
|
+
* @param core - The PlanetCore instance.
|
|
611
|
+
*/
|
|
612
|
+
install(core) {
|
|
613
|
+
this.core = core;
|
|
614
|
+
core.logger.info("[OrbitSignal] Initializing Mail Service");
|
|
615
|
+
if (!this.config.transport && !this.config.devMode) {
|
|
616
|
+
this.config.transport = new LogTransport();
|
|
617
|
+
}
|
|
618
|
+
if (this.config.devMode) {
|
|
619
|
+
this.devMailbox = new DevMailbox();
|
|
620
|
+
this.config.transport = new MemoryTransport(this.devMailbox);
|
|
621
|
+
core.logger.info("[OrbitSignal] Dev Mode Enabled: Emails will be intercepted to Dev Mailbox");
|
|
622
|
+
const devServer = new DevServer(this.devMailbox, this.config.devUiPrefix || "/__mail", {
|
|
623
|
+
allowInProduction: this.config.devUiAllowInProduction,
|
|
624
|
+
gate: this.config.devUiGate
|
|
625
|
+
});
|
|
626
|
+
devServer.register(core);
|
|
627
|
+
}
|
|
628
|
+
core.container.singleton("mail", () => this);
|
|
629
|
+
core.adapter.use("*", async (c, next) => {
|
|
630
|
+
c.set("mail", this);
|
|
631
|
+
await next();
|
|
632
|
+
return void 0;
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Send a mailable instance
|
|
637
|
+
*
|
|
638
|
+
* @param mailable - The mailable object to send.
|
|
639
|
+
* @returns A promise that resolves when the email is sent.
|
|
640
|
+
* @throws {Error} If the message is missing "from" or "to" addresses, or if no transport is configured.
|
|
641
|
+
*/
|
|
642
|
+
async send(mailable) {
|
|
643
|
+
const envelope = await mailable.buildEnvelope(this.config);
|
|
644
|
+
if (!envelope.from) {
|
|
645
|
+
throw new Error('Message is missing "from" address');
|
|
646
|
+
}
|
|
647
|
+
if (!envelope.to || envelope.to.length === 0) {
|
|
648
|
+
throw new Error('Message is missing "to" address');
|
|
649
|
+
}
|
|
650
|
+
const content = await mailable.renderContent();
|
|
651
|
+
const message = {
|
|
652
|
+
...envelope,
|
|
653
|
+
from: envelope.from,
|
|
654
|
+
to: envelope.to,
|
|
655
|
+
subject: envelope.subject || "(No Subject)",
|
|
656
|
+
priority: envelope.priority || "normal",
|
|
657
|
+
html: content.html
|
|
658
|
+
};
|
|
659
|
+
if (content.text) {
|
|
660
|
+
message.text = content.text;
|
|
661
|
+
}
|
|
662
|
+
if (!this.config.transport) {
|
|
663
|
+
throw new Error("[OrbitSignal] No transport configured. Did you call register the orbit?");
|
|
664
|
+
}
|
|
665
|
+
await this.config.transport.send(message);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Queue a mailable instance
|
|
669
|
+
*/
|
|
670
|
+
async queue(mailable) {
|
|
671
|
+
try {
|
|
672
|
+
const queue = this.core?.container.make("queue");
|
|
673
|
+
if (queue) {
|
|
674
|
+
await queue.push(mailable);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
} catch (_e) {
|
|
678
|
+
}
|
|
679
|
+
await this.send(mailable);
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
|
|
230
683
|
// src/transports/SesTransport.ts
|
|
231
684
|
import { SESClient, SendRawEmailCommand } from "@aws-sdk/client-ses";
|
|
232
685
|
import nodemailer from "nodemailer";
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravito/signal",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Powerful email framework for Gravito applications with Dev UI and multi-renderer support.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
10
|
-
"test": "bun test"
|
|
9
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --external @gravito/core --external @gravito/stream --external @gravito/prism",
|
|
10
|
+
"test": "bun test",
|
|
11
|
+
"test:coverage": "bun test --coverage --coverage-threshold=80",
|
|
12
|
+
"test:ci": "bun test --coverage --coverage-threshold=80",
|
|
13
|
+
"typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck"
|
|
11
14
|
},
|
|
12
15
|
"keywords": [
|
|
13
16
|
"gravito",
|
|
14
17
|
"mail",
|
|
15
18
|
"smtp",
|
|
16
|
-
"mjml",
|
|
17
19
|
"email",
|
|
18
20
|
"react-email",
|
|
19
21
|
"vue-email"
|
|
@@ -22,13 +24,12 @@
|
|
|
22
24
|
"license": "MIT",
|
|
23
25
|
"dependencies": {
|
|
24
26
|
"@aws-sdk/client-ses": "^3.953.0",
|
|
25
|
-
"
|
|
26
|
-
"nodemailer": "^6.9.1"
|
|
27
|
+
"nodemailer": "^7.0.11"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
|
-
"gravito
|
|
30
|
-
"@gravito/stream": "
|
|
31
|
-
"@gravito/prism": "
|
|
30
|
+
"@gravito/core": "workspace:*",
|
|
31
|
+
"@gravito/stream": "workspace:*",
|
|
32
|
+
"@gravito/prism": "workspace:*",
|
|
32
33
|
"react": "^19.0.0",
|
|
33
34
|
"react-dom": "^19.0.0",
|
|
34
35
|
"vue": "^3.0.0"
|
|
@@ -48,17 +49,17 @@
|
|
|
48
49
|
}
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
|
-
"@gravito/prism": "
|
|
52
|
-
"@gravito/stream": "
|
|
53
|
-
"@types/mjml": "^4.7.4",
|
|
52
|
+
"@gravito/prism": "workspace:*",
|
|
53
|
+
"@gravito/stream": "workspace:*",
|
|
54
54
|
"@types/nodemailer": "^6.4.14",
|
|
55
55
|
"@types/react": "^19.0.0",
|
|
56
56
|
"@types/react-dom": "^19.0.0",
|
|
57
57
|
"@vue/server-renderer": "^3.0.0",
|
|
58
|
-
"gravito
|
|
58
|
+
"@gravito/core": "workspace:*",
|
|
59
59
|
"tsup": "8.5.1",
|
|
60
|
-
"typescript": "^5.
|
|
61
|
-
"vue": "^3.0.0"
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"vue": "^3.0.0",
|
|
62
|
+
"bun-types": "latest"
|
|
62
63
|
},
|
|
63
64
|
"publishConfig": {
|
|
64
65
|
"access": "public"
|
|
@@ -69,4 +70,4 @@
|
|
|
69
70
|
"url": "git+https://github.com/gravito-framework/gravito.git",
|
|
70
71
|
"directory": "packages/signal"
|
|
71
72
|
}
|
|
72
|
-
}
|
|
73
|
+
}
|