@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/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 };