@tstdl/base 0.93.97 → 0.93.99
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/audit/auditor.d.ts +1 -1
- package/context/context.d.ts +1 -1
- package/context/context.js +4 -0
- package/cookie/cookie.js +5 -1
- package/css/css-variables.d.ts +38 -0
- package/css/css-variables.js +38 -0
- package/document-management/api/document-management.api.d.ts +17 -3
- package/document-management/api/document-management.api.js +8 -1
- package/document-management/models/document-category.model.js +0 -1
- package/document-management/models/document-property.model.js +0 -1
- package/document-management/server/api/document-management.api.d.ts +1 -0
- package/document-management/server/api/document-management.api.js +13 -1
- package/document-management/server/drizzle/{0000_needy_steel_serpent.sql → 0000_silly_chimera.sql} +0 -2
- package/document-management/server/drizzle/meta/0000_snapshot.json +1 -15
- package/document-management/server/drizzle/meta/_journal.json +2 -2
- package/document-management/server/services/document-statistics.service.d.ts +6 -0
- package/document-management/server/services/document-statistics.service.js +167 -0
- package/document-management/server/services/index.d.ts +1 -0
- package/document-management/server/services/index.js +1 -0
- package/document-management/service-models/document-statistics.view-model.d.ts +38 -0
- package/document-management/service-models/document-statistics.view-model.js +160 -0
- package/document-management/service-models/document.service-model.d.ts +1 -1
- package/document-management/service-models/index.d.ts +1 -0
- package/document-management/service-models/index.js +1 -0
- package/document-management/tests/document-management-core.test.js +2 -2
- package/document-management/tests/document-management.api.test.d.ts +1 -0
- package/document-management/tests/document-management.api.test.js +102 -0
- package/document-management/tests/document-statistics.service.test.d.ts +1 -0
- package/document-management/tests/document-statistics.service.test.js +495 -0
- package/document-management/tests/enum-helpers.test.js +3 -2
- package/enumeration/enumeration.d.ts +24 -0
- package/enumeration/enumeration.js +20 -0
- package/examples/document-management/main.js +1 -1
- package/intl/number-parser.d.ts +16 -9
- package/intl/number-parser.js +31 -19
- package/module/module.js +3 -0
- package/notification/api/notification.api.d.ts +78 -0
- package/notification/api/notification.api.js +81 -0
- package/notification/enums.d.ts +22 -0
- package/notification/enums.js +19 -0
- package/notification/index.d.ts +2 -0
- package/notification/index.js +2 -0
- package/notification/models/in-app-notification.model.d.ts +9 -0
- package/notification/models/in-app-notification.model.js +42 -0
- package/notification/models/index.d.ts +6 -0
- package/notification/models/index.js +6 -0
- package/notification/models/notification-category.model.d.ts +17 -0
- package/notification/models/notification-category.model.js +41 -0
- package/notification/models/notification-log.model.d.ts +13 -0
- package/notification/models/notification-log.model.js +59 -0
- package/notification/models/notification-preference.model.d.ts +9 -0
- package/notification/models/notification-preference.model.js +45 -0
- package/notification/models/notification-table.d.ts +3 -0
- package/notification/models/notification-table.js +4 -0
- package/notification/models/web-push-subscription.model.d.ts +8 -0
- package/notification/models/web-push-subscription.model.js +41 -0
- package/notification/server/api/notification.api-controller.d.ts +16 -0
- package/notification/server/api/notification.api-controller.js +51 -0
- package/notification/server/drizzle/0000_glorious_randall.sql +90 -0
- package/notification/server/drizzle/meta/0000_snapshot.json +652 -0
- package/notification/server/drizzle/meta/_journal.json +13 -0
- package/notification/server/drizzle.config.d.ts +2 -0
- package/notification/server/drizzle.config.js +11 -0
- package/notification/server/index.d.ts +4 -0
- package/notification/server/index.js +4 -0
- package/notification/server/module.d.ts +12 -0
- package/notification/server/module.js +21 -0
- package/notification/server/providers/channel-provider.d.ts +4 -0
- package/notification/server/providers/channel-provider.js +1 -0
- package/notification/server/providers/email-channel-provider.d.ts +6 -0
- package/notification/server/providers/email-channel-provider.js +34 -0
- package/notification/server/providers/in-app-channel-provider.d.ts +7 -0
- package/notification/server/providers/in-app-channel-provider.js +31 -0
- package/notification/server/providers/index.d.ts +4 -0
- package/notification/server/providers/index.js +4 -0
- package/notification/server/providers/web-push-channel-provider.d.ts +6 -0
- package/notification/server/providers/web-push-channel-provider.js +26 -0
- package/notification/server/schemas.d.ts +25 -0
- package/notification/server/schemas.js +12 -0
- package/notification/server/services/index.d.ts +8 -0
- package/notification/server/services/index.js +8 -0
- package/notification/server/services/notification-category.service.d.ts +11 -0
- package/notification/server/services/notification-category.service.js +41 -0
- package/notification/server/services/notification-delivery.task.d.ts +9 -0
- package/notification/server/services/notification-delivery.task.js +1 -0
- package/notification/server/services/notification-delivery.worker.d.ts +12 -0
- package/notification/server/services/notification-delivery.worker.js +108 -0
- package/notification/server/services/notification-sse.service.d.ts +13 -0
- package/notification/server/services/notification-sse.service.js +74 -0
- package/notification/server/services/notification-template.d.ts +12 -0
- package/notification/server/services/notification-template.js +1 -0
- package/notification/server/services/notification-template.service.d.ts +7 -0
- package/notification/server/services/notification-template.service.js +29 -0
- package/notification/server/services/notification.service.d.ts +17 -0
- package/notification/server/services/notification.service.js +80 -0
- package/notification/server/services/singleton.d.ts +3 -0
- package/notification/server/services/singleton.js +10 -0
- package/notification/tests/notification-category.service.test.d.ts +1 -0
- package/notification/tests/notification-category.service.test.js +36 -0
- package/notification/tests/notification-flow.test.d.ts +1 -0
- package/notification/tests/notification-flow.test.js +112 -0
- package/notification/tests/notification-sse.service.test.d.ts +1 -0
- package/notification/tests/notification-sse.service.test.js +20 -0
- package/notification/tests/test-notification.model.d.ts +4 -0
- package/notification/tests/test-notification.model.js +25 -0
- package/object-storage/google/google.object-storage-provider.d.ts +0 -1
- package/object-storage/google/google.object-storage-provider.js +0 -1
- package/object-storage/index.d.ts +0 -1
- package/object-storage/index.js +0 -1
- package/object-storage/s3/s3.object-storage-provider.d.ts +0 -1
- package/object-storage/s3/s3.object-storage-provider.js +0 -1
- package/orm/server/transactional.d.ts +3 -2
- package/orm/server/transactional.js +3 -2
- package/package.json +4 -2
- package/pool/pool.d.ts +1 -1
- package/promise/cancelable-promise.d.ts +1 -0
- package/promise/cancelable-promise.js +1 -0
- package/promise/index.d.ts +1 -0
- package/promise/index.js +1 -0
- package/random/number-generator/index.d.ts +1 -0
- package/random/number-generator/index.js +1 -0
- package/sse/data-stream.js +16 -3
- package/task-queue/task-queue.d.ts +7 -2
- package/task-queue/task-queue.js +4 -1
- package/unit-test/integration-setup.d.ts +7 -6
- package/unit-test/integration-setup.js +7 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { inject, Injector } from '../../injector/index.js';
|
|
2
|
+
import { Database, migrate } from '../../orm/server/index.js';
|
|
3
|
+
export class NotificationConfiguration {
|
|
4
|
+
database;
|
|
5
|
+
}
|
|
6
|
+
export function configureNotification({ injector, ...config }) {
|
|
7
|
+
const targetInjector = injector ?? Injector;
|
|
8
|
+
targetInjector.register(NotificationConfiguration, { useValue: config });
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Migrates the notification schema.
|
|
12
|
+
*/
|
|
13
|
+
export async function migrateNotificationSchema() {
|
|
14
|
+
const connection = inject(NotificationConfiguration, undefined, { optional: true })?.database?.connection;
|
|
15
|
+
const database = inject(Database, connection);
|
|
16
|
+
await migrate(database, {
|
|
17
|
+
migrationsSchema: 'notification',
|
|
18
|
+
migrationsTable: '_migrations',
|
|
19
|
+
migrationsFolder: import.meta.resolve('./drizzle'),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { NotificationLog } from '../../models/notification-log.model.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
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 { inject } from '../../../injector/index.js';
|
|
8
|
+
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
|
+
import { NotificationTemplateService } from '../services/notification-template.service.js';
|
|
14
|
+
let EmailChannelProvider = class EmailChannelProvider {
|
|
15
|
+
#mailService = inject(MailService);
|
|
16
|
+
#userRepository = injectRepository(User);
|
|
17
|
+
#templateService = inject(NotificationTemplateService);
|
|
18
|
+
async send(notification) {
|
|
19
|
+
const user = await this.#userRepository.loadByQuery({ tenantId: notification.tenantId, id: notification.userId });
|
|
20
|
+
const content = await this.#templateService.render(notification, 'en'); // TODO: get user language
|
|
21
|
+
await this.#mailService.send({
|
|
22
|
+
to: user.email,
|
|
23
|
+
subject: content.emailSubject ?? content.title,
|
|
24
|
+
content: {
|
|
25
|
+
text: content.emailText ?? content.message,
|
|
26
|
+
html: content.emailHtml,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
EmailChannelProvider = __decorate([
|
|
32
|
+
NotificationSingleton()
|
|
33
|
+
], EmailChannelProvider);
|
|
34
|
+
export { EmailChannelProvider };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Transactional } from '../../../orm/server/index.js';
|
|
2
|
+
import { NotificationLog } from '../../models/index.js';
|
|
3
|
+
import type { ChannelProvider } from './channel-provider.js';
|
|
4
|
+
export declare class InAppChannelProvider extends Transactional implements ChannelProvider {
|
|
5
|
+
#private;
|
|
6
|
+
send(notification: NotificationLog): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { inject } from '../../../injector/index.js';
|
|
8
|
+
import { injectRepository, Transactional } from '../../../orm/server/index.js';
|
|
9
|
+
import { InAppNotification, NotificationLog } from '../../models/index.js';
|
|
10
|
+
import { NotificationSingleton } from '../services/singleton.js';
|
|
11
|
+
import { NotificationSseService } from '../services/notification-sse.service.js';
|
|
12
|
+
let InAppChannelProvider = class InAppChannelProvider extends Transactional {
|
|
13
|
+
#inAppRepository = injectRepository(InAppNotification);
|
|
14
|
+
#sseService = inject(NotificationSseService);
|
|
15
|
+
async send(notification) {
|
|
16
|
+
const inApp = await this.transaction(async (tx) => {
|
|
17
|
+
return await this.#inAppRepository.withTransaction(tx).insert({
|
|
18
|
+
tenantId: notification.tenantId,
|
|
19
|
+
userId: notification.userId,
|
|
20
|
+
logId: notification.id,
|
|
21
|
+
readAt: null,
|
|
22
|
+
archivedAt: null,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
await this.#sseService.send({ ...inApp, log: notification });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
InAppChannelProvider = __decorate([
|
|
29
|
+
NotificationSingleton()
|
|
30
|
+
], InAppChannelProvider);
|
|
31
|
+
export { InAppChannelProvider };
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { injectRepository } from '../../../orm/server/index.js';
|
|
8
|
+
import { NotificationLog, WebPushSubscription } from '../../models/index.js';
|
|
9
|
+
import { NotificationSingleton } from '../services/singleton.js';
|
|
10
|
+
let WebPushChannelProvider = class WebPushChannelProvider {
|
|
11
|
+
#subscriptionRepository = injectRepository(WebPushSubscription);
|
|
12
|
+
async send(notification) {
|
|
13
|
+
const subscriptions = await this.#subscriptionRepository.loadManyByQuery({
|
|
14
|
+
tenantId: notification.tenantId,
|
|
15
|
+
userId: notification.userId
|
|
16
|
+
});
|
|
17
|
+
for (const subscription of subscriptions) {
|
|
18
|
+
// TODO: Use a Web Push library (e.g. web-push) to send the notification
|
|
19
|
+
// Using subscription.endpoint, subscription.p256dh, subscription.auth
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
WebPushChannelProvider = __decorate([
|
|
24
|
+
NotificationSingleton()
|
|
25
|
+
], WebPushChannelProvider);
|
|
26
|
+
export { WebPushChannelProvider };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { InAppNotification, NotificationCategory, NotificationLog, NotificationPreference, WebPushSubscription } from '../models/index.js';
|
|
2
|
+
export declare const notificationSchema: import("../../orm/server/index.js").DatabaseSchema<"notification">;
|
|
3
|
+
export declare const notificationChannel: import("../../orm/enums.js").PgEnumFromEnumeration<{
|
|
4
|
+
readonly InApp: "in-app";
|
|
5
|
+
readonly Email: "email";
|
|
6
|
+
readonly WebPush: "web-push";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const notificationPriority: import("../../orm/enums.js").PgEnumFromEnumeration<{
|
|
9
|
+
readonly Low: "low";
|
|
10
|
+
readonly Medium: "medium";
|
|
11
|
+
readonly High: "high";
|
|
12
|
+
readonly Urgent: "urgent";
|
|
13
|
+
}>;
|
|
14
|
+
export declare const notificationStatus: import("../../orm/enums.js").PgEnumFromEnumeration<{
|
|
15
|
+
readonly Pending: "pending";
|
|
16
|
+
readonly Sent: "sent";
|
|
17
|
+
readonly Delivered: "delivered";
|
|
18
|
+
readonly Read: "read";
|
|
19
|
+
readonly Failed: "failed";
|
|
20
|
+
}>;
|
|
21
|
+
export declare const inAppNotification: import("../../orm/server/types.js").PgTableFromType<typeof InAppNotification, "notification">;
|
|
22
|
+
export declare const notificationCategory: import("../../orm/server/types.js").PgTableFromType<typeof NotificationCategory, "notification">;
|
|
23
|
+
export declare const notificationLog: import("../../orm/server/types.js").PgTableFromType<typeof NotificationLog, "notification">;
|
|
24
|
+
export declare const notificationPreference: import("../../orm/server/types.js").PgTableFromType<typeof NotificationPreference, "notification">;
|
|
25
|
+
export declare const webPushSubscription: import("../../orm/server/types.js").PgTableFromType<typeof WebPushSubscription, "notification">;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { databaseSchema } from '../../orm/server/index.js';
|
|
2
|
+
import { NotificationChannel, NotificationPriority, NotificationStatus } from '../enums.js';
|
|
3
|
+
import { InAppNotification, NotificationCategory, NotificationLog, NotificationPreference, WebPushSubscription } from '../models/index.js';
|
|
4
|
+
export const notificationSchema = databaseSchema('notification');
|
|
5
|
+
export const notificationChannel = notificationSchema.getEnum(NotificationChannel);
|
|
6
|
+
export const notificationPriority = notificationSchema.getEnum(NotificationPriority);
|
|
7
|
+
export const notificationStatus = notificationSchema.getEnum(NotificationStatus);
|
|
8
|
+
export const inAppNotification = notificationSchema.getTable(InAppNotification);
|
|
9
|
+
export const notificationCategory = notificationSchema.getTable(NotificationCategory);
|
|
10
|
+
export const notificationLog = notificationSchema.getTable(NotificationLog);
|
|
11
|
+
export const notificationPreference = notificationSchema.getTable(NotificationPreference);
|
|
12
|
+
export const webPushSubscription = notificationSchema.getTable(WebPushSubscription);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './notification-category.service.js';
|
|
2
|
+
export * from './notification-delivery.task.js';
|
|
3
|
+
export * from './notification-delivery.worker.js';
|
|
4
|
+
export * from './notification-sse.service.js';
|
|
5
|
+
export * from './notification-template.service.js';
|
|
6
|
+
export * from './notification-template.js';
|
|
7
|
+
export * from './notification.service.js';
|
|
8
|
+
export * from './singleton.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './notification-category.service.js';
|
|
2
|
+
export * from './notification-delivery.task.js';
|
|
3
|
+
export * from './notification-delivery.worker.js';
|
|
4
|
+
export * from './notification-sse.service.js';
|
|
5
|
+
export * from './notification-template.service.js';
|
|
6
|
+
export * from './notification-template.js';
|
|
7
|
+
export * from './notification.service.js';
|
|
8
|
+
export * from './singleton.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Transactional } from '../../../orm/server/index.js';
|
|
2
|
+
import { NotificationCategory, type EscalationRule, type ThrottlingConfig } from '../../models/index.js';
|
|
3
|
+
export type CategoryInitializationData = {
|
|
4
|
+
label: string;
|
|
5
|
+
throttling?: ThrottlingConfig;
|
|
6
|
+
escalations?: EscalationRule[];
|
|
7
|
+
};
|
|
8
|
+
export declare class NotificationCategoryService extends Transactional {
|
|
9
|
+
readonly repository: import("../../../orm/server/index.js").EntityRepository<NotificationCategory>;
|
|
10
|
+
initializeCategories<T extends string>(tenantId: string, categoryData: Record<T, CategoryInitializationData>): Promise<Record<T, NotificationCategory>>;
|
|
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 { injectRepository, Transactional } from '../../../orm/server/index.js';
|
|
8
|
+
import { fromEntries, objectEntries } from '../../../utils/object/index.js';
|
|
9
|
+
import { assertDefinedPass, isUndefined } from '../../../utils/type-guards.js';
|
|
10
|
+
import { NotificationCategory } from '../../models/index.js';
|
|
11
|
+
import { NotificationSingleton } from './singleton.js';
|
|
12
|
+
let NotificationCategoryService = class NotificationCategoryService extends Transactional {
|
|
13
|
+
repository = injectRepository(NotificationCategory);
|
|
14
|
+
async initializeCategories(tenantId, categoryData) {
|
|
15
|
+
const categoryEntries = objectEntries(categoryData);
|
|
16
|
+
const categoryMap = await this.transaction(async (tx) => {
|
|
17
|
+
const dbCategories = await this.repository.withTransaction(tx).loadManyByQuery({ tenantId });
|
|
18
|
+
const enumKeyCategoryMap = new Map(dbCategories.map((category) => [category.key, category]));
|
|
19
|
+
for (const [key, data] of categoryEntries) {
|
|
20
|
+
const category = enumKeyCategoryMap.get(key);
|
|
21
|
+
const throttling = data.throttling ?? null;
|
|
22
|
+
const escalations = data.escalations ?? null;
|
|
23
|
+
if (isUndefined(category)) {
|
|
24
|
+
const newCategory = await this.repository.withTransaction(tx).insert({ tenantId, key, label: data.label, throttling, escalations });
|
|
25
|
+
enumKeyCategoryMap.set(key, newCategory);
|
|
26
|
+
}
|
|
27
|
+
else if ((category.label != data.label) || (JSON.stringify(category.throttling) != JSON.stringify(throttling)) || (JSON.stringify(category.escalations) != JSON.stringify(escalations))) {
|
|
28
|
+
const updatedCategory = await this.repository.withTransaction(tx).updateByQuery({ tenantId, id: category.id }, { label: data.label, throttling, escalations });
|
|
29
|
+
enumKeyCategoryMap.set(key, updatedCategory);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return enumKeyCategoryMap;
|
|
33
|
+
});
|
|
34
|
+
const mappedCategories = categoryEntries.map(([key]) => [key, assertDefinedPass(categoryMap.get(key), 'Could not map notification category.')]);
|
|
35
|
+
return fromEntries(mappedCategories);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
NotificationCategoryService = __decorate([
|
|
39
|
+
NotificationSingleton()
|
|
40
|
+
], NotificationCategoryService);
|
|
41
|
+
export { NotificationCategoryService };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { NotificationLog } from '../../models/index.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CancellationSignal } from '../../../cancellation/token.js';
|
|
2
|
+
import { Transactional } from '../../../orm/server/index.js';
|
|
3
|
+
import { TaskProcessResult } from '../../../task-queue/task-queue.js';
|
|
4
|
+
import { NotificationChannel } from '../../enums.js';
|
|
5
|
+
import type { ChannelProvider } from '../providers/channel-provider.js';
|
|
6
|
+
export declare class NotificationDeliveryWorker extends Transactional {
|
|
7
|
+
#private;
|
|
8
|
+
registerProvider(channel: NotificationChannel, provider: ChannelProvider): void;
|
|
9
|
+
run(cancellationSignal: CancellationSignal): void;
|
|
10
|
+
deliver(notificationId: string): Promise<TaskProcessResult<void>>;
|
|
11
|
+
private sendToChannel;
|
|
12
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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 { inject } from '../../../injector/index.js';
|
|
8
|
+
import { Logger } from '../../../logger/logger.js';
|
|
9
|
+
import { injectRepository, Transactional } from '../../../orm/server/index.js';
|
|
10
|
+
import { RateLimiter } from '../../../rate-limit/rate-limiter.js';
|
|
11
|
+
import { TaskProcessResult, TaskQueue } from '../../../task-queue/task-queue.js';
|
|
12
|
+
import { isDefined, isNotNull, isUndefined } from '../../../utils/type-guards.js';
|
|
13
|
+
import { NotificationChannel, NotificationStatus } from '../../enums.js';
|
|
14
|
+
import { InAppNotification, NotificationCategory, NotificationLog, NotificationPreference } from '../../models/index.js';
|
|
15
|
+
import { NotificationSingleton } from './singleton.js';
|
|
16
|
+
let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transactional {
|
|
17
|
+
#notificationLogRepository = injectRepository(NotificationLog);
|
|
18
|
+
#inAppRepository = injectRepository(InAppNotification);
|
|
19
|
+
#preferenceRepository = injectRepository(NotificationPreference);
|
|
20
|
+
#categoryRepository = injectRepository(NotificationCategory);
|
|
21
|
+
#taskQueue = inject((TaskQueue), 'notification');
|
|
22
|
+
#rateLimiter = inject(RateLimiter, 'notification');
|
|
23
|
+
#logger = inject(Logger, 'NotificationDeliveryWorker');
|
|
24
|
+
#providers = new Map();
|
|
25
|
+
registerProvider(channel, provider) {
|
|
26
|
+
this.#providers.set(channel, provider);
|
|
27
|
+
}
|
|
28
|
+
run(cancellationSignal) {
|
|
29
|
+
this.#taskQueue.process({ cancellationSignal }, async (context) => {
|
|
30
|
+
const { notificationId } = context.data;
|
|
31
|
+
try {
|
|
32
|
+
const result = await this.deliver(notificationId);
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
this.#logger.error(`Failed to deliver notification ${notificationId}`, error);
|
|
37
|
+
return TaskProcessResult.Fail(error);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async deliver(notificationId) {
|
|
42
|
+
return await this.transaction(async (tx) => {
|
|
43
|
+
const notification = await this.#notificationLogRepository.withTransaction(tx).loadByQuery({ id: notificationId });
|
|
44
|
+
const category = await this.#categoryRepository.withTransaction(tx).loadByQuery({ id: notification.categoryId });
|
|
45
|
+
const step = notification.currentStep;
|
|
46
|
+
// 1. Throttling
|
|
47
|
+
if (step == 0 && isNotNull(category.throttling)) {
|
|
48
|
+
const resource = `${notification.tenantId}:${notification.userId}:${category.key}`;
|
|
49
|
+
const acquired = await this.#rateLimiter.withTransaction(tx).tryAcquire(resource);
|
|
50
|
+
if (!acquired) {
|
|
51
|
+
this.#logger.info(`Throttled notification ${notificationId} for user ${notification.userId} in category ${category.key}`);
|
|
52
|
+
return TaskProcessResult.RescheduleBy(category.throttling.intervalMs / category.throttling.limit);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 2. Initial Delivery
|
|
56
|
+
if (step == 0) {
|
|
57
|
+
const preferences = await this.#preferenceRepository.withTransaction(tx).loadManyByQuery({
|
|
58
|
+
tenantId: notification.tenantId,
|
|
59
|
+
userId: notification.userId,
|
|
60
|
+
categoryId: notification.categoryId,
|
|
61
|
+
});
|
|
62
|
+
const enabledChannels = preferences.length > 0
|
|
63
|
+
? preferences.filter((p) => p.enabled).map((p) => p.channel)
|
|
64
|
+
: [NotificationChannel.InApp];
|
|
65
|
+
for (const channel of enabledChannels) {
|
|
66
|
+
await this.sendToChannel(notification, channel);
|
|
67
|
+
}
|
|
68
|
+
await this.#notificationLogRepository.withTransaction(tx).updateByQuery({ id: notificationId }, { status: NotificationStatus.Sent, currentStep: 1 });
|
|
69
|
+
// Check for escalation
|
|
70
|
+
if (isNotNull(category.escalations) && category.escalations.length > 0) {
|
|
71
|
+
const firstEscalation = category.escalations[0];
|
|
72
|
+
return TaskProcessResult.RescheduleBy(firstEscalation.delayMs);
|
|
73
|
+
}
|
|
74
|
+
return TaskProcessResult.Complete();
|
|
75
|
+
}
|
|
76
|
+
// 3. Escalation Steps
|
|
77
|
+
if (isNotNull(category.escalations) && step > 0 && step <= category.escalations.length) {
|
|
78
|
+
const rule = category.escalations[step - 1];
|
|
79
|
+
// Check if already read (for In-App escalation)
|
|
80
|
+
const inApp = await this.#inAppRepository.withTransaction(tx).tryLoadByQuery({ logId: notificationId });
|
|
81
|
+
if (isDefined(inApp?.readAt)) {
|
|
82
|
+
this.#logger.debug(`Notification ${notificationId} already read, skipping escalation step ${step}`);
|
|
83
|
+
return TaskProcessResult.Complete();
|
|
84
|
+
}
|
|
85
|
+
await this.sendToChannel(notification, rule.channel);
|
|
86
|
+
const nextStep = step + 1;
|
|
87
|
+
await this.#notificationLogRepository.withTransaction(tx).updateByQuery({ id: notificationId }, { currentStep: nextStep });
|
|
88
|
+
if (step < category.escalations.length) {
|
|
89
|
+
const nextRule = category.escalations[step];
|
|
90
|
+
return TaskProcessResult.RescheduleBy(nextRule.delayMs);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return TaskProcessResult.Complete();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async sendToChannel(notification, channel) {
|
|
97
|
+
const provider = this.#providers.get(channel);
|
|
98
|
+
if (isUndefined(provider)) {
|
|
99
|
+
this.#logger.warn(`No provider registered for channel ${channel}`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
await provider.send(notification);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
NotificationDeliveryWorker = __decorate([
|
|
106
|
+
NotificationSingleton()
|
|
107
|
+
], NotificationDeliveryWorker);
|
|
108
|
+
export { NotificationDeliveryWorker };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ServerSentEventsSource } from '../../../sse/server-sent-events-source.js';
|
|
2
|
+
import type { InAppNotification } from '../../models/index.js';
|
|
3
|
+
type NotificationMessage = InAppNotification & {
|
|
4
|
+
log?: any;
|
|
5
|
+
};
|
|
6
|
+
export declare class NotificationSseService {
|
|
7
|
+
#private;
|
|
8
|
+
constructor();
|
|
9
|
+
register(tenantId: string, userId: string): ServerSentEventsSource;
|
|
10
|
+
send(notification: NotificationMessage): Promise<void>;
|
|
11
|
+
private dispatchToLocal;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { inject } from '../../../injector/index.js';
|
|
11
|
+
import { MessageBusProvider } from '../../../message-bus/message-bus-provider.js';
|
|
12
|
+
import { toObservable } from '../../../signals/api.js';
|
|
13
|
+
import { ServerSentEventsSource } from '../../../sse/server-sent-events-source.js';
|
|
14
|
+
import { NotificationSingleton } from './singleton.js';
|
|
15
|
+
let NotificationSseService = class NotificationSseService {
|
|
16
|
+
#messageBusProvider = inject(MessageBusProvider);
|
|
17
|
+
#messageBus = this.#messageBusProvider.get('notification');
|
|
18
|
+
#sources = new Map(); // tenantId -> userId -> Sources
|
|
19
|
+
constructor() {
|
|
20
|
+
this.#messageBus.allMessages$.subscribe((message) => {
|
|
21
|
+
this.dispatchToLocal(message);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
register(tenantId, userId) {
|
|
25
|
+
const source = new ServerSentEventsSource();
|
|
26
|
+
let tenantMap = this.#sources.get(tenantId);
|
|
27
|
+
if (tenantMap == null) {
|
|
28
|
+
tenantMap = new Map();
|
|
29
|
+
this.#sources.set(tenantId, tenantMap);
|
|
30
|
+
}
|
|
31
|
+
let userSources = tenantMap.get(userId);
|
|
32
|
+
if (userSources == null) {
|
|
33
|
+
userSources = new Set();
|
|
34
|
+
tenantMap.set(userId, userSources);
|
|
35
|
+
}
|
|
36
|
+
userSources.add(source);
|
|
37
|
+
toObservable(source.closed).subscribe((closed) => {
|
|
38
|
+
if (closed) {
|
|
39
|
+
const currentTenantMap = this.#sources.get(tenantId);
|
|
40
|
+
const currentUserSources = currentTenantMap?.get(userId);
|
|
41
|
+
if (currentUserSources) {
|
|
42
|
+
currentUserSources.delete(source);
|
|
43
|
+
if (currentUserSources.size == 0) {
|
|
44
|
+
currentTenantMap?.delete(userId);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (currentTenantMap?.size == 0) {
|
|
48
|
+
this.#sources.delete(tenantId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return source;
|
|
53
|
+
}
|
|
54
|
+
async send(notification) {
|
|
55
|
+
await this.#messageBus.publish(notification);
|
|
56
|
+
}
|
|
57
|
+
dispatchToLocal(notification) {
|
|
58
|
+
const tenantMap = this.#sources.get(notification.tenantId);
|
|
59
|
+
const userSources = tenantMap?.get(notification.userId);
|
|
60
|
+
if (userSources != null) {
|
|
61
|
+
for (const source of userSources) {
|
|
62
|
+
void source.sendJson({
|
|
63
|
+
name: 'notification',
|
|
64
|
+
data: notification,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
NotificationSseService = __decorate([
|
|
71
|
+
NotificationSingleton(),
|
|
72
|
+
__metadata("design:paramtypes", [])
|
|
73
|
+
], NotificationSseService);
|
|
74
|
+
export { NotificationSseService };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NotificationLog } from '../../models/index.js';
|
|
2
|
+
export interface NotificationTemplate<T extends NotificationLog = NotificationLog> {
|
|
3
|
+
key: string;
|
|
4
|
+
render(notification: T, language: string): Promise<NotificationContent>;
|
|
5
|
+
}
|
|
6
|
+
export type NotificationContent = {
|
|
7
|
+
title: string;
|
|
8
|
+
message: string;
|
|
9
|
+
emailSubject?: string;
|
|
10
|
+
emailHtml?: string;
|
|
11
|
+
emailText?: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NotificationLog } from '../../models/index.js';
|
|
2
|
+
import type { NotificationContent, NotificationTemplate } from './notification-template.js';
|
|
3
|
+
export declare class NotificationTemplateService {
|
|
4
|
+
#private;
|
|
5
|
+
register(template: NotificationTemplate<any>): void;
|
|
6
|
+
render(notification: NotificationLog, language?: string): Promise<NotificationContent>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 { isUndefined } from '../../../utils/type-guards.js';
|
|
8
|
+
import { NotificationSingleton } from './singleton.js';
|
|
9
|
+
let NotificationTemplateService = class NotificationTemplateService {
|
|
10
|
+
#templates = new Map();
|
|
11
|
+
register(template) {
|
|
12
|
+
this.#templates.set(template.key, template);
|
|
13
|
+
}
|
|
14
|
+
async render(notification, language = 'en') {
|
|
15
|
+
const template = this.#templates.get(notification.type);
|
|
16
|
+
if (isUndefined(template)) {
|
|
17
|
+
// Fallback for simple notifications without a specific template
|
|
18
|
+
return {
|
|
19
|
+
title: 'New Notification',
|
|
20
|
+
message: 'You have a new notification.',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return await template.render(notification, language);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
NotificationTemplateService = __decorate([
|
|
27
|
+
NotificationSingleton()
|
|
28
|
+
], NotificationTemplateService);
|
|
29
|
+
export { NotificationTemplateService };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Transactional } from '../../../orm/server/index.js';
|
|
2
|
+
import type { NotificationChannel } from '../../enums.js';
|
|
3
|
+
import { NotificationLog, NotificationPreference } from '../../models/index.js';
|
|
4
|
+
export declare class NotificationService extends Transactional {
|
|
5
|
+
#private;
|
|
6
|
+
send(notification: NotificationLog): Promise<void>;
|
|
7
|
+
listInApp(tenantId: string, userId: string, options?: {
|
|
8
|
+
limit?: number;
|
|
9
|
+
offset?: number;
|
|
10
|
+
includeArchived?: boolean;
|
|
11
|
+
}): Promise<any[]>;
|
|
12
|
+
markRead(tenantId: string, userId: string, id: string): Promise<void>;
|
|
13
|
+
archive(tenantId: string, userId: string, id: string): Promise<void>;
|
|
14
|
+
getPreferences(tenantId: string, userId: string): Promise<NotificationPreference[]>;
|
|
15
|
+
updatePreference(tenantId: string, userId: string, categoryId: string, channel: NotificationChannel, enabled: boolean): Promise<void>;
|
|
16
|
+
registerWebPush(tenantId: string, userId: string, endpoint: string, p256dh: string, auth: string): Promise<void>;
|
|
17
|
+
}
|