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.
Files changed (133) hide show
  1. package/README.md +227 -12
  2. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/next.config.d.ts +1 -1
  3. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/next.config.js +9 -4
  4. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/forgot-password-notification-context.d.ts +6 -6
  5. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/forgot-password-notification-context.js +16 -17
  6. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/route.d.ts +7 -5
  7. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/route.js +105 -79
  8. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/login/route.d.ts +4 -5
  9. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/login/route.js +96 -66
  10. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/reset-password/route.d.ts +7 -5
  11. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/reset-password/route.js +95 -71
  12. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/email-verification-notification-context.d.ts +6 -6
  13. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/email-verification-notification-context.js +18 -18
  14. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/route.d.ts +4 -5
  15. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/route.js +124 -96
  16. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/verify-email/route.d.ts +7 -5
  17. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/verify-email/route.js +94 -70
  18. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/login/page.d.ts +1 -1
  19. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/login/page.js +67 -55
  20. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/reset-password/[token]/page.d.ts +1 -1
  21. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/reset-password/[token]/page.js +76 -63
  22. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/signup/page.d.ts +1 -1
  23. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/signup/page.js +87 -63
  24. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email/[token]/page.d.ts +1 -1
  25. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email/[token]/page.js +50 -35
  26. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email-sent/page.d.ts +1 -1
  27. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email-sent/page.js +12 -12
  28. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/layout.d.ts +7 -5
  29. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/layout.js +15 -16
  30. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/page.d.ts +1 -1
  31. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/page.js +65 -21
  32. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/AuthLayout.d.ts +7 -4
  33. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/AuthLayout.js +7 -8
  34. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/accordion.d.ts +18 -6
  35. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/accordion.js +86 -48
  36. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert-dialog.d.ts +58 -14
  37. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert-dialog.js +135 -53
  38. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert.d.ts +21 -7
  39. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert.js +85 -49
  40. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/avatar.d.ts +14 -5
  41. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/avatar.js +77 -40
  42. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/button.d.ts +25 -9
  43. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/button.js +80 -58
  44. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/card.d.ts +19 -7
  45. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/card.js +98 -48
  46. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/checkbox.d.ts +6 -3
  47. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/checkbox.js +66 -42
  48. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/collapsible.d.ts +10 -4
  49. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/collapsible.js +48 -35
  50. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dialog.d.ts +40 -13
  51. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dialog.js +116 -50
  52. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dropdown-menu.d.ts +83 -19
  53. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dropdown-menu.js +170 -68
  54. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/form.d.ts +53 -21
  55. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/form.js +137 -83
  56. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/input.d.ts +8 -2
  57. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/input.js +60 -37
  58. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/label.d.ts +10 -4
  59. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/label.js +61 -40
  60. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/menubar.d.ts +77 -23
  61. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/menubar.js +188 -64
  62. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/select.d.ts +48 -12
  63. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/select.js +148 -66
  64. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/textarea.d.ts +8 -2
  65. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/textarea.js +59 -37
  66. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/auth.d.ts +10 -10
  67. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/auth.js +54 -55
  68. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/core.d.ts +14 -11
  69. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/core.js +1 -2
  70. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/email.js +0 -1
  71. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/logger.d.ts +3 -3
  72. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/logger.js +9 -10
  73. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/schemas/auth.d.ts +66 -32
  74. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/schemas/auth.js +74 -54
  75. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/auth.js +7 -8
  76. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications-with-queue.d.ts +14 -4
  77. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications-with-queue.js +14 -10
  78. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications.d.ts +11 -7
  79. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications.js +39 -26
  80. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/temporal-queue-service.d.ts +11 -8
  81. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/temporal-queue-service.js +14 -15
  82. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/temporal.d.ts +1 -1
  83. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/temporal.js +9 -10
  84. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/utils.d.ts +1 -1
  85. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/utils.js +4 -5
  86. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/activities.d.ts +6 -5
  87. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/activities.js +14 -14
  88. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/config.d.ts +1 -1
  89. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/config.js +2 -3
  90. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/constants.d.ts +1 -1
  91. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/constants.js +1 -2
  92. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/worker.js +20 -22
  93. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/workflows.d.ts +3 -2
  94. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/workflows.js +10 -9
  95. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/tailwind.config.d.ts +74 -74
  96. package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/tailwind.config.js +80 -81
  97. package/dist/implementations/vintasend-nodemailer/src/index.js +6 -4
  98. package/dist/implementations/vintasend-nodemailer/src/nodemailer-notification-adapter.d.ts +36 -14
  99. package/dist/implementations/vintasend-nodemailer/src/nodemailer-notification-adapter.js +26 -28
  100. package/dist/implementations/vintasend-prisma/src/index.js +6 -4
  101. package/dist/implementations/vintasend-prisma/src/prisma-notification-backend.d.ts +232 -138
  102. package/dist/implementations/vintasend-prisma/src/prisma-notification-backend.js +275 -262
  103. package/dist/implementations/vintasend-pug/src/index.js +6 -4
  104. package/dist/implementations/vintasend-pug/src/pug-email-template-renderer.d.ts +18 -7
  105. package/dist/implementations/vintasend-pug/src/pug-email-template-renderer.js +19 -21
  106. package/dist/implementations/vintasend-winston/src/index.js +6 -4
  107. package/dist/implementations/vintasend-winston/src/winston-logger.d.ts +6 -6
  108. package/dist/implementations/vintasend-winston/src/winston-logger.js +65 -50
  109. package/dist/index.d.ts +13 -5
  110. package/dist/index.js +13 -1
  111. package/dist/services/attachment-manager/base-attachment-manager.d.ts +42 -0
  112. package/dist/services/attachment-manager/base-attachment-manager.js +115 -0
  113. package/dist/services/attachment-manager/local-file-attachment-manager.d.ts +58 -0
  114. package/dist/services/attachment-manager/local-file-attachment-manager.js +192 -0
  115. package/dist/services/notification-adapters/base-notification-adapter.d.ts +36 -4
  116. package/dist/services/notification-adapters/base-notification-adapter.js +73 -4
  117. package/dist/services/notification-backends/base-notification-backend.d.ts +54 -11
  118. package/dist/services/notification-backends/base-notification-backend.js +12 -0
  119. package/dist/services/notification-context-generators-map.d.ts +1 -1
  120. package/dist/services/notification-context-registry.d.ts +14 -9
  121. package/dist/services/notification-context-registry.js +30 -31
  122. package/dist/services/notification-queue-service/base-notification-queue-service.d.ts +1 -1
  123. package/dist/services/notification-service.d.ts +73 -13
  124. package/dist/services/notification-service.js +125 -8
  125. package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +3 -3
  126. package/dist/services/notification-template-renderers/base-notification-template-renderer.d.ts +2 -2
  127. package/dist/types/attachment.d.ts +42 -0
  128. package/dist/types/attachment.js +7 -0
  129. package/dist/types/notification-type-config.d.ts +2 -2
  130. package/dist/types/notification.d.ts +21 -0
  131. package/dist/types/one-off-notification.d.ts +72 -0
  132. package/dist/types/one-off-notification.js +2 -0
  133. 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<DatabaseNotification<Config>[]>;
6
- getPendingNotifications(page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
7
- getAllFutureNotifications(): Promise<DatabaseNotification<Config>[]>;
8
- getFutureNotifications(page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
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<DatabaseNotification<Config>[]>;
13
- getNotifications(page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
14
- bulkPersistNotifications(notifications: Omit<Notification<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
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<DatabaseNotification<Config>>;
17
- markAsFailed(notificationId: Config['NotificationIdType'], checkIsPending: boolean): Promise<DatabaseNotification<Config>>;
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<DatabaseNotification<Config> | null>;
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 "../types/notification-context-generators";
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
- generate(params: Record<string, JsonPrimitive>): JsonObject | Promise<JsonObject>;
3
+ generate(params: Record<string, JsonPrimitive>): JsonObject | Promise<JsonObject>;
4
4
  }
5
5
  export declare class NotificationContextRegistry<T extends Record<string, ContextGenerator>> {
6
- private static instance;
7
- private readonly contexts;
8
- private constructor();
9
- static initialize<T extends Record<string, ContextGenerator>>(contexts: T): NotificationContextRegistry<T>;
10
- static getInstance<T extends Record<string, ContextGenerator>>(): NotificationContextRegistry<T>;
11
- static resetInstance(): void;
12
- getContext<K extends keyof T>(key: K, parameters: Parameters<T[K]['generate']>[0]): Promise<JsonObject>;
13
- getAvailableContexts(): (keyof T)[];
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
- "use strict";
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
- constructor(contexts) {
6
- this.contexts = contexts;
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
- static initialize(contexts) {
9
- if (NotificationContextRegistry.instance) {
10
- throw new Error('NotificationContextRegistry already initialized');
11
- }
12
- NotificationContextRegistry.instance = new NotificationContextRegistry(contexts);
13
- return NotificationContextRegistry.instance;
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
- static getInstance() {
16
- if (!NotificationContextRegistry.instance) {
17
- throw new Error('NotificationContextRegistry not initialized');
18
- }
19
- return NotificationContextRegistry.instance;
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
- static resetInstance() {
22
- NotificationContextRegistry.instance = null;
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 "../../types/notification-type-config";
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 { BaseNotificationAdapter } from './notification-adapters/base-notification-adapter';
5
- import type { BaseNotificationTemplateRenderer } from './notification-template-renderers/base-notification-template-renderer';
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
- create<AdaptersList extends BaseNotificationAdapter<BaseNotificationTemplateRenderer<Config>, Config>[], Backend extends BaseNotificationBackend<Config>, Logger extends BaseLogger, QueueService extends BaseNotificationQueueService<Config>>(adapters: AdaptersList, backend: Backend, logger: Logger, contextGeneratorsMap: BaseNotificationTypeConfig['ContextMap'], queueService?: QueueService, options?: VintaSendOptions): VintaSend<Config, AdaptersList, Backend, Logger, QueueService>;
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: DatabaseNotification<Config>): Promise<void>;
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
- getAllFutureNotifications(): Promise<DatabaseNotification<Config>[]>;
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<DatabaseNotification<Config>[]>;
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<DatabaseNotification<Config>[]>;
34
- getNotification(notificationId: Config['NotificationIdType'], forUpdate?: boolean): Promise<DatabaseNotification<Config> | null>;
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<Notification<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
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
- create(adapters, backend, logger, contextGeneratorsMap, queueService, options = {
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 notifications = await this.backend.getNotifications(pageNumber, batchSize);
267
- while (notifications.length > 0) {
383
+ let allNotifications = await this.backend.getNotifications(pageNumber, batchSize);
384
+ while (allNotifications.length > 0) {
268
385
  pageNumber += 1;
269
- const notificationsWitoutId = notifications.map((notification) => {
386
+ const notificationsWithoutId = allNotifications.map((notification) => {
270
387
  const { id, ...notificationWithoutId } = notification;
271
388
  return notificationWithoutId;
272
389
  });
273
- await destinationBackend.bulkPersistNotifications(notificationsWitoutId);
274
- notifications = await this.backend.getNotifications(pageNumber, batchSize);
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: Notification<Config>, context: JsonObject): Promise<EmailTemplate>;
12
+ render(notification: AnyNotification<Config>, context: JsonObject): Promise<EmailTemplate>;
13
13
  }