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 +84 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/services/git-commit-sha/base-git-commit-sha-provider.d.ts +45 -0
- package/dist/services/git-commit-sha/base-git-commit-sha-provider.js +2 -0
- package/dist/services/git-commit-sha/index.d.ts +1 -0
- package/dist/services/git-commit-sha/index.js +2 -0
- package/dist/services/notification-backends/base-notification-backend.d.ts +14 -3
- package/dist/services/notification-backends/base-notification-backend.js +8 -0
- package/dist/services/notification-service.d.ts +26 -2
- package/dist/services/notification-service.js +83 -62
- package/dist/types/notification.d.ts +3 -0
- package/dist/types/one-off-notification.d.ts +3 -0
- package/package.json +1 -1
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 @@
|
|
|
1
|
+
export type { BaseGitCommitShaProvider } from './base-git-commit-sha-provider';
|
|
@@ -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?:
|
|
39
|
-
subjectTemplate?:
|
|
40
|
-
contextName?:
|
|
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>(
|
|
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
|
-
|
|
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
|
|
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 ${
|
|
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 ${
|
|
106
|
+
throw new Error(`No adapter found for notification type ${notificationWithExecutionGitCommitSha.notificationType}`);
|
|
87
107
|
}
|
|
88
108
|
return;
|
|
89
109
|
}
|
|
90
|
-
if (!
|
|
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 ${
|
|
101
|
-
await this.queueService.enqueueNotification(
|
|
102
|
-
this.logger.info(`Enqueued notification ${
|
|
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 ${
|
|
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 (
|
|
112
|
-
context =
|
|
131
|
+
if (notificationWithExecutionGitCommitSha.contextUsed) {
|
|
132
|
+
context = notificationWithExecutionGitCommitSha.contextUsed;
|
|
113
133
|
}
|
|
114
134
|
else {
|
|
115
135
|
try {
|
|
116
|
-
context = await this.getNotificationContext(
|
|
117
|
-
this.logger.info(`Generated context for notification ${
|
|
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 ${
|
|
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 ${
|
|
129
|
-
await adapter.send(
|
|
130
|
-
this.logger.info(`Sent notification ${
|
|
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 ${
|
|
153
|
+
this.logger.error(`Error sending notification ${notificationWithExecutionGitCommitSha.id} with adapter ${adapter.key}: ${sendError}`);
|
|
134
154
|
try {
|
|
135
|
-
await this.backend.markAsFailed(
|
|
155
|
+
await this.backend.markAsFailed(notificationWithExecutionGitCommitSha.id, true);
|
|
136
156
|
}
|
|
137
157
|
catch (markFailedError) {
|
|
138
|
-
this.logger.error(`Error marking notification ${
|
|
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(
|
|
163
|
+
await this.backend.markAsSent(notificationWithExecutionGitCommitSha.id, true);
|
|
144
164
|
}
|
|
145
165
|
catch (markSentError) {
|
|
146
|
-
this.logger.error(`Error marking notification ${
|
|
166
|
+
this.logger.error(`Error marking notification ${notificationWithExecutionGitCommitSha.id} as sent: ${markSentError}`);
|
|
147
167
|
}
|
|
148
168
|
try {
|
|
149
|
-
await this.backend.storeAdapterAndContextUsed(
|
|
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 ${
|
|
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
|
|
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(
|
|
402
|
+
await adapter.send(notificationWithExecutionGitCommitSha, context);
|
|
382
403
|
}
|
|
383
404
|
catch (sendError) {
|
|
384
|
-
this.logger.error(`Error sending notification ${
|
|
405
|
+
this.logger.error(`Error sending notification ${notificationWithExecutionGitCommitSha.id} with adapter ${adapter.key}: ${sendError}`);
|
|
385
406
|
try {
|
|
386
|
-
await this.backend.markAsFailed(
|
|
407
|
+
await this.backend.markAsFailed(notificationWithExecutionGitCommitSha.id, true);
|
|
387
408
|
}
|
|
388
409
|
catch (markFailedError) {
|
|
389
|
-
this.logger.error(`Error marking notification ${
|
|
410
|
+
this.logger.error(`Error marking notification ${notificationWithExecutionGitCommitSha.id} as failed: ${markFailedError}`);
|
|
390
411
|
}
|
|
391
412
|
}
|
|
392
413
|
try {
|
|
393
|
-
await this.backend.markAsSent(
|
|
414
|
+
await this.backend.markAsSent(notificationWithExecutionGitCommitSha.id, true);
|
|
394
415
|
}
|
|
395
416
|
catch (markSentError) {
|
|
396
|
-
this.logger.error(`Error marking notification ${
|
|
417
|
+
this.logger.error(`Error marking notification ${notificationWithExecutionGitCommitSha.id} as sent: ${markSentError}`);
|
|
397
418
|
}
|
|
398
419
|
}
|
|
399
420
|
try {
|
|
400
|
-
await this.backend.storeAdapterAndContextUsed(
|
|
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 ${
|
|
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
|
/**
|