@web-ts-toolkit/message-service 0.52.0 → 0.53.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.mjs
CHANGED
|
@@ -205,25 +205,32 @@ function includesAction(templateCd, actionCd, registry) {
|
|
|
205
205
|
|
|
206
206
|
// src/schemas/methods.ts
|
|
207
207
|
import { Types } from "mongoose";
|
|
208
|
+
function isValidMessageUserRoles(roles) {
|
|
209
|
+
if (roles === void 0) return true;
|
|
210
|
+
if (!Array.isArray(roles)) return false;
|
|
211
|
+
for (const role of roles) {
|
|
212
|
+
if (typeof role !== "string") return false;
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
208
216
|
function isSender(user) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return fromUser !== null && userId !== null && fromUser === userId;
|
|
217
|
+
if (!isValidMessageUserRoles(user?.roles)) return false;
|
|
218
|
+
return matchesStoredUserId(this.fromUser, user);
|
|
212
219
|
}
|
|
213
220
|
function isReceiver(user) {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
return toUser
|
|
221
|
+
const roles = user?.roles;
|
|
222
|
+
if (!isValidMessageUserRoles(roles)) return false;
|
|
223
|
+
return matchesStoredUserId(this.toUser, user) || this.toRoles.some((r) => roles?.includes(r));
|
|
217
224
|
}
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return
|
|
225
|
+
function matchesStoredUserId(value, user) {
|
|
226
|
+
const stored = value instanceof Types.ObjectId || value === null || typeof value !== "object" ? value : value._id;
|
|
227
|
+
const userId = normalizeId(user?._id);
|
|
228
|
+
if (userId === null) return false;
|
|
229
|
+
if (stored instanceof Types.ObjectId && /^[a-f\d]{24}$/i.test(userId)) {
|
|
230
|
+
return stored.toHexString() === userId.toLowerCase();
|
|
231
|
+
}
|
|
232
|
+
const storedId = normalizeId(stored);
|
|
233
|
+
return storedId !== null && storedId === userId;
|
|
227
234
|
}
|
|
228
235
|
function normalizeId(value) {
|
|
229
236
|
if (typeof value === "string") {
|
|
@@ -233,6 +240,11 @@ function normalizeId(value) {
|
|
|
233
240
|
return value instanceof Types.ObjectId ? value.toHexString() : null;
|
|
234
241
|
}
|
|
235
242
|
|
|
243
|
+
// src/user-id.ts
|
|
244
|
+
function normalizeUserIdForStorage(id) {
|
|
245
|
+
return typeof id === "string" ? id.trim() : id;
|
|
246
|
+
}
|
|
247
|
+
|
|
236
248
|
// src/errors.ts
|
|
237
249
|
var TRANSACTION_REQUIRED_MESSAGE = "message-service requires MongoDB replica set or sharded-cluster transactions for idempotent batch creation";
|
|
238
250
|
var MessageNotFoundError = class extends Error {
|
|
@@ -285,6 +297,16 @@ var ActionNotAllowedError = class extends Error {
|
|
|
285
297
|
markRuntimeError(this, this.name);
|
|
286
298
|
}
|
|
287
299
|
};
|
|
300
|
+
var ActionInputValidationError = class extends Error {
|
|
301
|
+
static [Symbol.hasInstance](value) {
|
|
302
|
+
return isRuntimeError(value, "ActionInputValidationError");
|
|
303
|
+
}
|
|
304
|
+
constructor(message = "invalid action input") {
|
|
305
|
+
super(message);
|
|
306
|
+
this.name = "ActionInputValidationError";
|
|
307
|
+
markRuntimeError(this, this.name);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
288
310
|
var InvalidMessageUserError = class extends Error {
|
|
289
311
|
static [Symbol.hasInstance](value) {
|
|
290
312
|
return isRuntimeError(value, "InvalidMessageUserError");
|
|
@@ -440,6 +462,16 @@ var MessageModelResolutionError = class extends Error {
|
|
|
440
462
|
}
|
|
441
463
|
}
|
|
442
464
|
};
|
|
465
|
+
var PaymentProviderRequiredError = class extends Error {
|
|
466
|
+
static [Symbol.hasInstance](value) {
|
|
467
|
+
return isRuntimeError(value, "PaymentProviderRequiredError");
|
|
468
|
+
}
|
|
469
|
+
constructor() {
|
|
470
|
+
super("paymentProvider is required for messages with a non-empty paymentCd");
|
|
471
|
+
this.name = "PaymentProviderRequiredError";
|
|
472
|
+
markRuntimeError(this, this.name);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
443
475
|
var PaymentSessionCompensationError = class extends Error {
|
|
444
476
|
static [Symbol.hasInstance](value) {
|
|
445
477
|
return isRuntimeError(value, "PaymentSessionCompensationError") || isRuntimeError(value, "PaymentSessionCompensationAggregateError");
|
|
@@ -650,6 +682,9 @@ function requireMessageUserId(user) {
|
|
|
650
682
|
if (!isValidMessageUserId(id)) {
|
|
651
683
|
throw new InvalidMessageUserError();
|
|
652
684
|
}
|
|
685
|
+
if (!isValidMessageUserRoles(user.roles)) {
|
|
686
|
+
throw new InvalidMessageUserError("user.roles must be an array of strings when provided");
|
|
687
|
+
}
|
|
653
688
|
return typeof id === "string" ? id.trim() : String(id);
|
|
654
689
|
}
|
|
655
690
|
var MessageService = class {
|
|
@@ -689,7 +724,11 @@ var MessageService = class {
|
|
|
689
724
|
);
|
|
690
725
|
this.clientRequestDelay = options.clientRequestDelay ?? this.delay;
|
|
691
726
|
this.clientRequestNow = options.clientRequestNow ?? (() => Date.now());
|
|
692
|
-
this.actionLeaseMs =
|
|
727
|
+
this.actionLeaseMs = this.validateDurationOption(
|
|
728
|
+
"actionLeaseMs",
|
|
729
|
+
options.actionLeaseMs === void 0 ? ACTION_LEASE_MS : options.actionLeaseMs,
|
|
730
|
+
1
|
|
731
|
+
);
|
|
693
732
|
}
|
|
694
733
|
// -------------------------------------------------------------------------
|
|
695
734
|
// Message lookup
|
|
@@ -735,10 +774,25 @@ var MessageService = class {
|
|
|
735
774
|
*
|
|
736
775
|
* User-facing entry point: `user` (and `payerUser` when provided) must
|
|
737
776
|
* carry a valid principal id (non-empty string or ObjectId; numbers,
|
|
738
|
-
* arrays, and plain objects are rejected with `InvalidMessageUserError`)
|
|
777
|
+
* arrays, and plain objects are rejected with `InvalidMessageUserError`),
|
|
778
|
+
* and optional `roles` must be undefined or an array of strings,
|
|
739
779
|
* before any template, provider, or model effect runs. Both the
|
|
740
780
|
* idempotent (`clientRequestId`) and direct branches enforce this.
|
|
741
781
|
*
|
|
782
|
+
* Each `PrepareResult.documents` selects that item's attachments; `[]` clears
|
|
783
|
+
* them. Omission/undefined retains the original caller `payload.documents`
|
|
784
|
+
* fallback, independently of the prepared business payload. Selected arrays
|
|
785
|
+
* are copied per item without mutating caller/preparation arrays.
|
|
786
|
+
*
|
|
787
|
+
* Each fresh item's `item.paymentCd || template.paymentCd || ''` requests a
|
|
788
|
+
* session when nonempty, independently of recipients. An empty item override
|
|
789
|
+
* inherits the template code. Absent/null providers throw
|
|
790
|
+
* `PaymentProviderRequiredError`; configured-provider rejections propagate and
|
|
791
|
+
* null/empty session returns throw `Error('payment session creation failed')`.
|
|
792
|
+
* The payer remains `(payerUser || user)._id`, not the prepared sender/recipient.
|
|
793
|
+
* Direct batches retain committed prefixes; idempotent batches commit atomically
|
|
794
|
+
* and compensate all known uncommitted sessions on failure.
|
|
795
|
+
*
|
|
742
796
|
* Idempotent replay contract (MSGF-06): a completed same-scope replay
|
|
743
797
|
* returns the current active/archive records merged from both collections
|
|
744
798
|
* (`Array<IMessage | IMessageArchive>`), sorted by exact
|
|
@@ -906,15 +960,17 @@ var MessageService = class {
|
|
|
906
960
|
* or matches one of the recipient's roles.
|
|
907
961
|
*
|
|
908
962
|
* User-facing operation: validates `user` via the shared principal
|
|
909
|
-
* contract. Returns `IMessage[]` ordered by
|
|
963
|
+
* contract before model resolution. Returns `IMessage[]` ordered by
|
|
964
|
+
* `{ createdAt: -1, _id: -1 }`.
|
|
910
965
|
*/
|
|
911
966
|
async listMessages(params) {
|
|
912
967
|
const { user, limit: rawLimit, skip: rawSkip, populate } = params;
|
|
913
968
|
const limit = this.normalizeListLimit(rawLimit);
|
|
914
969
|
const skip = this.normalizeListSkip(rawSkip);
|
|
970
|
+
const filter = this.buildVisibilityFilter(user);
|
|
915
971
|
const Message = this.resolveModel("active");
|
|
916
972
|
const query = this.applyPopulate(
|
|
917
|
-
Message.find(
|
|
973
|
+
Message.find(filter).sort({ createdAt: -1, _id: -1 }).skip(skip).limit(limit),
|
|
918
974
|
populate
|
|
919
975
|
);
|
|
920
976
|
return query;
|
|
@@ -923,11 +979,13 @@ var MessageService = class {
|
|
|
923
979
|
* Count active (non-archived) messages visible to a user.
|
|
924
980
|
* Useful for badge indicators ("3 new messages").
|
|
925
981
|
*
|
|
926
|
-
* User-facing operation: validates `user` via the shared principal contract
|
|
982
|
+
* User-facing operation: validates `user` via the shared principal contract
|
|
983
|
+
* before model resolution.
|
|
927
984
|
*/
|
|
928
985
|
async countMessages(user) {
|
|
986
|
+
const filter = this.buildVisibilityFilter(user);
|
|
929
987
|
const Message = this.resolveModel("active");
|
|
930
|
-
return Message.countDocuments(
|
|
988
|
+
return Message.countDocuments(filter);
|
|
931
989
|
}
|
|
932
990
|
// -------------------------------------------------------------------------
|
|
933
991
|
// Get actions for a message
|
|
@@ -954,8 +1012,7 @@ var MessageService = class {
|
|
|
954
1012
|
* render empty), distinct from creation-time `templateData`.
|
|
955
1013
|
*/
|
|
956
1014
|
async getActions(messageId, usertype, options) {
|
|
957
|
-
const
|
|
958
|
-
const authorizedUser = { ...options.user, _id: user };
|
|
1015
|
+
const authorizedUser = this.buildAuthorizedUser(options?.user);
|
|
959
1016
|
const message = await this.findMessage(messageId);
|
|
960
1017
|
if (!message) return null;
|
|
961
1018
|
if (!options.isAdmin) {
|
|
@@ -989,18 +1046,33 @@ var MessageService = class {
|
|
|
989
1046
|
* Execute an action with a durable claim, stable handler attempt key,
|
|
990
1047
|
* transactional archive, and post-commit sender notification status.
|
|
991
1048
|
*
|
|
1049
|
+
* `actionLeaseMs` controls when a same-action retry may take over. Expiry alone
|
|
1050
|
+
* does not cancel the handler or reject a still-current owner; takeover fences
|
|
1051
|
+
* stale completion/failure writes with `ActionConflictError`. There is no
|
|
1052
|
+
* heartbeat or renewal; external effects must deduplicate by `actionAttemptId`.
|
|
1053
|
+
*
|
|
992
1054
|
* User-facing operation: `data.user` is validated before any template or
|
|
993
|
-
* model effect.
|
|
1055
|
+
* model effect. Handler users contain only the documented `MessageUser`
|
|
1056
|
+
* fields, read explicitly (including getters), with a copied roles array;
|
|
1057
|
+
* the caller's user and original `req` are not modified.
|
|
1058
|
+
* Authorization and handler selection bind to the persisted
|
|
994
1059
|
* claim, not the caller-supplied `data.message` copy. Archived messages
|
|
995
1060
|
* are re-read by id on their owning connection and apply a stored
|
|
996
1061
|
* sender/receiver relationship gate before disclosing current attempt IDs
|
|
997
1062
|
* or notification state. A missing archive throws `MessageNotFoundError`.
|
|
1063
|
+
* `data.input` is explicit submitted data, never inferred from `req`. The
|
|
1064
|
+
* current claimed action's optional synchronous pure `parseInput` supplies
|
|
1065
|
+
* `ctx.input` after authorization. Invalid input throws
|
|
1066
|
+
* `ActionInputValidationError`; unexpected parser failures preserve their
|
|
1067
|
+
* cause as server errors. Both use fenced pre-handler release: fresh claims
|
|
1068
|
+
* return to active, retries retain their stable attempt/action as retryable.
|
|
1069
|
+
* Hosts own input bounds, runtime validation, and durable command/result
|
|
1070
|
+
* binding at first host reservation (not at package claim).
|
|
998
1071
|
* Returns the handler's value (`unknown`; templates
|
|
999
1072
|
* document their own shape).
|
|
1000
1073
|
*/
|
|
1001
1074
|
async handleAction(templateCd, actionCd, data) {
|
|
1002
|
-
const
|
|
1003
|
-
const authorizedUser = { ...data.user, _id: userId };
|
|
1075
|
+
const authorizedUser = this.buildAuthorizedUser(data.user);
|
|
1004
1076
|
if (this.isArchivedMessage(data.message)) {
|
|
1005
1077
|
const archived = await this.resolveModel("archive", data.message).findById(
|
|
1006
1078
|
data.message._id
|
|
@@ -1020,25 +1092,28 @@ var MessageService = class {
|
|
|
1020
1092
|
}
|
|
1021
1093
|
const claim = await this.claimAction(data.message, actionCd, authorizedUser);
|
|
1022
1094
|
let authorizedAction;
|
|
1095
|
+
let input;
|
|
1023
1096
|
try {
|
|
1024
1097
|
authorizedAction = this.authorizeClaimedAction(templateCd, actionCd, claim.message, authorizedUser, {
|
|
1025
1098
|
permissions: data.permissions
|
|
1026
1099
|
});
|
|
1027
|
-
|
|
1100
|
+
input = this.parseActionInput(authorizedAction, data.input);
|
|
1101
|
+
} catch (preHandlerError) {
|
|
1028
1102
|
try {
|
|
1029
|
-
await this.releaseDeniedActionClaim(claim,
|
|
1103
|
+
await this.releaseDeniedActionClaim(claim, preHandlerError);
|
|
1030
1104
|
} catch (releaseError) {
|
|
1031
1105
|
if (!(releaseError instanceof ActionConflictError)) {
|
|
1032
1106
|
throw releaseError;
|
|
1033
1107
|
}
|
|
1034
1108
|
}
|
|
1035
|
-
throw
|
|
1109
|
+
throw preHandlerError;
|
|
1036
1110
|
}
|
|
1037
1111
|
const ctx = this.buildActionContext({
|
|
1038
1112
|
...data,
|
|
1039
1113
|
user: authorizedUser,
|
|
1040
1114
|
message: claim.message,
|
|
1041
|
-
actionAttemptId: claim.actionAttemptId
|
|
1115
|
+
actionAttemptId: claim.actionAttemptId,
|
|
1116
|
+
input
|
|
1042
1117
|
});
|
|
1043
1118
|
let result;
|
|
1044
1119
|
try {
|
|
@@ -1159,13 +1234,14 @@ var MessageService = class {
|
|
|
1159
1234
|
}
|
|
1160
1235
|
}
|
|
1161
1236
|
async buildMessageDocument(template, m, ctx, clientRequestScope, clientRequestItemIndex) {
|
|
1162
|
-
const toUser = m.toUser
|
|
1163
|
-
const toRoles = (m.toRoles && m.toRoles.length > 0 ? m.toRoles : this.adminRoles).slice();
|
|
1237
|
+
const toUser = m.toUser == null ? null : normalizeUserIdForStorage(m.toUser);
|
|
1238
|
+
const toRoles = (m.toRoles && m.toRoles.length > 0 ? m.toRoles : toUser === null ? this.adminRoles : []).slice();
|
|
1164
1239
|
const type = m.type || template.type || "notification";
|
|
1165
1240
|
const paymentCd = m.paymentCd || template.paymentCd || "";
|
|
1166
1241
|
let paymentSession = null;
|
|
1167
1242
|
try {
|
|
1168
|
-
if (paymentCd
|
|
1243
|
+
if (paymentCd) {
|
|
1244
|
+
if (!this.paymentProvider) throw new PaymentProviderRequiredError();
|
|
1169
1245
|
paymentSession = await this.paymentProvider.createSession(
|
|
1170
1246
|
(ctx.payerUser || ctx.user)._id,
|
|
1171
1247
|
paymentCd,
|
|
@@ -1177,12 +1253,12 @@ var MessageService = class {
|
|
|
1177
1253
|
return {
|
|
1178
1254
|
type,
|
|
1179
1255
|
templateCd: template.templateCd,
|
|
1180
|
-
fromUser: m.fromUser || ctx.user._id,
|
|
1256
|
+
fromUser: normalizeUserIdForStorage(m.fromUser || ctx.user._id),
|
|
1181
1257
|
toUser,
|
|
1182
1258
|
toRoles,
|
|
1183
1259
|
senderContent: interpolated.senderContent,
|
|
1184
1260
|
receiverContent: interpolated.receiverContent,
|
|
1185
|
-
documents: ctx.payload.documents || [],
|
|
1261
|
+
documents: (m.documents !== void 0 ? m.documents : ctx.payload.documents || []).slice(),
|
|
1186
1262
|
paymentSession,
|
|
1187
1263
|
paymentCd,
|
|
1188
1264
|
payload: m.payload || ctx.payload,
|
|
@@ -1292,12 +1368,36 @@ var MessageService = class {
|
|
|
1292
1368
|
message: data.message,
|
|
1293
1369
|
user: data.user,
|
|
1294
1370
|
actionAttemptId: data.actionAttemptId,
|
|
1371
|
+
input: data.input,
|
|
1295
1372
|
getModel: this.getTemplateModelResolver(data.message),
|
|
1296
1373
|
expireSession: this.expirePaymentSession,
|
|
1297
1374
|
refundPayment: this.refundPaymentSession,
|
|
1298
1375
|
req: data.req
|
|
1299
1376
|
};
|
|
1300
1377
|
}
|
|
1378
|
+
parseActionInput(action, input) {
|
|
1379
|
+
if (!action.parseInput) return input;
|
|
1380
|
+
try {
|
|
1381
|
+
const parsed = action.parseInput(input);
|
|
1382
|
+
if (parsed !== null && (typeof parsed === "object" || typeof parsed === "function")) {
|
|
1383
|
+
const then = parsed.then;
|
|
1384
|
+
if (typeof then === "function") {
|
|
1385
|
+
void Promise.resolve().then(() => {
|
|
1386
|
+
const discarded = Reflect.apply(then, parsed, [() => {
|
|
1387
|
+
}, () => {
|
|
1388
|
+
}]);
|
|
1389
|
+
return discarded === parsed ? void 0 : discarded;
|
|
1390
|
+
}).catch(() => {
|
|
1391
|
+
});
|
|
1392
|
+
throw new Error("action.parseInput must be synchronous");
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return parsed;
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
if (error instanceof ActionInputValidationError) throw error;
|
|
1398
|
+
throw new Error("action.parseInput failed", { cause: error });
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1301
1401
|
async claimAction(message, actionCd, user) {
|
|
1302
1402
|
const Message = this.resolveModel("active", message);
|
|
1303
1403
|
const messageId = message._id;
|
|
@@ -1624,6 +1724,11 @@ var MessageService = class {
|
|
|
1624
1724
|
requireUserId(user) {
|
|
1625
1725
|
return requireMessageUserId(user);
|
|
1626
1726
|
}
|
|
1727
|
+
buildAuthorizedUser(user) {
|
|
1728
|
+
const _id = this.requireUserId(user);
|
|
1729
|
+
const { id, roles, displayName } = user;
|
|
1730
|
+
return { _id, id, roles: roles?.slice(), displayName };
|
|
1731
|
+
}
|
|
1627
1732
|
async findByClientRequestScope(scope) {
|
|
1628
1733
|
const Message = this.resolveModel("active");
|
|
1629
1734
|
const docs = await Message.find(scope).sort({ clientRequestItemIndex: 1, _id: 1 }).limit(Number.MAX_SAFE_INTEGER);
|
|
@@ -1917,7 +2022,7 @@ function createArchiveMethod(ctx) {
|
|
|
1917
2022
|
const archiveDoc = {
|
|
1918
2023
|
...source,
|
|
1919
2024
|
actionCd,
|
|
1920
|
-
archivedBy,
|
|
2025
|
+
archivedBy: normalizeUserIdForStorage(archivedBy),
|
|
1921
2026
|
archivedAt: /* @__PURE__ */ new Date(),
|
|
1922
2027
|
actionNotificationState: "none",
|
|
1923
2028
|
actionNotificationError: null,
|
|
@@ -1986,22 +2091,31 @@ function createPostSaveEmailHook(ctx) {
|
|
|
1986
2091
|
if (!title || ctx.emailNotificationExclusions.includes(normalizeEmailTitle(title))) {
|
|
1987
2092
|
return;
|
|
1988
2093
|
}
|
|
1989
|
-
let
|
|
2094
|
+
let email;
|
|
1990
2095
|
try {
|
|
1991
|
-
|
|
1992
|
-
|
|
2096
|
+
if (ctx.emailRecipientResolver) {
|
|
2097
|
+
email = await ctx.emailRecipientResolver({
|
|
2098
|
+
recipientId: this.toUser,
|
|
2099
|
+
connection: this.constructor.db,
|
|
2100
|
+
message: this
|
|
2101
|
+
});
|
|
2102
|
+
} else {
|
|
2103
|
+
const User = resolveDocumentModel(this, ctx.userModelName, "user");
|
|
2104
|
+
const user = await User.findById(this.toUser).select("email").lean();
|
|
2105
|
+
email = user?.email;
|
|
2106
|
+
}
|
|
1993
2107
|
} catch (error) {
|
|
1994
2108
|
await reportEmailFailure(ctx, "recipientLookup", error, this, title);
|
|
1995
2109
|
return;
|
|
1996
2110
|
}
|
|
1997
|
-
if (!
|
|
2111
|
+
if (!email) {
|
|
1998
2112
|
return;
|
|
1999
2113
|
}
|
|
2000
2114
|
const long = this.receiverContent.long || "";
|
|
2001
2115
|
const short = this.receiverContent.short || "";
|
|
2002
2116
|
const body = long.length > short.length ? long : short;
|
|
2003
2117
|
try {
|
|
2004
|
-
await ctx.emailNotifier(
|
|
2118
|
+
await ctx.emailNotifier(email, title, body);
|
|
2005
2119
|
} catch (error) {
|
|
2006
2120
|
await reportEmailFailure(ctx, "notifier", error, this, title);
|
|
2007
2121
|
}
|
|
@@ -2031,6 +2145,7 @@ function resolveConfig(config) {
|
|
|
2031
2145
|
return {
|
|
2032
2146
|
onTransactionCleanupFailure: config?.onTransactionCleanupFailure,
|
|
2033
2147
|
emailNotifier: config?.emailNotifier ?? null,
|
|
2148
|
+
emailRecipientResolver: config?.emailRecipientResolver,
|
|
2034
2149
|
onEmailDeliveryFailure: config?.onEmailDeliveryFailure,
|
|
2035
2150
|
emailNotificationExclusions: (config?.emailNotificationExclusions ?? []).map(normalizeEmailTitle),
|
|
2036
2151
|
userModelName: config?.userModelName ?? "User",
|
|
@@ -2040,7 +2155,7 @@ function resolveConfig(config) {
|
|
|
2040
2155
|
}
|
|
2041
2156
|
function buildMessageSchema(config) {
|
|
2042
2157
|
const resolved = resolveConfig(config);
|
|
2043
|
-
if (resolved.emailNotifier) {
|
|
2158
|
+
if (resolved.emailNotifier && !resolved.emailRecipientResolver) {
|
|
2044
2159
|
assertModelRegistered(resolved.connection, resolved.userModelName, "user");
|
|
2045
2160
|
}
|
|
2046
2161
|
const schema = new mongoose3.Schema(BaseMessageFields, {
|
|
@@ -2091,6 +2206,7 @@ function buildMessageSchema(config) {
|
|
|
2091
2206
|
"save",
|
|
2092
2207
|
createPostSaveEmailHook({
|
|
2093
2208
|
emailNotifier: resolved.emailNotifier,
|
|
2209
|
+
emailRecipientResolver: resolved.emailRecipientResolver,
|
|
2094
2210
|
onEmailDeliveryFailure: resolved.onEmailDeliveryFailure,
|
|
2095
2211
|
emailNotificationExclusions: resolved.emailNotificationExclusions,
|
|
2096
2212
|
userModelName: resolved.userModelName
|
|
@@ -2317,6 +2433,9 @@ function isMongooseCastError(error) {
|
|
|
2317
2433
|
return !!error && typeof error === "object" && error.name === "CastError";
|
|
2318
2434
|
}
|
|
2319
2435
|
function mapServiceError(error) {
|
|
2436
|
+
if (error instanceof ActionInputValidationError) {
|
|
2437
|
+
throw new JsonRouter.clientErrors.BadRequestError("invalid action input");
|
|
2438
|
+
}
|
|
2320
2439
|
if (error instanceof MessageNotFoundError) {
|
|
2321
2440
|
throw new JsonRouter.clientErrors.NotFoundError("message not found");
|
|
2322
2441
|
}
|
|
@@ -2368,6 +2487,7 @@ var SERVICE_CONSTRUCTION_KEYS = [
|
|
|
2368
2487
|
"registry",
|
|
2369
2488
|
"defaultListLimit",
|
|
2370
2489
|
"maxListLimit",
|
|
2490
|
+
"actionLeaseMs",
|
|
2371
2491
|
"clientRequestLeaseMs",
|
|
2372
2492
|
"clientRequestWaitMs",
|
|
2373
2493
|
"clientRequestPollMs",
|
|
@@ -2406,6 +2526,7 @@ function createMessageRoutes(options) {
|
|
|
2406
2526
|
registry: construction.registry,
|
|
2407
2527
|
defaultListLimit: construction.defaultListLimit,
|
|
2408
2528
|
maxListLimit: construction.maxListLimit,
|
|
2529
|
+
actionLeaseMs: construction.actionLeaseMs,
|
|
2409
2530
|
clientRequestLeaseMs: construction.clientRequestLeaseMs,
|
|
2410
2531
|
clientRequestWaitMs: construction.clientRequestWaitMs,
|
|
2411
2532
|
clientRequestPollMs: construction.clientRequestPollMs,
|
|
@@ -2476,7 +2597,13 @@ function createMessageRoutes(options) {
|
|
|
2476
2597
|
}
|
|
2477
2598
|
const permissions = getPermissions(req);
|
|
2478
2599
|
try {
|
|
2479
|
-
return await service.handleAction(message.templateCd, actionCd, {
|
|
2600
|
+
return await service.handleAction(message.templateCd, actionCd, {
|
|
2601
|
+
message,
|
|
2602
|
+
user,
|
|
2603
|
+
permissions,
|
|
2604
|
+
input: req.body,
|
|
2605
|
+
req
|
|
2606
|
+
});
|
|
2480
2607
|
} catch (error) {
|
|
2481
2608
|
if (error instanceof ActionNotificationPendingError) {
|
|
2482
2609
|
return new JsonRouter.success.Accepted({ message: error.message, actionAttemptId: error.actionAttemptId });
|
|
@@ -2499,6 +2626,7 @@ function defaultGetIdentity(req) {
|
|
|
2499
2626
|
}
|
|
2500
2627
|
export {
|
|
2501
2628
|
ActionConflictError,
|
|
2629
|
+
ActionInputValidationError,
|
|
2502
2630
|
ActionNotAllowedError,
|
|
2503
2631
|
ActionNotFoundError,
|
|
2504
2632
|
ActionNotificationPendingError,
|
|
@@ -2528,6 +2656,7 @@ export {
|
|
|
2528
2656
|
MessageTransactionRequiredError,
|
|
2529
2657
|
NoopEmailProvider,
|
|
2530
2658
|
NoopPaymentProvider,
|
|
2659
|
+
PaymentProviderRequiredError,
|
|
2531
2660
|
PaymentSessionCompensationAggregateError,
|
|
2532
2661
|
PaymentSessionCompensationError,
|
|
2533
2662
|
TemplateNotFoundError,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@web-ts-toolkit/message-service",
|
|
3
3
|
"description": "Template-driven messaging core for Mongoose + Express applications",
|
|
4
4
|
"homepage": "https://web-ts-toolkit.pages.dev/docs/packages/message-service",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.53.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongoose",
|
|
8
8
|
"messaging",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"node": ">=22"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@web-ts-toolkit/express-json-router": "0.
|
|
34
|
-
"@web-ts-toolkit/utils": "0.
|
|
33
|
+
"@web-ts-toolkit/express-json-router": "0.53.0",
|
|
34
|
+
"@web-ts-toolkit/utils": "0.53.0",
|
|
35
35
|
"handlebars": "^4.7.8"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|