vintasend 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -12
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/next.config.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/next.config.js +9 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/forgot-password-notification-context.d.ts +6 -6
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/forgot-password-notification-context.js +16 -17
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/route.d.ts +7 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/route.js +105 -79
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/login/route.d.ts +4 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/login/route.js +96 -66
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/reset-password/route.d.ts +7 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/reset-password/route.js +95 -71
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/email-verification-notification-context.d.ts +6 -6
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/email-verification-notification-context.js +18 -18
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/route.d.ts +4 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/route.js +124 -96
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/verify-email/route.d.ts +7 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/verify-email/route.js +94 -70
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/login/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/login/page.js +67 -55
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/reset-password/[token]/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/reset-password/[token]/page.js +76 -63
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/signup/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/signup/page.js +87 -63
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email/[token]/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email/[token]/page.js +50 -35
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email-sent/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email-sent/page.js +12 -12
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/layout.d.ts +7 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/layout.js +15 -16
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/page.js +65 -21
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/AuthLayout.d.ts +7 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/AuthLayout.js +7 -8
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/accordion.d.ts +18 -6
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/accordion.js +86 -48
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert-dialog.d.ts +58 -14
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert-dialog.js +135 -53
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert.d.ts +21 -7
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert.js +85 -49
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/avatar.d.ts +14 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/avatar.js +77 -40
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/button.d.ts +25 -9
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/button.js +80 -58
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/card.d.ts +19 -7
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/card.js +98 -48
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/checkbox.d.ts +6 -3
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/checkbox.js +66 -42
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/collapsible.d.ts +10 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/collapsible.js +48 -35
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dialog.d.ts +40 -13
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dialog.js +116 -50
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dropdown-menu.d.ts +83 -19
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dropdown-menu.js +170 -68
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/form.d.ts +53 -21
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/form.js +137 -83
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/input.d.ts +8 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/input.js +60 -37
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/label.d.ts +10 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/label.js +61 -40
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/menubar.d.ts +77 -23
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/menubar.js +188 -64
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/select.d.ts +48 -12
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/select.js +148 -66
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/textarea.d.ts +8 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/textarea.js +59 -37
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/auth.d.ts +10 -10
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/auth.js +54 -55
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/core.d.ts +14 -11
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/core.js +1 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/email.js +0 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/logger.d.ts +3 -3
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/logger.js +9 -10
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/schemas/auth.d.ts +66 -32
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/schemas/auth.js +74 -54
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/auth.js +7 -8
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications-with-queue.d.ts +14 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications-with-queue.js +14 -10
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications.d.ts +11 -7
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications.js +39 -26
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/temporal-queue-service.d.ts +11 -8
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/temporal-queue-service.js +14 -15
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/temporal.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/temporal.js +9 -10
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/utils.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/utils.js +4 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/activities.d.ts +6 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/activities.js +14 -14
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/config.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/config.js +2 -3
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/constants.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/constants.js +1 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/worker.js +20 -22
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/workflows.d.ts +3 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/workflows.js +10 -9
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/tailwind.config.d.ts +74 -74
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/tailwind.config.js +80 -81
- package/dist/implementations/vintasend-nodemailer/src/index.js +6 -4
- package/dist/implementations/vintasend-nodemailer/src/nodemailer-notification-adapter.d.ts +36 -14
- package/dist/implementations/vintasend-nodemailer/src/nodemailer-notification-adapter.js +26 -28
- package/dist/implementations/vintasend-prisma/src/index.js +6 -4
- package/dist/implementations/vintasend-prisma/src/prisma-notification-backend.d.ts +232 -138
- package/dist/implementations/vintasend-prisma/src/prisma-notification-backend.js +275 -262
- package/dist/implementations/vintasend-pug/src/index.js +6 -4
- package/dist/implementations/vintasend-pug/src/pug-email-template-renderer.d.ts +18 -7
- package/dist/implementations/vintasend-pug/src/pug-email-template-renderer.js +19 -21
- package/dist/implementations/vintasend-winston/src/index.js +6 -4
- package/dist/implementations/vintasend-winston/src/winston-logger.d.ts +6 -6
- package/dist/implementations/vintasend-winston/src/winston-logger.js +65 -50
- package/dist/index.d.ts +13 -5
- package/dist/index.js +13 -1
- package/dist/services/attachment-manager/base-attachment-manager.d.ts +42 -0
- package/dist/services/attachment-manager/base-attachment-manager.js +115 -0
- package/dist/services/attachment-manager/local-file-attachment-manager.d.ts +58 -0
- package/dist/services/attachment-manager/local-file-attachment-manager.js +192 -0
- package/dist/services/notification-adapters/base-notification-adapter.d.ts +36 -4
- package/dist/services/notification-adapters/base-notification-adapter.js +73 -4
- package/dist/services/notification-backends/base-notification-backend.d.ts +54 -11
- package/dist/services/notification-backends/base-notification-backend.js +12 -0
- package/dist/services/notification-context-generators-map.d.ts +1 -1
- package/dist/services/notification-context-registry.d.ts +14 -9
- package/dist/services/notification-context-registry.js +30 -31
- package/dist/services/notification-queue-service/base-notification-queue-service.d.ts +1 -1
- package/dist/services/notification-service.d.ts +73 -13
- package/dist/services/notification-service.js +125 -8
- package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +3 -3
- package/dist/services/notification-template-renderers/base-notification-template-renderer.d.ts +2 -2
- package/dist/types/attachment.d.ts +42 -0
- package/dist/types/attachment.js +7 -0
- package/dist/types/notification-type-config.d.ts +2 -2
- package/dist/types/notification.d.ts +21 -0
- package/dist/types/one-off-notification.d.ts +72 -0
- package/dist/types/one-off-notification.js +2 -0
- package/package.json +13 -11
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BaseNotificationAdapter = void 0;
|
|
4
|
+
exports.isOneOffNotification = isOneOffNotification;
|
|
5
|
+
/**
|
|
6
|
+
* Type guard to check if a notification is a one-off notification
|
|
7
|
+
*/
|
|
8
|
+
function isOneOffNotification(notification) {
|
|
9
|
+
return ('emailOrPhone' in notification && 'firstName' in notification && 'lastName' in notification);
|
|
10
|
+
}
|
|
4
11
|
class BaseNotificationAdapter {
|
|
5
12
|
constructor(templateRenderer, notificationType, enqueueNotifications) {
|
|
6
13
|
this.templateRenderer = templateRenderer;
|
|
@@ -8,18 +15,80 @@ class BaseNotificationAdapter {
|
|
|
8
15
|
this.enqueueNotifications = enqueueNotifications;
|
|
9
16
|
this.key = null;
|
|
10
17
|
this.backend = null;
|
|
18
|
+
this.logger = null;
|
|
11
19
|
}
|
|
12
|
-
|
|
13
|
-
send(notification, context) {
|
|
20
|
+
send(_notification, _context) {
|
|
14
21
|
if (this.backend === null) {
|
|
15
22
|
return Promise.reject(new Error('Backend not injected'));
|
|
16
23
|
}
|
|
17
24
|
return Promise.resolve();
|
|
18
25
|
}
|
|
19
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Check if this adapter supports attachments
|
|
28
|
+
*/
|
|
29
|
+
get supportsAttachments() {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Prepare attachments for sending
|
|
34
|
+
* Override in adapters that support attachments
|
|
35
|
+
*/
|
|
36
|
+
async prepareAttachments(attachments) {
|
|
37
|
+
var _a, _b;
|
|
38
|
+
if (this.supportsAttachments && attachments.length > 0) {
|
|
39
|
+
(_b = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn) === null || _b === void 0 ? void 0 : _b.call(_a, `Adapter ${this.key} claims to support attachments but prepareAttachments is not implemented`);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get the recipient email address from a notification.
|
|
45
|
+
* For one-off notifications, returns the emailOrPhone field directly.
|
|
46
|
+
* For regular notifications, fetches the email from the user via backend.
|
|
47
|
+
*/
|
|
48
|
+
async getRecipientEmail(notification) {
|
|
49
|
+
if (isOneOffNotification(notification)) {
|
|
50
|
+
return notification.emailOrPhone;
|
|
51
|
+
}
|
|
52
|
+
// Regular notification - get from user via backend
|
|
53
|
+
if (!this.backend) {
|
|
54
|
+
throw new Error('Backend not injected');
|
|
55
|
+
}
|
|
56
|
+
const userEmail = await this.backend.getUserEmailFromNotification(notification.id);
|
|
57
|
+
if (!userEmail) {
|
|
58
|
+
throw new Error(`User email not found for notification ${notification.id}`);
|
|
59
|
+
}
|
|
60
|
+
return userEmail;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the recipient name from a notification.
|
|
64
|
+
* For one-off notifications, returns the firstName and lastName fields directly.
|
|
65
|
+
* For regular notifications, attempts to extract from context or returns empty strings.
|
|
66
|
+
*/
|
|
67
|
+
getRecipientName(notification, context) {
|
|
68
|
+
if (isOneOffNotification(notification)) {
|
|
69
|
+
return {
|
|
70
|
+
firstName: notification.firstName,
|
|
71
|
+
lastName: notification.lastName,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Regular notification - try to get from context
|
|
75
|
+
if (context && typeof context === 'object' && !Array.isArray(context)) {
|
|
76
|
+
const jsonContext = context;
|
|
77
|
+
return {
|
|
78
|
+
firstName: (typeof jsonContext.firstName === 'string' ? jsonContext.firstName : '') || '',
|
|
79
|
+
lastName: (typeof jsonContext.lastName === 'string' ? jsonContext.lastName : '') || '',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
firstName: '',
|
|
84
|
+
lastName: '',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
20
87
|
injectBackend(backend) {
|
|
21
88
|
this.backend = backend;
|
|
22
89
|
}
|
|
23
|
-
|
|
90
|
+
injectLogger(logger) {
|
|
91
|
+
this.logger = logger;
|
|
92
|
+
}
|
|
24
93
|
}
|
|
25
94
|
exports.BaseNotificationAdapter = BaseNotificationAdapter;
|
|
@@ -1,25 +1,68 @@
|
|
|
1
|
+
import type { AttachmentFileRecord, StoredAttachment } from '../../types/attachment';
|
|
1
2
|
import type { InputJsonValue } from '../../types/json-values';
|
|
2
|
-
import type { DatabaseNotification, Notification } from '../../types/notification';
|
|
3
|
+
import type { AnyDatabaseNotification, AnyNotification, DatabaseNotification, DatabaseOneOffNotification, Notification, OneOffNotificationInput } from '../../types/notification';
|
|
3
4
|
import type { BaseNotificationTypeConfig } from '../../types/notification-type-config';
|
|
4
5
|
export interface BaseNotificationBackend<Config extends BaseNotificationTypeConfig> {
|
|
5
|
-
getAllPendingNotifications(): Promise<
|
|
6
|
-
getPendingNotifications(page: number, pageSize: number): Promise<
|
|
7
|
-
getAllFutureNotifications(): Promise<
|
|
8
|
-
getFutureNotifications(page: number, pageSize: number): Promise<
|
|
6
|
+
getAllPendingNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
|
|
7
|
+
getPendingNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
|
|
8
|
+
getAllFutureNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
|
|
9
|
+
getFutureNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
|
|
9
10
|
getAllFutureNotificationsFromUser(userId: Config['UserIdType']): Promise<DatabaseNotification<Config>[]>;
|
|
10
11
|
getFutureNotificationsFromUser(userId: Config['UserIdType'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
|
|
11
12
|
persistNotification(notification: Omit<Notification<Config>, 'id'>): Promise<DatabaseNotification<Config>>;
|
|
12
|
-
getAllNotifications(): Promise<
|
|
13
|
-
getNotifications(page: number, pageSize: number): Promise<
|
|
14
|
-
bulkPersistNotifications(notifications: Omit<
|
|
13
|
+
getAllNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
|
|
14
|
+
getNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
|
|
15
|
+
bulkPersistNotifications(notifications: Omit<AnyNotification<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
|
|
15
16
|
persistNotificationUpdate(notificationId: Config['NotificationIdType'], notification: Partial<Omit<Notification<Config>, 'id'>>): Promise<DatabaseNotification<Config>>;
|
|
16
|
-
markAsSent(notificationId: Config['NotificationIdType'], checkIsPending: boolean): Promise<
|
|
17
|
-
markAsFailed(notificationId: Config['NotificationIdType'], checkIsPending: boolean): Promise<
|
|
17
|
+
markAsSent(notificationId: Config['NotificationIdType'], checkIsPending: boolean): Promise<AnyDatabaseNotification<Config>>;
|
|
18
|
+
markAsFailed(notificationId: Config['NotificationIdType'], checkIsPending: boolean): Promise<AnyDatabaseNotification<Config>>;
|
|
18
19
|
markAsRead(notificationId: Config['NotificationIdType'], checkIsSent: boolean): Promise<DatabaseNotification<Config>>;
|
|
19
20
|
cancelNotification(notificationId: Config['NotificationIdType']): Promise<void>;
|
|
20
|
-
getNotification(notificationId: Config['NotificationIdType'], forUpdate: boolean): Promise<
|
|
21
|
+
getNotification(notificationId: Config['NotificationIdType'], forUpdate: boolean): Promise<AnyDatabaseNotification<Config> | null>;
|
|
21
22
|
filterAllInAppUnreadNotifications(userId: Config['UserIdType']): Promise<DatabaseNotification<Config>[]>;
|
|
22
23
|
filterInAppUnreadNotifications(userId: Config['UserIdType'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
|
|
23
24
|
getUserEmailFromNotification(notificationId: Config['NotificationIdType']): Promise<string | undefined>;
|
|
24
25
|
storeContextUsed(notificationId: Config['NotificationIdType'], context: InputJsonValue): Promise<void>;
|
|
26
|
+
persistOneOffNotification(notification: Omit<OneOffNotificationInput<Config>, 'id'>): Promise<DatabaseOneOffNotification<Config>>;
|
|
27
|
+
persistOneOffNotificationUpdate(notificationId: Config['NotificationIdType'], notification: Partial<Omit<OneOffNotificationInput<Config>, 'id'>>): Promise<DatabaseOneOffNotification<Config>>;
|
|
28
|
+
getOneOffNotification(notificationId: Config['NotificationIdType'], forUpdate: boolean): Promise<DatabaseOneOffNotification<Config> | null>;
|
|
29
|
+
getAllOneOffNotifications(): Promise<DatabaseOneOffNotification<Config>[]>;
|
|
30
|
+
getOneOffNotifications(page: number, pageSize: number): Promise<DatabaseOneOffNotification<Config>[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Get an attachment file record by ID
|
|
33
|
+
*/
|
|
34
|
+
getAttachmentFile?(fileId: string): Promise<AttachmentFileRecord | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Find an attachment file by checksum for deduplication.
|
|
37
|
+
* Backend queries its database for files with matching checksums.
|
|
38
|
+
* Used during file upload to avoid storing duplicate files.
|
|
39
|
+
*/
|
|
40
|
+
findAttachmentFileByChecksum?(checksum: string): Promise<AttachmentFileRecord | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Delete an attachment file (only if not referenced by any notifications)
|
|
43
|
+
*/
|
|
44
|
+
deleteAttachmentFile?(fileId: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Get all attachment files not referenced by any notifications (for cleanup)
|
|
47
|
+
*/
|
|
48
|
+
getOrphanedAttachmentFiles?(): Promise<AttachmentFileRecord[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Get all attachments for a specific notification
|
|
51
|
+
*/
|
|
52
|
+
getAttachments?(notificationId: Config['NotificationIdType']): Promise<StoredAttachment[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Delete a specific attachment from a notification
|
|
55
|
+
*/
|
|
56
|
+
deleteNotificationAttachment?(notificationId: Config['NotificationIdType'], attachmentId: string): Promise<void>;
|
|
25
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Type guard to check if backend supports attachment operations
|
|
60
|
+
*/
|
|
61
|
+
export declare function supportsAttachments<Config extends BaseNotificationTypeConfig>(backend: BaseNotificationBackend<Config>): backend is BaseNotificationBackend<Config> & {
|
|
62
|
+
getAttachmentFile(fileId: string): Promise<AttachmentFileRecord | null>;
|
|
63
|
+
findAttachmentFileByChecksum(checksum: string): Promise<AttachmentFileRecord | null>;
|
|
64
|
+
deleteAttachmentFile(fileId: string): Promise<void>;
|
|
65
|
+
getOrphanedAttachmentFiles(): Promise<AttachmentFileRecord[]>;
|
|
66
|
+
getAttachments(notificationId: Config['NotificationIdType']): Promise<StoredAttachment[]>;
|
|
67
|
+
deleteNotificationAttachment(notificationId: Config['NotificationIdType'], attachmentId: string): Promise<void>;
|
|
68
|
+
};
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.supportsAttachments = supportsAttachments;
|
|
4
|
+
/**
|
|
5
|
+
* Type guard to check if backend supports attachment operations
|
|
6
|
+
*/
|
|
7
|
+
function supportsAttachments(backend) {
|
|
8
|
+
return (typeof backend.getAttachmentFile === 'function' &&
|
|
9
|
+
typeof backend.findAttachmentFileByChecksum === 'function' &&
|
|
10
|
+
typeof backend.deleteAttachmentFile === 'function' &&
|
|
11
|
+
typeof backend.getOrphanedAttachmentFiles === 'function' &&
|
|
12
|
+
typeof backend.getAttachments === 'function' &&
|
|
13
|
+
typeof backend.deleteNotificationAttachment === 'function');
|
|
14
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContextGenerator } from
|
|
1
|
+
import type { ContextGenerator } from '../types/notification-context-generators';
|
|
2
2
|
export declare class NotificationContextGeneratorsMap<ContextMapType extends {
|
|
3
3
|
[key: string]: ContextGenerator;
|
|
4
4
|
}> {
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { JsonObject, JsonPrimitive } from '../types/json-values';
|
|
2
2
|
export interface ContextGenerator {
|
|
3
|
-
|
|
3
|
+
generate(params: Record<string, JsonPrimitive>): JsonObject | Promise<JsonObject>;
|
|
4
4
|
}
|
|
5
5
|
export declare class NotificationContextRegistry<T extends Record<string, ContextGenerator>> {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
private static instance;
|
|
7
|
+
private readonly contexts;
|
|
8
|
+
private constructor();
|
|
9
|
+
static initialize<T extends Record<string, ContextGenerator>>(
|
|
10
|
+
contexts: T,
|
|
11
|
+
): NotificationContextRegistry<T>;
|
|
12
|
+
static getInstance<T extends Record<string, ContextGenerator>>(): NotificationContextRegistry<T>;
|
|
13
|
+
static resetInstance(): void;
|
|
14
|
+
getContext<K extends keyof T>(
|
|
15
|
+
key: K,
|
|
16
|
+
parameters: Parameters<T[K]['generate']>[0],
|
|
17
|
+
): Promise<JsonObject>;
|
|
18
|
+
getAvailableContexts(): (keyof T)[];
|
|
14
19
|
}
|
|
@@ -1,40 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
2
|
exports.NotificationContextRegistry = void 0;
|
|
4
3
|
class NotificationContextRegistry {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
constructor(contexts) {
|
|
5
|
+
this.contexts = contexts;
|
|
6
|
+
}
|
|
7
|
+
static initialize(contexts) {
|
|
8
|
+
if (NotificationContextRegistry.instance) {
|
|
9
|
+
throw new Error('NotificationContextRegistry already initialized');
|
|
7
10
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
NotificationContextRegistry.instance = new NotificationContextRegistry(contexts);
|
|
12
|
+
return NotificationContextRegistry.instance;
|
|
13
|
+
}
|
|
14
|
+
static getInstance() {
|
|
15
|
+
if (!NotificationContextRegistry.instance) {
|
|
16
|
+
throw new Error('NotificationContextRegistry not initialized');
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
return NotificationContextRegistry.instance;
|
|
19
|
+
}
|
|
20
|
+
static resetInstance() {
|
|
21
|
+
NotificationContextRegistry.instance = null;
|
|
22
|
+
}
|
|
23
|
+
async getContext(key, parameters) {
|
|
24
|
+
const contextGenerator = this.contexts[key];
|
|
25
|
+
if (!contextGenerator) {
|
|
26
|
+
throw new Error(`Context generator not found for context: ${key}`);
|
|
20
27
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async getContext(key, parameters) {
|
|
25
|
-
const contextGenerator = this.contexts[key];
|
|
26
|
-
if (!contextGenerator) {
|
|
27
|
-
throw new Error(`Context generator not found for context: ${key}`);
|
|
28
|
-
}
|
|
29
|
-
const result = contextGenerator.generate(parameters);
|
|
30
|
-
if (result instanceof Promise) {
|
|
31
|
-
return await result;
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
getAvailableContexts() {
|
|
36
|
-
return Object.keys(this.contexts);
|
|
28
|
+
const result = contextGenerator.generate(parameters);
|
|
29
|
+
if (result instanceof Promise) {
|
|
30
|
+
return await result;
|
|
37
31
|
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
getAvailableContexts() {
|
|
35
|
+
return Object.keys(this.contexts);
|
|
36
|
+
}
|
|
38
37
|
}
|
|
39
38
|
exports.NotificationContextRegistry = NotificationContextRegistry;
|
|
40
39
|
// biome-ignore lint/suspicious/noExplicitAny: This won't be used since we'll always initialize the registry
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaseNotificationTypeConfig } from
|
|
1
|
+
import type { BaseNotificationTypeConfig } from '../../types/notification-type-config';
|
|
2
2
|
export interface BaseNotificationQueueService<Config extends BaseNotificationTypeConfig> {
|
|
3
3
|
enqueueNotification(notificationId: Config['NotificationIdType']): Promise<void>;
|
|
4
4
|
}
|
|
@@ -1,43 +1,103 @@
|
|
|
1
|
-
import type { DatabaseNotification, Notification } from '../types/notification';
|
|
2
1
|
import type { JsonObject } from '../types/json-values';
|
|
2
|
+
import type { AnyDatabaseNotification, AnyNotification, DatabaseNotification, DatabaseOneOffNotification, Notification } from '../types/notification';
|
|
3
3
|
import type { BaseNotificationTypeConfig } from '../types/notification-type-config';
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
import type { BaseNotificationBackend } from './notification-backends/base-notification-backend';
|
|
4
|
+
import type { OneOffNotificationInput } from '../types/one-off-notification';
|
|
5
|
+
import type { BaseAttachmentManager } from './attachment-manager/base-attachment-manager';
|
|
7
6
|
import type { BaseLogger } from './loggers/base-logger';
|
|
7
|
+
import { type BaseNotificationAdapter } from './notification-adapters/base-notification-adapter';
|
|
8
|
+
import type { BaseNotificationBackend } from './notification-backends/base-notification-backend';
|
|
8
9
|
import type { BaseNotificationQueueService } from './notification-queue-service/base-notification-queue-service';
|
|
10
|
+
import type { BaseNotificationTemplateRenderer } from './notification-template-renderers/base-notification-template-renderer';
|
|
9
11
|
type VintaSendOptions = {
|
|
10
12
|
raiseErrorOnFailedSend: boolean;
|
|
11
13
|
};
|
|
12
14
|
export declare class VintaSendFactory<Config extends BaseNotificationTypeConfig> {
|
|
13
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new VintaSend notification service instance
|
|
17
|
+
*
|
|
18
|
+
* @param adapters - Array of notification adapters (email, SMS, push, etc.)
|
|
19
|
+
* @param backend - Notification storage backend
|
|
20
|
+
* @param logger - Logger instance
|
|
21
|
+
* @param contextGeneratorsMap - Map of context generators for notification rendering
|
|
22
|
+
* @param queueService - Optional queue service for background notification processing
|
|
23
|
+
* @param attachmentManager - Optional attachment manager for file handling
|
|
24
|
+
* @param options - Optional configuration options
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Without attachments or options
|
|
28
|
+
* factory.create(adapters, backend, logger, contextGeneratorsMap);
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // With queue service and options (note: pass undefined for attachmentManager)
|
|
32
|
+
* factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, undefined, { raiseErrorOnFailedSend: true });
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // With attachments and options
|
|
36
|
+
* factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, { raiseErrorOnFailedSend: true });
|
|
37
|
+
*
|
|
38
|
+
* @since v0.4.0 - BREAKING CHANGE: attachmentManager parameter added before options
|
|
39
|
+
* @see https://github.com/vintasoftware/vintasend-ts/blob/main/README.md#migrating-to-v040-attachment-support
|
|
40
|
+
*/
|
|
41
|
+
create<AdaptersList extends BaseNotificationAdapter<BaseNotificationTemplateRenderer<Config>, Config>[], Backend extends BaseNotificationBackend<Config>, Logger extends BaseLogger, QueueService extends BaseNotificationQueueService<Config>, AttachmentMgr extends BaseAttachmentManager>(adapters: AdaptersList, backend: Backend, logger: Logger, contextGeneratorsMap: BaseNotificationTypeConfig['ContextMap'], queueService?: QueueService, attachmentManager?: AttachmentMgr, options?: VintaSendOptions): VintaSend<Config, AdaptersList, Backend, Logger, QueueService, AttachmentMgr>;
|
|
14
42
|
}
|
|
15
|
-
export declare class VintaSend<Config extends BaseNotificationTypeConfig, AdaptersList extends BaseNotificationAdapter<BaseNotificationTemplateRenderer<Config>, Config>[], Backend extends BaseNotificationBackend<Config>, Logger extends BaseLogger, QueueService extends BaseNotificationQueueService<Config
|
|
43
|
+
export declare class VintaSend<Config extends BaseNotificationTypeConfig, AdaptersList extends BaseNotificationAdapter<BaseNotificationTemplateRenderer<Config>, Config>[], Backend extends BaseNotificationBackend<Config>, Logger extends BaseLogger, QueueService extends BaseNotificationQueueService<Config>, AttachmentMgr extends BaseAttachmentManager> {
|
|
16
44
|
private adapters;
|
|
17
45
|
private backend;
|
|
18
46
|
private logger;
|
|
19
47
|
private queueService?;
|
|
48
|
+
private attachmentManager?;
|
|
20
49
|
private options;
|
|
21
50
|
private contextGeneratorsMap;
|
|
22
|
-
constructor(adapters: AdaptersList, backend: Backend, logger: Logger, contextGeneratorsMap: Config['ContextMap'], queueService?: QueueService | undefined, options?: VintaSendOptions);
|
|
51
|
+
constructor(adapters: AdaptersList, backend: Backend, logger: Logger, contextGeneratorsMap: Config['ContextMap'], queueService?: QueueService | undefined, attachmentManager?: AttachmentMgr | undefined, options?: VintaSendOptions);
|
|
23
52
|
registerQueueService(queueService: QueueService): void;
|
|
24
|
-
send(notification:
|
|
53
|
+
send(notification: AnyDatabaseNotification<Config>): Promise<void>;
|
|
25
54
|
createNotification(notification: Omit<Notification<Config>, 'id'>): Promise<DatabaseNotification<Config>>;
|
|
26
55
|
updateNotification(notificationId: Config['NotificationIdType'], notification: Partial<Omit<Notification<Config>, 'id'>>): Promise<DatabaseNotification<Config>>;
|
|
27
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Creates and sends a one-off notification.
|
|
58
|
+
* One-off notifications are sent directly to an email/phone without requiring a user account.
|
|
59
|
+
*
|
|
60
|
+
* @param notification - The one-off notification to create (without id)
|
|
61
|
+
* @returns The created database notification
|
|
62
|
+
*/
|
|
63
|
+
createOneOffNotification(notification: Omit<OneOffNotificationInput<Config>, 'id'>): Promise<DatabaseOneOffNotification<Config>>;
|
|
64
|
+
/**
|
|
65
|
+
* Updates a one-off notification and re-sends it if the sendAfter date is in the past.
|
|
66
|
+
*
|
|
67
|
+
* @param notificationId - The ID of the notification to update
|
|
68
|
+
* @param notification - The partial notification data to update
|
|
69
|
+
* @returns The updated database notification
|
|
70
|
+
*/
|
|
71
|
+
updateOneOffNotification(notificationId: Config['NotificationIdType'], notification: Partial<Omit<OneOffNotificationInput<Config>, 'id'>>): Promise<DatabaseOneOffNotification<Config>>;
|
|
72
|
+
/**
|
|
73
|
+
* Validates that an email or phone number has a basic valid format.
|
|
74
|
+
*
|
|
75
|
+
* @param emailOrPhone - The email or phone string to validate
|
|
76
|
+
* @throws Error if the format is invalid
|
|
77
|
+
*/
|
|
78
|
+
private validateEmailOrPhone;
|
|
79
|
+
getAllFutureNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
|
|
28
80
|
getAllFutureNotificationsFromUser(userId: Config['NotificationIdType']): Promise<DatabaseNotification<Config>[]>;
|
|
29
81
|
getFutureNotificationsFromUser(userId: Config['NotificationIdType'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
|
|
30
|
-
getFutureNotifications(page: number, pageSize: number): Promise<
|
|
82
|
+
getFutureNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
|
|
31
83
|
getNotificationContext<ContextName extends string & keyof Config['ContextMap']>(contextName: ContextName, parameters: Parameters<ReturnType<typeof this.contextGeneratorsMap.getContextGenerator<ContextName>>['generate']>[0]): Promise<JsonObject>;
|
|
32
84
|
sendPendingNotifications(): Promise<void>;
|
|
33
|
-
getPendingNotifications(page: number, pageSize: number): Promise<
|
|
34
|
-
getNotification(notificationId: Config['NotificationIdType'], forUpdate?: boolean): Promise<
|
|
85
|
+
getPendingNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
|
|
86
|
+
getNotification(notificationId: Config['NotificationIdType'], forUpdate?: boolean): Promise<AnyDatabaseNotification<Config> | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Gets a one-off notification by ID.
|
|
89
|
+
*
|
|
90
|
+
* @param notificationId - The ID of the one-off notification to retrieve
|
|
91
|
+
* @param forUpdate - Whether the notification is being retrieved for update (default: false)
|
|
92
|
+
* @returns The one-off notification or null if not found
|
|
93
|
+
*/
|
|
94
|
+
getOneOffNotification(notificationId: Config['NotificationIdType'], forUpdate?: boolean): Promise<DatabaseOneOffNotification<Config> | null>;
|
|
35
95
|
markRead(notificationId: Config['NotificationIdType'], checkIsSent?: boolean): Promise<DatabaseNotification<Config>>;
|
|
36
96
|
getInAppUnread(userId: Config['NotificationIdType']): Promise<DatabaseNotification<Config>[]>;
|
|
37
97
|
cancelNotification(notificationId: Config['NotificationIdType']): Promise<void>;
|
|
38
98
|
resendNotification(notificationId: Config['NotificationIdType'], useStoredContextIfAvailable?: boolean): Promise<DatabaseNotification<Config> | undefined>;
|
|
39
99
|
delayedSend(notificationId: Config['NotificationIdType']): Promise<void>;
|
|
40
|
-
bulkPersistNotifications(notifications: Omit<
|
|
100
|
+
bulkPersistNotifications(notifications: Omit<AnyNotification<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
|
|
41
101
|
migrateToBackend<DestinationBackend extends BaseNotificationBackend<Config>>(destinationBackend: DestinationBackend, batchSize?: number): Promise<void>;
|
|
42
102
|
}
|
|
43
103
|
export {};
|
|
@@ -1,27 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VintaSend = exports.VintaSendFactory = void 0;
|
|
4
|
+
const base_notification_adapter_1 = require("./notification-adapters/base-notification-adapter");
|
|
4
5
|
const notification_context_generators_map_1 = require("./notification-context-generators-map");
|
|
5
6
|
class VintaSendFactory {
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new VintaSend notification service instance
|
|
9
|
+
*
|
|
10
|
+
* @param adapters - Array of notification adapters (email, SMS, push, etc.)
|
|
11
|
+
* @param backend - Notification storage backend
|
|
12
|
+
* @param logger - Logger instance
|
|
13
|
+
* @param contextGeneratorsMap - Map of context generators for notification rendering
|
|
14
|
+
* @param queueService - Optional queue service for background notification processing
|
|
15
|
+
* @param attachmentManager - Optional attachment manager for file handling
|
|
16
|
+
* @param options - Optional configuration options
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Without attachments or options
|
|
20
|
+
* factory.create(adapters, backend, logger, contextGeneratorsMap);
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // With queue service and options (note: pass undefined for attachmentManager)
|
|
24
|
+
* factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, undefined, { raiseErrorOnFailedSend: true });
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // With attachments and options
|
|
28
|
+
* factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, { raiseErrorOnFailedSend: true });
|
|
29
|
+
*
|
|
30
|
+
* @since v0.4.0 - BREAKING CHANGE: attachmentManager parameter added before options
|
|
31
|
+
* @see https://github.com/vintasoftware/vintasend-ts/blob/main/README.md#migrating-to-v040-attachment-support
|
|
32
|
+
*/
|
|
33
|
+
create(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options = {
|
|
7
34
|
raiseErrorOnFailedSend: false,
|
|
8
35
|
}) {
|
|
9
|
-
return new VintaSend(adapters, backend, logger, contextGeneratorsMap, queueService, options);
|
|
36
|
+
return new VintaSend(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options);
|
|
10
37
|
}
|
|
11
38
|
}
|
|
12
39
|
exports.VintaSendFactory = VintaSendFactory;
|
|
40
|
+
// Type guard to check if backend has attachment manager injection support
|
|
41
|
+
function hasAttachmentManagerInjection(backend) {
|
|
42
|
+
return ('injectAttachmentManager' in backend &&
|
|
43
|
+
// biome-ignore lint/suspicious/noExplicitAny:: this is a necessary check
|
|
44
|
+
typeof backend.injectAttachmentManager === 'function');
|
|
45
|
+
}
|
|
13
46
|
class VintaSend {
|
|
14
|
-
constructor(adapters, backend, logger, contextGeneratorsMap, queueService, options = {
|
|
47
|
+
constructor(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options = {
|
|
15
48
|
raiseErrorOnFailedSend: false,
|
|
16
49
|
}) {
|
|
17
50
|
this.adapters = adapters;
|
|
18
51
|
this.backend = backend;
|
|
19
52
|
this.logger = logger;
|
|
20
53
|
this.queueService = queueService;
|
|
54
|
+
this.attachmentManager = attachmentManager;
|
|
21
55
|
this.options = options;
|
|
22
56
|
this.contextGeneratorsMap = new notification_context_generators_map_1.NotificationContextGeneratorsMap(contextGeneratorsMap);
|
|
23
57
|
for (const adapter of adapters) {
|
|
24
58
|
adapter.injectBackend(backend);
|
|
59
|
+
adapter.injectLogger(logger);
|
|
60
|
+
}
|
|
61
|
+
// Inject attachment manager into backend if both exist
|
|
62
|
+
if (this.attachmentManager && hasAttachmentManagerInjection(backend)) {
|
|
63
|
+
backend.injectAttachmentManager(this.attachmentManager);
|
|
25
64
|
}
|
|
26
65
|
}
|
|
27
66
|
registerQueueService(queueService) {
|
|
@@ -119,6 +158,66 @@ class VintaSend {
|
|
|
119
158
|
this.logger.info(`Notification ${notificationId} updated`);
|
|
120
159
|
return updatedNotification;
|
|
121
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Creates and sends a one-off notification.
|
|
163
|
+
* One-off notifications are sent directly to an email/phone without requiring a user account.
|
|
164
|
+
*
|
|
165
|
+
* @param notification - The one-off notification to create (without id)
|
|
166
|
+
* @returns The created database notification
|
|
167
|
+
*/
|
|
168
|
+
async createOneOffNotification(notification) {
|
|
169
|
+
// Validate email or phone format
|
|
170
|
+
this.validateEmailOrPhone(notification.emailOrPhone);
|
|
171
|
+
const createdNotification = await this.backend.persistOneOffNotification(notification);
|
|
172
|
+
this.logger.info(`One-off notification ${createdNotification.id} created`);
|
|
173
|
+
if (!notification.sendAfter || notification.sendAfter <= new Date()) {
|
|
174
|
+
this.logger.info(`One-off notification ${createdNotification.id} sent immediately`);
|
|
175
|
+
await this.send(createdNotification);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
this.logger.info(`One-off notification ${createdNotification.id} scheduled for ${notification.sendAfter}`);
|
|
179
|
+
}
|
|
180
|
+
return createdNotification;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Updates a one-off notification and re-sends it if the sendAfter date is in the past.
|
|
184
|
+
*
|
|
185
|
+
* @param notificationId - The ID of the notification to update
|
|
186
|
+
* @param notification - The partial notification data to update
|
|
187
|
+
* @returns The updated database notification
|
|
188
|
+
*/
|
|
189
|
+
async updateOneOffNotification(notificationId, notification) {
|
|
190
|
+
// Validate email or phone format if provided
|
|
191
|
+
if (notification.emailOrPhone !== undefined) {
|
|
192
|
+
this.validateEmailOrPhone(notification.emailOrPhone);
|
|
193
|
+
}
|
|
194
|
+
const updatedNotification = await this.backend.persistOneOffNotificationUpdate(notificationId, notification);
|
|
195
|
+
this.logger.info(`One-off notification ${notificationId} updated`);
|
|
196
|
+
if (!updatedNotification.sendAfter || updatedNotification.sendAfter <= new Date()) {
|
|
197
|
+
this.logger.info(`One-off notification ${notificationId} sent after update`);
|
|
198
|
+
await this.send(updatedNotification);
|
|
199
|
+
}
|
|
200
|
+
return updatedNotification;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Validates that an email or phone number has a basic valid format.
|
|
204
|
+
*
|
|
205
|
+
* @param emailOrPhone - The email or phone string to validate
|
|
206
|
+
* @throws Error if the format is invalid
|
|
207
|
+
*/
|
|
208
|
+
validateEmailOrPhone(emailOrPhone) {
|
|
209
|
+
// Basic non-empty check
|
|
210
|
+
if (emailOrPhone === '' || emailOrPhone.trim() === '') {
|
|
211
|
+
throw new Error('emailOrPhone cannot be empty');
|
|
212
|
+
}
|
|
213
|
+
// Check if it's an email (has @ with characters before and after)
|
|
214
|
+
const isEmail = /^.+@.+\..+$/.test(emailOrPhone);
|
|
215
|
+
// Check if it's a phone (10-15 digits, optionally starting with +)
|
|
216
|
+
const isPhone = /^\+?[0-9]{10,15}$/.test(emailOrPhone);
|
|
217
|
+
if (!isEmail && !isPhone) {
|
|
218
|
+
throw new Error('Invalid email or phone format');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
122
221
|
async getAllFutureNotifications() {
|
|
123
222
|
return this.backend.getAllFutureNotifications();
|
|
124
223
|
}
|
|
@@ -148,6 +247,16 @@ class VintaSend {
|
|
|
148
247
|
async getNotification(notificationId, forUpdate = false) {
|
|
149
248
|
return this.backend.getNotification(notificationId, forUpdate);
|
|
150
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Gets a one-off notification by ID.
|
|
252
|
+
*
|
|
253
|
+
* @param notificationId - The ID of the one-off notification to retrieve
|
|
254
|
+
* @param forUpdate - Whether the notification is being retrieved for update (default: false)
|
|
255
|
+
* @returns The one-off notification or null if not found
|
|
256
|
+
*/
|
|
257
|
+
async getOneOffNotification(notificationId, forUpdate = false) {
|
|
258
|
+
return this.backend.getOneOffNotification(notificationId, forUpdate);
|
|
259
|
+
}
|
|
151
260
|
async markRead(notificationId, checkIsSent = true) {
|
|
152
261
|
const notification = await this.backend.markAsRead(notificationId, checkIsSent);
|
|
153
262
|
this.logger.info(`Notification ${notificationId} marked as read`);
|
|
@@ -169,6 +278,14 @@ class VintaSend {
|
|
|
169
278
|
}
|
|
170
279
|
return;
|
|
171
280
|
}
|
|
281
|
+
// Check if this is a one-off notification (which cannot be resent this way)
|
|
282
|
+
if ((0, base_notification_adapter_1.isOneOffNotification)(notification)) {
|
|
283
|
+
this.logger.error(`Cannot resend one-off notification ${notificationId} using resendNotification. One-off notifications are not supported.`);
|
|
284
|
+
if (this.options.raiseErrorOnFailedSend) {
|
|
285
|
+
throw new Error(`Cannot resend one-off notification ${notificationId}. One-off notifications must be resent using a different method.`);
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
172
289
|
if (notification.sendAfter && notification.sendAfter > new Date()) {
|
|
173
290
|
this.logger.error(`Notification ${notificationId} is scheduled for the future`);
|
|
174
291
|
if (this.options.raiseErrorOnFailedSend) {
|
|
@@ -263,15 +380,15 @@ class VintaSend {
|
|
|
263
380
|
}
|
|
264
381
|
async migrateToBackend(destinationBackend, batchSize = 5000) {
|
|
265
382
|
let pageNumber = 0;
|
|
266
|
-
let
|
|
267
|
-
while (
|
|
383
|
+
let allNotifications = await this.backend.getNotifications(pageNumber, batchSize);
|
|
384
|
+
while (allNotifications.length > 0) {
|
|
268
385
|
pageNumber += 1;
|
|
269
|
-
const
|
|
386
|
+
const notificationsWithoutId = allNotifications.map((notification) => {
|
|
270
387
|
const { id, ...notificationWithoutId } = notification;
|
|
271
388
|
return notificationWithoutId;
|
|
272
389
|
});
|
|
273
|
-
await destinationBackend.bulkPersistNotifications(
|
|
274
|
-
|
|
390
|
+
await destinationBackend.bulkPersistNotifications(notificationsWithoutId);
|
|
391
|
+
allNotifications = await this.backend.getNotifications(pageNumber, batchSize);
|
|
275
392
|
}
|
|
276
393
|
}
|
|
277
394
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { BaseNotificationTemplateRenderer } from './base-notification-template-renderer';
|
|
2
|
-
import type { Notification } from '../../types/notification';
|
|
3
1
|
import type { Buffer } from 'node:buffer';
|
|
4
2
|
import type { JsonObject } from '../../types/json-values';
|
|
3
|
+
import type { AnyNotification } from '../../types/notification';
|
|
5
4
|
import type { BaseNotificationTypeConfig } from '../../types/notification-type-config';
|
|
5
|
+
import type { BaseNotificationTemplateRenderer } from './base-notification-template-renderer';
|
|
6
6
|
export type Attachment = File | Buffer | string;
|
|
7
7
|
export type EmailTemplate = {
|
|
8
8
|
subject: string;
|
|
9
9
|
body: string;
|
|
10
10
|
};
|
|
11
11
|
export interface BaseEmailTemplateRenderer<Config extends BaseNotificationTypeConfig> extends BaseNotificationTemplateRenderer<Config, EmailTemplate> {
|
|
12
|
-
render(notification:
|
|
12
|
+
render(notification: AnyNotification<Config>, context: JsonObject): Promise<EmailTemplate>;
|
|
13
13
|
}
|