@web-ts-toolkit/message-service 0.6.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/LICENSE +201 -0
- package/README.md +198 -0
- package/index.d.mts +607 -0
- package/index.d.ts +607 -0
- package/index.js +802 -0
- package/index.mjs +742 -0
- package/package.json +51 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
import JsonRouter from '@web-ts-toolkit/express-json-router';
|
|
3
|
+
import { Request, Response, NextFunction } from 'express';
|
|
4
|
+
|
|
5
|
+
interface PrepareContext {
|
|
6
|
+
user: MessageUser;
|
|
7
|
+
roles: string[];
|
|
8
|
+
identity: Record<string, unknown>;
|
|
9
|
+
permissions: Record<string, boolean>;
|
|
10
|
+
payload: Record<string, unknown>;
|
|
11
|
+
getModel: (name: string) => mongoose.Model<unknown>;
|
|
12
|
+
req?: unknown;
|
|
13
|
+
}
|
|
14
|
+
interface PrepareResult {
|
|
15
|
+
type?: string;
|
|
16
|
+
templateData?: Record<string, unknown>;
|
|
17
|
+
fromUser?: UserId;
|
|
18
|
+
toUser?: UserId;
|
|
19
|
+
toRoles?: string[];
|
|
20
|
+
payload?: Record<string, unknown>;
|
|
21
|
+
display?: Record<string, unknown>;
|
|
22
|
+
paymentCd?: string;
|
|
23
|
+
priceArgs?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
interface ActionContext {
|
|
26
|
+
message: IMessage | IMessageArchive;
|
|
27
|
+
user: MessageUser;
|
|
28
|
+
getModel: (name: string) => mongoose.Model<unknown>;
|
|
29
|
+
expireSession?: (sessionId: string) => Promise<void>;
|
|
30
|
+
refundPayment?: (sessionId: string) => Promise<void>;
|
|
31
|
+
req?: unknown;
|
|
32
|
+
}
|
|
33
|
+
interface SenderNotificationContent {
|
|
34
|
+
title?: string;
|
|
35
|
+
long: string;
|
|
36
|
+
short?: string;
|
|
37
|
+
documents?: mongoose.Types.ObjectId[];
|
|
38
|
+
}
|
|
39
|
+
interface ActionConfirmation {
|
|
40
|
+
title: string;
|
|
41
|
+
message: string;
|
|
42
|
+
notesLabel?: string;
|
|
43
|
+
requireNotes?: boolean;
|
|
44
|
+
documents?: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface MessageAction {
|
|
47
|
+
actionCd: string;
|
|
48
|
+
name: string;
|
|
49
|
+
variant: string;
|
|
50
|
+
isDefault?: boolean;
|
|
51
|
+
sender: boolean;
|
|
52
|
+
receiver: boolean;
|
|
53
|
+
permission?: string;
|
|
54
|
+
condition?: (message: IMessage) => boolean;
|
|
55
|
+
confirmation?: ActionConfirmation;
|
|
56
|
+
payload?: Record<string, unknown>;
|
|
57
|
+
senderNotification?: string | ((ctx: ActionContext) => Promise<string | SenderNotificationContent>);
|
|
58
|
+
runHandler: (ctx: ActionContext) => Promise<unknown>;
|
|
59
|
+
}
|
|
60
|
+
type UiTemplate = string | {
|
|
61
|
+
sender?: string;
|
|
62
|
+
receiver?: string;
|
|
63
|
+
};
|
|
64
|
+
interface MessageTemplate {
|
|
65
|
+
templateCd: string;
|
|
66
|
+
type: string;
|
|
67
|
+
description: string;
|
|
68
|
+
senderContent: {
|
|
69
|
+
title: string;
|
|
70
|
+
long: string;
|
|
71
|
+
short: string;
|
|
72
|
+
};
|
|
73
|
+
receiverContent: {
|
|
74
|
+
title: string;
|
|
75
|
+
long: string;
|
|
76
|
+
short: string;
|
|
77
|
+
};
|
|
78
|
+
uiTemplate: UiTemplate;
|
|
79
|
+
paymentCd?: string;
|
|
80
|
+
prepareMessage: (ctx: PrepareContext) => Promise<PrepareResult | PrepareResult[] | null>;
|
|
81
|
+
actions: MessageAction[];
|
|
82
|
+
}
|
|
83
|
+
interface InterpolatedContent {
|
|
84
|
+
title: string;
|
|
85
|
+
long: string;
|
|
86
|
+
short: string;
|
|
87
|
+
}
|
|
88
|
+
interface InterpolatedAction {
|
|
89
|
+
actionCd: string;
|
|
90
|
+
name: string;
|
|
91
|
+
variant: string;
|
|
92
|
+
/**
|
|
93
|
+
* When multiple actions are available, the UI may auto-submit or pre-select
|
|
94
|
+
* the one with `isDefault: true`. Templates should set this on at most one
|
|
95
|
+
* action per usertype.
|
|
96
|
+
*/
|
|
97
|
+
isDefault?: boolean;
|
|
98
|
+
confirmation?: ActionConfirmation;
|
|
99
|
+
payload?: Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
interface InterpolationResult {
|
|
102
|
+
senderContent: InterpolatedContent;
|
|
103
|
+
receiverContent: InterpolatedContent;
|
|
104
|
+
uiTemplate: UiTemplate;
|
|
105
|
+
actions: InterpolatedAction[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The role a user plays with respect to a message.
|
|
109
|
+
* `'sender'` is the user who created the message; `'receiver'` is the
|
|
110
|
+
* intended recipient (a direct user or a member of one of the `toRoles`).
|
|
111
|
+
*/
|
|
112
|
+
type Usertype = 'sender' | 'receiver';
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* In-memory registry for message templates.
|
|
116
|
+
* Templates are looked up by their `templateCd`.
|
|
117
|
+
*
|
|
118
|
+
* Recommended: create one `TemplateRegistry` per app and pass it to
|
|
119
|
+
* `MessageService({ registry })`. Use the `defaultRegistry` only for
|
|
120
|
+
* simple cases or quick experiments.
|
|
121
|
+
*/
|
|
122
|
+
declare class TemplateRegistry {
|
|
123
|
+
private templates;
|
|
124
|
+
/**
|
|
125
|
+
* Register a template. Overwrites if templateCd already exists.
|
|
126
|
+
*/
|
|
127
|
+
register(template: MessageTemplate): void;
|
|
128
|
+
/**
|
|
129
|
+
* Register multiple templates at once.
|
|
130
|
+
*/
|
|
131
|
+
registerAll(templates: MessageTemplate[]): void;
|
|
132
|
+
/**
|
|
133
|
+
* Find a template by its templateCd.
|
|
134
|
+
*/
|
|
135
|
+
find(templateCd: string): MessageTemplate | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* Check if a template exists.
|
|
138
|
+
*/
|
|
139
|
+
has(templateCd: string): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Get all registered template codes.
|
|
142
|
+
*/
|
|
143
|
+
getTemplateCodes(): string[];
|
|
144
|
+
/**
|
|
145
|
+
* Get all registered templates.
|
|
146
|
+
*/
|
|
147
|
+
getAll(): MessageTemplate[];
|
|
148
|
+
/**
|
|
149
|
+
* Remove a template by templateCd.
|
|
150
|
+
*/
|
|
151
|
+
unregister(templateCd: string): boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Clear all templates.
|
|
154
|
+
*/
|
|
155
|
+
clear(): void;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Default global registry instance.
|
|
159
|
+
*/
|
|
160
|
+
declare const defaultRegistry: TemplateRegistry;
|
|
161
|
+
/**
|
|
162
|
+
* Check if a given actionCd exists in a registered template.
|
|
163
|
+
* Used by the Message model's archive() method.
|
|
164
|
+
*/
|
|
165
|
+
declare function includesAction(templateCd: string, actionCd: string, registry: TemplateRegistry): boolean;
|
|
166
|
+
|
|
167
|
+
interface IMessageContent {
|
|
168
|
+
title: string;
|
|
169
|
+
long: string;
|
|
170
|
+
short: string;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The high-level category of a message. Common values are 'notification',
|
|
174
|
+
* 'request', 'reminder', 'approval'. Templates can use any string; the
|
|
175
|
+
* `(string & {})` trick preserves the literal types in autocomplete while
|
|
176
|
+
* still allowing arbitrary strings.
|
|
177
|
+
*/
|
|
178
|
+
type MessageType = 'notification' | 'request' | 'reminder' | 'approval' | (string & {});
|
|
179
|
+
type UserId = mongoose.Types.ObjectId | string;
|
|
180
|
+
/**
|
|
181
|
+
* Minimal user shape accepted by MessageService methods.
|
|
182
|
+
* `displayName` is optional but templates that interpolate `{{displayName}}`
|
|
183
|
+
* will fall back to an empty string if it is absent.
|
|
184
|
+
*/
|
|
185
|
+
interface MessageUser {
|
|
186
|
+
_id: UserId;
|
|
187
|
+
id?: string;
|
|
188
|
+
roles?: string[];
|
|
189
|
+
displayName?: string;
|
|
190
|
+
}
|
|
191
|
+
interface IMessageMethods {
|
|
192
|
+
isSender(user: MessageUser): boolean;
|
|
193
|
+
isReceiver(user: MessageUser): boolean;
|
|
194
|
+
archive(actionCd: string, archivedBy: UserId, registry: TemplateRegistry): Promise<void>;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Base fields shared between IMessage and IMessageArchive.
|
|
198
|
+
*
|
|
199
|
+
* `display` is stored as-is from the template's `PrepareResult.display` —
|
|
200
|
+
* unlike `senderContent` and `receiverContent`, it is NOT interpolated at
|
|
201
|
+
* create time. Use it for static per-message configuration (priority,
|
|
202
|
+
* category, etc.) rather than user-facing text.
|
|
203
|
+
*/
|
|
204
|
+
interface IBaseMessage {
|
|
205
|
+
templateCd: string;
|
|
206
|
+
type: MessageType;
|
|
207
|
+
fromUser: UserId | null;
|
|
208
|
+
toUser: UserId | null;
|
|
209
|
+
toRoles: string[];
|
|
210
|
+
senderContent: IMessageContent;
|
|
211
|
+
receiverContent: IMessageContent;
|
|
212
|
+
documents: mongoose.Types.ObjectId[];
|
|
213
|
+
paymentSession: string | null;
|
|
214
|
+
paymentCd: string;
|
|
215
|
+
payload: Record<string, unknown>;
|
|
216
|
+
display: Record<string, unknown>;
|
|
217
|
+
createdAt: Date;
|
|
218
|
+
updatedAt: Date;
|
|
219
|
+
}
|
|
220
|
+
type MongooseQueryHelpers = Record<string, unknown>;
|
|
221
|
+
type IMessage = mongoose.Document<unknown, MongooseQueryHelpers, IMessageMethods> & IBaseMessage & IMessageMethods & {
|
|
222
|
+
__v?: number;
|
|
223
|
+
};
|
|
224
|
+
type IMessageArchive = mongoose.Document<unknown, MongooseQueryHelpers, IMessageMethods> & IBaseMessage & {
|
|
225
|
+
actionCd: string;
|
|
226
|
+
archivedBy: UserId;
|
|
227
|
+
archivedAt: Date;
|
|
228
|
+
__v?: number;
|
|
229
|
+
} & IMessageMethods;
|
|
230
|
+
|
|
231
|
+
/** Model name constants — use these when calling mongoose.model() or getModel() */
|
|
232
|
+
declare const MESSAGE_MODEL_NAME = "Message";
|
|
233
|
+
declare const MESSAGE_ARCHIVE_MODEL_NAME = "MessageArchive";
|
|
234
|
+
declare const MessageContentSchema: mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
235
|
+
_id: false;
|
|
236
|
+
}, {
|
|
237
|
+
title: string;
|
|
238
|
+
long: string;
|
|
239
|
+
short: string;
|
|
240
|
+
}, mongoose.Document<unknown, {}, {
|
|
241
|
+
title: string;
|
|
242
|
+
long: string;
|
|
243
|
+
short: string;
|
|
244
|
+
}, {
|
|
245
|
+
id: string;
|
|
246
|
+
}, Omit<mongoose.DefaultSchemaOptions, "_id"> & {
|
|
247
|
+
_id: false;
|
|
248
|
+
}> & Omit<{
|
|
249
|
+
title: string;
|
|
250
|
+
long: string;
|
|
251
|
+
short: string;
|
|
252
|
+
} & {
|
|
253
|
+
_id: mongoose.Types.ObjectId;
|
|
254
|
+
} & {
|
|
255
|
+
__v: number;
|
|
256
|
+
}, "id"> & {
|
|
257
|
+
id: string;
|
|
258
|
+
}, unknown, {
|
|
259
|
+
title: string;
|
|
260
|
+
long: string;
|
|
261
|
+
short: string;
|
|
262
|
+
} & {
|
|
263
|
+
_id: mongoose.Types.ObjectId;
|
|
264
|
+
} & {
|
|
265
|
+
__v: number;
|
|
266
|
+
}>;
|
|
267
|
+
/**
|
|
268
|
+
* Base message fields shared between Message and MessageArchive.
|
|
269
|
+
*
|
|
270
|
+
* All ObjectId fields use generic refs — consumers should configure
|
|
271
|
+
* their own Mongoose populate paths for their domain models.
|
|
272
|
+
*
|
|
273
|
+
* Consumers can extend this object with additional fields (e.g. toOrg, toOrgAdmin)
|
|
274
|
+
* by spreading it into their own schema definition.
|
|
275
|
+
*/
|
|
276
|
+
declare const BaseMessageFields: Record<string, mongoose.SchemaTypeOptions<unknown>>;
|
|
277
|
+
|
|
278
|
+
type EmailNotifier = (email: string, title: string, message: string) => Promise<void> | void;
|
|
279
|
+
interface MessageSchemaConfig {
|
|
280
|
+
/**
|
|
281
|
+
* Called on every message save (unless excluded) to send an email
|
|
282
|
+
* notification to the recipient. Pass `null` (the default) to disable.
|
|
283
|
+
*
|
|
284
|
+
* When `null`, no pre-save hook is registered at all.
|
|
285
|
+
*/
|
|
286
|
+
emailNotifier?: EmailNotifier | null;
|
|
287
|
+
/**
|
|
288
|
+
* Message titles (lowercased, after trim) that should NOT trigger email
|
|
289
|
+
* notifications. Comparison is case-insensitive and matches the *compiled*
|
|
290
|
+
* title — i.e. the interpolated result. Use this to suppress emails for
|
|
291
|
+
* low-priority messages.
|
|
292
|
+
*/
|
|
293
|
+
emailNotificationExclusions?: string[];
|
|
294
|
+
/**
|
|
295
|
+
* Name of the Mongoose model that holds the recipient user records.
|
|
296
|
+
* Defaults to `'User'`. The pre-save hook uses `mongoose.model(name)`
|
|
297
|
+
* to look up the recipient's email address.
|
|
298
|
+
*
|
|
299
|
+
* The model MUST be registered with Mongoose before the first save —
|
|
300
|
+
* `buildMessageSchema` does a sanity check to give a clear error if not.
|
|
301
|
+
*/
|
|
302
|
+
userModelName?: string;
|
|
303
|
+
/**
|
|
304
|
+
* Name of the Mongoose model used for archived messages.
|
|
305
|
+
* Defaults to `'MessageArchive'`. Used by the `archive()` instance method.
|
|
306
|
+
*/
|
|
307
|
+
archiveModelName?: string;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Build a fresh Message schema with the given configuration.
|
|
311
|
+
* Prefer this over the default `MessageSchema` export when you need
|
|
312
|
+
* an email notifier, exclusions, or custom model names.
|
|
313
|
+
*
|
|
314
|
+
* Note: when `emailNotifier` is set, the configured `userModelName` MUST
|
|
315
|
+
* be registered with Mongoose before calling this function. The schema
|
|
316
|
+
* factory checks this eagerly to fail fast.
|
|
317
|
+
*/
|
|
318
|
+
declare function buildMessageSchema(config?: MessageSchemaConfig): mongoose.Schema;
|
|
319
|
+
/**
|
|
320
|
+
* Default Message schema with no email notifier and no pre-save hook.
|
|
321
|
+
* Provided for backwards compatibility and simple use cases.
|
|
322
|
+
* Use `buildMessageSchema(config)` when you need custom behavior.
|
|
323
|
+
*/
|
|
324
|
+
declare const MessageSchema: mongoose.Schema;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Build a fresh MessageArchive schema.
|
|
328
|
+
* Mirrors the default Message schema but adds archive-specific fields
|
|
329
|
+
* (actionCd, archivedBy, archivedAt) and no pre-save email hook.
|
|
330
|
+
*/
|
|
331
|
+
declare function buildMessageArchiveSchema(): mongoose.Schema;
|
|
332
|
+
/**
|
|
333
|
+
* Default MessageArchive schema. Provided for backwards compatibility.
|
|
334
|
+
* Use `buildMessageArchiveSchema()` if you need a fresh instance.
|
|
335
|
+
*/
|
|
336
|
+
declare const MessageArchiveSchema: mongoose.Schema;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Apply the standard set of action filters (usertype match, permission, condition).
|
|
340
|
+
* This is the single source of truth used by both the template engine (for UI)
|
|
341
|
+
* and the server-side action handler (for authorization).
|
|
342
|
+
*
|
|
343
|
+
* The `name` and confirmation `title`/`message`/`notesLabel` strings are
|
|
344
|
+
* compiled against the same `data` passed to `interpolateTemplate`.
|
|
345
|
+
*/
|
|
346
|
+
declare function filterActions(actions: MessageAction[], usertype: Usertype, options?: {
|
|
347
|
+
permissions?: Record<string, boolean>;
|
|
348
|
+
message?: Record<string, unknown>;
|
|
349
|
+
data?: Record<string, unknown>;
|
|
350
|
+
}): InterpolatedAction[];
|
|
351
|
+
/**
|
|
352
|
+
* Check if the given user is allowed to execute the action on the given message.
|
|
353
|
+
* Returns true if the action is permitted, false otherwise.
|
|
354
|
+
* Mirrors the `filterActions` logic exactly so the UI and the server agree.
|
|
355
|
+
*/
|
|
356
|
+
declare function isActionAllowed(action: MessageAction, user: MessageUser, message: IMessage | IMessageArchive, options?: {
|
|
357
|
+
permissions?: Record<string, boolean>;
|
|
358
|
+
}): boolean;
|
|
359
|
+
/**
|
|
360
|
+
* Interpolate a template's sender/receiver content and filter actions
|
|
361
|
+
* for the given usertype (sender or receiver).
|
|
362
|
+
*/
|
|
363
|
+
declare function interpolateTemplate(template: MessageTemplate, data: Record<string, unknown>, usertype: Usertype, options?: {
|
|
364
|
+
permissions?: Record<string, boolean>;
|
|
365
|
+
message?: Record<string, unknown>;
|
|
366
|
+
}): InterpolationResult;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Email provider interface.
|
|
370
|
+
* Host app implements this to send email notifications.
|
|
371
|
+
*/
|
|
372
|
+
interface EmailProvider {
|
|
373
|
+
sendNotification(to: string, title: string, body: string): Promise<void>;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* No-op email provider that silently discards emails.
|
|
377
|
+
* Useful for testing or when email is not needed.
|
|
378
|
+
*/
|
|
379
|
+
declare class NoopEmailProvider implements EmailProvider {
|
|
380
|
+
sendNotification(_to: string, _title: string, _body: string): Promise<void>;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Payment provider interface.
|
|
385
|
+
*
|
|
386
|
+
* Host apps implement this to handle payment sessions for messages
|
|
387
|
+
* that require payment (e.g. Stripe, Adyen, Paddle).
|
|
388
|
+
*
|
|
389
|
+
* `priceArgs` is intentionally a free-form `Record<string, unknown>` so
|
|
390
|
+
* providers can accept their own metadata (currency, line items, etc.).
|
|
391
|
+
* Templates should document what they pass.
|
|
392
|
+
*/
|
|
393
|
+
interface PaymentProvider {
|
|
394
|
+
/**
|
|
395
|
+
* Create a checkout/session for the given user and pricing code.
|
|
396
|
+
* Return the session id, or `null` to indicate the session could not
|
|
397
|
+
* be created (the message creation flow will throw in that case).
|
|
398
|
+
*/
|
|
399
|
+
createSession(user: UserId, code: string, priceArgs?: Record<string, unknown>): Promise<string | null>;
|
|
400
|
+
/**
|
|
401
|
+
* Expire a session that was never completed.
|
|
402
|
+
* Should be idempotent.
|
|
403
|
+
*/
|
|
404
|
+
expireSession(sessionId: string): Promise<void>;
|
|
405
|
+
/**
|
|
406
|
+
* Refund a completed payment.
|
|
407
|
+
* Should be idempotent.
|
|
408
|
+
*/
|
|
409
|
+
refundPayment(sessionId: string): Promise<void>;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* No-op payment provider that returns null for session creation
|
|
413
|
+
* and does nothing for expire/refund. Useful for apps that don't use payments
|
|
414
|
+
* or for tests.
|
|
415
|
+
*/
|
|
416
|
+
declare class NoopPaymentProvider implements PaymentProvider {
|
|
417
|
+
createSession(_user: UserId, _code: string, _priceArgs?: Record<string, unknown>): Promise<string | null>;
|
|
418
|
+
expireSession(_sessionId: string): Promise<void>;
|
|
419
|
+
refundPayment(_sessionId: string): Promise<void>;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
interface MessageServiceOptions {
|
|
423
|
+
getModel: (name: string) => mongoose.Model<unknown>;
|
|
424
|
+
paymentProvider?: PaymentProvider | null;
|
|
425
|
+
adminRoles?: string[];
|
|
426
|
+
registry?: TemplateRegistry;
|
|
427
|
+
/**
|
|
428
|
+
* Maximum number of messages returned by `listMessages` when no explicit
|
|
429
|
+
* limit is provided. Defaults to 50.
|
|
430
|
+
*/
|
|
431
|
+
defaultListLimit?: number;
|
|
432
|
+
/**
|
|
433
|
+
* Hard upper bound for `listMessages` limit to prevent abuse. Defaults to 100.
|
|
434
|
+
*/
|
|
435
|
+
maxListLimit?: number;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* The `templateCd` used for generic notifications created via
|
|
439
|
+
* `createNotification`. These messages have no actions, so
|
|
440
|
+
* `getActions` returns an empty list for them.
|
|
441
|
+
*/
|
|
442
|
+
declare const GENERIC_NOTIFICATION_TEMPLATE_CD = "__generic-notification__";
|
|
443
|
+
declare class MessageNotFoundError extends Error {
|
|
444
|
+
constructor(messageId: string);
|
|
445
|
+
}
|
|
446
|
+
declare class MessageArchivedError extends Error {
|
|
447
|
+
constructor(messageId: string);
|
|
448
|
+
}
|
|
449
|
+
declare class TemplateNotFoundError extends Error {
|
|
450
|
+
constructor(templateCd: string);
|
|
451
|
+
}
|
|
452
|
+
declare class ActionNotFoundError extends Error {
|
|
453
|
+
constructor(templateCd: string, actionCd: string);
|
|
454
|
+
}
|
|
455
|
+
declare class ActionNotAllowedError extends Error {
|
|
456
|
+
constructor();
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Core message service for creating messages, getting actions, and handling actions.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* const service = new MessageService({ getModel: mongoose.model.bind(mongoose) });
|
|
463
|
+
*/
|
|
464
|
+
declare class MessageService {
|
|
465
|
+
private getModel;
|
|
466
|
+
private paymentProvider;
|
|
467
|
+
private expirePaymentSession?;
|
|
468
|
+
private refundPaymentSession?;
|
|
469
|
+
private adminRoles;
|
|
470
|
+
private registry;
|
|
471
|
+
private defaultListLimit;
|
|
472
|
+
private maxListLimit;
|
|
473
|
+
constructor(options: MessageServiceOptions);
|
|
474
|
+
/**
|
|
475
|
+
* Find a message by id, falling back to the archive. Returns null if
|
|
476
|
+
* the message does not exist in either collection.
|
|
477
|
+
*/
|
|
478
|
+
findMessage(messageId: string, options?: {
|
|
479
|
+
populate?: string | string[] | mongoose.PopulateOptions | mongoose.PopulateOptions[];
|
|
480
|
+
select?: string | Record<string, 0 | 1 | boolean>;
|
|
481
|
+
}): Promise<IMessage | IMessageArchive | null>;
|
|
482
|
+
findMessageOrThrow(messageId: string, options?: {
|
|
483
|
+
populate?: string | string[] | mongoose.PopulateOptions | mongoose.PopulateOptions[];
|
|
484
|
+
select?: string | Record<string, 0 | 1 | boolean>;
|
|
485
|
+
}): Promise<IMessage | IMessageArchive>;
|
|
486
|
+
createMessage(params: {
|
|
487
|
+
templateCd: string;
|
|
488
|
+
user: MessageUser;
|
|
489
|
+
roles?: string[];
|
|
490
|
+
identity?: Record<string, unknown>;
|
|
491
|
+
permissions?: Record<string, boolean>;
|
|
492
|
+
payload?: Record<string, unknown>;
|
|
493
|
+
payerUser?: MessageUser;
|
|
494
|
+
req?: unknown;
|
|
495
|
+
/**
|
|
496
|
+
* Optional client-supplied request id. If a message with this
|
|
497
|
+
* `clientRequestId` already exists, that message is returned instead
|
|
498
|
+
* of creating a duplicate. Useful for double-submit protection.
|
|
499
|
+
*/
|
|
500
|
+
clientRequestId?: string;
|
|
501
|
+
}): Promise<IMessage[]>;
|
|
502
|
+
createNotification(params: {
|
|
503
|
+
fromUser?: UserId | null;
|
|
504
|
+
toUser?: UserId | null;
|
|
505
|
+
toRoles?: string[];
|
|
506
|
+
receiverContent: {
|
|
507
|
+
title: string;
|
|
508
|
+
long: string;
|
|
509
|
+
short?: string;
|
|
510
|
+
};
|
|
511
|
+
senderContent?: {
|
|
512
|
+
title: string;
|
|
513
|
+
long: string;
|
|
514
|
+
short?: string;
|
|
515
|
+
};
|
|
516
|
+
documents?: mongoose.Types.ObjectId[];
|
|
517
|
+
}): Promise<IMessage>;
|
|
518
|
+
/**
|
|
519
|
+
* Build a Mongoose filter for messages visible to the given user.
|
|
520
|
+
* Exposed so callers can use the same visibility rules for custom
|
|
521
|
+
* queries (e.g. with `populate`).
|
|
522
|
+
*/
|
|
523
|
+
buildVisibilityFilter(user: MessageUser): Record<string, unknown>;
|
|
524
|
+
/**
|
|
525
|
+
* List active (non-archived) messages visible to a user.
|
|
526
|
+
* Returns messages where the user is the sender, the receiver,
|
|
527
|
+
* or matches one of the recipient's roles.
|
|
528
|
+
*/
|
|
529
|
+
listMessages(params: {
|
|
530
|
+
user: MessageUser;
|
|
531
|
+
limit?: number;
|
|
532
|
+
skip?: number;
|
|
533
|
+
populate?: string | string[] | mongoose.PopulateOptions | mongoose.PopulateOptions[];
|
|
534
|
+
}): Promise<IMessage[]>;
|
|
535
|
+
/**
|
|
536
|
+
* Count active (non-archived) messages visible to a user.
|
|
537
|
+
* Useful for badge indicators ("3 new messages").
|
|
538
|
+
*/
|
|
539
|
+
countMessages(user: MessageUser): Promise<number>;
|
|
540
|
+
getActions(messageId: string, usertype: Usertype, options?: {
|
|
541
|
+
permissions?: Record<string, boolean>;
|
|
542
|
+
message?: IMessage | IMessageArchive;
|
|
543
|
+
user?: MessageUser;
|
|
544
|
+
isAdmin?: boolean;
|
|
545
|
+
populate?: string | string[] | mongoose.PopulateOptions | mongoose.PopulateOptions[];
|
|
546
|
+
}): Promise<{
|
|
547
|
+
uiTemplate: UiTemplate;
|
|
548
|
+
actions: InterpolatedAction[];
|
|
549
|
+
} | null>;
|
|
550
|
+
handleAction(templateCd: string, actionCd: string, data: {
|
|
551
|
+
message: IMessage | IMessageArchive;
|
|
552
|
+
user: MessageUser;
|
|
553
|
+
permissions?: Record<string, boolean>;
|
|
554
|
+
req?: unknown;
|
|
555
|
+
}): Promise<unknown>;
|
|
556
|
+
private persistItem;
|
|
557
|
+
private buildActionContext;
|
|
558
|
+
private runSenderNotification;
|
|
559
|
+
private findByClientRequestId;
|
|
560
|
+
private isArchivedMessage;
|
|
561
|
+
private applyPopulate;
|
|
562
|
+
private findByIdWithOptions;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
interface MessageRoutesOptions {
|
|
566
|
+
/** Mongoose model getter */
|
|
567
|
+
getModel: (name: string) => mongoose.Model<unknown>;
|
|
568
|
+
/** Payment provider (optional — enables payment session handling) */
|
|
569
|
+
paymentProvider?: PaymentProvider | null;
|
|
570
|
+
/** Admin roles that receive messages when no toUser/toRoles specified */
|
|
571
|
+
adminRoles?: string[];
|
|
572
|
+
/**
|
|
573
|
+
* Custom template registry. Use this to isolate templates per app or test
|
|
574
|
+
* instead of relying on the global `defaultRegistry`. Pass the same registry
|
|
575
|
+
* instance to `MessageService` if you also construct one directly.
|
|
576
|
+
*/
|
|
577
|
+
registry?: TemplateRegistry;
|
|
578
|
+
/** Custom auth middleware applied to all routes */
|
|
579
|
+
authMiddleware?: ((req: Request, res: Response, next: NextFunction) => void)[];
|
|
580
|
+
/** Extract user from request (default: req._user || req.user) */
|
|
581
|
+
getUser?: (req: Request) => MessageUser | undefined;
|
|
582
|
+
/** Extract permissions from request (default: req._permissions || {}) */
|
|
583
|
+
getPermissions?: (req: Request) => Record<string, boolean>;
|
|
584
|
+
/** Extract identity from request (default: req._identity || {}) */
|
|
585
|
+
getIdentity?: (req: Request) => Record<string, unknown>;
|
|
586
|
+
/**
|
|
587
|
+
* Permission key that, when truthy, makes `getActions` return an empty
|
|
588
|
+
* action list (read-only view). Defaults to `'is.admin'`.
|
|
589
|
+
*/
|
|
590
|
+
adminPermissionKey?: string;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Create a JsonRouter with the message template routes.
|
|
594
|
+
* Mount via `router.original` and apply your own auth/permission middleware.
|
|
595
|
+
*
|
|
596
|
+
* Routes:
|
|
597
|
+
* POST /new/:templateCd — create message from template
|
|
598
|
+
* GET /:id/actions/:usertype — get available actions for a message
|
|
599
|
+
* GET /:id/action/:actionCd — execute an action (GET)
|
|
600
|
+
* POST /:id/action/:actionCd — execute an action (POST)
|
|
601
|
+
*/
|
|
602
|
+
declare function createMessageRoutes(options: MessageRoutesOptions): {
|
|
603
|
+
router: JsonRouter;
|
|
604
|
+
service: MessageService;
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
export { type ActionConfirmation, type ActionContext, ActionNotAllowedError, ActionNotFoundError, BaseMessageFields, type EmailNotifier, type EmailProvider, GENERIC_NOTIFICATION_TEMPLATE_CD, type IBaseMessage, type IMessage, type IMessageArchive, type IMessageContent, type IMessageMethods, type InterpolatedAction, type InterpolatedContent, type InterpolationResult, MESSAGE_ARCHIVE_MODEL_NAME, MESSAGE_MODEL_NAME, type MessageAction, MessageArchiveSchema, MessageArchivedError, MessageContentSchema, MessageNotFoundError, type MessageRoutesOptions, MessageSchema, type MessageSchemaConfig, MessageService, type MessageServiceOptions, type MessageTemplate, type MessageType, type MessageUser, NoopEmailProvider, NoopPaymentProvider, type PaymentProvider, type PrepareContext, type PrepareResult, type SenderNotificationContent, TemplateNotFoundError, TemplateRegistry, type UiTemplate, type Usertype, buildMessageArchiveSchema, buildMessageSchema, createMessageRoutes, defaultRegistry, filterActions, includesAction, interpolateTemplate, isActionAllowed };
|