vintasend 0.3.0 → 0.4.3

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 (131) hide show
  1. package/README.md +142 -3
  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 -6
  110. package/dist/index.js +12 -3
  111. package/dist/services/attachment-manager/base-attachment-manager.d.ts +42 -0
  112. package/dist/services/attachment-manager/base-attachment-manager.js +114 -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 +17 -4
  116. package/dist/services/notification-adapters/base-notification-adapter.js +23 -5
  117. package/dist/services/notification-backends/base-notification-backend.d.ts +39 -1
  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 +35 -7
  124. package/dist/services/notification-service.js +47 -3
  125. package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +7 -2
  126. package/dist/types/attachment.d.ts +42 -0
  127. package/dist/types/attachment.js +7 -0
  128. package/dist/types/notification-type-config.d.ts +2 -2
  129. package/dist/types/notification.d.ts +5 -1
  130. package/dist/types/one-off-notification.d.ts +4 -0
  131. package/package.json +13 -11
@@ -6,7 +6,7 @@ exports.isOneOffNotification = isOneOffNotification;
6
6
  * Type guard to check if a notification is a one-off notification
7
7
  */
8
8
  function isOneOffNotification(notification) {
9
- return 'emailOrPhone' in notification && 'firstName' in notification && 'lastName' in notification;
9
+ return ('emailOrPhone' in notification && 'firstName' in notification && 'lastName' in notification);
10
10
  }
11
11
  class BaseNotificationAdapter {
12
12
  constructor(templateRenderer, notificationType, enqueueNotifications) {
@@ -15,15 +15,31 @@ class BaseNotificationAdapter {
15
15
  this.enqueueNotifications = enqueueNotifications;
16
16
  this.key = null;
17
17
  this.backend = null;
18
+ this.logger = null;
18
19
  }
19
- ;
20
- send(notification, context) {
20
+ send(_notification, _context) {
21
21
  if (this.backend === null) {
22
22
  return Promise.reject(new Error('Backend not injected'));
23
23
  }
24
24
  return Promise.resolve();
25
25
  }
26
- ;
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
+ }
27
43
  /**
28
44
  * Get the recipient email address from a notification.
29
45
  * For one-off notifications, returns the emailOrPhone field directly.
@@ -71,6 +87,8 @@ class BaseNotificationAdapter {
71
87
  injectBackend(backend) {
72
88
  this.backend = backend;
73
89
  }
74
- ;
90
+ injectLogger(logger) {
91
+ this.logger = logger;
92
+ }
75
93
  }
76
94
  exports.BaseNotificationAdapter = BaseNotificationAdapter;
@@ -1,5 +1,6 @@
1
+ import type { AttachmentFileRecord, StoredAttachment } from '../../types/attachment';
1
2
  import type { InputJsonValue } from '../../types/json-values';
2
- import type { DatabaseNotification, Notification, DatabaseOneOffNotification, OneOffNotificationInput, AnyNotification, AnyDatabaseNotification } 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
6
  getAllPendingNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
@@ -27,4 +28,41 @@ export interface BaseNotificationBackend<Config extends BaseNotificationTypeConf
27
28
  getOneOffNotification(notificationId: Config['NotificationIdType'], forUpdate: boolean): Promise<DatabaseOneOffNotification<Config> | null>;
28
29
  getAllOneOffNotifications(): Promise<DatabaseOneOffNotification<Config>[]>;
29
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>;
30
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,26 +1,54 @@
1
- import type { DatabaseNotification, Notification, AnyNotification, AnyDatabaseNotification, DatabaseOneOffNotification } from '../types/notification';
2
- import type { OneOffNotificationInput } from '../types/one-off-notification';
3
1
  import type { JsonObject } from '../types/json-values';
2
+ import type { AnyDatabaseNotification, AnyNotification, DatabaseNotification, DatabaseOneOffNotification, Notification } from '../types/notification';
4
3
  import type { BaseNotificationTypeConfig } from '../types/notification-type-config';
4
+ import type { OneOffNotificationInput } from '../types/one-off-notification';
5
+ import type { BaseAttachmentManager } from './attachment-manager/base-attachment-manager';
6
+ import type { BaseLogger } from './loggers/base-logger';
5
7
  import { type BaseNotificationAdapter } from './notification-adapters/base-notification-adapter';
6
- import type { BaseNotificationTemplateRenderer } from './notification-template-renderers/base-notification-template-renderer';
7
8
  import type { BaseNotificationBackend } from './notification-backends/base-notification-backend';
8
- import type { BaseLogger } from './loggers/base-logger';
9
9
  import type { BaseNotificationQueueService } from './notification-queue-service/base-notification-queue-service';
10
+ import type { BaseNotificationTemplateRenderer } from './notification-template-renderers/base-notification-template-renderer';
10
11
  type VintaSendOptions = {
11
12
  raiseErrorOnFailedSend: boolean;
12
13
  };
13
14
  export declare class VintaSendFactory<Config extends BaseNotificationTypeConfig> {
14
- 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>;
15
42
  }
16
- 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> {
17
44
  private adapters;
18
45
  private backend;
19
46
  private logger;
20
47
  private queueService?;
48
+ private attachmentManager?;
21
49
  private options;
22
50
  private contextGeneratorsMap;
23
- 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);
24
52
  registerQueueService(queueService: QueueService): void;
25
53
  send(notification: AnyDatabaseNotification<Config>): Promise<void>;
26
54
  createNotification(notification: Omit<Notification<Config>, 'id'>): Promise<DatabaseNotification<Config>>;
@@ -4,25 +4,69 @@ exports.VintaSend = exports.VintaSendFactory = void 0;
4
4
  const base_notification_adapter_1 = require("./notification-adapters/base-notification-adapter");
5
5
  const notification_context_generators_map_1 = require("./notification-context-generators-map");
6
6
  class VintaSendFactory {
7
- 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 = {
8
34
  raiseErrorOnFailedSend: false,
9
35
  }) {
10
- return new VintaSend(adapters, backend, logger, contextGeneratorsMap, queueService, options);
36
+ return new VintaSend(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options);
11
37
  }
12
38
  }
13
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
+ }
14
46
  class VintaSend {
15
- constructor(adapters, backend, logger, contextGeneratorsMap, queueService, options = {
47
+ constructor(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options = {
16
48
  raiseErrorOnFailedSend: false,
17
49
  }) {
18
50
  this.adapters = adapters;
19
51
  this.backend = backend;
20
52
  this.logger = logger;
21
53
  this.queueService = queueService;
54
+ this.attachmentManager = attachmentManager;
22
55
  this.options = options;
23
56
  this.contextGeneratorsMap = new notification_context_generators_map_1.NotificationContextGeneratorsMap(contextGeneratorsMap);
24
57
  for (const adapter of adapters) {
25
58
  adapter.injectBackend(backend);
59
+ adapter.injectLogger(logger);
60
+ // Inject logger into template renderer if it supports it
61
+ // biome-ignore lint/suspicious/noExplicitAny: accessing protected templateRenderer property
62
+ const templateRenderer = adapter.templateRenderer;
63
+ if (templateRenderer && typeof templateRenderer.injectLogger === 'function') {
64
+ templateRenderer.injectLogger(logger);
65
+ }
66
+ }
67
+ // Inject attachment manager into backend if both exist
68
+ if (this.attachmentManager && hasAttachmentManagerInjection(backend)) {
69
+ backend.injectAttachmentManager(this.attachmentManager);
26
70
  }
27
71
  }
28
72
  registerQueueService(queueService) {
@@ -1,8 +1,9 @@
1
- import type { BaseNotificationTemplateRenderer } from './base-notification-template-renderer';
2
- import type { AnyNotification } 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 { BaseLogger } from '../loggers/base-logger';
6
+ import type { BaseNotificationTemplateRenderer } from './base-notification-template-renderer';
6
7
  export type Attachment = File | Buffer | string;
7
8
  export type EmailTemplate = {
8
9
  subject: string;
@@ -10,4 +11,8 @@ export type EmailTemplate = {
10
11
  };
11
12
  export interface BaseEmailTemplateRenderer<Config extends BaseNotificationTypeConfig> extends BaseNotificationTemplateRenderer<Config, EmailTemplate> {
12
13
  render(notification: AnyNotification<Config>, context: JsonObject): Promise<EmailTemplate>;
14
+ /**
15
+ * Inject logger into the template renderer (optional - called by VintaSend when logger exists)
16
+ */
17
+ injectLogger?(logger: BaseLogger): void;
13
18
  }
@@ -0,0 +1,42 @@
1
+ import type { Readable } from 'node:stream';
2
+ export type FileAttachment = Buffer | ReadableStream | Readable | string;
3
+ export interface NotificationAttachmentUpload {
4
+ file: FileAttachment;
5
+ filename: string;
6
+ contentType?: string;
7
+ description?: string;
8
+ }
9
+ export interface NotificationAttachmentReference {
10
+ fileId: string;
11
+ description?: string;
12
+ }
13
+ export type NotificationAttachment = NotificationAttachmentUpload | NotificationAttachmentReference;
14
+ export declare function isAttachmentReference(attachment: NotificationAttachment): attachment is NotificationAttachmentReference;
15
+ export interface AttachmentFile {
16
+ read(): Promise<Buffer>;
17
+ stream(): Promise<ReadableStream>;
18
+ url(expiresIn?: number): Promise<string>;
19
+ delete(): Promise<void>;
20
+ }
21
+ export interface AttachmentFileRecord {
22
+ id: string;
23
+ filename: string;
24
+ contentType: string;
25
+ size: number;
26
+ checksum: string;
27
+ storageMetadata: Record<string, unknown>;
28
+ createdAt: Date;
29
+ updatedAt: Date;
30
+ }
31
+ export interface StoredAttachment {
32
+ id: string;
33
+ fileId: string;
34
+ filename: string;
35
+ contentType: string;
36
+ size: number;
37
+ checksum: string;
38
+ createdAt: Date;
39
+ file: AttachmentFile;
40
+ description?: string;
41
+ storageMetadata: Record<string, unknown>;
42
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAttachmentReference = isAttachmentReference;
4
+ // Type guard to check if attachment is a reference
5
+ function isAttachmentReference(attachment) {
6
+ return 'fileId' in attachment;
7
+ }
@@ -1,5 +1,5 @@
1
- import type { ContextGenerator } from "./notification-context-generators";
2
- import type { Identifier } from "./identifier";
1
+ import type { Identifier } from './identifier';
2
+ import type { ContextGenerator } from './notification-context-generators';
3
3
  export type BaseNotificationTypeConfig = {
4
4
  ContextMap: Record<string, ContextGenerator>;
5
5
  NotificationIdType: Identifier;
@@ -1,9 +1,10 @@
1
+ import type { NotificationAttachment, StoredAttachment } from './attachment';
1
2
  import type { InputJsonValue, JsonValue } from './json-values';
2
3
  import type { NotificationStatus } from './notification-status';
3
4
  import type { NotificationType } from './notification-type';
4
5
  import type { BaseNotificationTypeConfig } from './notification-type-config';
5
6
  import type { DatabaseOneOffNotification, OneOffNotification, OneOffNotificationInput } from './one-off-notification';
6
- export type { OneOffNotificationInput, OneOffNotificationResendWithContextInput, DatabaseOneOffNotification, OneOffNotification, } from './one-off-notification';
7
+ export type { DatabaseOneOffNotification, OneOffNotification, OneOffNotificationInput, OneOffNotificationResendWithContextInput, } from './one-off-notification';
7
8
  export type NotificationInput<Config extends BaseNotificationTypeConfig> = {
8
9
  userId: Config['UserIdType'];
9
10
  notificationType: NotificationType;
@@ -14,6 +15,7 @@ export type NotificationInput<Config extends BaseNotificationTypeConfig> = {
14
15
  sendAfter: Date | null;
15
16
  subjectTemplate: string | null;
16
17
  extraParams: InputJsonValue | null;
18
+ attachments?: NotificationAttachment[];
17
19
  };
18
20
  export type NotificationResendWithContextInput<Config extends BaseNotificationTypeConfig> = {
19
21
  userId: Config['UserIdType'];
@@ -26,6 +28,7 @@ export type NotificationResendWithContextInput<Config extends BaseNotificationTy
26
28
  sendAfter: Date | null;
27
29
  subjectTemplate: string | null;
28
30
  extraParams: InputJsonValue | null;
31
+ attachments?: NotificationAttachment[];
29
32
  };
30
33
  export type DatabaseNotification<Config extends BaseNotificationTypeConfig> = {
31
34
  id: Config['NotificationIdType'];
@@ -45,6 +48,7 @@ export type DatabaseNotification<Config extends BaseNotificationTypeConfig> = {
45
48
  readAt: Date | null;
46
49
  createdAt?: Date;
47
50
  updatedAt?: Date;
51
+ attachments?: StoredAttachment[];
48
52
  };
49
53
  export type Notification<Config extends BaseNotificationTypeConfig> = NotificationInput<Config> | NotificationResendWithContextInput<Config> | DatabaseNotification<Config>;
50
54
  /**
@@ -1,3 +1,4 @@
1
+ import type { NotificationAttachment, StoredAttachment } from './attachment';
1
2
  import type { InputJsonValue, JsonValue } from './json-values';
2
3
  import type { NotificationStatus } from './notification-status';
3
4
  import type { NotificationType } from './notification-type';
@@ -18,6 +19,7 @@ export type OneOffNotificationInput<Config extends BaseNotificationTypeConfig> =
18
19
  sendAfter: Date | null;
19
20
  subjectTemplate: string | null;
20
21
  extraParams: InputJsonValue | null;
22
+ attachments?: NotificationAttachment[];
21
23
  };
22
24
  /**
23
25
  * Input type for resending a one-off notification with stored context.
@@ -36,6 +38,7 @@ export type OneOffNotificationResendWithContextInput<Config extends BaseNotifica
36
38
  sendAfter: Date | null;
37
39
  subjectTemplate: string | null;
38
40
  extraParams: InputJsonValue | null;
41
+ attachments?: NotificationAttachment[];
39
42
  };
40
43
  /**
41
44
  * Database representation of a one-off notification.
@@ -61,6 +64,7 @@ export type DatabaseOneOffNotification<Config extends BaseNotificationTypeConfig
61
64
  readAt: Date | null;
62
65
  createdAt?: Date;
63
66
  updatedAt?: Date;
67
+ attachments?: StoredAttachment[];
64
68
  };
65
69
  /**
66
70
  * Union type representing any one-off notification (input, resend, or database).
package/package.json CHANGED
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "name": "vintasend",
3
- "version": "0.3.0",
3
+ "version": "0.4.3",
4
4
  "main": "dist/index.js",
5
- "files": [
6
- "dist"
7
- ],
5
+ "files": ["dist"],
8
6
  "author": "Hugo Bessa",
9
7
  "license": "MIT",
10
8
  "scripts": {
11
9
  "lint": "biome check .",
12
10
  "format": "biome format .",
13
- "check": "biome check --apply .",
11
+ "check": "biome check --write .",
14
12
  "build": "tsc",
15
13
  "prepublishOnly": "npm run build",
16
14
  "test": "jest",
@@ -18,11 +16,15 @@
18
16
  "test:coverage": "jest --coverage"
19
17
  },
20
18
  "devDependencies": {
21
- "@biomejs/biome": "1.9.4",
22
- "@types/jest": "^29.5.14",
23
- "@types/node": "^22.13.1",
24
- "jest": "^29.7.0",
25
- "ts-jest": "^29.2.6",
26
- "typescript": "^5.8.2"
19
+ "@biomejs/biome": "^2.3.11",
20
+ "@types/jest": "^30.0.0",
21
+ "@types/mime-types": "^3.0.1",
22
+ "@types/node": "^25.0.8",
23
+ "jest": "^30.2.0",
24
+ "ts-jest": "^29.4.6",
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "dependencies": {
28
+ "mime-types": "^3.0.2"
27
29
  }
28
30
  }