@web-ts-toolkit/message-service 0.52.0 → 0.54.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/README.md +833 -11
- package/index.d.mts +270 -943
- package/index.d.ts +270 -943
- package/index.js +174 -43
- package/index.mjs +172 -43
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ActionConflictError: () => ActionConflictError,
|
|
34
|
+
ActionInputValidationError: () => ActionInputValidationError,
|
|
34
35
|
ActionNotAllowedError: () => ActionNotAllowedError,
|
|
35
36
|
ActionNotFoundError: () => ActionNotFoundError,
|
|
36
37
|
ActionNotificationPendingError: () => ActionNotificationPendingError,
|
|
@@ -60,6 +61,7 @@ __export(index_exports, {
|
|
|
60
61
|
MessageTransactionRequiredError: () => MessageTransactionRequiredError,
|
|
61
62
|
NoopEmailProvider: () => NoopEmailProvider,
|
|
62
63
|
NoopPaymentProvider: () => NoopPaymentProvider,
|
|
64
|
+
PaymentProviderRequiredError: () => PaymentProviderRequiredError,
|
|
63
65
|
PaymentSessionCompensationAggregateError: () => PaymentSessionCompensationAggregateError,
|
|
64
66
|
PaymentSessionCompensationError: () => PaymentSessionCompensationError,
|
|
65
67
|
TemplateNotFoundError: () => TemplateNotFoundError,
|
|
@@ -290,25 +292,32 @@ function includesAction(templateCd, actionCd, registry) {
|
|
|
290
292
|
|
|
291
293
|
// src/schemas/methods.ts
|
|
292
294
|
var import_mongoose2 = require("mongoose");
|
|
295
|
+
function isValidMessageUserRoles(roles) {
|
|
296
|
+
if (roles === void 0) return true;
|
|
297
|
+
if (!Array.isArray(roles)) return false;
|
|
298
|
+
for (const role of roles) {
|
|
299
|
+
if (typeof role !== "string") return false;
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
293
303
|
function isSender(user) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return fromUser !== null && userId !== null && fromUser === userId;
|
|
304
|
+
if (!isValidMessageUserRoles(user?.roles)) return false;
|
|
305
|
+
return matchesStoredUserId(this.fromUser, user);
|
|
297
306
|
}
|
|
298
307
|
function isReceiver(user) {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
return toUser
|
|
308
|
+
const roles = user?.roles;
|
|
309
|
+
if (!isValidMessageUserRoles(roles)) return false;
|
|
310
|
+
return matchesStoredUserId(this.toUser, user) || this.toRoles.some((r) => roles?.includes(r));
|
|
302
311
|
}
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return
|
|
312
|
+
function matchesStoredUserId(value, user) {
|
|
313
|
+
const stored = value instanceof import_mongoose2.Types.ObjectId || value === null || typeof value !== "object" ? value : value._id;
|
|
314
|
+
const userId = normalizeId(user?._id);
|
|
315
|
+
if (userId === null) return false;
|
|
316
|
+
if (stored instanceof import_mongoose2.Types.ObjectId && /^[a-f\d]{24}$/i.test(userId)) {
|
|
317
|
+
return stored.toHexString() === userId.toLowerCase();
|
|
318
|
+
}
|
|
319
|
+
const storedId = normalizeId(stored);
|
|
320
|
+
return storedId !== null && storedId === userId;
|
|
312
321
|
}
|
|
313
322
|
function normalizeId(value) {
|
|
314
323
|
if (typeof value === "string") {
|
|
@@ -318,6 +327,11 @@ function normalizeId(value) {
|
|
|
318
327
|
return value instanceof import_mongoose2.Types.ObjectId ? value.toHexString() : null;
|
|
319
328
|
}
|
|
320
329
|
|
|
330
|
+
// src/user-id.ts
|
|
331
|
+
function normalizeUserIdForStorage(id) {
|
|
332
|
+
return typeof id === "string" ? id.trim() : id;
|
|
333
|
+
}
|
|
334
|
+
|
|
321
335
|
// src/errors.ts
|
|
322
336
|
var TRANSACTION_REQUIRED_MESSAGE = "message-service requires MongoDB replica set or sharded-cluster transactions for idempotent batch creation";
|
|
323
337
|
var MessageNotFoundError = class extends Error {
|
|
@@ -370,6 +384,16 @@ var ActionNotAllowedError = class extends Error {
|
|
|
370
384
|
markRuntimeError(this, this.name);
|
|
371
385
|
}
|
|
372
386
|
};
|
|
387
|
+
var ActionInputValidationError = class extends Error {
|
|
388
|
+
static [Symbol.hasInstance](value) {
|
|
389
|
+
return isRuntimeError(value, "ActionInputValidationError");
|
|
390
|
+
}
|
|
391
|
+
constructor(message = "invalid action input") {
|
|
392
|
+
super(message);
|
|
393
|
+
this.name = "ActionInputValidationError";
|
|
394
|
+
markRuntimeError(this, this.name);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
373
397
|
var InvalidMessageUserError = class extends Error {
|
|
374
398
|
static [Symbol.hasInstance](value) {
|
|
375
399
|
return isRuntimeError(value, "InvalidMessageUserError");
|
|
@@ -525,6 +549,16 @@ var MessageModelResolutionError = class extends Error {
|
|
|
525
549
|
}
|
|
526
550
|
}
|
|
527
551
|
};
|
|
552
|
+
var PaymentProviderRequiredError = class extends Error {
|
|
553
|
+
static [Symbol.hasInstance](value) {
|
|
554
|
+
return isRuntimeError(value, "PaymentProviderRequiredError");
|
|
555
|
+
}
|
|
556
|
+
constructor() {
|
|
557
|
+
super("paymentProvider is required for messages with a non-empty paymentCd");
|
|
558
|
+
this.name = "PaymentProviderRequiredError";
|
|
559
|
+
markRuntimeError(this, this.name);
|
|
560
|
+
}
|
|
561
|
+
};
|
|
528
562
|
var PaymentSessionCompensationError = class extends Error {
|
|
529
563
|
static [Symbol.hasInstance](value) {
|
|
530
564
|
return isRuntimeError(value, "PaymentSessionCompensationError") || isRuntimeError(value, "PaymentSessionCompensationAggregateError");
|
|
@@ -735,6 +769,9 @@ function requireMessageUserId(user) {
|
|
|
735
769
|
if (!isValidMessageUserId(id)) {
|
|
736
770
|
throw new InvalidMessageUserError();
|
|
737
771
|
}
|
|
772
|
+
if (!isValidMessageUserRoles(user.roles)) {
|
|
773
|
+
throw new InvalidMessageUserError("user.roles must be an array of strings when provided");
|
|
774
|
+
}
|
|
738
775
|
return typeof id === "string" ? id.trim() : String(id);
|
|
739
776
|
}
|
|
740
777
|
var MessageService = class {
|
|
@@ -774,7 +811,11 @@ var MessageService = class {
|
|
|
774
811
|
);
|
|
775
812
|
this.clientRequestDelay = options.clientRequestDelay ?? this.delay;
|
|
776
813
|
this.clientRequestNow = options.clientRequestNow ?? (() => Date.now());
|
|
777
|
-
this.actionLeaseMs =
|
|
814
|
+
this.actionLeaseMs = this.validateDurationOption(
|
|
815
|
+
"actionLeaseMs",
|
|
816
|
+
options.actionLeaseMs === void 0 ? ACTION_LEASE_MS : options.actionLeaseMs,
|
|
817
|
+
1
|
|
818
|
+
);
|
|
778
819
|
}
|
|
779
820
|
// -------------------------------------------------------------------------
|
|
780
821
|
// Message lookup
|
|
@@ -820,10 +861,25 @@ var MessageService = class {
|
|
|
820
861
|
*
|
|
821
862
|
* User-facing entry point: `user` (and `payerUser` when provided) must
|
|
822
863
|
* carry a valid principal id (non-empty string or ObjectId; numbers,
|
|
823
|
-
* arrays, and plain objects are rejected with `InvalidMessageUserError`)
|
|
864
|
+
* arrays, and plain objects are rejected with `InvalidMessageUserError`),
|
|
865
|
+
* and optional `roles` must be undefined or an array of strings,
|
|
824
866
|
* before any template, provider, or model effect runs. Both the
|
|
825
867
|
* idempotent (`clientRequestId`) and direct branches enforce this.
|
|
826
868
|
*
|
|
869
|
+
* Each `PrepareResult.documents` selects that item's attachments; `[]` clears
|
|
870
|
+
* them. Omission/undefined retains the original caller `payload.documents`
|
|
871
|
+
* fallback, independently of the prepared business payload. Selected arrays
|
|
872
|
+
* are copied per item without mutating caller/preparation arrays.
|
|
873
|
+
*
|
|
874
|
+
* Each fresh item's `item.paymentCd || template.paymentCd || ''` requests a
|
|
875
|
+
* session when nonempty, independently of recipients. An empty item override
|
|
876
|
+
* inherits the template code. Absent/null providers throw
|
|
877
|
+
* `PaymentProviderRequiredError`; configured-provider rejections propagate and
|
|
878
|
+
* null/empty session returns throw `Error('payment session creation failed')`.
|
|
879
|
+
* The payer remains `(payerUser || user)._id`, not the prepared sender/recipient.
|
|
880
|
+
* Direct batches retain committed prefixes; idempotent batches commit atomically
|
|
881
|
+
* and compensate all known uncommitted sessions on failure.
|
|
882
|
+
*
|
|
827
883
|
* Idempotent replay contract (MSGF-06): a completed same-scope replay
|
|
828
884
|
* returns the current active/archive records merged from both collections
|
|
829
885
|
* (`Array<IMessage | IMessageArchive>`), sorted by exact
|
|
@@ -991,15 +1047,17 @@ var MessageService = class {
|
|
|
991
1047
|
* or matches one of the recipient's roles.
|
|
992
1048
|
*
|
|
993
1049
|
* User-facing operation: validates `user` via the shared principal
|
|
994
|
-
* contract. Returns `IMessage[]` ordered by
|
|
1050
|
+
* contract before model resolution. Returns `IMessage[]` ordered by
|
|
1051
|
+
* `{ createdAt: -1, _id: -1 }`.
|
|
995
1052
|
*/
|
|
996
1053
|
async listMessages(params) {
|
|
997
1054
|
const { user, limit: rawLimit, skip: rawSkip, populate } = params;
|
|
998
1055
|
const limit = this.normalizeListLimit(rawLimit);
|
|
999
1056
|
const skip = this.normalizeListSkip(rawSkip);
|
|
1057
|
+
const filter = this.buildVisibilityFilter(user);
|
|
1000
1058
|
const Message = this.resolveModel("active");
|
|
1001
1059
|
const query = this.applyPopulate(
|
|
1002
|
-
Message.find(
|
|
1060
|
+
Message.find(filter).sort({ createdAt: -1, _id: -1 }).skip(skip).limit(limit),
|
|
1003
1061
|
populate
|
|
1004
1062
|
);
|
|
1005
1063
|
return query;
|
|
@@ -1008,11 +1066,13 @@ var MessageService = class {
|
|
|
1008
1066
|
* Count active (non-archived) messages visible to a user.
|
|
1009
1067
|
* Useful for badge indicators ("3 new messages").
|
|
1010
1068
|
*
|
|
1011
|
-
* User-facing operation: validates `user` via the shared principal contract
|
|
1069
|
+
* User-facing operation: validates `user` via the shared principal contract
|
|
1070
|
+
* before model resolution.
|
|
1012
1071
|
*/
|
|
1013
1072
|
async countMessages(user) {
|
|
1073
|
+
const filter = this.buildVisibilityFilter(user);
|
|
1014
1074
|
const Message = this.resolveModel("active");
|
|
1015
|
-
return Message.countDocuments(
|
|
1075
|
+
return Message.countDocuments(filter);
|
|
1016
1076
|
}
|
|
1017
1077
|
// -------------------------------------------------------------------------
|
|
1018
1078
|
// Get actions for a message
|
|
@@ -1039,8 +1099,7 @@ var MessageService = class {
|
|
|
1039
1099
|
* render empty), distinct from creation-time `templateData`.
|
|
1040
1100
|
*/
|
|
1041
1101
|
async getActions(messageId, usertype, options) {
|
|
1042
|
-
const
|
|
1043
|
-
const authorizedUser = { ...options.user, _id: user };
|
|
1102
|
+
const authorizedUser = this.buildAuthorizedUser(options?.user);
|
|
1044
1103
|
const message = await this.findMessage(messageId);
|
|
1045
1104
|
if (!message) return null;
|
|
1046
1105
|
if (!options.isAdmin) {
|
|
@@ -1074,18 +1133,33 @@ var MessageService = class {
|
|
|
1074
1133
|
* Execute an action with a durable claim, stable handler attempt key,
|
|
1075
1134
|
* transactional archive, and post-commit sender notification status.
|
|
1076
1135
|
*
|
|
1136
|
+
* `actionLeaseMs` controls when a same-action retry may take over. Expiry alone
|
|
1137
|
+
* does not cancel the handler or reject a still-current owner; takeover fences
|
|
1138
|
+
* stale completion/failure writes with `ActionConflictError`. There is no
|
|
1139
|
+
* heartbeat or renewal; external effects must deduplicate by `actionAttemptId`.
|
|
1140
|
+
*
|
|
1077
1141
|
* User-facing operation: `data.user` is validated before any template or
|
|
1078
|
-
* model effect.
|
|
1142
|
+
* model effect. Handler users contain only the documented `MessageUser`
|
|
1143
|
+
* fields, read explicitly (including getters), with a copied roles array;
|
|
1144
|
+
* the caller's user and original `req` are not modified.
|
|
1145
|
+
* Authorization and handler selection bind to the persisted
|
|
1079
1146
|
* claim, not the caller-supplied `data.message` copy. Archived messages
|
|
1080
1147
|
* are re-read by id on their owning connection and apply a stored
|
|
1081
1148
|
* sender/receiver relationship gate before disclosing current attempt IDs
|
|
1082
1149
|
* or notification state. A missing archive throws `MessageNotFoundError`.
|
|
1150
|
+
* `data.input` is explicit submitted data, never inferred from `req`. The
|
|
1151
|
+
* current claimed action's optional synchronous pure `parseInput` supplies
|
|
1152
|
+
* `ctx.input` after authorization. Invalid input throws
|
|
1153
|
+
* `ActionInputValidationError`; unexpected parser failures preserve their
|
|
1154
|
+
* cause as server errors. Both use fenced pre-handler release: fresh claims
|
|
1155
|
+
* return to active, retries retain their stable attempt/action as retryable.
|
|
1156
|
+
* Hosts own input bounds, runtime validation, and durable command/result
|
|
1157
|
+
* binding at first host reservation (not at package claim).
|
|
1083
1158
|
* Returns the handler's value (`unknown`; templates
|
|
1084
1159
|
* document their own shape).
|
|
1085
1160
|
*/
|
|
1086
1161
|
async handleAction(templateCd, actionCd, data) {
|
|
1087
|
-
const
|
|
1088
|
-
const authorizedUser = { ...data.user, _id: userId };
|
|
1162
|
+
const authorizedUser = this.buildAuthorizedUser(data.user);
|
|
1089
1163
|
if (this.isArchivedMessage(data.message)) {
|
|
1090
1164
|
const archived = await this.resolveModel("archive", data.message).findById(
|
|
1091
1165
|
data.message._id
|
|
@@ -1105,25 +1179,28 @@ var MessageService = class {
|
|
|
1105
1179
|
}
|
|
1106
1180
|
const claim = await this.claimAction(data.message, actionCd, authorizedUser);
|
|
1107
1181
|
let authorizedAction;
|
|
1182
|
+
let input;
|
|
1108
1183
|
try {
|
|
1109
1184
|
authorizedAction = this.authorizeClaimedAction(templateCd, actionCd, claim.message, authorizedUser, {
|
|
1110
1185
|
permissions: data.permissions
|
|
1111
1186
|
});
|
|
1112
|
-
|
|
1187
|
+
input = this.parseActionInput(authorizedAction, data.input);
|
|
1188
|
+
} catch (preHandlerError) {
|
|
1113
1189
|
try {
|
|
1114
|
-
await this.releaseDeniedActionClaim(claim,
|
|
1190
|
+
await this.releaseDeniedActionClaim(claim, preHandlerError);
|
|
1115
1191
|
} catch (releaseError) {
|
|
1116
1192
|
if (!(releaseError instanceof ActionConflictError)) {
|
|
1117
1193
|
throw releaseError;
|
|
1118
1194
|
}
|
|
1119
1195
|
}
|
|
1120
|
-
throw
|
|
1196
|
+
throw preHandlerError;
|
|
1121
1197
|
}
|
|
1122
1198
|
const ctx = this.buildActionContext({
|
|
1123
1199
|
...data,
|
|
1124
1200
|
user: authorizedUser,
|
|
1125
1201
|
message: claim.message,
|
|
1126
|
-
actionAttemptId: claim.actionAttemptId
|
|
1202
|
+
actionAttemptId: claim.actionAttemptId,
|
|
1203
|
+
input
|
|
1127
1204
|
});
|
|
1128
1205
|
let result;
|
|
1129
1206
|
try {
|
|
@@ -1244,13 +1321,14 @@ var MessageService = class {
|
|
|
1244
1321
|
}
|
|
1245
1322
|
}
|
|
1246
1323
|
async buildMessageDocument(template, m, ctx, clientRequestScope, clientRequestItemIndex) {
|
|
1247
|
-
const toUser = m.toUser
|
|
1248
|
-
const toRoles = (m.toRoles && m.toRoles.length > 0 ? m.toRoles : this.adminRoles).slice();
|
|
1324
|
+
const toUser = m.toUser == null ? null : normalizeUserIdForStorage(m.toUser);
|
|
1325
|
+
const toRoles = (m.toRoles && m.toRoles.length > 0 ? m.toRoles : toUser === null ? this.adminRoles : []).slice();
|
|
1249
1326
|
const type = m.type || template.type || "notification";
|
|
1250
1327
|
const paymentCd = m.paymentCd || template.paymentCd || "";
|
|
1251
1328
|
let paymentSession = null;
|
|
1252
1329
|
try {
|
|
1253
|
-
if (paymentCd
|
|
1330
|
+
if (paymentCd) {
|
|
1331
|
+
if (!this.paymentProvider) throw new PaymentProviderRequiredError();
|
|
1254
1332
|
paymentSession = await this.paymentProvider.createSession(
|
|
1255
1333
|
(ctx.payerUser || ctx.user)._id,
|
|
1256
1334
|
paymentCd,
|
|
@@ -1262,12 +1340,12 @@ var MessageService = class {
|
|
|
1262
1340
|
return {
|
|
1263
1341
|
type,
|
|
1264
1342
|
templateCd: template.templateCd,
|
|
1265
|
-
fromUser: m.fromUser || ctx.user._id,
|
|
1343
|
+
fromUser: normalizeUserIdForStorage(m.fromUser || ctx.user._id),
|
|
1266
1344
|
toUser,
|
|
1267
1345
|
toRoles,
|
|
1268
1346
|
senderContent: interpolated.senderContent,
|
|
1269
1347
|
receiverContent: interpolated.receiverContent,
|
|
1270
|
-
documents: ctx.payload.documents || [],
|
|
1348
|
+
documents: (m.documents !== void 0 ? m.documents : ctx.payload.documents || []).slice(),
|
|
1271
1349
|
paymentSession,
|
|
1272
1350
|
paymentCd,
|
|
1273
1351
|
payload: m.payload || ctx.payload,
|
|
@@ -1377,12 +1455,36 @@ var MessageService = class {
|
|
|
1377
1455
|
message: data.message,
|
|
1378
1456
|
user: data.user,
|
|
1379
1457
|
actionAttemptId: data.actionAttemptId,
|
|
1458
|
+
input: data.input,
|
|
1380
1459
|
getModel: this.getTemplateModelResolver(data.message),
|
|
1381
1460
|
expireSession: this.expirePaymentSession,
|
|
1382
1461
|
refundPayment: this.refundPaymentSession,
|
|
1383
1462
|
req: data.req
|
|
1384
1463
|
};
|
|
1385
1464
|
}
|
|
1465
|
+
parseActionInput(action, input) {
|
|
1466
|
+
if (!action.parseInput) return input;
|
|
1467
|
+
try {
|
|
1468
|
+
const parsed = action.parseInput(input);
|
|
1469
|
+
if (parsed !== null && (typeof parsed === "object" || typeof parsed === "function")) {
|
|
1470
|
+
const then = parsed.then;
|
|
1471
|
+
if (typeof then === "function") {
|
|
1472
|
+
void Promise.resolve().then(() => {
|
|
1473
|
+
const discarded = Reflect.apply(then, parsed, [() => {
|
|
1474
|
+
}, () => {
|
|
1475
|
+
}]);
|
|
1476
|
+
return discarded === parsed ? void 0 : discarded;
|
|
1477
|
+
}).catch(() => {
|
|
1478
|
+
});
|
|
1479
|
+
throw new Error("action.parseInput must be synchronous");
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return parsed;
|
|
1483
|
+
} catch (error) {
|
|
1484
|
+
if (error instanceof ActionInputValidationError) throw error;
|
|
1485
|
+
throw new Error("action.parseInput failed", { cause: error });
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1386
1488
|
async claimAction(message, actionCd, user) {
|
|
1387
1489
|
const Message = this.resolveModel("active", message);
|
|
1388
1490
|
const messageId = message._id;
|
|
@@ -1709,6 +1811,11 @@ var MessageService = class {
|
|
|
1709
1811
|
requireUserId(user) {
|
|
1710
1812
|
return requireMessageUserId(user);
|
|
1711
1813
|
}
|
|
1814
|
+
buildAuthorizedUser(user) {
|
|
1815
|
+
const _id = this.requireUserId(user);
|
|
1816
|
+
const { id, roles, displayName } = user;
|
|
1817
|
+
return { _id, id, roles: roles?.slice(), displayName };
|
|
1818
|
+
}
|
|
1712
1819
|
async findByClientRequestScope(scope) {
|
|
1713
1820
|
const Message = this.resolveModel("active");
|
|
1714
1821
|
const docs = await Message.find(scope).sort({ clientRequestItemIndex: 1, _id: 1 }).limit(Number.MAX_SAFE_INTEGER);
|
|
@@ -2002,7 +2109,7 @@ function createArchiveMethod(ctx) {
|
|
|
2002
2109
|
const archiveDoc = {
|
|
2003
2110
|
...source,
|
|
2004
2111
|
actionCd,
|
|
2005
|
-
archivedBy,
|
|
2112
|
+
archivedBy: normalizeUserIdForStorage(archivedBy),
|
|
2006
2113
|
archivedAt: /* @__PURE__ */ new Date(),
|
|
2007
2114
|
actionNotificationState: "none",
|
|
2008
2115
|
actionNotificationError: null,
|
|
@@ -2071,22 +2178,31 @@ function createPostSaveEmailHook(ctx) {
|
|
|
2071
2178
|
if (!title || ctx.emailNotificationExclusions.includes(normalizeEmailTitle(title))) {
|
|
2072
2179
|
return;
|
|
2073
2180
|
}
|
|
2074
|
-
let
|
|
2181
|
+
let email;
|
|
2075
2182
|
try {
|
|
2076
|
-
|
|
2077
|
-
|
|
2183
|
+
if (ctx.emailRecipientResolver) {
|
|
2184
|
+
email = await ctx.emailRecipientResolver({
|
|
2185
|
+
recipientId: this.toUser,
|
|
2186
|
+
connection: this.constructor.db,
|
|
2187
|
+
message: this
|
|
2188
|
+
});
|
|
2189
|
+
} else {
|
|
2190
|
+
const User = resolveDocumentModel(this, ctx.userModelName, "user");
|
|
2191
|
+
const user = await User.findById(this.toUser).select("email").lean();
|
|
2192
|
+
email = user?.email;
|
|
2193
|
+
}
|
|
2078
2194
|
} catch (error) {
|
|
2079
2195
|
await reportEmailFailure(ctx, "recipientLookup", error, this, title);
|
|
2080
2196
|
return;
|
|
2081
2197
|
}
|
|
2082
|
-
if (!
|
|
2198
|
+
if (!email) {
|
|
2083
2199
|
return;
|
|
2084
2200
|
}
|
|
2085
2201
|
const long = this.receiverContent.long || "";
|
|
2086
2202
|
const short = this.receiverContent.short || "";
|
|
2087
2203
|
const body = long.length > short.length ? long : short;
|
|
2088
2204
|
try {
|
|
2089
|
-
await ctx.emailNotifier(
|
|
2205
|
+
await ctx.emailNotifier(email, title, body);
|
|
2090
2206
|
} catch (error) {
|
|
2091
2207
|
await reportEmailFailure(ctx, "notifier", error, this, title);
|
|
2092
2208
|
}
|
|
@@ -2116,6 +2232,7 @@ function resolveConfig(config) {
|
|
|
2116
2232
|
return {
|
|
2117
2233
|
onTransactionCleanupFailure: config?.onTransactionCleanupFailure,
|
|
2118
2234
|
emailNotifier: config?.emailNotifier ?? null,
|
|
2235
|
+
emailRecipientResolver: config?.emailRecipientResolver,
|
|
2119
2236
|
onEmailDeliveryFailure: config?.onEmailDeliveryFailure,
|
|
2120
2237
|
emailNotificationExclusions: (config?.emailNotificationExclusions ?? []).map(normalizeEmailTitle),
|
|
2121
2238
|
userModelName: config?.userModelName ?? "User",
|
|
@@ -2125,7 +2242,7 @@ function resolveConfig(config) {
|
|
|
2125
2242
|
}
|
|
2126
2243
|
function buildMessageSchema(config) {
|
|
2127
2244
|
const resolved = resolveConfig(config);
|
|
2128
|
-
if (resolved.emailNotifier) {
|
|
2245
|
+
if (resolved.emailNotifier && !resolved.emailRecipientResolver) {
|
|
2129
2246
|
assertModelRegistered(resolved.connection, resolved.userModelName, "user");
|
|
2130
2247
|
}
|
|
2131
2248
|
const schema = new import_mongoose4.default.Schema(BaseMessageFields, {
|
|
@@ -2176,6 +2293,7 @@ function buildMessageSchema(config) {
|
|
|
2176
2293
|
"save",
|
|
2177
2294
|
createPostSaveEmailHook({
|
|
2178
2295
|
emailNotifier: resolved.emailNotifier,
|
|
2296
|
+
emailRecipientResolver: resolved.emailRecipientResolver,
|
|
2179
2297
|
onEmailDeliveryFailure: resolved.onEmailDeliveryFailure,
|
|
2180
2298
|
emailNotificationExclusions: resolved.emailNotificationExclusions,
|
|
2181
2299
|
userModelName: resolved.userModelName
|
|
@@ -2402,6 +2520,9 @@ function isMongooseCastError(error) {
|
|
|
2402
2520
|
return !!error && typeof error === "object" && error.name === "CastError";
|
|
2403
2521
|
}
|
|
2404
2522
|
function mapServiceError(error) {
|
|
2523
|
+
if (error instanceof ActionInputValidationError) {
|
|
2524
|
+
throw new import_express_json_router.default.clientErrors.BadRequestError("invalid action input");
|
|
2525
|
+
}
|
|
2405
2526
|
if (error instanceof MessageNotFoundError) {
|
|
2406
2527
|
throw new import_express_json_router.default.clientErrors.NotFoundError("message not found");
|
|
2407
2528
|
}
|
|
@@ -2453,6 +2574,7 @@ var SERVICE_CONSTRUCTION_KEYS = [
|
|
|
2453
2574
|
"registry",
|
|
2454
2575
|
"defaultListLimit",
|
|
2455
2576
|
"maxListLimit",
|
|
2577
|
+
"actionLeaseMs",
|
|
2456
2578
|
"clientRequestLeaseMs",
|
|
2457
2579
|
"clientRequestWaitMs",
|
|
2458
2580
|
"clientRequestPollMs",
|
|
@@ -2491,6 +2613,7 @@ function createMessageRoutes(options) {
|
|
|
2491
2613
|
registry: construction.registry,
|
|
2492
2614
|
defaultListLimit: construction.defaultListLimit,
|
|
2493
2615
|
maxListLimit: construction.maxListLimit,
|
|
2616
|
+
actionLeaseMs: construction.actionLeaseMs,
|
|
2494
2617
|
clientRequestLeaseMs: construction.clientRequestLeaseMs,
|
|
2495
2618
|
clientRequestWaitMs: construction.clientRequestWaitMs,
|
|
2496
2619
|
clientRequestPollMs: construction.clientRequestPollMs,
|
|
@@ -2561,7 +2684,13 @@ function createMessageRoutes(options) {
|
|
|
2561
2684
|
}
|
|
2562
2685
|
const permissions = getPermissions(req);
|
|
2563
2686
|
try {
|
|
2564
|
-
return await service.handleAction(message.templateCd, actionCd, {
|
|
2687
|
+
return await service.handleAction(message.templateCd, actionCd, {
|
|
2688
|
+
message,
|
|
2689
|
+
user,
|
|
2690
|
+
permissions,
|
|
2691
|
+
input: req.body,
|
|
2692
|
+
req
|
|
2693
|
+
});
|
|
2565
2694
|
} catch (error) {
|
|
2566
2695
|
if (error instanceof ActionNotificationPendingError) {
|
|
2567
2696
|
return new import_express_json_router.default.success.Accepted({ message: error.message, actionAttemptId: error.actionAttemptId });
|
|
@@ -2585,6 +2714,7 @@ function defaultGetIdentity(req) {
|
|
|
2585
2714
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2586
2715
|
0 && (module.exports = {
|
|
2587
2716
|
ActionConflictError,
|
|
2717
|
+
ActionInputValidationError,
|
|
2588
2718
|
ActionNotAllowedError,
|
|
2589
2719
|
ActionNotFoundError,
|
|
2590
2720
|
ActionNotificationPendingError,
|
|
@@ -2614,6 +2744,7 @@ function defaultGetIdentity(req) {
|
|
|
2614
2744
|
MessageTransactionRequiredError,
|
|
2615
2745
|
NoopEmailProvider,
|
|
2616
2746
|
NoopPaymentProvider,
|
|
2747
|
+
PaymentProviderRequiredError,
|
|
2617
2748
|
PaymentSessionCompensationAggregateError,
|
|
2618
2749
|
PaymentSessionCompensationError,
|
|
2619
2750
|
TemplateNotFoundError,
|