@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.
Files changed (126) hide show
  1. package/audit/auditor.d.ts +1 -1
  2. package/context/context.d.ts +1 -1
  3. package/context/context.js +4 -0
  4. package/cookie/cookie.js +5 -1
  5. package/css/css-variables.d.ts +38 -0
  6. package/css/css-variables.js +38 -0
  7. package/document-management/api/document-management.api.d.ts +17 -3
  8. package/document-management/api/document-management.api.js +8 -1
  9. package/document-management/models/document-category.model.js +0 -1
  10. package/document-management/models/document-property.model.js +0 -1
  11. package/document-management/server/api/document-management.api.d.ts +1 -0
  12. package/document-management/server/api/document-management.api.js +13 -1
  13. package/document-management/server/drizzle/{0000_needy_steel_serpent.sql → 0000_silly_chimera.sql} +0 -2
  14. package/document-management/server/drizzle/meta/0000_snapshot.json +1 -15
  15. package/document-management/server/drizzle/meta/_journal.json +2 -2
  16. package/document-management/server/services/document-statistics.service.d.ts +6 -0
  17. package/document-management/server/services/document-statistics.service.js +167 -0
  18. package/document-management/server/services/index.d.ts +1 -0
  19. package/document-management/server/services/index.js +1 -0
  20. package/document-management/service-models/document-statistics.view-model.d.ts +38 -0
  21. package/document-management/service-models/document-statistics.view-model.js +160 -0
  22. package/document-management/service-models/document.service-model.d.ts +1 -1
  23. package/document-management/service-models/index.d.ts +1 -0
  24. package/document-management/service-models/index.js +1 -0
  25. package/document-management/tests/document-management-core.test.js +2 -2
  26. package/document-management/tests/document-management.api.test.d.ts +1 -0
  27. package/document-management/tests/document-management.api.test.js +102 -0
  28. package/document-management/tests/document-statistics.service.test.d.ts +1 -0
  29. package/document-management/tests/document-statistics.service.test.js +495 -0
  30. package/document-management/tests/enum-helpers.test.js +3 -2
  31. package/enumeration/enumeration.d.ts +24 -0
  32. package/enumeration/enumeration.js +20 -0
  33. package/examples/document-management/main.js +1 -1
  34. package/intl/number-parser.d.ts +16 -9
  35. package/intl/number-parser.js +31 -19
  36. package/module/module.js +3 -0
  37. package/notification/api/notification.api.d.ts +78 -0
  38. package/notification/api/notification.api.js +81 -0
  39. package/notification/enums.d.ts +22 -0
  40. package/notification/enums.js +19 -0
  41. package/notification/index.d.ts +2 -0
  42. package/notification/index.js +2 -0
  43. package/notification/models/in-app-notification.model.d.ts +9 -0
  44. package/notification/models/in-app-notification.model.js +42 -0
  45. package/notification/models/index.d.ts +6 -0
  46. package/notification/models/index.js +6 -0
  47. package/notification/models/notification-category.model.d.ts +17 -0
  48. package/notification/models/notification-category.model.js +41 -0
  49. package/notification/models/notification-log.model.d.ts +13 -0
  50. package/notification/models/notification-log.model.js +59 -0
  51. package/notification/models/notification-preference.model.d.ts +9 -0
  52. package/notification/models/notification-preference.model.js +45 -0
  53. package/notification/models/notification-table.d.ts +3 -0
  54. package/notification/models/notification-table.js +4 -0
  55. package/notification/models/web-push-subscription.model.d.ts +8 -0
  56. package/notification/models/web-push-subscription.model.js +41 -0
  57. package/notification/server/api/notification.api-controller.d.ts +16 -0
  58. package/notification/server/api/notification.api-controller.js +51 -0
  59. package/notification/server/drizzle/0000_glorious_randall.sql +90 -0
  60. package/notification/server/drizzle/meta/0000_snapshot.json +652 -0
  61. package/notification/server/drizzle/meta/_journal.json +13 -0
  62. package/notification/server/drizzle.config.d.ts +2 -0
  63. package/notification/server/drizzle.config.js +11 -0
  64. package/notification/server/index.d.ts +4 -0
  65. package/notification/server/index.js +4 -0
  66. package/notification/server/module.d.ts +12 -0
  67. package/notification/server/module.js +21 -0
  68. package/notification/server/providers/channel-provider.d.ts +4 -0
  69. package/notification/server/providers/channel-provider.js +1 -0
  70. package/notification/server/providers/email-channel-provider.d.ts +6 -0
  71. package/notification/server/providers/email-channel-provider.js +34 -0
  72. package/notification/server/providers/in-app-channel-provider.d.ts +7 -0
  73. package/notification/server/providers/in-app-channel-provider.js +31 -0
  74. package/notification/server/providers/index.d.ts +4 -0
  75. package/notification/server/providers/index.js +4 -0
  76. package/notification/server/providers/web-push-channel-provider.d.ts +6 -0
  77. package/notification/server/providers/web-push-channel-provider.js +26 -0
  78. package/notification/server/schemas.d.ts +25 -0
  79. package/notification/server/schemas.js +12 -0
  80. package/notification/server/services/index.d.ts +8 -0
  81. package/notification/server/services/index.js +8 -0
  82. package/notification/server/services/notification-category.service.d.ts +11 -0
  83. package/notification/server/services/notification-category.service.js +41 -0
  84. package/notification/server/services/notification-delivery.task.d.ts +9 -0
  85. package/notification/server/services/notification-delivery.task.js +1 -0
  86. package/notification/server/services/notification-delivery.worker.d.ts +12 -0
  87. package/notification/server/services/notification-delivery.worker.js +108 -0
  88. package/notification/server/services/notification-sse.service.d.ts +13 -0
  89. package/notification/server/services/notification-sse.service.js +74 -0
  90. package/notification/server/services/notification-template.d.ts +12 -0
  91. package/notification/server/services/notification-template.js +1 -0
  92. package/notification/server/services/notification-template.service.d.ts +7 -0
  93. package/notification/server/services/notification-template.service.js +29 -0
  94. package/notification/server/services/notification.service.d.ts +17 -0
  95. package/notification/server/services/notification.service.js +80 -0
  96. package/notification/server/services/singleton.d.ts +3 -0
  97. package/notification/server/services/singleton.js +10 -0
  98. package/notification/tests/notification-category.service.test.d.ts +1 -0
  99. package/notification/tests/notification-category.service.test.js +36 -0
  100. package/notification/tests/notification-flow.test.d.ts +1 -0
  101. package/notification/tests/notification-flow.test.js +112 -0
  102. package/notification/tests/notification-sse.service.test.d.ts +1 -0
  103. package/notification/tests/notification-sse.service.test.js +20 -0
  104. package/notification/tests/test-notification.model.d.ts +4 -0
  105. package/notification/tests/test-notification.model.js +25 -0
  106. package/object-storage/google/google.object-storage-provider.d.ts +0 -1
  107. package/object-storage/google/google.object-storage-provider.js +0 -1
  108. package/object-storage/index.d.ts +0 -1
  109. package/object-storage/index.js +0 -1
  110. package/object-storage/s3/s3.object-storage-provider.d.ts +0 -1
  111. package/object-storage/s3/s3.object-storage-provider.js +0 -1
  112. package/orm/server/transactional.d.ts +3 -2
  113. package/orm/server/transactional.js +3 -2
  114. package/package.json +4 -2
  115. package/pool/pool.d.ts +1 -1
  116. package/promise/cancelable-promise.d.ts +1 -0
  117. package/promise/cancelable-promise.js +1 -0
  118. package/promise/index.d.ts +1 -0
  119. package/promise/index.js +1 -0
  120. package/random/number-generator/index.d.ts +1 -0
  121. package/random/number-generator/index.js +1 -0
  122. package/sse/data-stream.js +16 -3
  123. package/task-queue/task-queue.d.ts +7 -2
  124. package/task-queue/task-queue.js +4 -1
  125. package/unit-test/integration-setup.d.ts +7 -6
  126. 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,4 @@
1
+ import { NotificationLog } from '../../models/notification-log.model.js';
2
+ export interface ChannelProvider {
3
+ send(notification: NotificationLog): Promise<void>;
4
+ }
@@ -0,0 +1 @@
1
+ import { NotificationLog } from '../../models/notification-log.model.js';
@@ -0,0 +1,6 @@
1
+ import { NotificationLog } from '../../models/index.js';
2
+ import type { ChannelProvider } from './channel-provider.js';
3
+ export declare class EmailChannelProvider implements ChannelProvider {
4
+ #private;
5
+ send(notification: NotificationLog): Promise<void>;
6
+ }
@@ -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,4 @@
1
+ export * from './email-channel-provider.js';
2
+ export * from './in-app-channel-provider.js';
3
+ export * from './web-push-channel-provider.js';
4
+ export * from './channel-provider.js';
@@ -0,0 +1,4 @@
1
+ export * from './email-channel-provider.js';
2
+ export * from './in-app-channel-provider.js';
3
+ export * from './web-push-channel-provider.js';
4
+ export * from './channel-provider.js';
@@ -0,0 +1,6 @@
1
+ import { NotificationLog } from '../../models/index.js';
2
+ import type { ChannelProvider } from './channel-provider.js';
3
+ export declare class WebPushChannelProvider implements ChannelProvider {
4
+ #private;
5
+ send(notification: NotificationLog): Promise<void>;
6
+ }
@@ -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,9 @@
1
+ export type NotificationTaskDefinitions = {
2
+ 'notification/deliver': {
3
+ data: {
4
+ notificationId: string;
5
+ };
6
+ state: void;
7
+ result: void;
8
+ };
9
+ };
@@ -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,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
+ }