@web-ts-toolkit/message-service 0.43.0 → 0.47.1
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 +230 -57
- package/index.d.mts +696 -111
- package/index.d.ts +696 -111
- package/index.js +1382 -686
- package/index.mjs +1363 -676
- package/package.json +3 -3
package/index.d.mts
CHANGED
|
@@ -2,6 +2,27 @@ import mongoose from 'mongoose';
|
|
|
2
2
|
import JsonRouter from '@web-ts-toolkit/express-json-router';
|
|
3
3
|
import { Request, Response, NextFunction } from 'express';
|
|
4
4
|
|
|
5
|
+
/** Diagnostics for a package-owned session whose `endSession()` rejected. */
|
|
6
|
+
type MessageTransactionCleanupFailureEvent = {
|
|
7
|
+
operation: 'createBatch' | 'actionArchive' | 'directArchive';
|
|
8
|
+
stage: 'endSession';
|
|
9
|
+
error: unknown;
|
|
10
|
+
} & ({
|
|
11
|
+
outcome: 'committed';
|
|
12
|
+
originalError?: never;
|
|
13
|
+
} | {
|
|
14
|
+
/** Transaction rejected; this does not certify rollback of an ambiguous commit. */
|
|
15
|
+
outcome: 'failed';
|
|
16
|
+
originalError: unknown;
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Best-effort diagnostic observer, awaited after session cleanup rejects.
|
|
20
|
+
* Throws/rejections are swallowed to preserve the committed result or primary
|
|
21
|
+
* transaction error. Keep it bounded; it is not a retry/compensation hook.
|
|
22
|
+
* Caller-owned sessions are never ended or reported by the package.
|
|
23
|
+
*/
|
|
24
|
+
type MessageTransactionCleanupFailureObserver = (event: MessageTransactionCleanupFailureEvent) => void | Promise<void>;
|
|
25
|
+
|
|
5
26
|
interface PrepareContext {
|
|
6
27
|
user: MessageUser;
|
|
7
28
|
roles: string[];
|
|
@@ -87,6 +108,40 @@ interface MessageTemplate {
|
|
|
87
108
|
prepareMessage: (ctx: PrepareContext) => Promise<PrepareResult | PrepareResult[] | null>;
|
|
88
109
|
actions: MessageAction[];
|
|
89
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Readonly uiTemplate view matching the registry freeze depth: a plain string
|
|
113
|
+
* passes through, while object forms are shallow-frozen at registration.
|
|
114
|
+
*/
|
|
115
|
+
type RegisteredUiTemplate = string | Readonly<{
|
|
116
|
+
sender?: string;
|
|
117
|
+
receiver?: string;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* Readonly action view matching the registry freeze depth: the action object,
|
|
121
|
+
* its `confirmation`, and its `payload` are each shallow-frozen. Function
|
|
122
|
+
* fields (`condition`, `runHandler`, `senderNotification`) keep their original
|
|
123
|
+
* identity and remain callable.
|
|
124
|
+
*/
|
|
125
|
+
type RegisteredMessageAction = Readonly<Omit<MessageAction, 'confirmation' | 'payload'>> & {
|
|
126
|
+
readonly confirmation?: Readonly<ActionConfirmation>;
|
|
127
|
+
readonly payload?: Readonly<Record<string, unknown>>;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Readonly template view matching the registry freeze depth: the top-level
|
|
131
|
+
* snapshot, `senderContent`/`receiverContent`, object `uiTemplate`s, the
|
|
132
|
+
* action array, and each action plus its `confirmation`/`payload` are frozen.
|
|
133
|
+
* Mutating a registered view throws at runtime; register a replacement
|
|
134
|
+
* template to change behavior.
|
|
135
|
+
*
|
|
136
|
+
* Registration inputs stay author-friendly and mutable (`MessageTemplate`);
|
|
137
|
+
* only registry outputs use this readonly view.
|
|
138
|
+
*/
|
|
139
|
+
type RegisteredMessageTemplate = Readonly<Omit<MessageTemplate, 'senderContent' | 'receiverContent' | 'uiTemplate' | 'actions'>> & {
|
|
140
|
+
readonly senderContent: Readonly<MessageTemplate['senderContent']>;
|
|
141
|
+
readonly receiverContent: Readonly<MessageTemplate['receiverContent']>;
|
|
142
|
+
readonly uiTemplate: RegisteredUiTemplate;
|
|
143
|
+
readonly actions: readonly RegisteredMessageAction[];
|
|
144
|
+
};
|
|
90
145
|
interface InterpolatedContent {
|
|
91
146
|
title: string;
|
|
92
147
|
long: string;
|
|
@@ -139,6 +194,9 @@ declare class TemplateRegistry {
|
|
|
139
194
|
private templates;
|
|
140
195
|
/**
|
|
141
196
|
* Register a template. Overwrites if templateCd already exists.
|
|
197
|
+
*
|
|
198
|
+
* Takes an author-friendly mutable `MessageTemplate`; the registry stores a
|
|
199
|
+
* frozen shallow snapshot (see `RegisteredMessageTemplate`).
|
|
142
200
|
*/
|
|
143
201
|
register(template: MessageTemplate): void;
|
|
144
202
|
/**
|
|
@@ -147,8 +205,13 @@ declare class TemplateRegistry {
|
|
|
147
205
|
registerAll(templates: MessageTemplate[]): void;
|
|
148
206
|
/**
|
|
149
207
|
* Find a template by its templateCd.
|
|
208
|
+
*
|
|
209
|
+
* Returns a readonly `RegisteredMessageTemplate` view matching the registry
|
|
210
|
+
* freeze depth (top-level, content objects, object uiTemplates, the action
|
|
211
|
+
* array, and each action plus its confirmation/payload are frozen).
|
|
212
|
+
* Mutating the result throws at runtime; register a replacement instead.
|
|
150
213
|
*/
|
|
151
|
-
find(templateCd: string):
|
|
214
|
+
find(templateCd: string): RegisteredMessageTemplate | undefined;
|
|
152
215
|
/**
|
|
153
216
|
* Check if a template exists.
|
|
154
217
|
*/
|
|
@@ -159,8 +222,10 @@ declare class TemplateRegistry {
|
|
|
159
222
|
getTemplateCodes(): string[];
|
|
160
223
|
/**
|
|
161
224
|
* Get all registered templates.
|
|
225
|
+
*
|
|
226
|
+
* Returns readonly `RegisteredMessageTemplate` views; see `find()`.
|
|
162
227
|
*/
|
|
163
|
-
getAll():
|
|
228
|
+
getAll(): RegisteredMessageTemplate[];
|
|
164
229
|
/**
|
|
165
230
|
* Remove a template by templateCd.
|
|
166
231
|
*/
|
|
@@ -204,10 +269,30 @@ interface MessageUser {
|
|
|
204
269
|
displayName?: string;
|
|
205
270
|
}
|
|
206
271
|
interface IMessageRelationshipMethods {
|
|
272
|
+
/** Match a string/ObjectId or populated party's `_id`; missing references never match. */
|
|
207
273
|
isSender(user: MessageUser): boolean;
|
|
274
|
+
/** Match the recipient identity (including populated `_id`) or an explicit recipient role. */
|
|
208
275
|
isReceiver(user: MessageUser): boolean;
|
|
209
276
|
}
|
|
210
277
|
interface IMessageMethods extends IMessageRelationshipMethods {
|
|
278
|
+
/**
|
|
279
|
+
* Trusted host-level archival primitive: atomically moves the active message
|
|
280
|
+
* into the archive collection on the document's owning connection.
|
|
281
|
+
*
|
|
282
|
+
* The host must authorize before calling (no sender/receiver, permission, or
|
|
283
|
+
* condition checks run here). Invalid `actionCd` throws
|
|
284
|
+
* `TemplateNotFoundError`/`ActionNotFoundError`; an invalid `archivedBy`
|
|
285
|
+
* throws `InvalidMessageUserError` — never a silent no-op success. A live
|
|
286
|
+
* in-flight service claim (`processing` without an expired lease) throws
|
|
287
|
+
* `ActionConflictError` and steals nothing. Repeat calls throw
|
|
288
|
+
* `MessageArchivedError` (archive copy exists) or `MessageNotFoundError`.
|
|
289
|
+
* Deployments without transaction support throw
|
|
290
|
+
* `MessageTransactionRequiredError` before writing anything.
|
|
291
|
+
* Confirmed commit resolves even if session cleanup rejects; configure
|
|
292
|
+
* `buildMessageSchema({ onTransactionCleanupFailure })` for diagnostics.
|
|
293
|
+
* Observer failures cannot change the outcome. An attached `$session()` is
|
|
294
|
+
* borrowed and never ended by this method; its caller owns cleanup.
|
|
295
|
+
*/
|
|
211
296
|
archive(actionCd: string, archivedBy: UserId, registry: TemplateRegistry): Promise<void>;
|
|
212
297
|
}
|
|
213
298
|
/**
|
|
@@ -237,6 +322,7 @@ interface IBaseMessage {
|
|
|
237
322
|
actionState: ActionLifecycleState;
|
|
238
323
|
actionCd: string | null;
|
|
239
324
|
actionAttemptId: string | null;
|
|
325
|
+
actionOwnerToken: string | null;
|
|
240
326
|
actionClaimedBy: string | null;
|
|
241
327
|
actionClaimedAt: Date | null;
|
|
242
328
|
actionLeaseExpiresAt: Date | null;
|
|
@@ -253,6 +339,7 @@ type IMessageArchive = mongoose.Document<unknown, MongooseQueryHelpers, IMessage
|
|
|
253
339
|
archivedBy: UserId;
|
|
254
340
|
archivedAt: Date;
|
|
255
341
|
actionAttemptId: string | null;
|
|
342
|
+
actionOwnerToken: string | null;
|
|
256
343
|
actionNotificationState: ActionNotificationState;
|
|
257
344
|
actionNotificationError: string | null;
|
|
258
345
|
actionNotificationAttemptedAt: Date | null;
|
|
@@ -322,7 +409,11 @@ interface MessageSchemaConfig {
|
|
|
322
409
|
* send a best-effort email notification to the recipient. Pass `null` (the
|
|
323
410
|
* default) to disable.
|
|
324
411
|
*
|
|
325
|
-
* When `null`, no
|
|
412
|
+
* When `null`, no save hooks are registered at all. When set, the schema
|
|
413
|
+
* registers a pre-save state capture plus a connection-local post-save
|
|
414
|
+
* hook: the post-save hook resolves user/archive models on the hydrated
|
|
415
|
+
* document's owning connection and skips session-bound writes (Mongoose
|
|
416
|
+
* save hooks run before the surrounding transaction commits).
|
|
326
417
|
*/
|
|
327
418
|
emailNotifier?: EmailNotifier | null;
|
|
328
419
|
/**
|
|
@@ -340,11 +431,15 @@ interface MessageSchemaConfig {
|
|
|
340
431
|
emailNotificationExclusions?: string[];
|
|
341
432
|
/**
|
|
342
433
|
* Name of the Mongoose model that holds the recipient user records.
|
|
343
|
-
* Defaults to `'User'`. The
|
|
344
|
-
*
|
|
434
|
+
* Defaults to `'User'`. The post-save email hook resolves it on the
|
|
435
|
+
* message document's owning connection
|
|
436
|
+
* (`document.constructor.db.model(name)`) to look up the recipient's email
|
|
437
|
+
* address, so connection-local apps must register the user model on the
|
|
438
|
+
* same connection as the message models.
|
|
345
439
|
*
|
|
346
|
-
* The model MUST be registered
|
|
347
|
-
* `buildMessageSchema`
|
|
440
|
+
* The model MUST be registered before the first save when `emailNotifier`
|
|
441
|
+
* is set — `buildMessageSchema` checks this eagerly to fail fast (pass
|
|
442
|
+
* `connection` when configuring connection-local schemas).
|
|
348
443
|
*/
|
|
349
444
|
userModelName?: string;
|
|
350
445
|
/**
|
|
@@ -358,12 +453,20 @@ interface MessageSchemaConfig {
|
|
|
358
453
|
* Defaults to `'MessageArchive'`. Used by the `archive()` instance method.
|
|
359
454
|
*/
|
|
360
455
|
archiveModelName?: string;
|
|
456
|
+
/**
|
|
457
|
+
* Best-effort diagnostics for `endSession()` rejection after direct `archive()`.
|
|
458
|
+
* Preserves committed success or the primary transaction error even when the
|
|
459
|
+
* observer throws/rejects. Awaited; keep it bounded. Borrowed document sessions
|
|
460
|
+
* remain caller-owned and are not ended/reported. Service writes use the
|
|
461
|
+
* separate `MessageServiceOptions.onTransactionCleanupFailure` observer.
|
|
462
|
+
*/
|
|
463
|
+
onTransactionCleanupFailure?: MessageTransactionCleanupFailureObserver;
|
|
361
464
|
}
|
|
362
465
|
type MessageModel = mongoose.Model<IBaseMessage, object, IMessageMethods>;
|
|
363
466
|
/**
|
|
364
467
|
* Build a fresh Message schema with the given configuration.
|
|
365
468
|
* Prefer this over the default `MessageSchema` export when you need
|
|
366
|
-
* an email notifier, exclusions, or custom model names.
|
|
469
|
+
* an email notifier, exclusions, cleanup diagnostics, or custom model names.
|
|
367
470
|
*
|
|
368
471
|
* Note: when `emailNotifier` is set, the configured `userModelName` MUST
|
|
369
472
|
* be registered with Mongoose before calling this function. The schema
|
|
@@ -371,7 +474,7 @@ type MessageModel = mongoose.Model<IBaseMessage, object, IMessageMethods>;
|
|
|
371
474
|
*/
|
|
372
475
|
declare function buildMessageSchema(config?: MessageSchemaConfig): mongoose.Schema<IBaseMessage, MessageModel, IMessageMethods>;
|
|
373
476
|
/**
|
|
374
|
-
* Default Message schema with no email notifier and no
|
|
477
|
+
* Default Message schema with no email notifier and no save hooks.
|
|
375
478
|
* Provided for backwards compatibility and simple use cases.
|
|
376
479
|
* Use `buildMessageSchema(config)` when you need custom behavior.
|
|
377
480
|
*/
|
|
@@ -546,6 +649,15 @@ declare const MessageSchema: mongoose.Schema<IBaseMessage, MessageModel, IMessag
|
|
|
546
649
|
}, "id" | keyof IMessageMethods> & mongoose.HydratedDocumentOverrides<{
|
|
547
650
|
id: string;
|
|
548
651
|
} & IMessageMethods>> | undefined;
|
|
652
|
+
actionOwnerToken?: mongoose.SchemaDefinitionProperty<string | null, IBaseMessage, mongoose.Document<unknown, {}, IBaseMessage, {
|
|
653
|
+
id: string;
|
|
654
|
+
}, mongoose.DefaultSchemaOptions> & Omit<IBaseMessage & {
|
|
655
|
+
_id: mongoose.Types.ObjectId;
|
|
656
|
+
} & {
|
|
657
|
+
__v: number;
|
|
658
|
+
}, "id" | keyof IMessageMethods> & mongoose.HydratedDocumentOverrides<{
|
|
659
|
+
id: string;
|
|
660
|
+
} & IMessageMethods>> | undefined;
|
|
549
661
|
actionClaimedBy?: mongoose.SchemaDefinitionProperty<string | null, IBaseMessage, mongoose.Document<unknown, {}, IBaseMessage, {
|
|
550
662
|
id: string;
|
|
551
663
|
}, mongoose.DefaultSchemaOptions> & Omit<IBaseMessage & {
|
|
@@ -945,6 +1057,23 @@ declare const MessageArchiveSchema: mongoose.Schema<MessageArchiveSchemaFields,
|
|
|
945
1057
|
}, "id" | keyof IMessageRelationshipMethods> & mongoose.HydratedDocumentOverrides<{
|
|
946
1058
|
id: string;
|
|
947
1059
|
} & IMessageRelationshipMethods>> | undefined;
|
|
1060
|
+
actionOwnerToken?: mongoose.SchemaDefinitionProperty<string | null, MessageArchiveSchemaFields, mongoose.Document<unknown, {}, MessageArchiveSchemaFields, {
|
|
1061
|
+
id: string;
|
|
1062
|
+
}, mongoose.DefaultSchemaOptions> & Omit<IBaseMessage & {
|
|
1063
|
+
actionCd: string;
|
|
1064
|
+
archivedBy: unknown;
|
|
1065
|
+
archivedAt: Date;
|
|
1066
|
+
actionAttemptId: string | null;
|
|
1067
|
+
actionNotificationState: "none" | "pending" | "sent" | "failed";
|
|
1068
|
+
actionNotificationError: string | null;
|
|
1069
|
+
actionNotificationAttemptedAt: Date | null;
|
|
1070
|
+
} & {
|
|
1071
|
+
_id: mongoose.Types.ObjectId;
|
|
1072
|
+
} & {
|
|
1073
|
+
__v: number;
|
|
1074
|
+
}, "id" | keyof IMessageRelationshipMethods> & mongoose.HydratedDocumentOverrides<{
|
|
1075
|
+
id: string;
|
|
1076
|
+
} & IMessageRelationshipMethods>> | undefined;
|
|
948
1077
|
actionClaimedBy?: mongoose.SchemaDefinitionProperty<string | null, MessageArchiveSchemaFields, mongoose.Document<unknown, {}, MessageArchiveSchemaFields, {
|
|
949
1078
|
id: string;
|
|
950
1079
|
}, mongoose.DefaultSchemaOptions> & Omit<IBaseMessage & {
|
|
@@ -1081,7 +1210,7 @@ declare const MessageArchiveSchema: mongoose.Schema<MessageArchiveSchemaFields,
|
|
|
1081
1210
|
}, "id" | keyof IMessageRelationshipMethods> & mongoose.HydratedDocumentOverrides<{
|
|
1082
1211
|
id: string;
|
|
1083
1212
|
} & IMessageRelationshipMethods>> | undefined;
|
|
1084
|
-
actionNotificationState?: mongoose.SchemaDefinitionProperty<"
|
|
1213
|
+
actionNotificationState?: mongoose.SchemaDefinitionProperty<"failed" | "none" | "pending" | "sent", MessageArchiveSchemaFields, mongoose.Document<unknown, {}, MessageArchiveSchemaFields, {
|
|
1085
1214
|
id: string;
|
|
1086
1215
|
}, mongoose.DefaultSchemaOptions> & Omit<IBaseMessage & {
|
|
1087
1216
|
actionCd: string;
|
|
@@ -1185,7 +1314,7 @@ declare const MessageRequestSchema: mongoose.Schema<MessageRequestSchemaFields,
|
|
|
1185
1314
|
}, "id"> & mongoose.HydratedDocumentOverrides<{
|
|
1186
1315
|
id: string;
|
|
1187
1316
|
}>> | undefined;
|
|
1188
|
-
state?: mongoose.SchemaDefinitionProperty<"
|
|
1317
|
+
state?: mongoose.SchemaDefinitionProperty<"failed" | "pending" | "completed", MessageRequestSchemaFields, mongoose.Document<unknown, {}, MessageRequestSchemaFields, {
|
|
1189
1318
|
id: string;
|
|
1190
1319
|
}, mongoose.DefaultSchemaOptions> & Omit<MessageRequestSchemaFields & {
|
|
1191
1320
|
_id: mongoose.Types.ObjectId;
|
|
@@ -1268,15 +1397,68 @@ declare const MessageRequestSchema: mongoose.Schema<MessageRequestSchemaFields,
|
|
|
1268
1397
|
}>> | undefined;
|
|
1269
1398
|
}, MessageRequestSchemaFields>;
|
|
1270
1399
|
|
|
1400
|
+
/**
|
|
1401
|
+
* Single explicit permission predicate shared by every permission boundary:
|
|
1402
|
+
* UI filtering (`filterActions`), action execution (`isActionAllowed`), and
|
|
1403
|
+
* the route's admin read-only lookup.
|
|
1404
|
+
*
|
|
1405
|
+
* A grant requires an **own** property of the permissions map whose value is
|
|
1406
|
+
* strictly `true`. Inherited properties (e.g. `constructor`, `toString`,
|
|
1407
|
+
* or custom keys from a prototype chain — including the default
|
|
1408
|
+
* `Object.prototype`) are denied, as are truthy non-booleans (`1`, `"true"`,
|
|
1409
|
+
* `{}`, ...), `false`, and absent keys.
|
|
1410
|
+
*
|
|
1411
|
+
* Normal object literals and null-prototype maps (`Object.create(null)`,
|
|
1412
|
+
* including JSON-parsed or `Object.assign`-built maps with own properties)
|
|
1413
|
+
* both work: the check uses `Object.prototype.hasOwnProperty.call`, so it is
|
|
1414
|
+
* safe for maps containing a `__proto__` key and for maps without a
|
|
1415
|
+
* prototype. Non-object permission containers (`null`, `undefined`,
|
|
1416
|
+
* primitives, arrays) never grant.
|
|
1417
|
+
*
|
|
1418
|
+
* Tightening note for custom `getPermissions` extractors: extractors that
|
|
1419
|
+
* previously relied on inherited or truthy non-boolean values must now return
|
|
1420
|
+
* own properties set to boolean `true`; anything else is treated as denied.
|
|
1421
|
+
*/
|
|
1422
|
+
declare function hasExplicitPermissionGrant(permissions: Record<string, boolean> | undefined | null, key: string): boolean;
|
|
1423
|
+
/**
|
|
1424
|
+
* Render sender/receiver content only, without evaluating action predicates.
|
|
1425
|
+
*
|
|
1426
|
+
* Creation uses this path with `PrepareResult.templateData`: content is a
|
|
1427
|
+
* function of preparation output only. Action `condition` predicates are never
|
|
1428
|
+
* invoked here, so a condition that accesses persisted message fields (e.g.
|
|
1429
|
+
* `message.payload.ready`) cannot fail creation. Action labels/confirmations
|
|
1430
|
+
* are a separate concern rendered from persisted `message.payload` at listing
|
|
1431
|
+
* time (see `filterActions`); there is deliberately no unified
|
|
1432
|
+
* interpolation-data feature.
|
|
1433
|
+
*
|
|
1434
|
+
* Escaping/trusted-template semantics match `interpolateTemplate`: Handlebars
|
|
1435
|
+
* with `noEscape: true`, plain-text output, trusted template strings only.
|
|
1436
|
+
*/
|
|
1437
|
+
declare function interpolateMessageContent(template: Pick<MessageTemplate | RegisteredMessageTemplate, 'senderContent' | 'receiverContent'>, data: Record<string, unknown>): Pick<InterpolationResult, 'senderContent' | 'receiverContent'>;
|
|
1438
|
+
/**
|
|
1439
|
+
* Resolve the uiTemplate for a usertype without evaluating actions.
|
|
1440
|
+
* Read-only/admin/archive listings use this plus an empty action list so
|
|
1441
|
+
* `condition` functions are never invoked merely to discard their results.
|
|
1442
|
+
*
|
|
1443
|
+
* Accepts both mutable author inputs and frozen registry views.
|
|
1444
|
+
*/
|
|
1445
|
+
declare function resolveUiTemplate(uiTemplate: UiTemplate | RegisteredMessageTemplate['uiTemplate'], usertype: Usertype): string;
|
|
1271
1446
|
/**
|
|
1272
1447
|
* Apply the standard set of action filters (usertype match, permission, condition).
|
|
1273
1448
|
* This is the single source of truth used by both the template engine (for UI)
|
|
1274
1449
|
* and the server-side action handler (for authorization).
|
|
1275
1450
|
*
|
|
1451
|
+
* Permission gating uses {@link hasExplicitPermissionGrant}: only an own
|
|
1452
|
+
* property strictly equal to `true` grants access.
|
|
1453
|
+
*
|
|
1276
1454
|
* The `name` and confirmation `title`/`message`/`notesLabel` strings are
|
|
1277
|
-
* compiled against the
|
|
1455
|
+
* compiled against the persisted `message.payload` snapshot passed as `data`
|
|
1456
|
+
* by `getActions` (via `interpolateTemplate`). This is intentionally different
|
|
1457
|
+
* from message content, which is rendered at creation time from
|
|
1458
|
+
* `PrepareResult.templateData`. Missing values in either source render as
|
|
1459
|
+
* empty strings via Handlebars.
|
|
1278
1460
|
*/
|
|
1279
|
-
declare function filterActions(actions: MessageAction[], usertype: Usertype, options?: {
|
|
1461
|
+
declare function filterActions(actions: readonly (MessageAction | RegisteredMessageAction)[], usertype: Usertype, options?: {
|
|
1280
1462
|
permissions?: Record<string, boolean>;
|
|
1281
1463
|
message?: Record<string, unknown>;
|
|
1282
1464
|
data?: Record<string, unknown>;
|
|
@@ -1285,15 +1467,26 @@ declare function filterActions(actions: MessageAction[], usertype: Usertype, opt
|
|
|
1285
1467
|
* Check if the given user is allowed to execute the action on the given message.
|
|
1286
1468
|
* Returns true if the action is permitted, false otherwise.
|
|
1287
1469
|
* Mirrors the `filterActions` logic exactly so the UI and the server agree.
|
|
1470
|
+
*
|
|
1471
|
+
* Permission gating uses {@link hasExplicitPermissionGrant}: only an own
|
|
1472
|
+
* property strictly equal to `true` grants access.
|
|
1288
1473
|
*/
|
|
1289
|
-
declare function isActionAllowed(action: MessageAction, user: MessageUser, message: IMessage | IMessageArchive, options?: {
|
|
1474
|
+
declare function isActionAllowed(action: MessageAction | RegisteredMessageAction, user: MessageUser, message: IMessage | IMessageArchive, options?: {
|
|
1290
1475
|
permissions?: Record<string, boolean>;
|
|
1291
1476
|
}): boolean;
|
|
1292
1477
|
/**
|
|
1293
1478
|
* Interpolate a template's sender/receiver content and filter actions
|
|
1294
1479
|
* for the given usertype (sender or receiver).
|
|
1480
|
+
*
|
|
1481
|
+
* Preserved public combined helper: content is rendered from `data`
|
|
1482
|
+
* (callers pass `templateData` at creation via `interpolateMessageContent`,
|
|
1483
|
+
* or persisted `message.payload` at listing time), while action predicates
|
|
1484
|
+
* receive the documented `message` context and action labels/confirmations
|
|
1485
|
+
* compile against `data`. Creation and read-only/admin/archive paths must use
|
|
1486
|
+
* `interpolateMessageContent`/`resolveUiTemplate` directly so conditions are
|
|
1487
|
+
* not evaluated unnecessarily.
|
|
1295
1488
|
*/
|
|
1296
|
-
declare function interpolateTemplate(template: MessageTemplate, data: Record<string, unknown>, usertype: Usertype, options?: {
|
|
1489
|
+
declare function interpolateTemplate(template: MessageTemplate | RegisteredMessageTemplate, data: Record<string, unknown>, usertype: Usertype, options?: {
|
|
1297
1490
|
permissions?: Record<string, boolean>;
|
|
1298
1491
|
message?: Record<string, unknown>;
|
|
1299
1492
|
}): InterpolationResult;
|
|
@@ -1319,13 +1512,30 @@ declare class NoopEmailProvider implements EmailProvider {
|
|
|
1319
1512
|
* Host apps implement this to handle payment sessions for messages
|
|
1320
1513
|
* that require payment (e.g. Stripe, Adyen, Paddle).
|
|
1321
1514
|
*
|
|
1322
|
-
* Message creation may create external sessions before MongoDB commit. If
|
|
1323
|
-
*
|
|
1324
|
-
* `expireSession()` for every
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1515
|
+
* Message creation may create external sessions before MongoDB commit. If any
|
|
1516
|
+
* prepared item, model/session setup step, or idempotent batch transaction
|
|
1517
|
+
* fails before commit, `MessageService` calls `expireSession()` for every
|
|
1518
|
+
* newly created uncommitted session tracked from preparation through the
|
|
1519
|
+
* commit attempt — including sessions from earlier batch items when a later
|
|
1520
|
+
* item's provider/render step fails, and sessions left uncommitted by model
|
|
1521
|
+
* resolution, `startSession()`, persistence, or rollback failures. Every
|
|
1522
|
+
* known session is attempted even when an earlier expiration or the
|
|
1523
|
+
* `onPaymentCompensationFailure` observer fails; multi-session batches then
|
|
1524
|
+
* throw `PaymentSessionCompensationAggregateError` (a
|
|
1525
|
+
* `PaymentSessionCompensationError` carrying every per-session failure in
|
|
1526
|
+
* `failures` with the triggering error as `originalError`), while a lone
|
|
1527
|
+
* session keeps the single-session `PaymentSessionCompensationError` shape.
|
|
1528
|
+
* Providers must make expiration idempotent because callers may retry cleanup
|
|
1529
|
+
* after ambiguous network or provider failures. Committed sessions are never
|
|
1530
|
+
* expired because post-commit housekeeping failed; a completed same-scope
|
|
1531
|
+
* retry replays the committed batch without creating replacement sessions.
|
|
1532
|
+
* Non-idempotent sequential creation keeps per-item semantics: only the
|
|
1533
|
+
* failing item is compensated, already-committed items are retained.
|
|
1534
|
+
* Process death before a returned session is recorded, or any ambiguous
|
|
1535
|
+
* provider/commit outcome this process cannot observe, must be reconciled
|
|
1536
|
+
* with the provider out of band. If expiration itself fails, message creation
|
|
1537
|
+
* fails with the compensation error and the optional service hook is invoked
|
|
1538
|
+
* once per failed session for observability.
|
|
1329
1539
|
*
|
|
1330
1540
|
* `priceArgs` is intentionally a free-form `Record<string, unknown>` so
|
|
1331
1541
|
* providers can accept their own metadata (currency, line items, etc.).
|
|
@@ -1360,67 +1570,8 @@ declare class NoopPaymentProvider implements PaymentProvider {
|
|
|
1360
1570
|
refundPayment(_sessionId: string): Promise<void>;
|
|
1361
1571
|
}
|
|
1362
1572
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
connection?: mongoose.Connection;
|
|
1366
|
-
modelNames?: Partial<MessageServiceModelNames>;
|
|
1367
|
-
paymentProvider?: PaymentProvider | null;
|
|
1368
|
-
/**
|
|
1369
|
-
* Called when the service cannot expire an external payment session that was
|
|
1370
|
-
* created for a message batch that did not commit.
|
|
1371
|
-
*/
|
|
1372
|
-
onPaymentCompensationFailure?: (event: PaymentCompensationFailureEvent) => void | Promise<void>;
|
|
1373
|
-
adminRoles?: string[];
|
|
1374
|
-
registry?: TemplateRegistry;
|
|
1375
|
-
/**
|
|
1376
|
-
* Maximum number of messages returned by `listMessages` when no explicit
|
|
1377
|
-
* limit is provided. Defaults to 50.
|
|
1378
|
-
*/
|
|
1379
|
-
defaultListLimit?: number;
|
|
1380
|
-
/**
|
|
1381
|
-
* Hard upper bound for `listMessages` limit to prevent abuse. Defaults to 100.
|
|
1382
|
-
*/
|
|
1383
|
-
maxListLimit?: number;
|
|
1384
|
-
/**
|
|
1385
|
-
* How long a pending idempotent create reservation lease remains live before
|
|
1386
|
-
* another caller may atomically take it over. Defaults to 30000 ms.
|
|
1387
|
-
*/
|
|
1388
|
-
clientRequestLeaseMs?: number;
|
|
1389
|
-
/**
|
|
1390
|
-
* Maximum time a duplicate idempotent create waits for completion or stale
|
|
1391
|
-
* lease takeover before raising `ClientRequestPendingError`. Defaults to 5000 ms.
|
|
1392
|
-
*/
|
|
1393
|
-
clientRequestWaitMs?: number;
|
|
1394
|
-
/** Poll interval while waiting for an idempotent create outcome. Defaults to 200 ms. */
|
|
1395
|
-
clientRequestPollMs?: number;
|
|
1396
|
-
/** Test hook for deterministic waiting; production uses `setTimeout`. */
|
|
1397
|
-
clientRequestDelay?: (ms: number) => Promise<void>;
|
|
1398
|
-
}
|
|
1399
|
-
interface MessageServiceModelNames {
|
|
1400
|
-
active: string;
|
|
1401
|
-
archive: string;
|
|
1402
|
-
request: string;
|
|
1403
|
-
user: string;
|
|
1404
|
-
}
|
|
1405
|
-
interface PaymentCompensationFailureEvent {
|
|
1406
|
-
operation: 'expire';
|
|
1407
|
-
sessionId: string;
|
|
1408
|
-
error: unknown;
|
|
1409
|
-
originalError: unknown;
|
|
1410
|
-
clientRequestId?: string;
|
|
1411
|
-
clientRequestOwnerId?: string;
|
|
1412
|
-
templateCd?: string;
|
|
1413
|
-
}
|
|
1414
|
-
type HydratedModelSource = {
|
|
1415
|
-
constructor: unknown;
|
|
1416
|
-
};
|
|
1417
|
-
type MessageModelRole = keyof MessageServiceModelNames;
|
|
1418
|
-
/**
|
|
1419
|
-
* The `templateCd` used for generic notifications created via
|
|
1420
|
-
* `createNotification`. These messages have no actions, so
|
|
1421
|
-
* `getActions` returns an empty list for them.
|
|
1422
|
-
*/
|
|
1423
|
-
declare const GENERIC_NOTIFICATION_TEMPLATE_CD = "__generic-notification__";
|
|
1573
|
+
/** Model roles the service resolves on a Mongoose connection or resolver. */
|
|
1574
|
+
type MessageModelRole = 'active' | 'archive' | 'request' | 'user';
|
|
1424
1575
|
declare class MessageNotFoundError extends Error {
|
|
1425
1576
|
static [Symbol.hasInstance](value: unknown): boolean;
|
|
1426
1577
|
constructor(messageId: string);
|
|
@@ -1472,12 +1623,28 @@ declare class InvalidPaginationValueError extends Error {
|
|
|
1472
1623
|
static [Symbol.hasInstance](value: unknown): boolean;
|
|
1473
1624
|
constructor(message: string);
|
|
1474
1625
|
}
|
|
1626
|
+
declare class InvalidMessageServiceOptionError extends Error {
|
|
1627
|
+
static [Symbol.hasInstance](value: unknown): boolean;
|
|
1628
|
+
constructor(message: string);
|
|
1629
|
+
}
|
|
1475
1630
|
declare class ClientRequestPendingError extends Error {
|
|
1476
1631
|
static [Symbol.hasInstance](value: unknown): boolean;
|
|
1477
1632
|
constructor(clientRequestId: string);
|
|
1478
1633
|
}
|
|
1479
1634
|
declare class ClientRequestFailedError extends Error {
|
|
1480
1635
|
static [Symbol.hasInstance](value: unknown): boolean;
|
|
1636
|
+
/**
|
|
1637
|
+
* Scoped request id for this recorded failure. Safe to expose: it is the
|
|
1638
|
+
* caller-supplied id within the caller's own owner/template scope.
|
|
1639
|
+
*/
|
|
1640
|
+
clientRequestId: string;
|
|
1641
|
+
/**
|
|
1642
|
+
* Recorded terminal-failure diagnostic (the `failureMessage` persisted on
|
|
1643
|
+
* the request record). Retained for internal observers/direct-service
|
|
1644
|
+
* callers; never serialized into HTTP responses. See `route-factory`
|
|
1645
|
+
* `mapServiceError`, which returns a stable public message without it.
|
|
1646
|
+
*/
|
|
1647
|
+
failureReason: string | null;
|
|
1481
1648
|
constructor(clientRequestId: string, reason?: string | null);
|
|
1482
1649
|
}
|
|
1483
1650
|
declare class ClientRequestInconsistentStateError extends Error {
|
|
@@ -1504,6 +1671,156 @@ declare class PaymentSessionCompensationError extends Error {
|
|
|
1504
1671
|
hookError?: unknown;
|
|
1505
1672
|
constructor(sessionId: string, operation: 'expire', compensationError: unknown, originalError: unknown, hookError?: unknown);
|
|
1506
1673
|
}
|
|
1674
|
+
/**
|
|
1675
|
+
* One failed `expireSession` (or compensation-hook) attempt within a batch.
|
|
1676
|
+
*/
|
|
1677
|
+
interface PaymentSessionCompensationFailure {
|
|
1678
|
+
sessionId: string;
|
|
1679
|
+
compensationError: unknown;
|
|
1680
|
+
hookError?: unknown;
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Thrown when compensating a multi-session batch leaves one or more
|
|
1684
|
+
* uncommitted sessions unexpired.
|
|
1685
|
+
*
|
|
1686
|
+
* Extends `PaymentSessionCompensationError` so existing single-session
|
|
1687
|
+
* `instanceof` checks keep working. `sessionId`/`compensationError`/
|
|
1688
|
+
* `hookError` mirror the first failure for backwards compatibility; the full
|
|
1689
|
+
* per-session list lives in `failures` and `originalError` is the failure
|
|
1690
|
+
* that triggered compensation. Every known session is attempted even when an
|
|
1691
|
+
* earlier cleanup fails, and the hook is invoked once per failed session.
|
|
1692
|
+
*/
|
|
1693
|
+
declare class PaymentSessionCompensationAggregateError extends PaymentSessionCompensationError {
|
|
1694
|
+
static [Symbol.hasInstance](value: unknown): boolean;
|
|
1695
|
+
failures: PaymentSessionCompensationFailure[];
|
|
1696
|
+
constructor(failures: PaymentSessionCompensationFailure[], originalError: unknown);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
interface MessageServiceOptions {
|
|
1700
|
+
getModel?: (name: string) => mongoose.Model<unknown>;
|
|
1701
|
+
connection?: mongoose.Connection;
|
|
1702
|
+
modelNames?: Partial<MessageServiceModelNames>;
|
|
1703
|
+
paymentProvider?: PaymentProvider | null;
|
|
1704
|
+
/**
|
|
1705
|
+
* Called when the service cannot expire an external payment session that was
|
|
1706
|
+
* created for a message batch that did not commit.
|
|
1707
|
+
*/
|
|
1708
|
+
onPaymentCompensationFailure?: (event: PaymentCompensationFailureEvent) => void | Promise<void>;
|
|
1709
|
+
/**
|
|
1710
|
+
* Observes `endSession()` rejection for service-owned batch/action transactions.
|
|
1711
|
+
* Confirmed commits and primary transaction errors are preserved even if this
|
|
1712
|
+
* observer throws/rejects. Awaited best-effort diagnostics; keep it bounded.
|
|
1713
|
+
* Configure `buildMessageSchema` separately for direct document `archive()`.
|
|
1714
|
+
*/
|
|
1715
|
+
onTransactionCleanupFailure?: MessageTransactionCleanupFailureObserver;
|
|
1716
|
+
adminRoles?: string[];
|
|
1717
|
+
registry?: TemplateRegistry;
|
|
1718
|
+
/**
|
|
1719
|
+
* Maximum number of messages returned by `listMessages` when no explicit
|
|
1720
|
+
* limit is provided. Defaults to 50.
|
|
1721
|
+
*/
|
|
1722
|
+
defaultListLimit?: number;
|
|
1723
|
+
/**
|
|
1724
|
+
* Hard upper bound for `listMessages` limit to prevent abuse. Defaults to 100.
|
|
1725
|
+
*/
|
|
1726
|
+
maxListLimit?: number;
|
|
1727
|
+
/**
|
|
1728
|
+
* How long a pending idempotent create reservation lease remains live before
|
|
1729
|
+
* another caller may atomically take it over. Defaults to 30000 ms.
|
|
1730
|
+
*
|
|
1731
|
+
* Must be a finite safe integer in `[1, 2147483647]` ms (positive lease).
|
|
1732
|
+
* The upper bound is Node's maximum `setTimeout` delay (`2^31 - 1`); larger
|
|
1733
|
+
* values would not fire on the intended cadence. Fractional, `NaN`,
|
|
1734
|
+
* infinite, negative, zero, or overflowing values throw
|
|
1735
|
+
* `InvalidMessageServiceOptionError` at construction.
|
|
1736
|
+
*/
|
|
1737
|
+
clientRequestLeaseMs?: number;
|
|
1738
|
+
/**
|
|
1739
|
+
* Maximum time a duplicate idempotent create waits for completion or stale
|
|
1740
|
+
* lease takeover before raising `ClientRequestPendingError`. Defaults to 5000 ms.
|
|
1741
|
+
*
|
|
1742
|
+
* Must be a finite safe integer in `[0, 2147483647]` ms (nonnegative wait).
|
|
1743
|
+
* `0` is a supported immediate check: a duplicate that cannot acquire or
|
|
1744
|
+
* replay on its first attempt raises `ClientRequestPendingError` without
|
|
1745
|
+
* sleeping. The wait deadline bounds polling only — it never cancels a hung
|
|
1746
|
+
* database/provider operation. Out-of-range values throw
|
|
1747
|
+
* `InvalidMessageServiceOptionError` at construction.
|
|
1748
|
+
*/
|
|
1749
|
+
clientRequestWaitMs?: number;
|
|
1750
|
+
/**
|
|
1751
|
+
* Poll interval while waiting for an idempotent create outcome. Defaults to 200 ms.
|
|
1752
|
+
*
|
|
1753
|
+
* Must be a finite safe integer in `[1, 2147483647]` ms (positive poll).
|
|
1754
|
+
* Out-of-range values throw `InvalidMessageServiceOptionError` at construction.
|
|
1755
|
+
*/
|
|
1756
|
+
clientRequestPollMs?: number;
|
|
1757
|
+
/** Test hook for deterministic waiting; production uses `setTimeout`. */
|
|
1758
|
+
clientRequestDelay?: (ms: number) => Promise<void>;
|
|
1759
|
+
/**
|
|
1760
|
+
* Test hook for deterministic time. Defaults to `Date.now`.
|
|
1761
|
+
*
|
|
1762
|
+
* The same clock drives the duplicate-wait deadline and the persisted
|
|
1763
|
+
* `leaseExpiresAt` wall-clock timestamps (`new Date(now())`), so fake
|
|
1764
|
+
* clocks stay coherent in tests. The stale-takeover filter compares
|
|
1765
|
+
* `leaseExpiresAt` against this clock, not an independent `new Date()`.
|
|
1766
|
+
* Production uses real wall-clock time.
|
|
1767
|
+
*/
|
|
1768
|
+
clientRequestNow?: () => number;
|
|
1769
|
+
}
|
|
1770
|
+
interface MessageServiceModelNames {
|
|
1771
|
+
active: string;
|
|
1772
|
+
archive: string;
|
|
1773
|
+
request: string;
|
|
1774
|
+
user: string;
|
|
1775
|
+
}
|
|
1776
|
+
interface PaymentCompensationFailureEvent {
|
|
1777
|
+
operation: 'expire';
|
|
1778
|
+
sessionId: string;
|
|
1779
|
+
error: unknown;
|
|
1780
|
+
originalError: unknown;
|
|
1781
|
+
clientRequestId?: string;
|
|
1782
|
+
clientRequestOwnerId?: string;
|
|
1783
|
+
templateCd?: string;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Maximum platform timer delay (`2^31 - 1` ms, ~24.8 days). Node's
|
|
1787
|
+
* `setTimeout` clamps/overflows beyond this, so lease/wait/poll values above
|
|
1788
|
+
* it are rejected at construction rather than silently misfiring.
|
|
1789
|
+
*/
|
|
1790
|
+
declare const MAX_MESSAGE_SERVICE_TIMEOUT_MS = 2147483647;
|
|
1791
|
+
type HydratedModelSource = {
|
|
1792
|
+
constructor: unknown;
|
|
1793
|
+
};
|
|
1794
|
+
/**
|
|
1795
|
+
* The `templateCd` used for generic notifications created via
|
|
1796
|
+
* `createNotification`. These messages have no actions, so
|
|
1797
|
+
* `getActions` returns an empty list for them.
|
|
1798
|
+
*/
|
|
1799
|
+
declare const GENERIC_NOTIFICATION_TEMPLATE_CD = "__generic-notification__";
|
|
1800
|
+
/**
|
|
1801
|
+
* Shared principal-validation contract for user-facing service entry points
|
|
1802
|
+
* and HTTP routes.
|
|
1803
|
+
*
|
|
1804
|
+
* A valid principal id is a non-empty string (after trimming) or a
|
|
1805
|
+
* `mongoose.Types.ObjectId` instance. Missing, null, empty/whitespace-only,
|
|
1806
|
+
* numeric, array, and plain-object ids are invalid. Valid string ids keep
|
|
1807
|
+
* their string type (trimmed); valid ObjectId instances are preserved as
|
|
1808
|
+
* ObjectId by callers and normalized to their hex string only where a string
|
|
1809
|
+
* scope/query key is required.
|
|
1810
|
+
*
|
|
1811
|
+
* `findMessage`/`findMessageOrThrow` and `createNotification` are trusted
|
|
1812
|
+
* host-level operations and intentionally do not take a principal; they are
|
|
1813
|
+
* not part of this contract.
|
|
1814
|
+
*/
|
|
1815
|
+
declare function isValidMessageUserId(id: unknown): id is string | mongoose.Types.ObjectId;
|
|
1816
|
+
/**
|
|
1817
|
+
* Assert a user-facing principal and return its normalized string identity.
|
|
1818
|
+
* Throws `InvalidMessageUserError` for missing/null/empty/numeric/array/
|
|
1819
|
+
* plain-object ids. String ids are trimmed; ObjectId ids become their hex
|
|
1820
|
+
* string. The caller's original `MessageUser` object (and its `_id` type) is
|
|
1821
|
+
* left untouched so ObjectId-backed schemas keep their native type.
|
|
1822
|
+
*/
|
|
1823
|
+
declare function requireMessageUserId(user: unknown): string;
|
|
1507
1824
|
/**
|
|
1508
1825
|
* Core message service for creating messages, getting actions, and handling actions.
|
|
1509
1826
|
*
|
|
@@ -1518,6 +1835,7 @@ declare class MessageService {
|
|
|
1518
1835
|
private expirePaymentSession?;
|
|
1519
1836
|
private refundPaymentSession?;
|
|
1520
1837
|
private onPaymentCompensationFailure?;
|
|
1838
|
+
private onTransactionCleanupFailure?;
|
|
1521
1839
|
private adminRoles;
|
|
1522
1840
|
private registry;
|
|
1523
1841
|
private defaultListLimit;
|
|
@@ -1526,20 +1844,51 @@ declare class MessageService {
|
|
|
1526
1844
|
private clientRequestWaitMs;
|
|
1527
1845
|
private clientRequestPollMs;
|
|
1528
1846
|
private clientRequestDelay;
|
|
1847
|
+
private clientRequestNow;
|
|
1529
1848
|
private actionLeaseMs;
|
|
1530
1849
|
constructor(options: MessageServiceOptions);
|
|
1531
1850
|
/**
|
|
1532
1851
|
* Find a message by id, falling back to the archive. Returns null if
|
|
1533
1852
|
* the message does not exist in either collection.
|
|
1853
|
+
*
|
|
1854
|
+
* Trusted host-level operation: takes no principal and performs no
|
|
1855
|
+
* user-identity validation. Hosts must authorize the returned document
|
|
1856
|
+
* before exposing it to a user (see `getActions`/`handleAction`).
|
|
1534
1857
|
*/
|
|
1535
1858
|
findMessage(messageId: string, options?: {
|
|
1536
1859
|
populate?: string | string[] | mongoose.PopulateOptions | mongoose.PopulateOptions[];
|
|
1537
1860
|
select?: string | Record<string, 0 | 1 | boolean>;
|
|
1538
1861
|
}): Promise<IMessage | IMessageArchive | null>;
|
|
1862
|
+
/**
|
|
1863
|
+
* Same as `findMessage`, but throws `MessageNotFoundError` when neither the
|
|
1864
|
+
* active nor the archive collection holds the id.
|
|
1865
|
+
*
|
|
1866
|
+
* Trusted host-level operation: no principal validation. Hosts must
|
|
1867
|
+
* authorize before exposing the result to a user.
|
|
1868
|
+
*/
|
|
1539
1869
|
findMessageOrThrow(messageId: string, options?: {
|
|
1540
1870
|
populate?: string | string[] | mongoose.PopulateOptions | mongoose.PopulateOptions[];
|
|
1541
1871
|
select?: string | Record<string, 0 | 1 | boolean>;
|
|
1542
1872
|
}): Promise<IMessage | IMessageArchive>;
|
|
1873
|
+
/**
|
|
1874
|
+
* Create messages from a trusted template as an authenticated user.
|
|
1875
|
+
*
|
|
1876
|
+
* User-facing entry point: `user` (and `payerUser` when provided) must
|
|
1877
|
+
* carry a valid principal id (non-empty string or ObjectId; numbers,
|
|
1878
|
+
* arrays, and plain objects are rejected with `InvalidMessageUserError`)
|
|
1879
|
+
* before any template, provider, or model effect runs. Both the
|
|
1880
|
+
* idempotent (`clientRequestId`) and direct branches enforce this.
|
|
1881
|
+
*
|
|
1882
|
+
* Idempotent replay contract (MSGF-06): a completed same-scope replay
|
|
1883
|
+
* returns the current active/archive records merged from both collections
|
|
1884
|
+
* (`Array<IMessage | IMessageArchive>`), sorted by exact
|
|
1885
|
+
* `clientRequestItemIndex`, without rerunning preparation/payment. Fresh
|
|
1886
|
+
* creates resolve active documents only. Archive entries are returned as
|
|
1887
|
+
* persisted `IMessageArchive` — never cast to `IMessage` and never given
|
|
1888
|
+
* fabricated active-only methods. Narrow with `'archivedAt' in doc`.
|
|
1889
|
+
* Archive retention bounds the replay window: deleted archive documents
|
|
1890
|
+
* surface as `ClientRequestInconsistentStateError`, not partial replays.
|
|
1891
|
+
*/
|
|
1543
1892
|
createMessage(params: {
|
|
1544
1893
|
templateCd: string;
|
|
1545
1894
|
user: MessageUser;
|
|
@@ -1555,9 +1904,16 @@ declare class MessageService {
|
|
|
1555
1904
|
* characters. Replays are scoped to the requester identity and template.
|
|
1556
1905
|
*/
|
|
1557
1906
|
clientRequestId?: unknown;
|
|
1558
|
-
}): Promise<IMessage
|
|
1907
|
+
}): Promise<Array<IMessage | IMessageArchive>>;
|
|
1559
1908
|
private createMessageWithReservation;
|
|
1560
1909
|
private createPreparedMessageBatch;
|
|
1910
|
+
/**
|
|
1911
|
+
* Create a generic notification without template preparation or actions.
|
|
1912
|
+
*
|
|
1913
|
+
* Trusted host-level operation: takes raw `UserId` values (string or
|
|
1914
|
+
* ObjectId, including null) and performs no principal validation. Hosts
|
|
1915
|
+
* own authentication/authorization for notification creation.
|
|
1916
|
+
*/
|
|
1561
1917
|
createNotification(params: {
|
|
1562
1918
|
fromUser?: UserId | null;
|
|
1563
1919
|
toUser?: UserId | null;
|
|
@@ -1578,12 +1934,17 @@ declare class MessageService {
|
|
|
1578
1934
|
* Build a Mongoose filter for messages visible to the given user.
|
|
1579
1935
|
* Exposed so callers can use the same visibility rules for custom
|
|
1580
1936
|
* queries (e.g. with `populate`).
|
|
1937
|
+
*
|
|
1938
|
+
* User-facing helper: validates `user` before building the filter.
|
|
1581
1939
|
*/
|
|
1582
1940
|
buildVisibilityFilter(user: MessageUser): Record<string, unknown>;
|
|
1583
1941
|
/**
|
|
1584
1942
|
* List active (non-archived) messages visible to a user.
|
|
1585
1943
|
* Returns messages where the user is the sender, the receiver,
|
|
1586
1944
|
* or matches one of the recipient's roles.
|
|
1945
|
+
*
|
|
1946
|
+
* User-facing operation: validates `user` via the shared principal
|
|
1947
|
+
* contract. Returns `IMessage[]` ordered by `{ createdAt: -1, _id: -1 }`.
|
|
1587
1948
|
*/
|
|
1588
1949
|
listMessages(params: {
|
|
1589
1950
|
user: MessageUser;
|
|
@@ -1594,18 +1955,57 @@ declare class MessageService {
|
|
|
1594
1955
|
/**
|
|
1595
1956
|
* Count active (non-archived) messages visible to a user.
|
|
1596
1957
|
* Useful for badge indicators ("3 new messages").
|
|
1958
|
+
*
|
|
1959
|
+
* User-facing operation: validates `user` via the shared principal contract.
|
|
1597
1960
|
*/
|
|
1598
1961
|
countMessages(user: MessageUser): Promise<number>;
|
|
1599
|
-
|
|
1962
|
+
/**
|
|
1963
|
+
* Get available actions for a message as an authenticated user.
|
|
1964
|
+
*
|
|
1965
|
+
* User-facing operation: `options.user` is required and must carry a valid
|
|
1966
|
+
* principal id (non-empty string or ObjectId). It is validated before any
|
|
1967
|
+
* model lookup or template operation; invalid principals throw
|
|
1968
|
+
* `InvalidMessageUserError`.
|
|
1969
|
+
*
|
|
1970
|
+
* Returns `{ uiTemplate, actions }` for visible messages, or `null` when the
|
|
1971
|
+
* message is missing, the user has no sender/receiver relationship (unless
|
|
1972
|
+
* `isAdmin`), or the template is unknown. Admin (`isAdmin`) and archived
|
|
1973
|
+
* views return the resolved `uiTemplate` with an empty action list without
|
|
1974
|
+
* evaluating action `condition` predicates. All listings
|
|
1975
|
+
* read the requested id from the configured store. Relationships are
|
|
1976
|
+
* checked on canonical stored IDs before optional presentation population.
|
|
1977
|
+
* Conditions see that read snapshot with `populate` applied (mutations still
|
|
1978
|
+
* check unpopulated atomic claims); this does not reserve an action or prevent
|
|
1979
|
+
* subsequent host writes. Action labels and
|
|
1980
|
+
* confirmations render from persisted `message.payload` (missing values
|
|
1981
|
+
* render empty), distinct from creation-time `templateData`.
|
|
1982
|
+
*/
|
|
1983
|
+
getActions(messageId: string, usertype: Usertype, options: {
|
|
1984
|
+
/** Authenticated caller; required. Validated before any effect. */
|
|
1985
|
+
user: MessageUser;
|
|
1600
1986
|
permissions?: Record<string, boolean>;
|
|
1987
|
+
/** @deprecated Ignored. The requested id is always re-read from storage; omit this option. */
|
|
1601
1988
|
message?: IMessage | IMessageArchive;
|
|
1602
|
-
user?: MessageUser;
|
|
1603
1989
|
isAdmin?: boolean;
|
|
1990
|
+
/** Presentation population for eligible active conditions, after relationship authorization. */
|
|
1604
1991
|
populate?: string | string[] | mongoose.PopulateOptions | mongoose.PopulateOptions[];
|
|
1605
1992
|
}): Promise<{
|
|
1606
1993
|
uiTemplate: UiTemplate;
|
|
1607
1994
|
actions: InterpolatedAction[];
|
|
1608
1995
|
} | null>;
|
|
1996
|
+
/**
|
|
1997
|
+
* Execute an action with a durable claim, stable handler attempt key,
|
|
1998
|
+
* transactional archive, and post-commit sender notification status.
|
|
1999
|
+
*
|
|
2000
|
+
* User-facing operation: `data.user` is validated before any template or
|
|
2001
|
+
* model effect. Authorization and handler selection bind to the persisted
|
|
2002
|
+
* claim, not the caller-supplied `data.message` copy. Archived messages
|
|
2003
|
+
* are re-read by id on their owning connection and apply a stored
|
|
2004
|
+
* sender/receiver relationship gate before disclosing current attempt IDs
|
|
2005
|
+
* or notification state. A missing archive throws `MessageNotFoundError`.
|
|
2006
|
+
* Returns the handler's value (`unknown`; templates
|
|
2007
|
+
* document their own shape).
|
|
2008
|
+
*/
|
|
1609
2009
|
handleAction(templateCd: string, actionCd: string, data: {
|
|
1610
2010
|
message: IMessage | IMessageArchive;
|
|
1611
2011
|
user: MessageUser;
|
|
@@ -1624,17 +2024,61 @@ declare class MessageService {
|
|
|
1624
2024
|
private compensatePaymentSession;
|
|
1625
2025
|
private buildActionContext;
|
|
1626
2026
|
private claimAction;
|
|
2027
|
+
/**
|
|
2028
|
+
* Relationship/permission policy for archived outcomes.
|
|
2029
|
+
*
|
|
2030
|
+
* Only the recorded sender or receiver may observe whether an archived
|
|
2031
|
+
* message is pending notification or terminally archived. This keeps
|
|
2032
|
+
* authorized retries (including pending-notification retries) available even
|
|
2033
|
+
* when the template has been unregistered, while unrelated callers receive a
|
|
2034
|
+
* stable `ActionNotAllowedError` without attempt IDs or notification state.
|
|
2035
|
+
*/
|
|
2036
|
+
private authorizeArchivedOutcome;
|
|
2037
|
+
/** Call only with an authoritative, unpopulated archive read. */
|
|
2038
|
+
private throwArchivedOutcome;
|
|
2039
|
+
/**
|
|
2040
|
+
* Authoritative post-claim authorization bound to the persisted document
|
|
2041
|
+
* returned by the atomic claim. Re-validates the requested template/action
|
|
2042
|
+
* selection and the sender/receiver, permission, and condition checks
|
|
2043
|
+
* against persisted `templateCd`, parties, roles, and payload data.
|
|
2044
|
+
*
|
|
2045
|
+
* Supported update protocol: the guarantee covers the document state as of
|
|
2046
|
+
* the atomic claim (`findOneAndUpdate` returning the persisted document).
|
|
2047
|
+
* Host writes that commit before the claim are observed; host writes that
|
|
2048
|
+
* commit after the claim (during handler execution) are not prevented. Hosts
|
|
2049
|
+
* must not mutate action-relevant fields (`templateCd`, `toUser`/`toRoles`/
|
|
2050
|
+
* `fromUser`, condition payload) concurrently with in-flight actions outside
|
|
2051
|
+
* a coordinated protocol. A fresh-document read alone is not a substitute
|
|
2052
|
+
* for this claim-bound check.
|
|
2053
|
+
*/
|
|
2054
|
+
private authorizeClaimedAction;
|
|
2055
|
+
private releaseDeniedActionClaim;
|
|
1627
2056
|
private markActionRetryable;
|
|
1628
2057
|
private archiveClaimedMessage;
|
|
1629
2058
|
private markActionNotificationState;
|
|
1630
2059
|
private runSenderNotification;
|
|
1631
2060
|
private normalizeClientRequestId;
|
|
1632
2061
|
private validatePositiveIntegerOption;
|
|
2062
|
+
private validateDurationOption;
|
|
1633
2063
|
private normalizeListLimit;
|
|
1634
2064
|
private normalizeListSkip;
|
|
1635
2065
|
private buildClientRequestScope;
|
|
1636
2066
|
private requireUserId;
|
|
1637
2067
|
private findByClientRequestScope;
|
|
2068
|
+
private findArchivedByClientRequestScope;
|
|
2069
|
+
/**
|
|
2070
|
+
* Merge one active snapshot and one archive snapshot for a completed scope.
|
|
2071
|
+
*
|
|
2072
|
+
* Archival moves a document with create-then-delete ordering, so with an
|
|
2073
|
+
* active-then-archive read order a moving item is always visible in at least
|
|
2074
|
+
* one snapshot (no miss). A commit landing between the two reads surfaces
|
|
2075
|
+
* the same `_id` in both snapshots; deduplicating by `_id` string keeps the
|
|
2076
|
+
* active copy and prevents double-counting. Returns the merged batch sorted
|
|
2077
|
+
* by exact `clientRequestItemIndex`, or `null` when the merged set does not
|
|
2078
|
+
* satisfy the exact distinct-index contract.
|
|
2079
|
+
*/
|
|
2080
|
+
private mergeReplayBatch;
|
|
2081
|
+
private buildCompletedReplay;
|
|
1638
2082
|
private findCompletedClientRequestReplay;
|
|
1639
2083
|
private acquireClientRequestStart;
|
|
1640
2084
|
private tryAcquireClientRequestLease;
|
|
@@ -1642,57 +2086,198 @@ declare class MessageService {
|
|
|
1642
2086
|
private tryTakeOverStaleClientRequestReservation;
|
|
1643
2087
|
private findClientRequestReservation;
|
|
1644
2088
|
private failClientRequestReservation;
|
|
1645
|
-
private isDuplicateKeyError;
|
|
1646
|
-
private isTransactionSupportError;
|
|
1647
2089
|
private delay;
|
|
1648
2090
|
private isArchivedMessage;
|
|
1649
2091
|
private applyPopulate;
|
|
1650
2092
|
private findByIdWithOptions;
|
|
1651
2093
|
}
|
|
1652
2094
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
2095
|
+
/** A populated party exposes only its identity and optional presentation fields. */
|
|
2096
|
+
interface PublicMessageParty {
|
|
2097
|
+
_id: string;
|
|
2098
|
+
displayName?: string;
|
|
2099
|
+
email?: string;
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Allowlisted HTTP message representation, shared by create/replay and host lists.
|
|
2103
|
+
* IDs are strings and dates are ISO strings. Archive-only fields are absent on
|
|
2104
|
+
* active messages. Payload/display remain host-controlled business data.
|
|
2105
|
+
*/
|
|
2106
|
+
interface PublicMessageDto {
|
|
2107
|
+
_id: string;
|
|
2108
|
+
templateCd: string;
|
|
2109
|
+
type: MessageType;
|
|
2110
|
+
fromUser: string | PublicMessageParty | null;
|
|
2111
|
+
toUser: string | PublicMessageParty | null;
|
|
2112
|
+
toRoles: string[];
|
|
2113
|
+
senderContent: IMessageContent;
|
|
2114
|
+
receiverContent: IMessageContent;
|
|
2115
|
+
documents: string[];
|
|
2116
|
+
/** Business payment session reference, intentionally available to clients. */
|
|
2117
|
+
paymentSession: string | null;
|
|
2118
|
+
paymentCd: string;
|
|
2119
|
+
payload: Record<string, unknown>;
|
|
2120
|
+
display: Record<string, unknown>;
|
|
2121
|
+
actionState: ActionLifecycleState;
|
|
2122
|
+
actionCd: string | null;
|
|
2123
|
+
/** Stable business deduplication ID, not the internal worker ownership token. */
|
|
2124
|
+
actionAttemptId: string | null;
|
|
2125
|
+
createdAt: string;
|
|
2126
|
+
updatedAt: string;
|
|
2127
|
+
archivedBy?: string | null;
|
|
2128
|
+
archivedAt?: string;
|
|
2129
|
+
actionNotificationState?: ActionNotificationState;
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Structural input for hydrated or plain stored messages, including populated
|
|
2133
|
+
* references. Unknown reference values are inspected only for IDs/presentation;
|
|
2134
|
+
* missing or unsupported parties become null, never arbitrary serialized objects.
|
|
2135
|
+
*/
|
|
2136
|
+
type PublicMessageSource = Pick<IBaseMessage, 'templateCd' | 'type' | 'toRoles' | 'senderContent' | 'receiverContent' | 'paymentSession' | 'paymentCd' | 'payload' | 'display' | 'actionState' | 'actionCd' | 'actionAttemptId' | 'createdAt' | 'updatedAt'> & {
|
|
2137
|
+
_id: unknown;
|
|
2138
|
+
fromUser: unknown;
|
|
2139
|
+
toUser: unknown;
|
|
2140
|
+
documents: readonly unknown[];
|
|
2141
|
+
archivedBy?: unknown;
|
|
2142
|
+
archivedAt?: Date;
|
|
2143
|
+
actionNotificationState?: ActionNotificationState;
|
|
2144
|
+
};
|
|
2145
|
+
/**
|
|
2146
|
+
* Serialize an already-authorized message without mutating its hydrated record.
|
|
2147
|
+
* Explicitly excludes diagnostics, owner tokens, claim/lease/request bookkeeping,
|
|
2148
|
+
* schema extensions and arbitrary populated-user fields. Does not authorize or
|
|
2149
|
+
* sanitize host payload/display/content or action handler results. Only populate
|
|
2150
|
+
* email/displayName when they are appropriate for the audience.
|
|
2151
|
+
*
|
|
2152
|
+
* @example
|
|
2153
|
+
* const data = (await service.listMessages({ user })).map(serializePublicMessage);
|
|
2154
|
+
*/
|
|
2155
|
+
declare function serializePublicMessage(message: PublicMessageSource): PublicMessageDto;
|
|
2156
|
+
|
|
2157
|
+
/**
|
|
2158
|
+
* Route-only behavior options shared by both composition paths.
|
|
2159
|
+
*
|
|
2160
|
+
* These never configure the underlying `MessageService`: auth middleware,
|
|
2161
|
+
* request extractors, and the admin read-only permission key. They apply
|
|
2162
|
+
* identically whether routes construct a service or reuse an injected one.
|
|
2163
|
+
*/
|
|
2164
|
+
interface MessageRoutesBehaviorOptions {
|
|
1668
2165
|
/** Custom auth middleware applied to all routes */
|
|
1669
2166
|
authMiddleware?: ((req: Request, res: Response, next: NextFunction) => void)[];
|
|
1670
2167
|
/**
|
|
1671
2168
|
* Extract user from request (default: req._user || req.user). All routes
|
|
1672
|
-
* require this extractor to return a user with a
|
|
1673
|
-
*
|
|
2169
|
+
* require this extractor to return a user with a valid `_id` (non-empty
|
|
2170
|
+
* string or ObjectId; numbers, arrays, and plain objects are rejected)
|
|
2171
|
+
* before any service, template, payment, model, or action side effect runs.
|
|
2172
|
+
* Shares the service's principal-validation contract.
|
|
1674
2173
|
*/
|
|
1675
2174
|
getUser?: (req: Request) => MessageUser | undefined;
|
|
1676
|
-
/**
|
|
2175
|
+
/**
|
|
2176
|
+
* Extract permissions from request (default: req._permissions || {}).
|
|
2177
|
+
* Permission grants throughout the package (UI filtering, action
|
|
2178
|
+
* execution, admin read-only) require an own property strictly equal to
|
|
2179
|
+
* `true`; custom extractors must return plain own-boolean maps. Inherited
|
|
2180
|
+
* properties and truthy non-booleans are denied.
|
|
2181
|
+
*/
|
|
1677
2182
|
getPermissions?: (req: Request) => Record<string, boolean>;
|
|
1678
2183
|
/** Extract identity from request (default: req._identity || {}) */
|
|
1679
2184
|
getIdentity?: (req: Request) => Record<string, unknown>;
|
|
1680
2185
|
/**
|
|
1681
|
-
* Permission key that, when
|
|
1682
|
-
* action list (read-only view). Defaults to
|
|
2186
|
+
* Permission key that, when granted as an own boolean `true`, makes
|
|
2187
|
+
* `getActions` return an empty action list (read-only view). Defaults to
|
|
2188
|
+
* `'is.admin'`. Uses the same explicit-grant predicate as UI filtering and
|
|
2189
|
+
* action execution, so inheritance (e.g. `constructor`) or truthy
|
|
2190
|
+
* non-booleans cannot enable admin read-only mode.
|
|
1683
2191
|
*/
|
|
1684
2192
|
adminPermissionKey?: string;
|
|
1685
2193
|
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Convenience-construction path: routes build and own a `MessageService`
|
|
2196
|
+
* from the full service option set. `service` must be absent; every other
|
|
2197
|
+
* service knob (`getModel`, `connection`, `modelNames`, payment, `registry`,
|
|
2198
|
+
* list limits, and `clientRequest*` timing/test hooks) is forwarded verbatim
|
|
2199
|
+
* to `new MessageService(...)` so the route service never drifts behind the
|
|
2200
|
+
* direct-service configuration surface.
|
|
2201
|
+
*/
|
|
2202
|
+
interface MessageRoutesConstructionOptions extends MessageRoutesBehaviorOptions, MessageServiceOptions {
|
|
2203
|
+
/**
|
|
2204
|
+
* Must be absent on this path. Supplying `service` together with any
|
|
2205
|
+
* service construction option is a conflict (see
|
|
2206
|
+
* `MessageRoutesInjectionOptions`); the factory throws
|
|
2207
|
+
* `InvalidMessageServiceOptionError` instead of silently ignoring options.
|
|
2208
|
+
*/
|
|
2209
|
+
service?: undefined;
|
|
2210
|
+
/** Mongoose model getter (required on this path for backwards compatibility). */
|
|
2211
|
+
getModel: NonNullable<MessageServiceOptions['getModel']>;
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Service-injection path: routes reuse the exact supplied configured
|
|
2215
|
+
* service (custom `modelNames`, `connection`, timing policies, providers,
|
|
2216
|
+
* and registry included). No second service or global registry is created.
|
|
2217
|
+
* Every service construction option is typed `never` here and rejected at
|
|
2218
|
+
* runtime; route-only behavior options remain allowed.
|
|
2219
|
+
*/
|
|
2220
|
+
interface MessageRoutesInjectionOptions extends MessageRoutesBehaviorOptions {
|
|
2221
|
+
/** Existing configured service reused verbatim (`returned.service === supplied`). */
|
|
2222
|
+
service: MessageService;
|
|
2223
|
+
getModel?: never;
|
|
2224
|
+
connection?: never;
|
|
2225
|
+
modelNames?: never;
|
|
2226
|
+
paymentProvider?: never;
|
|
2227
|
+
onPaymentCompensationFailure?: never;
|
|
2228
|
+
onTransactionCleanupFailure?: never;
|
|
2229
|
+
adminRoles?: never;
|
|
2230
|
+
registry?: never;
|
|
2231
|
+
defaultListLimit?: never;
|
|
2232
|
+
maxListLimit?: never;
|
|
2233
|
+
clientRequestLeaseMs?: never;
|
|
2234
|
+
clientRequestWaitMs?: never;
|
|
2235
|
+
clientRequestPollMs?: never;
|
|
2236
|
+
clientRequestDelay?: never;
|
|
2237
|
+
clientRequestNow?: never;
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Supported route composition options: either construct a service from
|
|
2241
|
+
* service options or reuse an existing service — never both.
|
|
2242
|
+
*
|
|
2243
|
+
* - Construction: `createMessageRoutes({ getModel, registry, ... })`.
|
|
2244
|
+
* - Injection: `createMessageRoutes({ service, getUser, ... })`.
|
|
2245
|
+
*
|
|
2246
|
+
* Combining `service` with any service construction option throws
|
|
2247
|
+
* `InvalidMessageServiceOptionError` at runtime (and fails strict type
|
|
2248
|
+
* checking via the `never` fields on the injection branch) rather than
|
|
2249
|
+
* silently ignoring the conflicting options.
|
|
2250
|
+
*/
|
|
2251
|
+
type MessageRoutesOptions = MessageRoutesConstructionOptions | MessageRoutesInjectionOptions;
|
|
1686
2252
|
/**
|
|
1687
2253
|
* Create a JsonRouter with the message template routes.
|
|
1688
2254
|
* Mount via `router.original` and apply your own auth/permission middleware.
|
|
1689
2255
|
*
|
|
2256
|
+
* Composition (MSGF-11):
|
|
2257
|
+
* - Convenience construction: `createMessageRoutes({ getModel, registry,
|
|
2258
|
+
* clientRequestWaitMs, ... })` builds and owns a `MessageService` from the
|
|
2259
|
+
* full service option set (no subsets).
|
|
2260
|
+
* - Service injection: `createMessageRoutes({ service })` reuses the exact
|
|
2261
|
+
* supplied configured service — custom `modelNames`, `connection`, timing
|
|
2262
|
+
* policies, providers, and registry included — without creating a second
|
|
2263
|
+
* service or touching the global registry. Route-only behavior options
|
|
2264
|
+
* (`authMiddleware`, `getUser`, `getPermissions`, `getIdentity`,
|
|
2265
|
+
* `adminPermissionKey`) still apply on both paths.
|
|
2266
|
+
*
|
|
2267
|
+
* Combining `service` with any service construction option throws
|
|
2268
|
+
* `InvalidMessageServiceOptionError` instead of silently ignoring conflicts.
|
|
2269
|
+
* No per-method service callbacks are accepted; routes call the service API
|
|
2270
|
+
* directly so authentication/validation/error mapping stay identical on both
|
|
2271
|
+
* paths.
|
|
2272
|
+
*
|
|
1690
2273
|
* Routes:
|
|
1691
|
-
* POST /new/:templateCd — create
|
|
2274
|
+
* POST /new/:templateCd — create/replay as PublicMessageDto[]
|
|
1692
2275
|
* GET /:id/actions/:usertype — get available actions for a message
|
|
1693
2276
|
* POST /:id/action/:actionCd — execute an action (POST)
|
|
1694
2277
|
*
|
|
1695
|
-
* All routes require a resolved user with a
|
|
2278
|
+
* All routes require a resolved user with a valid `_id` (non-empty string
|
|
2279
|
+
* or ObjectId; numbers, arrays, and plain objects are rejected with 401).
|
|
2280
|
+
* Route parameters
|
|
1696
2281
|
* are validated before service/model/template lookup, and mutating actions are
|
|
1697
2282
|
* intentionally POST-only.
|
|
1698
2283
|
*/
|
|
@@ -1701,4 +2286,4 @@ declare function createMessageRoutes(options: MessageRoutesOptions): {
|
|
|
1701
2286
|
service: MessageService;
|
|
1702
2287
|
};
|
|
1703
2288
|
|
|
1704
|
-
export { type ActionConfirmation, ActionConflictError, type ActionContext, type ActionLifecycleState, ActionNotAllowedError, ActionNotFoundError, ActionNotificationPendingError, type ActionNotificationState, ActionRetryableError, ActionTemplateMismatchError, BaseMessageFields, ClientRequestFailedError, ClientRequestInconsistentStateError, ClientRequestPendingError, type EmailDeliveryFailureEvent, type EmailDeliveryFailureStage, type EmailNotifier, type EmailProvider, GENERIC_NOTIFICATION_TEMPLATE_CD, type IBaseMessage, type IMessage, type IMessageArchive, type IMessageContent, type IMessageMethods, type InterpolatedAction, type InterpolatedContent, type InterpolationResult, InvalidClientRequestIdError, InvalidMessageUserError, InvalidPaginationValueError, MESSAGE_ARCHIVE_MODEL_NAME, MESSAGE_MODEL_NAME, MESSAGE_REQUEST_MODEL_NAME, type MessageAction, MessageArchiveSchema, MessageArchivedError, MessageContentSchema, MessageModelResolutionError, MessageNotFoundError, MessageRequestSchema, type MessageRoutesOptions, MessageSchema, type MessageSchemaConfig, MessageService, type MessageServiceModelNames, type MessageServiceOptions, type MessageTemplate, MessageTransactionRequiredError, type MessageType, type MessageUser, NoopEmailProvider, NoopPaymentProvider, type PaymentCompensationFailureEvent, type PaymentProvider, PaymentSessionCompensationError, type PrepareContext, type PrepareResult, type SenderNotificationContent, TemplateNotFoundError, TemplateRegistry, TemplateRegistryValidationError, type UiTemplate, type UserId, type Usertype, buildMessageArchiveSchema, buildMessageRequestSchema, buildMessageSchema, createMessageRoutes, defaultRegistry, filterActions, includesAction, interpolateTemplate, isActionAllowed };
|
|
2289
|
+
export { type ActionConfirmation, ActionConflictError, type ActionContext, type ActionLifecycleState, ActionNotAllowedError, ActionNotFoundError, ActionNotificationPendingError, type ActionNotificationState, ActionRetryableError, ActionTemplateMismatchError, BaseMessageFields, ClientRequestFailedError, ClientRequestInconsistentStateError, ClientRequestPendingError, type EmailDeliveryFailureEvent, type EmailDeliveryFailureStage, type EmailNotifier, type EmailProvider, GENERIC_NOTIFICATION_TEMPLATE_CD, type IBaseMessage, type IMessage, type IMessageArchive, type IMessageContent, type IMessageMethods, type InterpolatedAction, type InterpolatedContent, type InterpolationResult, InvalidClientRequestIdError, InvalidMessageServiceOptionError, InvalidMessageUserError, InvalidPaginationValueError, MAX_MESSAGE_SERVICE_TIMEOUT_MS, MESSAGE_ARCHIVE_MODEL_NAME, MESSAGE_MODEL_NAME, MESSAGE_REQUEST_MODEL_NAME, type MessageAction, MessageArchiveSchema, MessageArchivedError, MessageContentSchema, MessageModelResolutionError, MessageNotFoundError, MessageRequestSchema, type MessageRoutesBehaviorOptions, type MessageRoutesConstructionOptions, type MessageRoutesInjectionOptions, type MessageRoutesOptions, MessageSchema, type MessageSchemaConfig, MessageService, type MessageServiceModelNames, type MessageServiceOptions, type MessageTemplate, type MessageTransactionCleanupFailureEvent, type MessageTransactionCleanupFailureObserver, MessageTransactionRequiredError, type MessageType, type MessageUser, NoopEmailProvider, NoopPaymentProvider, type PaymentCompensationFailureEvent, type PaymentProvider, PaymentSessionCompensationAggregateError, PaymentSessionCompensationError, type PaymentSessionCompensationFailure, type PrepareContext, type PrepareResult, type PublicMessageDto, type PublicMessageParty, type PublicMessageSource, type RegisteredMessageAction, type RegisteredMessageTemplate, type RegisteredUiTemplate, type SenderNotificationContent, TemplateNotFoundError, TemplateRegistry, TemplateRegistryValidationError, type UiTemplate, type UserId, type Usertype, buildMessageArchiveSchema, buildMessageRequestSchema, buildMessageSchema, createMessageRoutes, defaultRegistry, filterActions, hasExplicitPermissionGrant, includesAction, interpolateMessageContent, interpolateTemplate, isActionAllowed, isValidMessageUserId, requireMessageUserId, resolveUiTemplate, serializePublicMessage };
|