vintasend 0.6.2 → 0.7.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 CHANGED
@@ -244,8 +244,92 @@ One-off notifications are stored in the same table as regular notifications usin
244
244
  - **Regular notifications**: Have `userId` set, `emailOrPhone` is null
245
245
  - **One-off notifications**: Have `emailOrPhone` set, `userId` is null
246
246
 
247
+ ## Git Commit SHA Tracking
248
+
249
+ VintaSend can persist which source-code version executed each notification send/render flow.
250
+
251
+ ### Field behavior
252
+
253
+ - Persisted notifications include `gitCommitSha: string | null`
254
+ - Notification input/resend payloads do not accept this field (`gitCommitSha` is system-managed)
255
+ - The SHA is resolved at execution time (send/render), not creation time
256
+
257
+ ### Configuring a provider
258
+
259
+ Use `BaseGitCommitShaProvider` with `VintaSendFactory.create(...)`.
260
+
261
+ Preferred factory style (object parameter):
262
+
263
+ ```typescript
264
+ import { VintaSendFactory, type BaseGitCommitShaProvider } from 'vintasend';
265
+
266
+ const gitCommitShaProvider: BaseGitCommitShaProvider = {
267
+ getCurrentGitCommitSha: () => process.env.GIT_COMMIT_SHA ?? null,
268
+ };
269
+
270
+ const vintaSend = new VintaSendFactory<NotificationTypeConfig>().create({
271
+ adapters: [adapter],
272
+ backend,
273
+ logger,
274
+ contextGeneratorsMap,
275
+ gitCommitShaProvider,
276
+ });
277
+ ```
278
+
279
+ > Positional `create(...)` parameters are still supported for backward compatibility, but object-style configuration is recommended.
280
+
281
+ ### Provider examples
282
+
283
+ Environment variable (CI/CD injected):
284
+
285
+ ```typescript
286
+ const envProvider: BaseGitCommitShaProvider = {
287
+ getCurrentGitCommitSha: () => process.env.GIT_COMMIT_SHA ?? null,
288
+ };
289
+ ```
290
+
291
+ Shell command (async):
292
+
293
+ ```typescript
294
+ import { execSync } from 'node:child_process';
295
+
296
+ const shellProvider: BaseGitCommitShaProvider = {
297
+ getCurrentGitCommitSha: async () => {
298
+ try {
299
+ return execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim() || null;
300
+ } catch {
301
+ return null;
302
+ }
303
+ },
304
+ };
305
+ ```
306
+
307
+ Static provider (for deterministic environments):
308
+
309
+ ```typescript
310
+ const staticProvider: BaseGitCommitShaProvider = {
311
+ getCurrentGitCommitSha: () => '0123456789abcdef0123456789abcdef01234567',
312
+ };
313
+ ```
314
+
247
315
  ### Migration Guides
248
316
 
317
+ #### Migrating to v0.7.0 (Git Commit SHA Tracking)
318
+
319
+ This migration is only needed if you're using the Prisma backend.
320
+
321
+ `gitCommitSha` is now persisted on notifications (regular and one-off) as a nullable, system-managed field.
322
+
323
+ 1. Update your Prisma schema (`Notification` model):
324
+ - Add `gitCommitSha String?`
325
+ - Add `@@index([gitCommitSha])`
326
+ 2. Run a migration in your app:
327
+ ```bash
328
+ prisma migrate dev --name add-notification-git-commit-sha
329
+ ```
330
+ 3. Optionally configure a `BaseGitCommitShaProvider` in `VintaSendFactory`.
331
+ 4. Do not add `gitCommitSha` to notification create/resend input payloads (it is provider-managed).
332
+
249
333
  #### Migrating to v0.4.0 (Attachment Support)
250
334
 
251
335
  Version 0.4.0 introduces file attachment support with a **breaking change** to the `VintaSendFactory.create()` method signature.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  export { BaseAttachmentManager } from './services/attachment-manager/base-attachment-manager';
2
2
  export type { LocalFileAttachmentManagerConfig } from './services/attachment-manager/local-file-attachment-manager';
3
3
  export { LocalFileAttachmentManager } from './services/attachment-manager/local-file-attachment-manager';
4
+ export type { BaseGitCommitShaProvider } from './services/git-commit-sha/base-git-commit-sha-provider';
4
5
  export { BaseNotificationAdapter, isOneOffNotification, } from './services/notification-adapters/base-notification-adapter';
5
6
  export type { BaseNotificationBackend } from './services/notification-backends/base-notification-backend';
6
7
  export { supportsAttachments, isFieldFilter } from './services/notification-backends/base-notification-backend';
7
- export type { NotificationFilter, NotificationFilterFields, DateRange, NotificationFilterCapabilities } from './services/notification-backends/base-notification-backend';
8
+ export type { NotificationFilter, NotificationFilterFields, DateRange, NotificationFilterCapabilities, StringFilterLookup, StringFieldFilter, } from './services/notification-backends/base-notification-backend';
8
9
  export type { BaseNotificationQueueService } from './services/notification-queue-service/base-notification-queue-service';
9
10
  export type { VintaSend } from './services/notification-service';
10
11
  export { VintaSendFactory } from './services/notification-service';
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ var base_attachment_manager_1 = require("./services/attachment-manager/base-atta
6
6
  Object.defineProperty(exports, "BaseAttachmentManager", { enumerable: true, get: function () { return base_attachment_manager_1.BaseAttachmentManager; } });
7
7
  var local_file_attachment_manager_1 = require("./services/attachment-manager/local-file-attachment-manager");
8
8
  Object.defineProperty(exports, "LocalFileAttachmentManager", { enumerable: true, get: function () { return local_file_attachment_manager_1.LocalFileAttachmentManager; } });
9
+ // Notification Adapters and Backends
9
10
  var base_notification_adapter_1 = require("./services/notification-adapters/base-notification-adapter");
10
11
  Object.defineProperty(exports, "BaseNotificationAdapter", { enumerable: true, get: function () { return base_notification_adapter_1.BaseNotificationAdapter; } });
11
12
  Object.defineProperty(exports, "isOneOffNotification", { enumerable: true, get: function () { return base_notification_adapter_1.isOneOffNotification; } });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Base interface for Git commit SHA providers.
3
+ *
4
+ * Implementations of this interface can resolve the current Git commit SHA
5
+ * at runtime, allowing VintaSend to automatically track which version of code
6
+ * produced each notification render.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * // Environment variable provider
11
+ * class EnvGitCommitShaProvider implements BaseGitCommitShaProvider {
12
+ * getCurrentGitCommitSha(): string | null {
13
+ * return process.env.GIT_COMMIT_SHA || null;
14
+ * }
15
+ * }
16
+ *
17
+ * // Shell command provider (for development)
18
+ * class ShellGitCommitShaProvider implements BaseGitCommitShaProvider {
19
+ * async getCurrentGitCommitSha(): Promise<string | null> {
20
+ * try {
21
+ * const { execSync } = require('child_process');
22
+ * const sha = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
23
+ * return sha || null;
24
+ * } catch {
25
+ * return null;
26
+ * }
27
+ * }
28
+ * }
29
+ * ```
30
+ */
31
+ export interface BaseGitCommitShaProvider {
32
+ /**
33
+ * Resolve the current Git commit SHA.
34
+ *
35
+ * Must return:
36
+ * - Full 40-character hex SHA (will be normalized to lowercase)
37
+ * - null if SHA cannot be determined
38
+ *
39
+ * May return synchronously or asynchronously.
40
+ * Invalid SHA formats will be rejected at notification creation boundary.
41
+ *
42
+ * @returns The current Git commit SHA, or null if unavailable
43
+ */
44
+ getCurrentGitCommitSha(): string | Promise<string | null> | null;
45
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export type { BaseGitCommitShaProvider } from './base-git-commit-sha-provider';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -12,6 +12,12 @@ export type DateRange = {
12
12
  from?: Date;
13
13
  to?: Date;
14
14
  };
15
+ export type StringFilterLookup = {
16
+ lookup: 'exact' | 'startsWith' | 'endsWith' | 'includes';
17
+ value: string;
18
+ caseSensitive?: boolean;
19
+ };
20
+ export type StringFieldFilter = string | StringFilterLookup;
15
21
  /**
16
22
  * Flat dotted key capability map describing which filter features a backend supports.
17
23
  * Use flat dotted keys for logical operators, fields, and negations:
@@ -35,9 +41,9 @@ export type NotificationFilterFields<Config extends BaseNotificationTypeConfig>
35
41
  notificationType?: NotificationType | NotificationType[];
36
42
  adapterUsed?: string | string[];
37
43
  userId?: Config['UserIdType'];
38
- bodyTemplate?: string;
39
- subjectTemplate?: string;
40
- contextName?: string;
44
+ bodyTemplate?: StringFieldFilter;
45
+ subjectTemplate?: StringFieldFilter;
46
+ contextName?: StringFieldFilter;
41
47
  sendAfterRange?: DateRange;
42
48
  createdAtRange?: DateRange;
43
49
  sentAtRange?: DateRange;
@@ -75,6 +81,10 @@ export declare const DEFAULT_BACKEND_FILTER_CAPABILITIES: {
75
81
  'negation.sendAfterRange': boolean;
76
82
  'negation.createdAtRange': boolean;
77
83
  'negation.sentAtRange': boolean;
84
+ 'stringLookups.startsWith': boolean;
85
+ 'stringLookups.endsWith': boolean;
86
+ 'stringLookups.includes': boolean;
87
+ 'stringLookups.caseInsensitive': boolean;
78
88
  };
79
89
  export interface BaseNotificationBackend<Config extends BaseNotificationTypeConfig> {
80
90
  getAllPendingNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
@@ -178,6 +188,7 @@ export interface BaseNotificationBackend<Config extends BaseNotificationTypeConf
178
188
  * Type guard to check if a filter is a field filter (leaf node).
179
189
  */
180
190
  export declare function isFieldFilter<Config extends BaseNotificationTypeConfig>(filter: NotificationFilter<Config>): filter is NotificationFilterFields<Config>;
191
+ export declare function isStringFilterLookup(value: StringFieldFilter): value is StringFilterLookup;
181
192
  /**
182
193
  * Type guard to check if backend supports attachment operations
183
194
  */
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_BACKEND_FILTER_CAPABILITIES = void 0;
4
4
  exports.isFieldFilter = isFieldFilter;
5
+ exports.isStringFilterLookup = isStringFilterLookup;
5
6
  exports.supportsAttachments = supportsAttachments;
6
7
  exports.DEFAULT_BACKEND_FILTER_CAPABILITIES = {
7
8
  'logical.and': true,
@@ -21,6 +22,10 @@ exports.DEFAULT_BACKEND_FILTER_CAPABILITIES = {
21
22
  'negation.sendAfterRange': true,
22
23
  'negation.createdAtRange': true,
23
24
  'negation.sentAtRange': true,
25
+ 'stringLookups.startsWith': true,
26
+ 'stringLookups.endsWith': true,
27
+ 'stringLookups.includes': true,
28
+ 'stringLookups.caseInsensitive': true,
24
29
  };
25
30
  /**
26
31
  * Type guard to check if a filter is a field filter (leaf node).
@@ -28,6 +33,9 @@ exports.DEFAULT_BACKEND_FILTER_CAPABILITIES = {
28
33
  function isFieldFilter(filter) {
29
34
  return !('and' in filter) && !('or' in filter) && !('not' in filter);
30
35
  }
36
+ function isStringFilterLookup(value) {
37
+ return typeof value === 'object' && value !== null && 'lookup' in value && 'value' in value;
38
+ }
31
39
  /**
32
40
  * Type guard to check if backend supports attachment operations
33
41
  */
@@ -3,6 +3,7 @@ import type { AnyDatabaseNotification, AnyNotification, DatabaseNotification, Da
3
3
  import type { BaseNotificationTypeConfig } from '../types/notification-type-config';
4
4
  import type { OneOffNotificationInput } from '../types/one-off-notification';
5
5
  import type { BaseAttachmentManager } from './attachment-manager/base-attachment-manager';
6
+ import type { BaseGitCommitShaProvider } from './git-commit-sha/base-git-commit-sha-provider';
6
7
  import type { BaseLogger } from './loggers/base-logger';
7
8
  import { type BaseNotificationAdapter } from './notification-adapters/base-notification-adapter';
8
9
  import { type BaseNotificationBackend, type NotificationFilterFields } from './notification-backends/base-notification-backend';
@@ -11,6 +12,16 @@ import type { BaseNotificationTemplateRenderer } from './notification-template-r
11
12
  type VintaSendOptions = {
12
13
  raiseErrorOnFailedSend: boolean;
13
14
  };
15
+ type VintaSendFactoryCreateParams<Config extends BaseNotificationTypeConfig, AdaptersList extends BaseNotificationAdapter<BaseNotificationTemplateRenderer<Config>, Config>[], Backend extends BaseNotificationBackend<Config>, Logger extends BaseLogger, QueueService extends BaseNotificationQueueService<Config>, AttachmentMgr extends BaseAttachmentManager> = {
16
+ adapters: AdaptersList;
17
+ backend: Backend;
18
+ logger: Logger;
19
+ contextGeneratorsMap: BaseNotificationTypeConfig['ContextMap'];
20
+ queueService?: QueueService;
21
+ attachmentManager?: AttachmentMgr;
22
+ options?: VintaSendOptions;
23
+ gitCommitShaProvider?: BaseGitCommitShaProvider;
24
+ };
14
25
  export declare class VintaSendFactory<Config extends BaseNotificationTypeConfig> {
15
26
  /**
16
27
  * Creates a new VintaSend notification service instance
@@ -38,7 +49,11 @@ export declare class VintaSendFactory<Config extends BaseNotificationTypeConfig>
38
49
  * @since v0.4.0 - BREAKING CHANGE: attachmentManager parameter added before options
39
50
  * @see https://github.com/vintasoftware/vintasend-ts/blob/main/README.md#migrating-to-v040-attachment-support
40
51
  */
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>;
52
+ create<AdaptersList extends BaseNotificationAdapter<BaseNotificationTemplateRenderer<Config>, Config>[], Backend extends BaseNotificationBackend<Config>, Logger extends BaseLogger, QueueService extends BaseNotificationQueueService<Config>, AttachmentMgr extends BaseAttachmentManager>(params: VintaSendFactoryCreateParams<Config, AdaptersList, Backend, Logger, QueueService, AttachmentMgr>): VintaSend<Config, AdaptersList, Backend, Logger, QueueService, AttachmentMgr>;
53
+ /**
54
+ * @deprecated Use the object parameter overload instead.
55
+ */
56
+ 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, gitCommitShaProvider?: BaseGitCommitShaProvider): VintaSend<Config, AdaptersList, Backend, Logger, QueueService, AttachmentMgr>;
42
57
  }
43
58
  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> {
44
59
  private adapters;
@@ -47,9 +62,14 @@ export declare class VintaSend<Config extends BaseNotificationTypeConfig, Adapte
47
62
  private queueService?;
48
63
  private attachmentManager?;
49
64
  private options;
65
+ private gitCommitShaProvider?;
50
66
  private contextGeneratorsMap;
51
- constructor(adapters: AdaptersList, backend: Backend, logger: Logger, contextGeneratorsMap: Config['ContextMap'], queueService?: QueueService | undefined, attachmentManager?: AttachmentMgr | undefined, options?: VintaSendOptions);
67
+ constructor(adapters: AdaptersList, backend: Backend, logger: Logger, contextGeneratorsMap: Config['ContextMap'], queueService?: QueueService | undefined, attachmentManager?: AttachmentMgr | undefined, options?: VintaSendOptions, gitCommitShaProvider?: BaseGitCommitShaProvider | undefined);
52
68
  registerQueueService(queueService: QueueService): void;
69
+ private normalizeGitCommitSha;
70
+ private resolveGitCommitShaForExecution;
71
+ private persistGitCommitShaForExecution;
72
+ private resolveAndPersistGitCommitShaForExecution;
53
73
  send(notification: AnyDatabaseNotification<Config>): Promise<void>;
54
74
  createNotification(notification: Omit<Notification<Config>, 'id'>): Promise<DatabaseNotification<Config>>;
55
75
  updateNotification(notificationId: Config['NotificationIdType'], notification: Partial<Omit<Notification<Config>, 'id'>>): Promise<DatabaseNotification<Config>>;
@@ -105,6 +125,10 @@ export declare class VintaSend<Config extends BaseNotificationTypeConfig, Adapte
105
125
  'negation.sendAfterRange': boolean;
106
126
  'negation.createdAtRange': boolean;
107
127
  'negation.sentAtRange': boolean;
128
+ 'stringLookups.startsWith': boolean;
129
+ 'stringLookups.endsWith': boolean;
130
+ 'stringLookups.includes': boolean;
131
+ 'stringLookups.caseInsensitive': boolean;
108
132
  }>;
109
133
  /**
110
134
  * Gets a one-off notification by ID.
@@ -5,36 +5,16 @@ const base_notification_adapter_1 = require("./notification-adapters/base-notifi
5
5
  const base_notification_backend_1 = require("./notification-backends/base-notification-backend");
6
6
  const notification_context_generators_map_1 = require("./notification-context-generators-map");
7
7
  class VintaSendFactory {
8
- /**
9
- * Creates a new VintaSend notification service instance
10
- *
11
- * @param adapters - Array of notification adapters (email, SMS, push, etc.)
12
- * @param backend - Notification storage backend
13
- * @param logger - Logger instance
14
- * @param contextGeneratorsMap - Map of context generators for notification rendering
15
- * @param queueService - Optional queue service for background notification processing
16
- * @param attachmentManager - Optional attachment manager for file handling
17
- * @param options - Optional configuration options
18
- *
19
- * @example
20
- * // Without attachments or options
21
- * factory.create(adapters, backend, logger, contextGeneratorsMap);
22
- *
23
- * @example
24
- * // With queue service and options (note: pass undefined for attachmentManager)
25
- * factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, undefined, { raiseErrorOnFailedSend: true });
26
- *
27
- * @example
28
- * // With attachments and options
29
- * factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, { raiseErrorOnFailedSend: true });
30
- *
31
- * @since v0.4.0 - BREAKING CHANGE: attachmentManager parameter added before options
32
- * @see https://github.com/vintasoftware/vintasend-ts/blob/main/README.md#migrating-to-v040-attachment-support
33
- */
34
- create(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options = {
8
+ create(adaptersOrParams, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options = {
35
9
  raiseErrorOnFailedSend: false,
36
- }) {
37
- return new VintaSend(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options);
10
+ }, gitCommitShaProvider) {
11
+ var _a;
12
+ if (!Array.isArray(adaptersOrParams)) {
13
+ return new VintaSend(adaptersOrParams.adapters, adaptersOrParams.backend, adaptersOrParams.logger, adaptersOrParams.contextGeneratorsMap, adaptersOrParams.queueService, adaptersOrParams.attachmentManager, (_a = adaptersOrParams.options) !== null && _a !== void 0 ? _a : {
14
+ raiseErrorOnFailedSend: false,
15
+ }, adaptersOrParams.gitCommitShaProvider);
16
+ }
17
+ return new VintaSend(adaptersOrParams, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options, gitCommitShaProvider);
38
18
  }
39
19
  }
40
20
  exports.VintaSendFactory = VintaSendFactory;
@@ -47,13 +27,14 @@ function hasAttachmentManagerInjection(backend) {
47
27
  class VintaSend {
48
28
  constructor(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, options = {
49
29
  raiseErrorOnFailedSend: false,
50
- }) {
30
+ }, gitCommitShaProvider) {
51
31
  this.adapters = adapters;
52
32
  this.backend = backend;
53
33
  this.logger = logger;
54
34
  this.queueService = queueService;
55
35
  this.attachmentManager = attachmentManager;
56
36
  this.options = options;
37
+ this.gitCommitShaProvider = gitCommitShaProvider;
57
38
  this.contextGeneratorsMap = new notification_context_generators_map_1.NotificationContextGeneratorsMap(contextGeneratorsMap);
58
39
  for (const adapter of adapters) {
59
40
  adapter.injectBackend(backend);
@@ -77,17 +58,56 @@ class VintaSend {
77
58
  registerQueueService(queueService) {
78
59
  this.queueService = queueService;
79
60
  }
61
+ normalizeGitCommitSha(gitCommitSha) {
62
+ const normalizedSha = gitCommitSha.trim().toLowerCase();
63
+ if (!/^[a-f0-9]{40}$/.test(normalizedSha)) {
64
+ throw new Error('Invalid gitCommitSha resolved by provider. Expected a 40-character hexadecimal SHA.');
65
+ }
66
+ return normalizedSha;
67
+ }
68
+ async resolveGitCommitShaForExecution() {
69
+ if (!this.gitCommitShaProvider) {
70
+ return null;
71
+ }
72
+ const resolvedGitCommitSha = await this.gitCommitShaProvider.getCurrentGitCommitSha();
73
+ if (resolvedGitCommitSha === null) {
74
+ return null;
75
+ }
76
+ return this.normalizeGitCommitSha(resolvedGitCommitSha);
77
+ }
78
+ async persistGitCommitShaForExecution(notification, gitCommitSha) {
79
+ var _a;
80
+ const currentGitCommitSha = (_a = notification.gitCommitSha) !== null && _a !== void 0 ? _a : null;
81
+ if (currentGitCommitSha === gitCommitSha) {
82
+ return notification;
83
+ }
84
+ if ((0, base_notification_adapter_1.isOneOffNotification)(notification)) {
85
+ const oneOffNotificationUpdate = {
86
+ gitCommitSha,
87
+ };
88
+ return this.backend.persistOneOffNotificationUpdate(notification.id, oneOffNotificationUpdate);
89
+ }
90
+ const notificationUpdate = {
91
+ gitCommitSha,
92
+ };
93
+ return this.backend.persistNotificationUpdate(notification.id, notificationUpdate);
94
+ }
95
+ async resolveAndPersistGitCommitShaForExecution(notification) {
96
+ const gitCommitSha = await this.resolveGitCommitShaForExecution();
97
+ return this.persistGitCommitShaForExecution(notification, gitCommitSha);
98
+ }
80
99
  async send(notification) {
81
100
  var _a;
82
- const adaptersOfType = this.adapters.filter((adapter) => adapter.notificationType === notification.notificationType);
101
+ const notificationWithExecutionGitCommitSha = await this.resolveAndPersistGitCommitShaForExecution(notification);
102
+ const adaptersOfType = this.adapters.filter((adapter) => adapter.notificationType === notificationWithExecutionGitCommitSha.notificationType);
83
103
  if (adaptersOfType.length === 0) {
84
- this.logger.error(`No adapter found for notification type ${notification.notificationType}`);
104
+ this.logger.error(`No adapter found for notification type ${notificationWithExecutionGitCommitSha.notificationType}`);
85
105
  if (this.options.raiseErrorOnFailedSend) {
86
- throw new Error(`No adapter found for notification type ${notification.notificationType}`);
106
+ throw new Error(`No adapter found for notification type ${notificationWithExecutionGitCommitSha.notificationType}`);
87
107
  }
88
108
  return;
89
109
  }
90
- if (!notification.id) {
110
+ if (!notificationWithExecutionGitCommitSha.id) {
91
111
  throw new Error("Notification wasn't created in the database. Please create it first");
92
112
  }
93
113
  for (const adapter of adaptersOfType) {
@@ -97,27 +117,27 @@ class VintaSend {
97
117
  continue;
98
118
  }
99
119
  try {
100
- this.logger.info(`Enqueuing notification ${notification.id} with adapter ${adapter.key}`);
101
- await this.queueService.enqueueNotification(notification.id);
102
- this.logger.info(`Enqueued notification ${notification.id} with adapter ${adapter.key} successfully`);
120
+ this.logger.info(`Enqueuing notification ${notificationWithExecutionGitCommitSha.id} with adapter ${adapter.key}`);
121
+ await this.queueService.enqueueNotification(notificationWithExecutionGitCommitSha.id);
122
+ this.logger.info(`Enqueued notification ${notificationWithExecutionGitCommitSha.id} with adapter ${adapter.key} successfully`);
103
123
  continue;
104
124
  }
105
125
  catch (enqueueError) {
106
- this.logger.error(`Error enqueuing notification ${notification.id}: ${enqueueError} with adapter ${adapter.key}`);
126
+ this.logger.error(`Error enqueuing notification ${notificationWithExecutionGitCommitSha.id}: ${enqueueError} with adapter ${adapter.key}`);
107
127
  continue;
108
128
  }
109
129
  }
110
130
  let context = null;
111
- if (notification.contextUsed) {
112
- context = notification.contextUsed;
131
+ if (notificationWithExecutionGitCommitSha.contextUsed) {
132
+ context = notificationWithExecutionGitCommitSha.contextUsed;
113
133
  }
114
134
  else {
115
135
  try {
116
- context = await this.getNotificationContext(notification.contextName, notification.contextParameters);
117
- this.logger.info(`Generated context for notification ${notification.id}`);
136
+ context = await this.getNotificationContext(notificationWithExecutionGitCommitSha.contextName, notificationWithExecutionGitCommitSha.contextParameters);
137
+ this.logger.info(`Generated context for notification ${notificationWithExecutionGitCommitSha.id}`);
118
138
  }
119
139
  catch (contextError) {
120
- this.logger.error(`Error getting context for notification ${notification.id}: ${contextError}`);
140
+ this.logger.error(`Error getting context for notification ${notificationWithExecutionGitCommitSha.id}: ${contextError}`);
121
141
  if (this.options.raiseErrorOnFailedSend) {
122
142
  throw contextError;
123
143
  }
@@ -125,31 +145,31 @@ class VintaSend {
125
145
  }
126
146
  }
127
147
  try {
128
- this.logger.info(`Sending notification ${notification.id} with adapter ${adapter.key}`);
129
- await adapter.send(notification, context);
130
- this.logger.info(`Sent notification ${notification.id} with adapter ${adapter.key} successfully`);
148
+ this.logger.info(`Sending notification ${notificationWithExecutionGitCommitSha.id} with adapter ${adapter.key}`);
149
+ await adapter.send(notificationWithExecutionGitCommitSha, context);
150
+ this.logger.info(`Sent notification ${notificationWithExecutionGitCommitSha.id} with adapter ${adapter.key} successfully`);
131
151
  }
132
152
  catch (sendError) {
133
- this.logger.error(`Error sending notification ${notification.id} with adapter ${adapter.key}: ${sendError}`);
153
+ this.logger.error(`Error sending notification ${notificationWithExecutionGitCommitSha.id} with adapter ${adapter.key}: ${sendError}`);
134
154
  try {
135
- await this.backend.markAsFailed(notification.id, true);
155
+ await this.backend.markAsFailed(notificationWithExecutionGitCommitSha.id, true);
136
156
  }
137
157
  catch (markFailedError) {
138
- this.logger.error(`Error marking notification ${notification.id} as failed: ${markFailedError}`);
158
+ this.logger.error(`Error marking notification ${notificationWithExecutionGitCommitSha.id} as failed: ${markFailedError}`);
139
159
  }
140
160
  continue;
141
161
  }
142
162
  try {
143
- await this.backend.markAsSent(notification.id, true);
163
+ await this.backend.markAsSent(notificationWithExecutionGitCommitSha.id, true);
144
164
  }
145
165
  catch (markSentError) {
146
- this.logger.error(`Error marking notification ${notification.id} as sent: ${markSentError}`);
166
+ this.logger.error(`Error marking notification ${notificationWithExecutionGitCommitSha.id} as sent: ${markSentError}`);
147
167
  }
148
168
  try {
149
- await this.backend.storeAdapterAndContextUsed(notification.id, (_a = adapter.key) !== null && _a !== void 0 ? _a : 'unknown', context !== null && context !== void 0 ? context : {});
169
+ await this.backend.storeAdapterAndContextUsed(notificationWithExecutionGitCommitSha.id, (_a = adapter.key) !== null && _a !== void 0 ? _a : 'unknown', context !== null && context !== void 0 ? context : {});
150
170
  }
151
171
  catch (storeContextError) {
152
- this.logger.error(`Error storing adapter and context for notification ${notification.id}: ${storeContextError}`);
172
+ this.logger.error(`Error storing adapter and context for notification ${notificationWithExecutionGitCommitSha.id}: ${storeContextError}`);
153
173
  }
154
174
  }
155
175
  }
@@ -373,34 +393,35 @@ class VintaSend {
373
393
  }
374
394
  return;
375
395
  }
376
- const context = await this.getNotificationContext(notification.contextName, notification.contextParameters);
396
+ const notificationWithExecutionGitCommitSha = await this.resolveAndPersistGitCommitShaForExecution(notification);
397
+ const context = await this.getNotificationContext(notificationWithExecutionGitCommitSha.contextName, notificationWithExecutionGitCommitSha.contextParameters);
377
398
  let lastAdapterKey = 'unknown';
378
399
  for (const adapter of enqueueNotificationsAdapters) {
379
400
  lastAdapterKey = (_a = adapter.key) !== null && _a !== void 0 ? _a : 'unknown';
380
401
  try {
381
- await adapter.send(notification, context);
402
+ await adapter.send(notificationWithExecutionGitCommitSha, context);
382
403
  }
383
404
  catch (sendError) {
384
- this.logger.error(`Error sending notification ${notification.id} with adapter ${adapter.key}: ${sendError}`);
405
+ this.logger.error(`Error sending notification ${notificationWithExecutionGitCommitSha.id} with adapter ${adapter.key}: ${sendError}`);
385
406
  try {
386
- await this.backend.markAsFailed(notification.id, true);
407
+ await this.backend.markAsFailed(notificationWithExecutionGitCommitSha.id, true);
387
408
  }
388
409
  catch (markFailedError) {
389
- this.logger.error(`Error marking notification ${notification.id} as failed: ${markFailedError}`);
410
+ this.logger.error(`Error marking notification ${notificationWithExecutionGitCommitSha.id} as failed: ${markFailedError}`);
390
411
  }
391
412
  }
392
413
  try {
393
- await this.backend.markAsSent(notification.id, true);
414
+ await this.backend.markAsSent(notificationWithExecutionGitCommitSha.id, true);
394
415
  }
395
416
  catch (markSentError) {
396
- this.logger.error(`Error marking notification ${notification.id} as sent: ${markSentError}`);
417
+ this.logger.error(`Error marking notification ${notificationWithExecutionGitCommitSha.id} as sent: ${markSentError}`);
397
418
  }
398
419
  }
399
420
  try {
400
- await this.backend.storeAdapterAndContextUsed(notification.id, lastAdapterKey, context);
421
+ await this.backend.storeAdapterAndContextUsed(notificationWithExecutionGitCommitSha.id, lastAdapterKey, context);
401
422
  }
402
423
  catch (storeContextError) {
403
- this.logger.error(`Error storing adapter and context for notification ${notification.id}: ${storeContextError}`);
424
+ this.logger.error(`Error storing adapter and context for notification ${notificationWithExecutionGitCommitSha.id}: ${storeContextError}`);
404
425
  }
405
426
  }
406
427
  async bulkPersistNotifications(notifications) {
@@ -15,6 +15,7 @@ export type NotificationInput<Config extends BaseNotificationTypeConfig> = {
15
15
  sendAfter: Date | null;
16
16
  subjectTemplate: string | null;
17
17
  extraParams: InputJsonValue | null;
18
+ gitCommitSha?: never;
18
19
  attachments?: NotificationAttachment[];
19
20
  };
20
21
  export type NotificationResendWithContextInput<Config extends BaseNotificationTypeConfig> = {
@@ -28,6 +29,7 @@ export type NotificationResendWithContextInput<Config extends BaseNotificationTy
28
29
  sendAfter: Date | null;
29
30
  subjectTemplate: string | null;
30
31
  extraParams: InputJsonValue | null;
32
+ gitCommitSha?: never;
31
33
  attachments?: NotificationAttachment[];
32
34
  };
33
35
  export type DatabaseNotification<Config extends BaseNotificationTypeConfig> = {
@@ -48,6 +50,7 @@ export type DatabaseNotification<Config extends BaseNotificationTypeConfig> = {
48
50
  readAt: Date | null;
49
51
  createdAt?: Date;
50
52
  updatedAt?: Date;
53
+ gitCommitSha: string | null;
51
54
  attachments?: StoredAttachment[];
52
55
  };
53
56
  export type Notification<Config extends BaseNotificationTypeConfig> = NotificationInput<Config> | NotificationResendWithContextInput<Config> | DatabaseNotification<Config>;
@@ -19,6 +19,7 @@ export type OneOffNotificationInput<Config extends BaseNotificationTypeConfig> =
19
19
  sendAfter: Date | null;
20
20
  subjectTemplate: string | null;
21
21
  extraParams: InputJsonValue | null;
22
+ gitCommitSha?: never;
22
23
  attachments?: NotificationAttachment[];
23
24
  };
24
25
  /**
@@ -38,6 +39,7 @@ export type OneOffNotificationResendWithContextInput<Config extends BaseNotifica
38
39
  sendAfter: Date | null;
39
40
  subjectTemplate: string | null;
40
41
  extraParams: InputJsonValue | null;
42
+ gitCommitSha?: never;
41
43
  attachments?: NotificationAttachment[];
42
44
  };
43
45
  /**
@@ -64,6 +66,7 @@ export type DatabaseOneOffNotification<Config extends BaseNotificationTypeConfig
64
66
  readAt: Date | null;
65
67
  createdAt?: Date;
66
68
  updatedAt?: Date;
69
+ gitCommitSha: string | null;
67
70
  attachments?: StoredAttachment[];
68
71
  };
69
72
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vintasend",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist"