@tstdl/base 0.93.100 → 0.93.102
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/authentication/client/authentication.service.d.ts +1 -1
- package/authentication/client/authentication.service.js +23 -11
- package/notification/api/index.d.ts +1 -0
- package/notification/api/index.js +1 -0
- package/notification/api/notification.api.d.ts +8 -16
- package/notification/api/notification.api.js +13 -26
- package/notification/index.d.ts +1 -1
- package/notification/index.js +1 -1
- package/notification/models/in-app-notification.model.d.ts +9 -4
- package/notification/models/in-app-notification.model.js +25 -10
- package/notification/models/index.d.ts +1 -1
- package/notification/models/index.js +1 -1
- package/notification/models/notification-log.model.d.ts +42 -5
- package/notification/models/notification-log.model.js +34 -20
- package/notification/models/notification-preference.model.d.ts +2 -2
- package/notification/models/notification-preference.model.js +9 -9
- package/notification/models/notification-type.model.d.ts +17 -0
- package/notification/models/{notification-category.model.js → notification-type.model.js} +12 -13
- package/notification/models/web-push-subscription.model.d.ts +2 -2
- package/notification/models/web-push-subscription.model.js +8 -7
- package/notification/server/api/notification.api-controller.d.ts +2 -2
- package/notification/server/api/notification.api-controller.js +4 -3
- package/notification/server/drizzle/{0000_glorious_randall.sql → 0000_shiny_the_anarchist.sql} +27 -32
- package/notification/server/drizzle/meta/0000_snapshot.json +179 -179
- package/notification/server/drizzle/meta/_journal.json +2 -2
- package/notification/server/module.d.ts +2 -0
- package/notification/server/module.js +1 -0
- package/notification/server/providers/channel-provider.d.ts +4 -3
- package/notification/server/providers/channel-provider.js +2 -1
- package/notification/server/providers/email-channel-provider.d.ts +3 -3
- package/notification/server/providers/email-channel-provider.js +7 -9
- package/notification/server/providers/in-app-channel-provider.d.ts +5 -5
- package/notification/server/providers/in-app-channel-provider.js +15 -16
- package/notification/server/providers/index.d.ts +1 -1
- package/notification/server/providers/index.js +1 -1
- package/notification/server/providers/web-push-channel-provider.d.ts +5 -4
- package/notification/server/providers/web-push-channel-provider.js +8 -7
- package/notification/server/schemas.d.ts +3 -3
- package/notification/server/schemas.js +3 -4
- package/notification/server/services/index.d.ts +2 -4
- package/notification/server/services/index.js +2 -4
- package/notification/server/services/notification-delivery.worker.d.ts +7 -1
- package/notification/server/services/notification-delivery.worker.js +49 -37
- package/notification/server/services/notification-sse.service.d.ts +4 -7
- package/notification/server/services/notification-sse.service.js +4 -11
- package/notification/server/services/notification-template.d.ts +2 -2
- package/notification/server/services/notification-template.js +3 -1
- package/notification/server/services/notification-template.service.d.ts +1 -1
- package/notification/server/services/notification-template.service.js +7 -3
- package/notification/server/services/notification-type.service.d.ts +11 -0
- package/notification/server/services/notification-type.service.js +41 -0
- package/notification/server/services/notification.service.d.ts +4 -5
- package/notification/server/services/notification.service.js +44 -27
- package/notification/tests/notification-api.test.js +95 -0
- package/notification/tests/notification-flow.test.js +174 -28
- package/notification/tests/notification-type.service.test.d.ts +1 -0
- package/notification/tests/notification-type.service.test.js +35 -0
- package/package.json +1 -1
- package/rate-limit/postgres/postgres-rate-limiter.d.ts +9 -4
- package/rate-limit/postgres/postgres-rate-limiter.js +17 -10
- package/rate-limit/rate-limiter.d.ts +6 -6
- package/rate-limit/tests/postgres-rate-limiter.test.js +1 -1
- package/task-queue/postgres/task-queue.js +1 -1
- package/task-queue/task-context.d.ts +1 -14
- package/task-queue/task-context.js +0 -30
- package/task-queue/task-queue.d.ts +4 -12
- package/task-queue/task-queue.js +38 -89
- package/task-queue/tests/extensive-dependencies.test.d.ts +1 -0
- package/task-queue/tests/extensive-dependencies.test.js +234 -0
- package/task-queue/tests/worker.test.js +0 -21
- package/task-queue/types.d.ts +1 -8
- package/notification/enums.d.ts +0 -22
- package/notification/enums.js +0 -19
- package/notification/models/notification-category.model.d.ts +0 -17
- package/notification/server/services/notification-category.service.d.ts +0 -11
- package/notification/server/services/notification-category.service.js +0 -41
- package/notification/server/services/notification-delivery.task.d.ts +0 -9
- package/notification/server/services/notification-delivery.task.js +0 -1
- package/notification/server/services/singleton.d.ts +0 -3
- package/notification/server/services/singleton.js +0 -10
- package/notification/tests/notification-category.service.test.js +0 -36
- package/notification/tests/test-notification.model.d.ts +0 -4
- package/notification/tests/test-notification.model.js +0 -25
- /package/notification/tests/{notification-category.service.test.d.ts → notification-api.test.d.ts} +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Injector } from '../../injector/index.js';
|
|
2
2
|
import { type DatabaseConfig } from '../../orm/server/index.js';
|
|
3
|
+
import type { NotificationChannel } from '../models/index.js';
|
|
3
4
|
export declare class NotificationConfiguration {
|
|
4
5
|
database?: DatabaseConfig;
|
|
6
|
+
defaultChannels?: NotificationChannel[];
|
|
5
7
|
}
|
|
6
8
|
export declare function configureNotification({ injector, ...config }: NotificationConfiguration & {
|
|
7
9
|
injector?: Injector;
|
|
@@ -2,6 +2,7 @@ import { inject, Injector } from '../../injector/index.js';
|
|
|
2
2
|
import { Database, migrate } from '../../orm/server/index.js';
|
|
3
3
|
export class NotificationConfiguration {
|
|
4
4
|
database;
|
|
5
|
+
defaultChannels;
|
|
5
6
|
}
|
|
6
7
|
export function configureNotification({ injector, ...config }) {
|
|
7
8
|
const targetInjector = injector ?? Injector;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { Transaction } from '../../../orm/server/index.js';
|
|
2
|
+
import type { NotificationLog } from '../../models/notification-log.model.js';
|
|
3
|
+
export declare abstract class ChannelProvider {
|
|
4
|
+
abstract send(notification: NotificationLog, tx?: Transaction): Promise<void>;
|
|
4
5
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export class ChannelProvider {
|
|
2
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { NotificationLog } from '../../models/index.js';
|
|
2
|
-
import
|
|
3
|
-
export declare class EmailChannelProvider
|
|
1
|
+
import type { NotificationLog } from '../../models/index.js';
|
|
2
|
+
import { ChannelProvider } from './channel-provider.js';
|
|
3
|
+
export declare class EmailChannelProvider extends ChannelProvider {
|
|
4
4
|
#private;
|
|
5
5
|
send(notification: NotificationLog): Promise<void>;
|
|
6
6
|
}
|
|
@@ -4,19 +4,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import {
|
|
7
|
+
import { SubjectService } from '../../../authentication/server/subject.service.js';
|
|
8
|
+
import { inject, Singleton } from '../../../injector/index.js';
|
|
8
9
|
import { MailService } from '../../../mail/mail.service.js';
|
|
9
|
-
import { User } from '../../../authentication/models/user.model.js';
|
|
10
|
-
import { injectRepository } from '../../../orm/server/index.js';
|
|
11
|
-
import { NotificationLog } from '../../models/index.js';
|
|
12
|
-
import { NotificationSingleton } from '../services/singleton.js';
|
|
13
10
|
import { NotificationTemplateService } from '../services/notification-template.service.js';
|
|
14
|
-
|
|
11
|
+
import { ChannelProvider } from './channel-provider.js';
|
|
12
|
+
let EmailChannelProvider = class EmailChannelProvider extends ChannelProvider {
|
|
15
13
|
#mailService = inject(MailService);
|
|
16
|
-
#
|
|
14
|
+
#subjectService = inject(SubjectService);
|
|
17
15
|
#templateService = inject(NotificationTemplateService);
|
|
18
16
|
async send(notification) {
|
|
19
|
-
const user = await this.#
|
|
17
|
+
const user = await this.#subjectService.getUser(notification.tenantId, notification.userId);
|
|
20
18
|
const content = await this.#templateService.render(notification, 'en'); // TODO: get user language
|
|
21
19
|
await this.#mailService.send({
|
|
22
20
|
to: user.email,
|
|
@@ -29,6 +27,6 @@ let EmailChannelProvider = class EmailChannelProvider {
|
|
|
29
27
|
}
|
|
30
28
|
};
|
|
31
29
|
EmailChannelProvider = __decorate([
|
|
32
|
-
|
|
30
|
+
Singleton()
|
|
33
31
|
], EmailChannelProvider);
|
|
34
32
|
export { EmailChannelProvider };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { NotificationLog } from '../../models/index.js';
|
|
3
|
-
import
|
|
4
|
-
export declare class InAppChannelProvider extends
|
|
1
|
+
import { type Transaction } from '../../../orm/server/index.js';
|
|
2
|
+
import { type NotificationLog } from '../../models/index.js';
|
|
3
|
+
import { ChannelProvider } from './channel-provider.js';
|
|
4
|
+
export declare class InAppChannelProvider extends ChannelProvider {
|
|
5
5
|
#private;
|
|
6
|
-
send(notification: NotificationLog): Promise<void>;
|
|
6
|
+
send(notification: NotificationLog, tx?: Transaction): Promise<void>;
|
|
7
7
|
}
|
|
@@ -4,28 +4,27 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { inject } from '../../../injector/index.js';
|
|
8
|
-
import { injectRepository
|
|
9
|
-
import { InAppNotification,
|
|
10
|
-
import { NotificationSingleton } from '../services/singleton.js';
|
|
7
|
+
import { inject, Singleton } from '../../../injector/index.js';
|
|
8
|
+
import { injectRepository } from '../../../orm/server/index.js';
|
|
9
|
+
import { InAppNotification, toInAppNotificationView } from '../../models/index.js';
|
|
11
10
|
import { NotificationSseService } from '../services/notification-sse.service.js';
|
|
12
|
-
|
|
11
|
+
import { ChannelProvider } from './channel-provider.js';
|
|
12
|
+
let InAppChannelProvider = class InAppChannelProvider extends ChannelProvider {
|
|
13
13
|
#inAppRepository = injectRepository(InAppNotification);
|
|
14
14
|
#sseService = inject(NotificationSseService);
|
|
15
|
-
async send(notification) {
|
|
16
|
-
const inApp = await this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
archivedAt: null,
|
|
23
|
-
});
|
|
15
|
+
async send(notification, tx) {
|
|
16
|
+
const inApp = await this.#inAppRepository.withOptionalTransaction(tx).insert({
|
|
17
|
+
tenantId: notification.tenantId,
|
|
18
|
+
userId: notification.userId,
|
|
19
|
+
logId: notification.id,
|
|
20
|
+
readTimestamp: null,
|
|
21
|
+
archiveTimestamp: null,
|
|
24
22
|
});
|
|
25
|
-
|
|
23
|
+
const inAppNotificationView = toInAppNotificationView(inApp, notification);
|
|
24
|
+
await this.#sseService.send(inAppNotificationView);
|
|
26
25
|
}
|
|
27
26
|
};
|
|
28
27
|
InAppChannelProvider = __decorate([
|
|
29
|
-
|
|
28
|
+
Singleton()
|
|
30
29
|
], InAppChannelProvider);
|
|
31
30
|
export { InAppChannelProvider };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
3
|
-
|
|
1
|
+
import { type Transaction } from '../../../orm/server/index.js';
|
|
2
|
+
import { type NotificationLog } from '../../models/index.js';
|
|
3
|
+
import { ChannelProvider } from './channel-provider.js';
|
|
4
|
+
export declare class WebPushChannelProvider extends ChannelProvider {
|
|
4
5
|
#private;
|
|
5
|
-
send(notification: NotificationLog): Promise<void>;
|
|
6
|
+
send(notification: NotificationLog, tx?: Transaction): Promise<void>;
|
|
6
7
|
}
|
|
@@ -4,15 +4,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
+
import { Singleton } from '../../../injector/index.js';
|
|
7
8
|
import { injectRepository } from '../../../orm/server/index.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
let WebPushChannelProvider = class WebPushChannelProvider {
|
|
9
|
+
import { WebPushSubscription } from '../../models/index.js';
|
|
10
|
+
import { ChannelProvider } from './channel-provider.js';
|
|
11
|
+
let WebPushChannelProvider = class WebPushChannelProvider extends ChannelProvider {
|
|
11
12
|
#subscriptionRepository = injectRepository(WebPushSubscription);
|
|
12
|
-
async send(notification) {
|
|
13
|
-
const subscriptions = await this.#subscriptionRepository.loadManyByQuery({
|
|
13
|
+
async send(notification, tx) {
|
|
14
|
+
const subscriptions = await this.#subscriptionRepository.withOptionalTransaction(tx).loadManyByQuery({
|
|
14
15
|
tenantId: notification.tenantId,
|
|
15
|
-
userId: notification.userId
|
|
16
|
+
userId: notification.userId,
|
|
16
17
|
});
|
|
17
18
|
for (const subscription of subscriptions) {
|
|
18
19
|
// TODO: Use a Web Push library (e.g. web-push) to send the notification
|
|
@@ -21,6 +22,6 @@ let WebPushChannelProvider = class WebPushChannelProvider {
|
|
|
21
22
|
}
|
|
22
23
|
};
|
|
23
24
|
WebPushChannelProvider = __decorate([
|
|
24
|
-
|
|
25
|
+
Singleton()
|
|
25
26
|
], WebPushChannelProvider);
|
|
26
27
|
export { WebPushChannelProvider };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InAppNotification,
|
|
1
|
+
import { InAppNotification, NotificationLogEntity, NotificationPreference, NotificationType, WebPushSubscription } from '../models/index.js';
|
|
2
2
|
export declare const notificationSchema: import("../../orm/server/index.js").DatabaseSchema<"notification">;
|
|
3
3
|
export declare const notificationChannel: import("../../orm/enums.js").PgEnumFromEnumeration<{
|
|
4
4
|
readonly InApp: "in-app";
|
|
@@ -19,7 +19,7 @@ export declare const notificationStatus: import("../../orm/enums.js").PgEnumFrom
|
|
|
19
19
|
readonly Failed: "failed";
|
|
20
20
|
}>;
|
|
21
21
|
export declare const inAppNotification: import("../../orm/server/types.js").PgTableFromType<typeof InAppNotification, "notification">;
|
|
22
|
-
export declare const
|
|
23
|
-
export declare const notificationLog: import("../../orm/server/types.js").PgTableFromType<typeof
|
|
22
|
+
export declare const notificationType: import("../../orm/server/types.js").PgTableFromType<typeof NotificationType, "notification">;
|
|
23
|
+
export declare const notificationLog: import("../../orm/server/types.js").PgTableFromType<typeof NotificationLogEntity, "notification">;
|
|
24
24
|
export declare const notificationPreference: import("../../orm/server/types.js").PgTableFromType<typeof NotificationPreference, "notification">;
|
|
25
25
|
export declare const webPushSubscription: import("../../orm/server/types.js").PgTableFromType<typeof WebPushSubscription, "notification">;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { databaseSchema } from '../../orm/server/index.js';
|
|
2
|
-
import { NotificationChannel, NotificationPriority, NotificationStatus } from '../
|
|
3
|
-
import { InAppNotification, NotificationCategory, NotificationLog, NotificationPreference, WebPushSubscription } from '../models/index.js';
|
|
2
|
+
import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationPreference, NotificationPriority, NotificationStatus, NotificationType, WebPushSubscription } from '../models/index.js';
|
|
4
3
|
export const notificationSchema = databaseSchema('notification');
|
|
5
4
|
export const notificationChannel = notificationSchema.getEnum(NotificationChannel);
|
|
6
5
|
export const notificationPriority = notificationSchema.getEnum(NotificationPriority);
|
|
7
6
|
export const notificationStatus = notificationSchema.getEnum(NotificationStatus);
|
|
8
7
|
export const inAppNotification = notificationSchema.getTable(InAppNotification);
|
|
9
|
-
export const
|
|
10
|
-
export const notificationLog = notificationSchema.getTable(
|
|
8
|
+
export const notificationType = notificationSchema.getTable(NotificationType);
|
|
9
|
+
export const notificationLog = notificationSchema.getTable(NotificationLogEntity);
|
|
11
10
|
export const notificationPreference = notificationSchema.getTable(NotificationPreference);
|
|
12
11
|
export const webPushSubscription = notificationSchema.getTable(WebPushSubscription);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
export * from './notification-category.service.js';
|
|
2
|
-
export * from './notification-delivery.task.js';
|
|
3
1
|
export * from './notification-delivery.worker.js';
|
|
4
2
|
export * from './notification-sse.service.js';
|
|
5
|
-
export * from './notification-template.service.js';
|
|
6
3
|
export * from './notification-template.js';
|
|
4
|
+
export * from './notification-template.service.js';
|
|
5
|
+
export * from './notification-type.service.js';
|
|
7
6
|
export * from './notification.service.js';
|
|
8
|
-
export * from './singleton.js';
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
export * from './notification-category.service.js';
|
|
2
|
-
export * from './notification-delivery.task.js';
|
|
3
1
|
export * from './notification-delivery.worker.js';
|
|
4
2
|
export * from './notification-sse.service.js';
|
|
5
|
-
export * from './notification-template.service.js';
|
|
6
3
|
export * from './notification-template.js';
|
|
4
|
+
export * from './notification-template.service.js';
|
|
5
|
+
export * from './notification-type.service.js';
|
|
7
6
|
export * from './notification.service.js';
|
|
8
|
-
export * from './singleton.js';
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { CancellationSignal } from '../../../cancellation/token.js';
|
|
2
2
|
import { Transactional } from '../../../orm/server/index.js';
|
|
3
|
+
import type { TaskDefinition, TaskDefinitionMap } from '../../../task-queue/index.js';
|
|
3
4
|
import { TaskProcessResult } from '../../../task-queue/task-queue.js';
|
|
4
|
-
import { NotificationChannel } from '../../
|
|
5
|
+
import { NotificationChannel } from '../../models/index.js';
|
|
5
6
|
import type { ChannelProvider } from '../providers/channel-provider.js';
|
|
7
|
+
export type NotificationTaskDefinitions = TaskDefinitionMap<{
|
|
8
|
+
'notification/deliver': TaskDefinition<{
|
|
9
|
+
notificationId: string;
|
|
10
|
+
}>;
|
|
11
|
+
}>;
|
|
6
12
|
export declare class NotificationDeliveryWorker extends Transactional {
|
|
7
13
|
#private;
|
|
8
14
|
registerProvider(channel: NotificationChannel, provider: ChannelProvider): void;
|
|
@@ -4,23 +4,23 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { inject } from '../../../injector/index.js';
|
|
7
|
+
import { inject, Singleton } from '../../../injector/index.js';
|
|
8
8
|
import { Logger } from '../../../logger/logger.js';
|
|
9
9
|
import { injectRepository, Transactional } from '../../../orm/server/index.js';
|
|
10
10
|
import { RateLimiter } from '../../../rate-limit/rate-limiter.js';
|
|
11
11
|
import { TaskProcessResult, TaskQueue } from '../../../task-queue/task-queue.js';
|
|
12
|
-
import {
|
|
13
|
-
import { NotificationChannel, NotificationStatus } from '../../
|
|
14
|
-
import {
|
|
15
|
-
import { NotificationSingleton } from './singleton.js';
|
|
12
|
+
import { isNotNull, isNotNullOrUndefined, isUndefined } from '../../../utils/type-guards.js';
|
|
13
|
+
import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationPreference, NotificationStatus, NotificationType } from '../../models/index.js';
|
|
14
|
+
import { NotificationConfiguration } from '../module.js';
|
|
16
15
|
let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transactional {
|
|
17
|
-
#notificationLogRepository = injectRepository(
|
|
16
|
+
#notificationLogRepository = injectRepository(NotificationLogEntity);
|
|
18
17
|
#inAppRepository = injectRepository(InAppNotification);
|
|
19
18
|
#preferenceRepository = injectRepository(NotificationPreference);
|
|
20
|
-
#
|
|
19
|
+
#typeRepository = injectRepository(NotificationType);
|
|
21
20
|
#taskQueue = inject((TaskQueue), 'notification');
|
|
22
21
|
#rateLimiter = inject(RateLimiter, 'notification');
|
|
23
22
|
#logger = inject(Logger, 'NotificationDeliveryWorker');
|
|
23
|
+
#configuration = inject(NotificationConfiguration, undefined, { optional: true });
|
|
24
24
|
#providers = new Map();
|
|
25
25
|
registerProvider(channel, provider) {
|
|
26
26
|
this.#providers.set(channel, provider);
|
|
@@ -40,16 +40,19 @@ let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transa
|
|
|
40
40
|
}
|
|
41
41
|
async deliver(notificationId) {
|
|
42
42
|
return await this.transaction(async (tx) => {
|
|
43
|
-
const notification = await this.#notificationLogRepository.withTransaction(tx).
|
|
44
|
-
const
|
|
43
|
+
const notification = await this.#notificationLogRepository.withTransaction(tx).load(notificationId);
|
|
44
|
+
const type = await this.#typeRepository.withTransaction(tx).loadByQuery({ key: notification.type });
|
|
45
45
|
const step = notification.currentStep;
|
|
46
46
|
// 1. Throttling
|
|
47
|
-
if (step == 0 && isNotNull(
|
|
48
|
-
const resource = `${notification.tenantId}:${notification.userId}:${
|
|
49
|
-
const acquired = await this.#rateLimiter.withTransaction(tx).tryAcquire(resource
|
|
47
|
+
if (step == 0 && isNotNull(type.throttling)) {
|
|
48
|
+
const resource = `${notification.tenantId}:${notification.userId}:${type.key}`;
|
|
49
|
+
const acquired = await this.#rateLimiter.withTransaction(tx).tryAcquire(resource, 1, {
|
|
50
|
+
burstCapacity: type.throttling.limit,
|
|
51
|
+
refillInterval: type.throttling.interval,
|
|
52
|
+
});
|
|
50
53
|
if (!acquired) {
|
|
51
|
-
this.#logger.info(`Throttled notification ${notificationId} for user ${notification.userId} in category ${
|
|
52
|
-
return TaskProcessResult.RescheduleBy(
|
|
54
|
+
this.#logger.info(`Throttled notification ${notificationId} for user ${notification.userId} in category ${type.key}`);
|
|
55
|
+
return TaskProcessResult.RescheduleBy(type.throttling.interval / type.throttling.limit);
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
// 2. Initial Delivery
|
|
@@ -57,52 +60,61 @@ let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transa
|
|
|
57
60
|
const preferences = await this.#preferenceRepository.withTransaction(tx).loadManyByQuery({
|
|
58
61
|
tenantId: notification.tenantId,
|
|
59
62
|
userId: notification.userId,
|
|
60
|
-
|
|
63
|
+
type: notification.type,
|
|
61
64
|
});
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
const defaultChannels = this.#configuration?.defaultChannels ?? [NotificationChannel.InApp];
|
|
66
|
+
const preferenceMap = new Map(preferences.map((p) => [p.channel, p.enabled]));
|
|
67
|
+
const enabledChannels = new Set();
|
|
68
|
+
for (const channel of defaultChannels) {
|
|
69
|
+
if (preferenceMap.get(channel) !== false) {
|
|
70
|
+
enabledChannels.add(channel);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const preference of preferences) {
|
|
74
|
+
if (preference.enabled) {
|
|
75
|
+
enabledChannels.add(preference.channel);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
65
78
|
for (const channel of enabledChannels) {
|
|
66
|
-
|
|
79
|
+
// TODO: what if an error occurs here? partial delivery? Should we use tasks to allow retrying individual channels?
|
|
80
|
+
await this.sendToChannel(notification, channel, tx);
|
|
67
81
|
}
|
|
68
|
-
await this.#notificationLogRepository.withTransaction(tx).
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return TaskProcessResult.RescheduleBy(firstEscalation.delayMs);
|
|
82
|
+
await this.#notificationLogRepository.withTransaction(tx).update(notificationId, { status: NotificationStatus.Sent, currentStep: 1 });
|
|
83
|
+
if (isNotNull(type.escalations) && (type.escalations.length > 0)) {
|
|
84
|
+
const firstEscalation = type.escalations[0];
|
|
85
|
+
return TaskProcessResult.RescheduleBy(firstEscalation.delay);
|
|
73
86
|
}
|
|
74
87
|
return TaskProcessResult.Complete();
|
|
75
88
|
}
|
|
76
89
|
// 3. Escalation Steps
|
|
77
|
-
if (isNotNull(
|
|
78
|
-
const rule =
|
|
90
|
+
if (isNotNull(type.escalations) && (step > 0) && (step <= type.escalations.length)) {
|
|
91
|
+
const rule = type.escalations[step - 1];
|
|
79
92
|
// Check if already read (for In-App escalation)
|
|
80
|
-
const inApp = await this.#inAppRepository.withTransaction(tx).tryLoadByQuery({ logId: notificationId });
|
|
81
|
-
if (
|
|
93
|
+
const inApp = await this.#inAppRepository.withTransaction(tx).tryLoadByQuery({ tenantId: notification.tenantId, userId: notification.userId, logId: notificationId });
|
|
94
|
+
if (isNotNullOrUndefined(inApp?.readTimestamp)) {
|
|
82
95
|
this.#logger.debug(`Notification ${notificationId} already read, skipping escalation step ${step}`);
|
|
83
96
|
return TaskProcessResult.Complete();
|
|
84
97
|
}
|
|
85
|
-
await this.sendToChannel(notification, rule.channel);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return TaskProcessResult.RescheduleBy(nextRule.delayMs);
|
|
98
|
+
await this.sendToChannel(notification, rule.channel, tx);
|
|
99
|
+
await this.#notificationLogRepository.withTransaction(tx).update(notificationId, { currentStep: step + 1 });
|
|
100
|
+
if (step < type.escalations.length) {
|
|
101
|
+
const nextRule = type.escalations[step];
|
|
102
|
+
return TaskProcessResult.RescheduleBy(nextRule.delay);
|
|
91
103
|
}
|
|
92
104
|
}
|
|
93
105
|
return TaskProcessResult.Complete();
|
|
94
106
|
});
|
|
95
107
|
}
|
|
96
|
-
async sendToChannel(notification, channel) {
|
|
108
|
+
async sendToChannel(notification, channel, tx) {
|
|
97
109
|
const provider = this.#providers.get(channel);
|
|
98
110
|
if (isUndefined(provider)) {
|
|
99
111
|
this.#logger.warn(`No provider registered for channel ${channel}`);
|
|
100
112
|
return;
|
|
101
113
|
}
|
|
102
|
-
await provider.send(notification);
|
|
114
|
+
await provider.send(notification, tx);
|
|
103
115
|
}
|
|
104
116
|
};
|
|
105
117
|
NotificationDeliveryWorker = __decorate([
|
|
106
|
-
|
|
118
|
+
Singleton()
|
|
107
119
|
], NotificationDeliveryWorker);
|
|
108
120
|
export { NotificationDeliveryWorker };
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
import { afterResolve } from '../../../injector/index.js';
|
|
1
2
|
import { ServerSentEventsSource } from '../../../sse/server-sent-events-source.js';
|
|
2
|
-
import type {
|
|
3
|
-
type NotificationMessage = InAppNotification & {
|
|
4
|
-
log?: any;
|
|
5
|
-
};
|
|
3
|
+
import type { InAppNotificationView } from '../../models/index.js';
|
|
6
4
|
export declare class NotificationSseService {
|
|
7
5
|
#private;
|
|
8
|
-
|
|
6
|
+
[afterResolve](): void;
|
|
9
7
|
register(tenantId: string, userId: string): ServerSentEventsSource;
|
|
10
|
-
send(notification:
|
|
8
|
+
send(notification: InAppNotificationView): Promise<void>;
|
|
11
9
|
private dispatchToLocal;
|
|
12
10
|
}
|
|
13
|
-
export {};
|
|
@@ -4,22 +4,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
};
|
|
10
|
-
import { inject } from '../../../injector/index.js';
|
|
7
|
+
import { afterResolve, inject, Singleton } from '../../../injector/index.js';
|
|
11
8
|
import { MessageBusProvider } from '../../../message-bus/message-bus-provider.js';
|
|
12
9
|
import { toObservable } from '../../../signals/api.js';
|
|
13
10
|
import { ServerSentEventsSource } from '../../../sse/server-sent-events-source.js';
|
|
14
|
-
import { NotificationSingleton } from './singleton.js';
|
|
15
11
|
let NotificationSseService = class NotificationSseService {
|
|
16
12
|
#messageBusProvider = inject(MessageBusProvider);
|
|
17
13
|
#messageBus = this.#messageBusProvider.get('notification');
|
|
18
14
|
#sources = new Map(); // tenantId -> userId -> Sources
|
|
19
|
-
|
|
20
|
-
this.#messageBus.allMessages$.subscribe((message) =>
|
|
21
|
-
this.dispatchToLocal(message);
|
|
22
|
-
});
|
|
15
|
+
[afterResolve]() {
|
|
16
|
+
this.#messageBus.allMessages$.subscribe((message) => this.dispatchToLocal(message));
|
|
23
17
|
}
|
|
24
18
|
register(tenantId, userId) {
|
|
25
19
|
const source = new ServerSentEventsSource();
|
|
@@ -68,7 +62,6 @@ let NotificationSseService = class NotificationSseService {
|
|
|
68
62
|
}
|
|
69
63
|
};
|
|
70
64
|
NotificationSseService = __decorate([
|
|
71
|
-
|
|
72
|
-
__metadata("design:paramtypes", [])
|
|
65
|
+
Singleton()
|
|
73
66
|
], NotificationSseService);
|
|
74
67
|
export { NotificationSseService };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NotificationLog } from '../../models/index.js';
|
|
2
|
-
export
|
|
2
|
+
export declare abstract class NotificationTemplate<T extends NotificationLog = NotificationLog> {
|
|
3
3
|
key: string;
|
|
4
|
-
render(notification: T, language: string): Promise<NotificationContent>;
|
|
4
|
+
abstract render(notification: T, language: string): Promise<NotificationContent>;
|
|
5
5
|
}
|
|
6
6
|
export type NotificationContent = {
|
|
7
7
|
title: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type NotificationLog } from '../../models/index.js';
|
|
2
2
|
import type { NotificationContent, NotificationTemplate } from './notification-template.js';
|
|
3
3
|
export declare class NotificationTemplateService {
|
|
4
4
|
#private;
|
|
@@ -4,15 +4,19 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
+
import { Singleton } from '../../../injector/index.js';
|
|
8
|
+
import { injectRepository } from '../../../orm/server/repository.js';
|
|
7
9
|
import { isUndefined } from '../../../utils/type-guards.js';
|
|
8
|
-
import {
|
|
10
|
+
import { NotificationType } from '../../models/index.js';
|
|
9
11
|
let NotificationTemplateService = class NotificationTemplateService {
|
|
12
|
+
#typeRepository = injectRepository(NotificationType);
|
|
10
13
|
#templates = new Map();
|
|
11
14
|
register(template) {
|
|
12
15
|
this.#templates.set(template.key, template);
|
|
13
16
|
}
|
|
14
17
|
async render(notification, language = 'en') {
|
|
15
|
-
const
|
|
18
|
+
const type = await this.#typeRepository.loadByQuery({ key: notification.type });
|
|
19
|
+
const template = this.#templates.get(type.key);
|
|
16
20
|
if (isUndefined(template)) {
|
|
17
21
|
// Fallback for simple notifications without a specific template
|
|
18
22
|
return {
|
|
@@ -24,6 +28,6 @@ let NotificationTemplateService = class NotificationTemplateService {
|
|
|
24
28
|
}
|
|
25
29
|
};
|
|
26
30
|
NotificationTemplateService = __decorate([
|
|
27
|
-
|
|
31
|
+
Singleton()
|
|
28
32
|
], NotificationTemplateService);
|
|
29
33
|
export { NotificationTemplateService };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Transactional } from '../../../orm/server/index.js';
|
|
2
|
+
import { NotificationType, type EscalationRule, type ThrottlingConfig } from '../../models/index.js';
|
|
3
|
+
export type TypeInitializationData = {
|
|
4
|
+
label: string;
|
|
5
|
+
throttling?: ThrottlingConfig;
|
|
6
|
+
escalations?: EscalationRule[];
|
|
7
|
+
};
|
|
8
|
+
export declare class NotificationTypeService extends Transactional {
|
|
9
|
+
readonly repository: import("../../../orm/server/index.js").EntityRepository<NotificationType>;
|
|
10
|
+
initializeTypes<T extends string>(typeData: Record<T, TypeInitializationData>): Promise<Record<T, NotificationType>>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Singleton } from '../../../injector/index.js';
|
|
8
|
+
import { injectRepository, Transactional } from '../../../orm/server/index.js';
|
|
9
|
+
import { fromEntries, objectEntries } from '../../../utils/object/index.js';
|
|
10
|
+
import { assertDefinedPass, isUndefined } from '../../../utils/type-guards.js';
|
|
11
|
+
import { NotificationType } from '../../models/index.js';
|
|
12
|
+
let NotificationTypeService = class NotificationTypeService extends Transactional {
|
|
13
|
+
repository = injectRepository(NotificationType);
|
|
14
|
+
async initializeTypes(typeData) {
|
|
15
|
+
const typeEntries = objectEntries(typeData);
|
|
16
|
+
const typeMap = await this.transaction(async (tx) => {
|
|
17
|
+
const dbTypes = await this.repository.withTransaction(tx).loadAll();
|
|
18
|
+
const enumKeyTypeMap = new Map(dbTypes.map((type) => [type.key, type]));
|
|
19
|
+
for (const [key, data] of typeEntries) {
|
|
20
|
+
const type = enumKeyTypeMap.get(key);
|
|
21
|
+
const throttling = data.throttling ?? null;
|
|
22
|
+
const escalations = data.escalations ?? null;
|
|
23
|
+
if (isUndefined(type)) {
|
|
24
|
+
const newType = await this.repository.withTransaction(tx).insert({ key, label: data.label, throttling, escalations });
|
|
25
|
+
enumKeyTypeMap.set(key, newType);
|
|
26
|
+
}
|
|
27
|
+
else if ((type.label != data.label) || (JSON.stringify(type.throttling) != JSON.stringify(throttling)) || (JSON.stringify(type.escalations) != JSON.stringify(escalations))) {
|
|
28
|
+
const updatedType = await this.repository.withTransaction(tx).updateByQuery({ id: type.id }, { label: data.label, throttling, escalations });
|
|
29
|
+
enumKeyTypeMap.set(key, updatedType);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return enumKeyTypeMap;
|
|
33
|
+
});
|
|
34
|
+
const mappedTypes = typeEntries.map(([key]) => [key, assertDefinedPass(typeMap.get(key), 'Could not map notification type.')]);
|
|
35
|
+
return fromEntries(mappedTypes);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
NotificationTypeService = __decorate([
|
|
39
|
+
Singleton()
|
|
40
|
+
], NotificationTypeService);
|
|
41
|
+
export { NotificationTypeService };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Transactional } from '../../../orm/server/index.js';
|
|
2
|
-
import type
|
|
3
|
-
import { NotificationLog, NotificationPreference } from '../../models/index.js';
|
|
2
|
+
import { NotificationPreference, type InAppNotificationView, type NotificationChannel, type NotificationLog } from '../../models/index.js';
|
|
4
3
|
export declare class NotificationService extends Transactional {
|
|
5
4
|
#private;
|
|
6
5
|
send(notification: NotificationLog): Promise<void>;
|
|
@@ -8,10 +7,10 @@ export declare class NotificationService extends Transactional {
|
|
|
8
7
|
limit?: number;
|
|
9
8
|
offset?: number;
|
|
10
9
|
includeArchived?: boolean;
|
|
11
|
-
}): Promise<
|
|
10
|
+
}): Promise<InAppNotificationView[]>;
|
|
12
11
|
markRead(tenantId: string, userId: string, id: string): Promise<void>;
|
|
13
12
|
archive(tenantId: string, userId: string, id: string): Promise<void>;
|
|
14
13
|
getPreferences(tenantId: string, userId: string): Promise<NotificationPreference[]>;
|
|
15
|
-
updatePreference(tenantId: string, userId: string,
|
|
16
|
-
registerWebPush(tenantId: string, userId: string, endpoint: string, p256dh:
|
|
14
|
+
updatePreference(tenantId: string, userId: string, type: string, channel: NotificationChannel, enabled: boolean): Promise<void>;
|
|
15
|
+
registerWebPush(tenantId: string, userId: string, endpoint: string, p256dh: Uint8Array<ArrayBuffer>, auth: Uint8Array<ArrayBuffer>): Promise<void>;
|
|
17
16
|
}
|