@getstrata/core 0.5.10 → 0.5.13
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/core/admin/formatValue.d.ts +3 -0
- package/dist/core/admin/index.d.ts +3 -0
- package/dist/core/admin/registry.d.ts +10 -0
- package/dist/core/admin/types.d.ts +24 -0
- package/dist/core/database/baseRepository.d.ts +4 -1
- package/dist/core/database/index.d.ts +2 -2
- package/dist/core/database/relationships.d.ts +46 -2
- package/dist/core/database/repositoryQuery.d.ts +4 -1
- package/dist/core/mail/mailer.d.ts +3 -1
- package/dist/core/mail/markdownMail.d.ts +15 -0
- package/dist/core/mail/markdownMailable.d.ts +12 -0
- package/dist/core/notifications/dispatcher.d.ts +13 -0
- package/dist/core/notifications/index.d.ts +3 -0
- package/dist/core/notifications/notification.d.ts +7 -0
- package/dist/core/notifications/types.d.ts +25 -0
- package/dist/core/queue/failedJobService.d.ts +1 -0
- package/dist/core/queue/publicQueue.d.ts +2 -1
- package/dist/entries/auth/guard.js +168 -0
- package/dist/entries/database.js +174 -0
- package/dist/entries/http/webErrorResponse.js +168 -0
- package/dist/entries/http.js +168 -0
- package/dist/entries/queue/createAppQueue.js +175 -3
- package/dist/entries/queue/failedJobService.js +6 -0
- package/dist/entries/queue/publicQueue.js +176 -3
- package/dist/entries/queue/queueMetrics.js +175 -3
- package/dist/entries/view.js +168 -0
- package/dist/framework/public-api.d.ts +12 -4
- package/dist/index.js +421 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -149,6 +149,56 @@ function resolveApplicationLogger() {
|
|
|
149
149
|
function resolveApplicationDependencies() {
|
|
150
150
|
return requireActiveApplicationContext().dependencies;
|
|
151
151
|
}
|
|
152
|
+
// ../../src/core/admin/formatValue.ts
|
|
153
|
+
function formatAdminValue(value, type = "text") {
|
|
154
|
+
if (value === null || value === undefined) {
|
|
155
|
+
return "";
|
|
156
|
+
}
|
|
157
|
+
if (type === "boolean") {
|
|
158
|
+
return value ? "yes" : "no";
|
|
159
|
+
}
|
|
160
|
+
if (type === "number") {
|
|
161
|
+
return String(value);
|
|
162
|
+
}
|
|
163
|
+
if (type === "datetime") {
|
|
164
|
+
if (value instanceof Date) {
|
|
165
|
+
return value.toISOString();
|
|
166
|
+
}
|
|
167
|
+
return String(value);
|
|
168
|
+
}
|
|
169
|
+
if (type === "code") {
|
|
170
|
+
if (typeof value === "string") {
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
return JSON.stringify(value, null, 2);
|
|
174
|
+
}
|
|
175
|
+
if (typeof value === "object") {
|
|
176
|
+
return JSON.stringify(value);
|
|
177
|
+
}
|
|
178
|
+
return String(value);
|
|
179
|
+
}
|
|
180
|
+
// ../../src/core/admin/registry.ts
|
|
181
|
+
class AdminResourceRegistry {
|
|
182
|
+
resources = new Map;
|
|
183
|
+
register(resource) {
|
|
184
|
+
if (this.resources.has(resource.name)) {
|
|
185
|
+
throw new Error(`Admin resource "${resource.name}" is already registered.`);
|
|
186
|
+
}
|
|
187
|
+
this.resources.set(resource.name, resource);
|
|
188
|
+
}
|
|
189
|
+
get(name) {
|
|
190
|
+
return this.resources.get(name);
|
|
191
|
+
}
|
|
192
|
+
list() {
|
|
193
|
+
return [...this.resources.values()].map(({ handlers: _handlers, ...definition }) => definition);
|
|
194
|
+
}
|
|
195
|
+
all() {
|
|
196
|
+
return [...this.resources.values()];
|
|
197
|
+
}
|
|
198
|
+
clear() {
|
|
199
|
+
this.resources.clear();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
152
202
|
// ../../src/core/auth/authContext.ts
|
|
153
203
|
import { AsyncLocalStorage } from "async_hooks";
|
|
154
204
|
var authContext = new AsyncLocalStorage;
|
|
@@ -942,6 +992,66 @@ function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
|
|
|
942
992
|
}
|
|
943
993
|
return groups;
|
|
944
994
|
}
|
|
995
|
+
function morphMany(definition) {
|
|
996
|
+
return {
|
|
997
|
+
type: "morphMany",
|
|
998
|
+
...definition
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function morphOne(definition) {
|
|
1002
|
+
return {
|
|
1003
|
+
type: "morphOne",
|
|
1004
|
+
...definition
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
function morphTo(definition) {
|
|
1008
|
+
return {
|
|
1009
|
+
type: "morphTo",
|
|
1010
|
+
...definition
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
function indexMorphManyRelation(parents, children, relation) {
|
|
1014
|
+
const groups = new Map;
|
|
1015
|
+
for (const parent of parents) {
|
|
1016
|
+
groups.set(parent[relation.localKey], []);
|
|
1017
|
+
}
|
|
1018
|
+
for (const child of children) {
|
|
1019
|
+
if (child[relation.morphTypeKey] !== relation.morphType) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
const key = child[relation.morphIdKey];
|
|
1023
|
+
const group = groups.get(key);
|
|
1024
|
+
if (!group) {
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
group.push(child);
|
|
1028
|
+
}
|
|
1029
|
+
return groups;
|
|
1030
|
+
}
|
|
1031
|
+
function indexMorphOneRelation(parents, children, relation) {
|
|
1032
|
+
const grouped = indexMorphManyRelation(parents, children, relation);
|
|
1033
|
+
const result = new Map;
|
|
1034
|
+
for (const parent of parents) {
|
|
1035
|
+
const matches = grouped.get(parent[relation.localKey]) ?? [];
|
|
1036
|
+
result.set(parent[relation.localKey], matches[0]);
|
|
1037
|
+
}
|
|
1038
|
+
return result;
|
|
1039
|
+
}
|
|
1040
|
+
function indexMorphToRelation(children, parentsByType, relation) {
|
|
1041
|
+
const result = new Map;
|
|
1042
|
+
for (const child of children) {
|
|
1043
|
+
const morphType = String(child[relation.morphTypeKey]);
|
|
1044
|
+
const parents = parentsByType.get(morphType);
|
|
1045
|
+
if (!parents) {
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
const parent = parents.get(child[relation.morphIdKey]);
|
|
1049
|
+
if (parent) {
|
|
1050
|
+
result.set(child[relation.morphIdKey], parent);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return result;
|
|
1054
|
+
}
|
|
945
1055
|
|
|
946
1056
|
// ../../src/config/database.ts
|
|
947
1057
|
function readInteger(name, fallback) {
|
|
@@ -1145,6 +1255,37 @@ class RepositoryQuery {
|
|
|
1145
1255
|
});
|
|
1146
1256
|
return this;
|
|
1147
1257
|
}
|
|
1258
|
+
withMorphMany(as, relation, childRepository, options = {}) {
|
|
1259
|
+
this.eagerLoads.push({
|
|
1260
|
+
kind: "morphMany",
|
|
1261
|
+
as,
|
|
1262
|
+
relation,
|
|
1263
|
+
repository: childRepository,
|
|
1264
|
+
options
|
|
1265
|
+
});
|
|
1266
|
+
return this;
|
|
1267
|
+
}
|
|
1268
|
+
withMorphOne(as, relation, childRepository, options = {}) {
|
|
1269
|
+
this.eagerLoads.push({
|
|
1270
|
+
kind: "morphOne",
|
|
1271
|
+
as,
|
|
1272
|
+
relation,
|
|
1273
|
+
repository: childRepository,
|
|
1274
|
+
options
|
|
1275
|
+
});
|
|
1276
|
+
return this;
|
|
1277
|
+
}
|
|
1278
|
+
withMorphTo(as, relation, repositoriesByType, options = {}) {
|
|
1279
|
+
this.eagerLoads.push({
|
|
1280
|
+
kind: "morphTo",
|
|
1281
|
+
as,
|
|
1282
|
+
relation,
|
|
1283
|
+
repository: this.repository,
|
|
1284
|
+
morphRepositories: repositoriesByType,
|
|
1285
|
+
options
|
|
1286
|
+
});
|
|
1287
|
+
return this;
|
|
1288
|
+
}
|
|
1148
1289
|
async get() {
|
|
1149
1290
|
const rows = await this.repository.findAll(this.buildOptions());
|
|
1150
1291
|
return await this.attach(rows);
|
|
@@ -1205,6 +1346,33 @@ class RepositoryQuery {
|
|
|
1205
1346
|
}));
|
|
1206
1347
|
continue;
|
|
1207
1348
|
}
|
|
1349
|
+
if (load.kind === "morphMany") {
|
|
1350
|
+
const relation2 = load.relation;
|
|
1351
|
+
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
|
|
1352
|
+
result = result.map((row) => ({
|
|
1353
|
+
...row,
|
|
1354
|
+
[load.as]: grouped2.get(row[relation2.localKey]) ?? []
|
|
1355
|
+
}));
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
if (load.kind === "morphOne") {
|
|
1359
|
+
const relation2 = load.relation;
|
|
1360
|
+
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
|
|
1361
|
+
result = result.map((row) => ({
|
|
1362
|
+
...row,
|
|
1363
|
+
[load.as]: grouped2.get(row[relation2.localKey])
|
|
1364
|
+
}));
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
if (load.kind === "morphTo") {
|
|
1368
|
+
const relation2 = load.relation;
|
|
1369
|
+
const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
|
|
1370
|
+
result = result.map((row) => ({
|
|
1371
|
+
...row,
|
|
1372
|
+
[load.as]: grouped2.get(row[relation2.morphIdKey])
|
|
1373
|
+
}));
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1208
1376
|
const relation = load.relation;
|
|
1209
1377
|
const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
|
|
1210
1378
|
result = result.map((row) => ({
|
|
@@ -1481,6 +1649,56 @@ class BaseRepository {
|
|
|
1481
1649
|
}, options);
|
|
1482
1650
|
return indexBelongsToRelation(children, parents, relation);
|
|
1483
1651
|
}
|
|
1652
|
+
async loadMorphManyForParents(parents, relation, options = {}) {
|
|
1653
|
+
if (parents.length === 0) {
|
|
1654
|
+
return indexMorphManyRelation(parents, [], relation);
|
|
1655
|
+
}
|
|
1656
|
+
const parentIds = [...new Set(parents.map((parent) => parent[relation.localKey]))];
|
|
1657
|
+
const children = await this.findWhere({
|
|
1658
|
+
[relation.morphTypeKey]: relation.morphType,
|
|
1659
|
+
[relation.morphIdKey]: parentIds
|
|
1660
|
+
}, options);
|
|
1661
|
+
return indexMorphManyRelation(parents, children, relation);
|
|
1662
|
+
}
|
|
1663
|
+
async loadMorphOneForParents(parents, relation, options = {}) {
|
|
1664
|
+
const grouped = await this.loadMorphManyForParents(parents, relation, options);
|
|
1665
|
+
const result = new Map;
|
|
1666
|
+
for (const parent of parents) {
|
|
1667
|
+
const matches = grouped.get(parent[relation.localKey]) ?? [];
|
|
1668
|
+
result.set(parent[relation.localKey], matches[0]);
|
|
1669
|
+
}
|
|
1670
|
+
return result;
|
|
1671
|
+
}
|
|
1672
|
+
async loadMorphToForChildren(children, relation, repositoriesByType, options = {}) {
|
|
1673
|
+
if (children.length === 0) {
|
|
1674
|
+
return new Map;
|
|
1675
|
+
}
|
|
1676
|
+
const idsByType = new Map;
|
|
1677
|
+
for (const child of children) {
|
|
1678
|
+
const morphType = String(child[relation.morphTypeKey]);
|
|
1679
|
+
const morphId = child[relation.morphIdKey];
|
|
1680
|
+
const ids = idsByType.get(morphType) ?? new Set;
|
|
1681
|
+
ids.add(morphId);
|
|
1682
|
+
idsByType.set(morphType, ids);
|
|
1683
|
+
}
|
|
1684
|
+
const parentsByType = new Map;
|
|
1685
|
+
for (const [morphType, ids] of idsByType) {
|
|
1686
|
+
const repository = repositoriesByType.get(morphType);
|
|
1687
|
+
if (!repository) {
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
const ownerKey = repository.getTable().primaryKey;
|
|
1691
|
+
const parents = await repository.withConnection(this.connection).findWhere({
|
|
1692
|
+
[ownerKey]: [...ids]
|
|
1693
|
+
}, options);
|
|
1694
|
+
const indexed = new Map;
|
|
1695
|
+
for (const parent of parents) {
|
|
1696
|
+
indexed.set(parent[ownerKey], parent);
|
|
1697
|
+
}
|
|
1698
|
+
parentsByType.set(morphType, indexed);
|
|
1699
|
+
}
|
|
1700
|
+
return indexMorphToRelation(children, parentsByType, relation);
|
|
1701
|
+
}
|
|
1484
1702
|
}
|
|
1485
1703
|
var baseRepository_default = BaseRepository;
|
|
1486
1704
|
// ../../src/core/database/bindConnection.ts
|
|
@@ -2698,18 +2916,7 @@ async function defaultSmtpTransport(config, message) {
|
|
|
2698
2916
|
await socket.write(`DATA\r
|
|
2699
2917
|
`);
|
|
2700
2918
|
await waitForSmtpResponse(readResponse, ["354"]);
|
|
2701
|
-
const payload =
|
|
2702
|
-
`From: ${config.from}`,
|
|
2703
|
-
`To: ${message.to}`,
|
|
2704
|
-
`Subject: ${message.subject}`,
|
|
2705
|
-
"MIME-Version: 1.0",
|
|
2706
|
-
"Content-Type: text/plain; charset=utf-8",
|
|
2707
|
-
"",
|
|
2708
|
-
message.body,
|
|
2709
|
-
".",
|
|
2710
|
-
""
|
|
2711
|
-
].join(`\r
|
|
2712
|
-
`);
|
|
2919
|
+
const payload = buildSmtpPayload(config.from, message);
|
|
2713
2920
|
await socket.write(payload);
|
|
2714
2921
|
await waitForSmtpResponse(readResponse, ["250"]);
|
|
2715
2922
|
await socket.write(`QUIT\r
|
|
@@ -2719,6 +2926,35 @@ async function defaultSmtpTransport(config, message) {
|
|
|
2719
2926
|
socket.end();
|
|
2720
2927
|
}
|
|
2721
2928
|
}
|
|
2929
|
+
function buildSmtpPayload(from, message) {
|
|
2930
|
+
const headers = [
|
|
2931
|
+
`From: ${from}`,
|
|
2932
|
+
`To: ${message.to}`,
|
|
2933
|
+
`Subject: ${message.subject}`,
|
|
2934
|
+
"MIME-Version: 1.0"
|
|
2935
|
+
];
|
|
2936
|
+
if (message.html) {
|
|
2937
|
+
const boundary = `strata-${Date.now().toString(36)}`;
|
|
2938
|
+
headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
|
|
2939
|
+
const parts = [
|
|
2940
|
+
`--${boundary}`,
|
|
2941
|
+
"Content-Type: text/plain; charset=utf-8",
|
|
2942
|
+
"",
|
|
2943
|
+
message.body,
|
|
2944
|
+
`--${boundary}`,
|
|
2945
|
+
"Content-Type: text/html; charset=utf-8",
|
|
2946
|
+
"",
|
|
2947
|
+
message.html,
|
|
2948
|
+
`--${boundary}--`,
|
|
2949
|
+
""
|
|
2950
|
+
];
|
|
2951
|
+
return [...headers, "", ...parts, ".", ""].join(`\r
|
|
2952
|
+
`);
|
|
2953
|
+
}
|
|
2954
|
+
headers.push("Content-Type: text/plain; charset=utf-8");
|
|
2955
|
+
return [...headers, "", message.body, ".", ""].join(`\r
|
|
2956
|
+
`);
|
|
2957
|
+
}
|
|
2722
2958
|
|
|
2723
2959
|
class LogMailDriver {
|
|
2724
2960
|
async send(message) {
|
|
@@ -2727,7 +2963,8 @@ class LogMailDriver {
|
|
|
2727
2963
|
channel: "mail",
|
|
2728
2964
|
to: message.to,
|
|
2729
2965
|
subject: message.subject,
|
|
2730
|
-
body: message.body
|
|
2966
|
+
body: message.body,
|
|
2967
|
+
...message.html ? { html: message.html } : {}
|
|
2731
2968
|
}));
|
|
2732
2969
|
}
|
|
2733
2970
|
}
|
|
@@ -4867,6 +5104,150 @@ function resetGracefulShutdownForTests() {
|
|
|
4867
5104
|
shutdownInstalled = false;
|
|
4868
5105
|
shuttingDown = false;
|
|
4869
5106
|
}
|
|
5107
|
+
// ../../src/core/mail/markdownMail.ts
|
|
5108
|
+
function escapeHtml(value) {
|
|
5109
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5110
|
+
}
|
|
5111
|
+
function stripMarkdown(markdown) {
|
|
5112
|
+
return markdown.replace(/^#{1,6}\s+/gm, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/^[-*]\s+/gm, "\u2022 ").replace(/```[\s\S]*?```/g, "").replace(/\n{3,}/g, `
|
|
5113
|
+
|
|
5114
|
+
`).trim();
|
|
5115
|
+
}
|
|
5116
|
+
function markdownToHtml(markdown) {
|
|
5117
|
+
const escaped = markdown.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5118
|
+
return escaped.replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>").replace(/^# (.+)$/gm, "<h1>$1</h1>").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>').replace(/^[-*]\s+(.+)$/gm, "<li>$1</li>").replace(/(<li>[\s\S]*?<\/li>\n?)+/g, (block) => `<ul>${block}</ul>`).replace(/```(\w+)?\n([\s\S]*?)```/g, "<pre><code>$2</code></pre>").split(/\n\n+/).map((block) => {
|
|
5119
|
+
if (block.startsWith("<")) {
|
|
5120
|
+
return block;
|
|
5121
|
+
}
|
|
5122
|
+
return `<p>${block.replace(/\n/g, " ")}</p>`;
|
|
5123
|
+
}).join(`
|
|
5124
|
+
`);
|
|
5125
|
+
}
|
|
5126
|
+
function wrapMarkdownMailLayout(bodyHtml, options = {}) {
|
|
5127
|
+
const title = escapeHtml(options.title ?? "GetStrata");
|
|
5128
|
+
const preview = escapeHtml(options.preview ?? "");
|
|
5129
|
+
const footer = escapeHtml(options.footer ?? "Sent by GetStrata");
|
|
5130
|
+
return `<!DOCTYPE html>
|
|
5131
|
+
<html lang="en">
|
|
5132
|
+
<head>
|
|
5133
|
+
<meta charset="utf-8">
|
|
5134
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5135
|
+
<title>${title}</title>
|
|
5136
|
+
<style>
|
|
5137
|
+
body { font-family: system-ui, sans-serif; line-height: 1.5; color: #111827; background: #f9fafb; margin: 0; padding: 24px; }
|
|
5138
|
+
.container { max-width: 640px; margin: 0 auto; background: #ffffff; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden; }
|
|
5139
|
+
.header { padding: 20px 24px; border-bottom: 1px solid #e5e7eb; font-weight: 600; }
|
|
5140
|
+
.content { padding: 24px; }
|
|
5141
|
+
.footer { padding: 16px 24px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 14px; }
|
|
5142
|
+
a { color: #2563eb; }
|
|
5143
|
+
code { background: #f3f4f6; padding: 2px 4px; border-radius: 4px; }
|
|
5144
|
+
pre { background: #111827; color: #f9fafb; padding: 12px; border-radius: 6px; overflow-x: auto; }
|
|
5145
|
+
</style>
|
|
5146
|
+
</head>
|
|
5147
|
+
<body>
|
|
5148
|
+
${preview ? `<span style="display:none;max-height:0;overflow:hidden;">${preview}</span>` : ""}
|
|
5149
|
+
<div class="container">
|
|
5150
|
+
<div class="header">${title}</div>
|
|
5151
|
+
<div class="content">${bodyHtml}</div>
|
|
5152
|
+
<div class="footer">${footer}</div>
|
|
5153
|
+
</div>
|
|
5154
|
+
</body>
|
|
5155
|
+
</html>`;
|
|
5156
|
+
}
|
|
5157
|
+
function renderMarkdownMail(markdown, options = {}) {
|
|
5158
|
+
const bodyHtml = markdownToHtml(markdown.trim());
|
|
5159
|
+
const html = wrapMarkdownMailLayout(bodyHtml, options);
|
|
5160
|
+
const text = stripMarkdown(markdown);
|
|
5161
|
+
return { html, text };
|
|
5162
|
+
}
|
|
5163
|
+
// ../../src/core/mail/markdownMailable.ts
|
|
5164
|
+
function buildMarkdownMailMessage(input) {
|
|
5165
|
+
const rendered = renderMarkdownMail(input.markdown, {
|
|
5166
|
+
title: input.layout?.title ?? input.subject,
|
|
5167
|
+
...input.layout
|
|
5168
|
+
});
|
|
5169
|
+
return {
|
|
5170
|
+
to: input.to,
|
|
5171
|
+
subject: input.subject,
|
|
5172
|
+
body: rendered.text,
|
|
5173
|
+
html: rendered.html
|
|
5174
|
+
};
|
|
5175
|
+
}
|
|
5176
|
+
async function sendMarkdownMail(mailer2, input) {
|
|
5177
|
+
await mailer2.send(buildMarkdownMailMessage(input));
|
|
5178
|
+
}
|
|
5179
|
+
// ../../src/core/notifications/dispatcher.ts
|
|
5180
|
+
class NotificationDispatcher {
|
|
5181
|
+
mailer;
|
|
5182
|
+
databaseStore;
|
|
5183
|
+
constructor(mailer2, databaseStore = null) {
|
|
5184
|
+
this.mailer = mailer2;
|
|
5185
|
+
this.databaseStore = databaseStore;
|
|
5186
|
+
}
|
|
5187
|
+
async send(notifiable, notification) {
|
|
5188
|
+
for (const channel of notification.via(notifiable)) {
|
|
5189
|
+
if (channel === "mail") {
|
|
5190
|
+
await this.sendMail(notifiable, notification);
|
|
5191
|
+
continue;
|
|
5192
|
+
}
|
|
5193
|
+
if (channel === "database") {
|
|
5194
|
+
await this.sendDatabase(notifiable, notification);
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
async sendMail(notifiable, notification) {
|
|
5199
|
+
const routed = notifiable.routeNotificationFor("mail");
|
|
5200
|
+
if (routed === null) {
|
|
5201
|
+
return;
|
|
5202
|
+
}
|
|
5203
|
+
const message = notification.toMail(notifiable);
|
|
5204
|
+
if (!message) {
|
|
5205
|
+
return;
|
|
5206
|
+
}
|
|
5207
|
+
if (message.markdown) {
|
|
5208
|
+
await this.mailer.send(buildMarkdownMailMessage({
|
|
5209
|
+
to: String(routed),
|
|
5210
|
+
subject: message.subject,
|
|
5211
|
+
markdown: message.markdown
|
|
5212
|
+
}));
|
|
5213
|
+
return;
|
|
5214
|
+
}
|
|
5215
|
+
await this.mailer.send({
|
|
5216
|
+
to: String(routed),
|
|
5217
|
+
subject: message.subject,
|
|
5218
|
+
body: message.body ?? "",
|
|
5219
|
+
...message.html ? { html: message.html } : {}
|
|
5220
|
+
});
|
|
5221
|
+
}
|
|
5222
|
+
async sendDatabase(notifiable, notification) {
|
|
5223
|
+
if (!this.databaseStore) {
|
|
5224
|
+
return;
|
|
5225
|
+
}
|
|
5226
|
+
const payload = notification.toDatabase(notifiable);
|
|
5227
|
+
if (!payload) {
|
|
5228
|
+
return;
|
|
5229
|
+
}
|
|
5230
|
+
await this.databaseStore.create({
|
|
5231
|
+
userId: Number(notifiable.getNotificationKey()),
|
|
5232
|
+
...payload
|
|
5233
|
+
});
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
function createNotificationDispatcher(mailer2, databaseStore) {
|
|
5237
|
+
return new NotificationDispatcher(mailer2, databaseStore ?? null);
|
|
5238
|
+
}
|
|
5239
|
+
// ../../src/core/notifications/notification.ts
|
|
5240
|
+
class Notification {
|
|
5241
|
+
via(_notifiable) {
|
|
5242
|
+
throw new Error("Notification subclasses must implement via().");
|
|
5243
|
+
}
|
|
5244
|
+
toMail(_notifiable) {
|
|
5245
|
+
return null;
|
|
5246
|
+
}
|
|
5247
|
+
toDatabase(_notifiable) {
|
|
5248
|
+
return null;
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
4870
5251
|
// ../../src/core/queue/index.ts
|
|
4871
5252
|
class Job {
|
|
4872
5253
|
maxAttempts;
|
|
@@ -4933,6 +5314,12 @@ class FailedJobService {
|
|
|
4933
5314
|
await this.repository.deleteById(id);
|
|
4934
5315
|
return failedJob;
|
|
4935
5316
|
}
|
|
5317
|
+
async delete(id) {
|
|
5318
|
+
const deleted = await this.repository.deleteById(id);
|
|
5319
|
+
if (!deleted) {
|
|
5320
|
+
throw new Error(`Failed job ${id} not found.`);
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
4936
5323
|
async flush() {
|
|
4937
5324
|
const jobs = await this.repository.findAll();
|
|
4938
5325
|
let deleted = 0;
|
|
@@ -4974,9 +5361,6 @@ class JobRegistry {
|
|
|
4974
5361
|
}
|
|
4975
5362
|
var jobRegistry = new JobRegistry;
|
|
4976
5363
|
|
|
4977
|
-
// ../../src/core/queue/redisQueue.ts
|
|
4978
|
-
var {RedisClient: RedisClient3 } = globalThis.Bun;
|
|
4979
|
-
|
|
4980
5364
|
// ../../src/config/queue.ts
|
|
4981
5365
|
var queueConfig = {
|
|
4982
5366
|
driver: process.env.QUEUE_DRIVER === "redis" || process.env.QUEUE_DRIVER === "async" || process.env.QUEUE_DRIVER === "sync" ? process.env.QUEUE_DRIVER : DEFAULT_QUEUE_DRIVER,
|
|
@@ -5015,6 +5399,7 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
5015
5399
|
}
|
|
5016
5400
|
|
|
5017
5401
|
// ../../src/core/queue/redisQueue.ts
|
|
5402
|
+
var {RedisClient: RedisClient3 } = globalThis.Bun;
|
|
5018
5403
|
var QUEUE_LIST_KEY = "workhub:queue:default";
|
|
5019
5404
|
var QUEUE_HIGH_KEY = "workhub:queue:high";
|
|
5020
5405
|
var QUEUE_LOW_KEY = "workhub:queue:low";
|
|
@@ -5370,6 +5755,7 @@ function validateObject(payload, schema) {
|
|
|
5370
5755
|
return output;
|
|
5371
5756
|
}
|
|
5372
5757
|
export {
|
|
5758
|
+
wrapMarkdownMailLayout,
|
|
5373
5759
|
withMigrationLock,
|
|
5374
5760
|
withMiddleware,
|
|
5375
5761
|
withErrorHandling,
|
|
@@ -5377,15 +5763,18 @@ export {
|
|
|
5377
5763
|
validateObject,
|
|
5378
5764
|
toResourceCollection,
|
|
5379
5765
|
toPaginatedResourceCollection,
|
|
5766
|
+
stripMarkdown,
|
|
5380
5767
|
stringRule,
|
|
5381
5768
|
storageFacade as storage,
|
|
5382
5769
|
setActiveApplicationContext,
|
|
5383
5770
|
serializeDate,
|
|
5771
|
+
sendMarkdownMail,
|
|
5384
5772
|
securedBindRouteModelByKey,
|
|
5385
5773
|
securedBindRouteModel,
|
|
5386
5774
|
runWithDatabaseConnection,
|
|
5387
5775
|
runWithAuthUser,
|
|
5388
5776
|
runSeedersFromDirectory,
|
|
5777
|
+
runQueueJob,
|
|
5389
5778
|
runInTransaction,
|
|
5390
5779
|
runGracefulShutdown,
|
|
5391
5780
|
runDueScheduledTasks,
|
|
@@ -5403,6 +5792,7 @@ export {
|
|
|
5403
5792
|
resolveApplicationCache,
|
|
5404
5793
|
resolveApplicationAuth,
|
|
5405
5794
|
required,
|
|
5795
|
+
renderMarkdownMail,
|
|
5406
5796
|
registerShutdownHandler,
|
|
5407
5797
|
registerModelRepository,
|
|
5408
5798
|
readSubmittedCsrfTokenFromBody,
|
|
@@ -5416,9 +5806,13 @@ export {
|
|
|
5416
5806
|
paginatedResponse,
|
|
5417
5807
|
normalizeMetricPath,
|
|
5418
5808
|
noContentResponse,
|
|
5809
|
+
morphTo,
|
|
5810
|
+
morphOne,
|
|
5811
|
+
morphMany,
|
|
5419
5812
|
minLength,
|
|
5420
5813
|
migrateDatabase,
|
|
5421
5814
|
maxLength,
|
|
5815
|
+
markdownToHtml,
|
|
5422
5816
|
mailer,
|
|
5423
5817
|
mail,
|
|
5424
5818
|
log,
|
|
@@ -5430,6 +5824,9 @@ export {
|
|
|
5430
5824
|
isEtagEnabled,
|
|
5431
5825
|
installGracefulShutdownSignals,
|
|
5432
5826
|
inferReferencedTable,
|
|
5827
|
+
indexMorphToRelation,
|
|
5828
|
+
indexMorphOneRelation,
|
|
5829
|
+
indexMorphManyRelation,
|
|
5433
5830
|
indexHasOneRelation,
|
|
5434
5831
|
indexHasManyRelation,
|
|
5435
5832
|
indexBelongsToRelation,
|
|
@@ -5442,6 +5839,7 @@ export {
|
|
|
5442
5839
|
getMigrationStatus,
|
|
5443
5840
|
getActiveDatabaseConnection,
|
|
5444
5841
|
freshDatabase,
|
|
5842
|
+
formatAdminValue,
|
|
5445
5843
|
filterMassAssignable,
|
|
5446
5844
|
events,
|
|
5447
5845
|
etagFromResource,
|
|
@@ -5459,6 +5857,7 @@ export {
|
|
|
5459
5857
|
createQueueWorker,
|
|
5460
5858
|
createQueue,
|
|
5461
5859
|
createProductionQueue,
|
|
5860
|
+
createNotificationDispatcher,
|
|
5462
5861
|
createMetricsMiddleware,
|
|
5463
5862
|
createMemoryThrottleMiddleware,
|
|
5464
5863
|
createLoginThrottleMiddleware,
|
|
@@ -5474,6 +5873,8 @@ export {
|
|
|
5474
5873
|
composeMiddleware,
|
|
5475
5874
|
compileBlueprint,
|
|
5476
5875
|
cache,
|
|
5876
|
+
buildSmtpPayload,
|
|
5877
|
+
buildMarkdownMailMessage,
|
|
5477
5878
|
bindDatabaseConnection2 as bindDatabaseConnection,
|
|
5478
5879
|
belongsToMany,
|
|
5479
5880
|
belongsTo,
|
|
@@ -5503,6 +5904,8 @@ export {
|
|
|
5503
5904
|
PostgresGrammar,
|
|
5504
5905
|
PolicyGate,
|
|
5505
5906
|
Policy,
|
|
5907
|
+
NotificationDispatcher,
|
|
5908
|
+
Notification,
|
|
5506
5909
|
NotFoundError,
|
|
5507
5910
|
MySqlGrammar,
|
|
5508
5911
|
Model,
|
|
@@ -5526,5 +5929,6 @@ export {
|
|
|
5526
5929
|
Blueprint,
|
|
5527
5930
|
baseRepository_default as BaseRepository,
|
|
5528
5931
|
BadRequestError,
|
|
5529
|
-
AsyncQueue
|
|
5932
|
+
AsyncQueue,
|
|
5933
|
+
AdminResourceRegistry
|
|
5530
5934
|
};
|