@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,80 @@
|
|
|
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 { TaskQueue } from '../../../task-queue/task-queue.js';
|
|
10
|
+
import { currentTimestamp } from '../../../utils/date-time.js';
|
|
11
|
+
import { NotificationStatus } from '../../enums.js';
|
|
12
|
+
import { InAppNotification, NotificationLog, NotificationPreference, WebPushSubscription } from '../../models/index.js';
|
|
13
|
+
import { NotificationSingleton } from './singleton.js';
|
|
14
|
+
let NotificationService = class NotificationService extends Transactional {
|
|
15
|
+
#notificationLogRepository = injectRepository(NotificationLog);
|
|
16
|
+
#inAppNotificationRepository = injectRepository(InAppNotification);
|
|
17
|
+
#preferenceRepository = injectRepository(NotificationPreference);
|
|
18
|
+
#webPushSubscriptionRepository = injectRepository(WebPushSubscription);
|
|
19
|
+
#taskQueue = inject((TaskQueue), 'notification');
|
|
20
|
+
async send(notification) {
|
|
21
|
+
await this.transaction(async (tx) => {
|
|
22
|
+
const notificationToInsert = {
|
|
23
|
+
...notification,
|
|
24
|
+
status: NotificationStatus.Pending,
|
|
25
|
+
currentStep: 0,
|
|
26
|
+
};
|
|
27
|
+
const insertedNotification = await this.#notificationLogRepository.withTransaction(tx).insert(notificationToInsert);
|
|
28
|
+
await this.#taskQueue.withTransaction(tx).enqueue('notification/deliver', { notificationId: insertedNotification.id });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async listInApp(tenantId, userId, options = {}) {
|
|
32
|
+
const inAppNotifications = await this.#inAppNotificationRepository.loadManyByQuery({
|
|
33
|
+
tenantId,
|
|
34
|
+
userId,
|
|
35
|
+
archivedAt: options.includeArchived ? undefined : null,
|
|
36
|
+
}, {
|
|
37
|
+
limit: options.limit,
|
|
38
|
+
offset: options.offset,
|
|
39
|
+
order: { 'metadata.createTimestamp': 'desc' },
|
|
40
|
+
});
|
|
41
|
+
const logIds = inAppNotifications.map((n) => n.logId);
|
|
42
|
+
const logs = await this.#notificationLogRepository.loadMany(logIds);
|
|
43
|
+
const logsMap = new Map(logs.map((log) => [log.id, log]));
|
|
44
|
+
return inAppNotifications.map((n) => ({
|
|
45
|
+
...n,
|
|
46
|
+
log: logsMap.get(n.logId),
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
async markRead(tenantId, userId, id) {
|
|
50
|
+
await this.#inAppNotificationRepository.updateByQuery({ tenantId, id, userId }, { readAt: currentTimestamp() });
|
|
51
|
+
}
|
|
52
|
+
async archive(tenantId, userId, id) {
|
|
53
|
+
await this.#inAppNotificationRepository.updateByQuery({ tenantId, id, userId }, { archivedAt: currentTimestamp() });
|
|
54
|
+
}
|
|
55
|
+
async getPreferences(tenantId, userId) {
|
|
56
|
+
return await this.#preferenceRepository.loadManyByQuery({ tenantId, userId });
|
|
57
|
+
}
|
|
58
|
+
async updatePreference(tenantId, userId, categoryId, channel, enabled) {
|
|
59
|
+
await this.#preferenceRepository.upsert(['tenantId', 'userId', 'categoryId', 'channel'], {
|
|
60
|
+
tenantId,
|
|
61
|
+
userId,
|
|
62
|
+
categoryId,
|
|
63
|
+
channel,
|
|
64
|
+
enabled,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async registerWebPush(tenantId, userId, endpoint, p256dh, auth) {
|
|
68
|
+
await this.#webPushSubscriptionRepository.upsert(['tenantId', 'userId', 'endpoint'], {
|
|
69
|
+
tenantId,
|
|
70
|
+
userId,
|
|
71
|
+
endpoint,
|
|
72
|
+
p256dh,
|
|
73
|
+
auth,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
NotificationService = __decorate([
|
|
78
|
+
NotificationSingleton()
|
|
79
|
+
], NotificationService);
|
|
80
|
+
export { NotificationService };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const notificationDatabaseConfigFactoryProvider: import("../../../injector/provider.js").FactoryProvider<unknown, unknown, import("../../../types/types.js").Record>;
|
|
2
|
+
export declare const notificationDatabaseConfigProvider: import("../../../injector/injector.js").ProvidersItem<unknown, unknown, import("../../../types/types.js").Record>;
|
|
3
|
+
export declare function NotificationSingleton(): ClassDecorator;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Singleton } from '../../../injector/decorators.js';
|
|
2
|
+
import { provide } from '../../../injector/injector.js';
|
|
3
|
+
import { factoryProvider } from '../../../injector/provider.js';
|
|
4
|
+
import { DatabaseConfig } from '../../../orm/server/index.js';
|
|
5
|
+
import { NotificationConfiguration } from '../module.js';
|
|
6
|
+
export const notificationDatabaseConfigFactoryProvider = factoryProvider((_, context) => context.resolve(NotificationConfiguration).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: 2 }));
|
|
7
|
+
export const notificationDatabaseConfigProvider = provide(DatabaseConfig, notificationDatabaseConfigFactoryProvider);
|
|
8
|
+
export function NotificationSingleton() {
|
|
9
|
+
return Singleton({ providers: [notificationDatabaseConfigProvider] });
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { runInInjectionContext } from '../../injector/index.js';
|
|
3
|
+
import { setupIntegrationTest, truncateTables } from '../../unit-test/index.js';
|
|
4
|
+
import { NotificationCategoryService } from '../server/services/notification-category.service.js';
|
|
5
|
+
describe('NotificationCategoryService', () => {
|
|
6
|
+
test('should initialize categories correctly', async () => {
|
|
7
|
+
const { injector, database } = await setupIntegrationTest({ modules: { notification: true, authentication: true } });
|
|
8
|
+
// Cleanup
|
|
9
|
+
await truncateTables(database, 'notification', ['category']);
|
|
10
|
+
const service = injector.resolve(NotificationCategoryService);
|
|
11
|
+
const tenantId = '00000000-0000-0000-0000-000000000001';
|
|
12
|
+
const categoryData = {
|
|
13
|
+
CAT1: { label: 'Category 1' },
|
|
14
|
+
CAT2: { label: 'Category 2', throttling: { limit: 1, intervalMs: 1000 } }
|
|
15
|
+
};
|
|
16
|
+
await runInInjectionContext(injector, async () => {
|
|
17
|
+
const result = await service.initializeCategories(tenantId, categoryData);
|
|
18
|
+
expect(result.CAT1.label).toBe('Category 1');
|
|
19
|
+
expect(result.CAT2.key).toBe('CAT2');
|
|
20
|
+
expect(result.CAT2.throttling?.limit).toBe(1);
|
|
21
|
+
// Verify persistence
|
|
22
|
+
const dbCategories = await service.repository.loadManyByQuery({ tenantId });
|
|
23
|
+
expect(dbCategories).toHaveLength(2);
|
|
24
|
+
// Update
|
|
25
|
+
const updatedData = {
|
|
26
|
+
CAT1: { label: 'Category 1 Updated' },
|
|
27
|
+
CAT2: { label: 'Category 2', throttling: { limit: 1, intervalMs: 1000 } }
|
|
28
|
+
};
|
|
29
|
+
const resultUpdated = await service.initializeCategories(tenantId, updatedData);
|
|
30
|
+
expect(resultUpdated.CAT1.label).toBe('Category 1 Updated');
|
|
31
|
+
const dbCategoriesUpdated = await service.repository.loadManyByQuery({ tenantId });
|
|
32
|
+
expect(dbCategoriesUpdated).toHaveLength(2);
|
|
33
|
+
expect(dbCategoriesUpdated.find((c) => c.key == 'CAT1')?.label).toBe('Category 1 Updated');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { SubjectService } from '../../authentication/server/subject.service.js';
|
|
3
|
+
import { runInInjectionContext } from '../../injector/index.js';
|
|
4
|
+
import { MailService } from '../../mail/mail.service.js';
|
|
5
|
+
import { injectRepository } from '../../orm/server/index.js';
|
|
6
|
+
import { setupIntegrationTest, truncateTables } from '../../unit-test/index.js';
|
|
7
|
+
import { NotificationChannel, NotificationStatus } from '../enums.js';
|
|
8
|
+
import { InAppNotification, NotificationLog } from '../models/index.js';
|
|
9
|
+
import { EmailChannelProvider } from '../server/providers/email-channel-provider.js';
|
|
10
|
+
import { NotificationCategoryService } from '../server/services/notification-category.service.js';
|
|
11
|
+
import { NotificationDeliveryWorker } from '../server/services/notification-delivery.worker.js';
|
|
12
|
+
import { NotificationService } from '../server/services/notification.service.js';
|
|
13
|
+
describe('Notification Flow (Integration)', () => {
|
|
14
|
+
let injector;
|
|
15
|
+
let database;
|
|
16
|
+
let notificationService;
|
|
17
|
+
let worker;
|
|
18
|
+
let categoryService;
|
|
19
|
+
let subjectService;
|
|
20
|
+
let mailServiceMock;
|
|
21
|
+
const schema = 'notification';
|
|
22
|
+
const tenantId = '00000000-0000-0000-0000-000000000000';
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
({ injector, database } = await setupIntegrationTest({
|
|
25
|
+
orm: { schema },
|
|
26
|
+
modules: {
|
|
27
|
+
taskQueue: true,
|
|
28
|
+
rateLimiter: true,
|
|
29
|
+
authentication: true,
|
|
30
|
+
messageBus: true,
|
|
31
|
+
signals: true,
|
|
32
|
+
notification: true,
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
// Mock MailService
|
|
36
|
+
mailServiceMock = { send: vi.fn() };
|
|
37
|
+
injector.register(MailService, { useValue: mailServiceMock });
|
|
38
|
+
// Resolve Services
|
|
39
|
+
notificationService = injector.resolve(NotificationService);
|
|
40
|
+
worker = injector.resolve(NotificationDeliveryWorker);
|
|
41
|
+
categoryService = injector.resolve(NotificationCategoryService);
|
|
42
|
+
subjectService = injector.resolve(SubjectService);
|
|
43
|
+
// Register providers
|
|
44
|
+
worker.registerProvider(NotificationChannel.Email, injector.resolve(EmailChannelProvider));
|
|
45
|
+
const InAppChannelProvider = (await import('../server/providers/in-app-channel-provider.js')).InAppChannelProvider;
|
|
46
|
+
worker.registerProvider(NotificationChannel.InApp, injector.resolve(InAppChannelProvider));
|
|
47
|
+
await truncateTables(database, 'authentication', ['user', 'subject']);
|
|
48
|
+
await truncateTables(database, 'notification', ['log', 'in_app', 'category', 'preference', 'web_push_subscription']);
|
|
49
|
+
});
|
|
50
|
+
test('should execute full notification flow with escalation', async () => {
|
|
51
|
+
await runInInjectionContext(injector, async () => {
|
|
52
|
+
// Resolve Dependencies synchronously at start
|
|
53
|
+
const logRepo = injectRepository(NotificationLog);
|
|
54
|
+
const inAppRepo = injectRepository(InAppNotification);
|
|
55
|
+
// 1. Setup Data
|
|
56
|
+
const user = await subjectService.createUser({
|
|
57
|
+
tenantId,
|
|
58
|
+
email: 'test@example.com',
|
|
59
|
+
firstName: 'Test',
|
|
60
|
+
lastName: 'User',
|
|
61
|
+
});
|
|
62
|
+
// Initialize Category with Escalation
|
|
63
|
+
const categories = await categoryService.initializeCategories(tenantId, {
|
|
64
|
+
TEST_CAT: {
|
|
65
|
+
label: 'Test Category',
|
|
66
|
+
escalations: [{ delayMs: 1000, channel: NotificationChannel.Email }],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
const category = categories.TEST_CAT;
|
|
70
|
+
// 2. Send Notification
|
|
71
|
+
const notification = Object.assign(new NotificationLog(), {
|
|
72
|
+
tenantId,
|
|
73
|
+
userId: user.id,
|
|
74
|
+
categoryId: category.id,
|
|
75
|
+
type: 'test',
|
|
76
|
+
priority: 'high',
|
|
77
|
+
payload: { message: 'Hello', testField: 'Test Value' },
|
|
78
|
+
});
|
|
79
|
+
await notificationService.send(notification);
|
|
80
|
+
// Verify Log Created
|
|
81
|
+
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
82
|
+
expect(logs).toHaveLength(1);
|
|
83
|
+
const log = logs[0];
|
|
84
|
+
expect(log.status).toBe(NotificationStatus.Pending);
|
|
85
|
+
expect(log.currentStep).toBe(0);
|
|
86
|
+
expect(log.payload.testField).toBe('Test Value');
|
|
87
|
+
// 3. Worker Execution - Step 0 (In-App)
|
|
88
|
+
const result0 = await worker.deliver(log.id);
|
|
89
|
+
// Verify Result
|
|
90
|
+
expect(result0.payload.action).toBe('reschedule');
|
|
91
|
+
// Verify In-App Created
|
|
92
|
+
const inApps = await inAppRepo.loadManyByQuery({ tenantId });
|
|
93
|
+
expect(inApps).toHaveLength(1);
|
|
94
|
+
expect(inApps[0].logId).toBe(log.id);
|
|
95
|
+
// Verify Log Updated
|
|
96
|
+
const logAfterStep0 = await logRepo.load(log.id);
|
|
97
|
+
expect(logAfterStep0.status).toBe(NotificationStatus.Sent);
|
|
98
|
+
expect(logAfterStep0.currentStep).toBe(1);
|
|
99
|
+
// 4. Worker Execution - Step 1 (Email Escalation)
|
|
100
|
+
const result1 = await worker.deliver(log.id);
|
|
101
|
+
// Verify Result
|
|
102
|
+
expect(result1.payload.action).toBe('complete');
|
|
103
|
+
// Verify Email Sent
|
|
104
|
+
expect(mailServiceMock.send).toHaveBeenCalled();
|
|
105
|
+
const mailArgs = mailServiceMock.send.mock.calls[0][0];
|
|
106
|
+
expect(mailArgs.to).toBe('test@example.com');
|
|
107
|
+
// Verify Log Updated
|
|
108
|
+
const logAfterStep1 = await logRepo.load(log.id);
|
|
109
|
+
expect(logAfterStep1.currentStep).toBe(2);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { runInInjectionContext } from '../../injector/index.js';
|
|
3
|
+
import { setupIntegrationTest } from '../../unit-test/index.js';
|
|
4
|
+
import { NotificationSseService } from '../server/services/notification-sse.service.js';
|
|
5
|
+
describe('NotificationSseService', () => {
|
|
6
|
+
test('should register and publish to bus', async () => {
|
|
7
|
+
const { injector } = await setupIntegrationTest({ modules: { messageBus: true, signals: true } });
|
|
8
|
+
const service = injector.resolve(NotificationSseService);
|
|
9
|
+
const tenantId = 't1';
|
|
10
|
+
const userId = 'u1';
|
|
11
|
+
await runInInjectionContext(injector, async () => {
|
|
12
|
+
const source = service.register(tenantId, userId);
|
|
13
|
+
expect(source).toBeDefined();
|
|
14
|
+
// We can't easily spy on the LocalMessageBus internals without more complex setup,
|
|
15
|
+
// but we can verify that sending doesn't throw.
|
|
16
|
+
const msg = { tenantId, userId, logId: 'l1' };
|
|
17
|
+
await expect(service.send(msg)).resolves.not.toThrow();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
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 { ChildEntity } from '../../orm/index.js';
|
|
11
|
+
import { StringProperty } from '../../schema/index.js';
|
|
12
|
+
import { NotificationLog } from '../models/notification-log.model.js';
|
|
13
|
+
import { NotificationTable } from '../models/notification-table.js';
|
|
14
|
+
let TestNotification = class TestNotification extends NotificationLog {
|
|
15
|
+
testField;
|
|
16
|
+
};
|
|
17
|
+
__decorate([
|
|
18
|
+
StringProperty(),
|
|
19
|
+
__metadata("design:type", String)
|
|
20
|
+
], TestNotification.prototype, "testField", void 0);
|
|
21
|
+
TestNotification = __decorate([
|
|
22
|
+
NotificationTable({ name: 'test_notification' }),
|
|
23
|
+
ChildEntity('test')
|
|
24
|
+
], TestNotification);
|
|
25
|
+
export { TestNotification };
|
|
@@ -29,7 +29,6 @@ export declare class GoogleObjectStorageProviderConfig {
|
|
|
29
29
|
*/
|
|
30
30
|
bucketPerModule?: boolean;
|
|
31
31
|
}
|
|
32
|
-
export declare const bucketPerModule: unique symbol;
|
|
33
32
|
export declare class GoogleObjectStorageProvider extends ObjectStorageProvider<GoogleObjectStorage> {
|
|
34
33
|
private readonly client;
|
|
35
34
|
private readonly bucket;
|
|
@@ -39,7 +39,6 @@ export class GoogleObjectStorageProviderConfig {
|
|
|
39
39
|
*/
|
|
40
40
|
bucketPerModule;
|
|
41
41
|
}
|
|
42
|
-
export const bucketPerModule = Symbol('bucket per module');
|
|
43
42
|
let GoogleObjectStorageProvider = class GoogleObjectStorageProvider extends ObjectStorageProvider {
|
|
44
43
|
client;
|
|
45
44
|
bucket;
|
package/object-storage/index.js
CHANGED
|
@@ -26,7 +26,6 @@ export declare class S3ObjectStorageProviderConfig {
|
|
|
26
26
|
*/
|
|
27
27
|
secretKey: string;
|
|
28
28
|
}
|
|
29
|
-
export declare const bucketPerModule: unique symbol;
|
|
30
29
|
export declare class S3ObjectStorageProvider extends ObjectStorageProvider<S3ObjectStorage> {
|
|
31
30
|
private readonly client;
|
|
32
31
|
private readonly bucket;
|
|
@@ -19,10 +19,11 @@ declare const getCurrentTransactionalContext: {
|
|
|
19
19
|
(required: true, debugFn: import("../../types/index.js").Function): TransactionalContext<unknown>;
|
|
20
20
|
(required?: false, debugFn?: import("../../types/index.js").Function): TransactionalContext<unknown> | null;
|
|
21
21
|
(required: boolean, debugFn: import("../../types/index.js").Function): TransactionalContext<unknown> | null;
|
|
22
|
-
}, runInTransactionalContext: <ReturnT>(context: TransactionalContext<unknown>, fn: () => ReturnT) => ReturnT,
|
|
23
|
-
export { getCurrentTransactionalContext, isInTransactionalContext, runInTransactionalContext };
|
|
22
|
+
}, isInTransactionalContext: () => boolean, runInTransactionalContext: <ReturnT>(context: TransactionalContext<unknown>, fn: () => ReturnT) => ReturnT, tryGetCurrentTransactionalContext: () => TransactionalContext<unknown> | null;
|
|
23
|
+
export { getCurrentTransactionalContext, isInTransactionalContext, runInTransactionalContext, tryGetCurrentTransactionalContext };
|
|
24
24
|
export declare abstract class Transactional<ContextData = unknown> {
|
|
25
25
|
#private;
|
|
26
|
+
protected transactionalContextData: ContextData | undefined;
|
|
26
27
|
readonly session: Database | PgTransaction;
|
|
27
28
|
readonly isInTransaction: boolean;
|
|
28
29
|
constructor();
|
|
@@ -6,8 +6,8 @@ import { isDefined, isNull, isUndefined } from '../../utils/type-guards.js';
|
|
|
6
6
|
import { Database } from './database.js';
|
|
7
7
|
import { DrizzleTransaction, Transaction } from './transaction.js';
|
|
8
8
|
const transactionCache = new WeakMap();
|
|
9
|
-
const { getCurrentTransactionalContext, runInTransactionalContext,
|
|
10
|
-
export { getCurrentTransactionalContext, isInTransactionalContext, runInTransactionalContext };
|
|
9
|
+
const { getCurrentTransactionalContext, isInTransactionalContext, runInTransactionalContext, tryGetCurrentTransactionalContext } = createContextProvider('Transactional');
|
|
10
|
+
export { getCurrentTransactionalContext, isInTransactionalContext, runInTransactionalContext, tryGetCurrentTransactionalContext };
|
|
11
11
|
function transactionalContextDataGuardFunction() {
|
|
12
12
|
throw new Error('function getTransactionalContextData must be implemented to use transactional context data.');
|
|
13
13
|
}
|
|
@@ -39,6 +39,7 @@ export class Transactional {
|
|
|
39
39
|
}
|
|
40
40
|
return cache;
|
|
41
41
|
}
|
|
42
|
+
transactionalContextData = this.#context.data;
|
|
42
43
|
session = this.#context.session ?? inject(Database);
|
|
43
44
|
isInTransaction = this.session instanceof DrizzlePgTransaction;
|
|
44
45
|
constructor() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.99",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -87,6 +87,8 @@
|
|
|
87
87
|
"./migration": "./migration/index.js",
|
|
88
88
|
"./module": "./module/index.js",
|
|
89
89
|
"./object-storage": "./object-storage/index.js",
|
|
90
|
+
"./object-storage/google": "./object-storage/google/index.js",
|
|
91
|
+
"./object-storage/s3": "./object-storage/s3/index.js",
|
|
90
92
|
"./openid-connect": "./openid-connect/index.js",
|
|
91
93
|
"./orm": "./orm/index.js",
|
|
92
94
|
"./orm/schemas": "./orm/schemas/index.js",
|
|
@@ -195,7 +197,7 @@
|
|
|
195
197
|
"typedoc-plugin-markdown": "4.9",
|
|
196
198
|
"typedoc-plugin-missing-exports": "4.1",
|
|
197
199
|
"typescript": "5.9",
|
|
198
|
-
"typescript-eslint": "8.
|
|
200
|
+
"typescript-eslint": "8.54",
|
|
199
201
|
"vite-tsconfig-paths": "6.0",
|
|
200
202
|
"vitest": "4.0"
|
|
201
203
|
},
|
package/pool/pool.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type CancelablePromiseResult<T, R> = {
|
|
|
10
10
|
};
|
|
11
11
|
export declare class CancelablePromise<T, R = void> extends CustomPromise<CancelablePromiseResult<T, R>> {
|
|
12
12
|
#private;
|
|
13
|
+
readonly [Symbol.toStringTag] = "CancelablePromise";
|
|
13
14
|
constructor(executor: CancelablePromiseExecutor<T>);
|
|
14
15
|
cancel(reason: R): void;
|
|
15
16
|
}
|
package/promise/index.d.ts
CHANGED
package/promise/index.js
CHANGED
package/sse/data-stream.js
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import { patch } from 'jsondiffpatch';
|
|
2
|
+
import { isErrorResponse, parseErrorResponse } from '../api/response.js';
|
|
2
3
|
import { hasOwnProperty } from '../utils/object/object.js';
|
|
3
|
-
import { finalize, map, merge, scan } from 'rxjs';
|
|
4
|
+
import { finalize, map, merge, scan, switchMap, throwError } from 'rxjs';
|
|
4
5
|
export class DataStream {
|
|
5
6
|
static parse(eventSource) {
|
|
6
7
|
const data$ = eventSource.message$('data').pipe(map((message) => ({ data: JSON.parse(message.data) })));
|
|
7
8
|
const delta$ = eventSource.message$('delta').pipe(map((message) => ({ delta: JSON.parse(message.data) })));
|
|
8
|
-
|
|
9
|
+
const stream$ = merge(data$, delta$).pipe(scan((data, message) => {
|
|
9
10
|
if (hasOwnProperty(message, 'data')) {
|
|
10
11
|
return message.data;
|
|
11
12
|
}
|
|
12
13
|
return patch(structuredClone(data), message.delta);
|
|
13
|
-
}, undefined)
|
|
14
|
+
}, undefined));
|
|
15
|
+
const error$ = eventSource.message$('error').pipe(switchMap((message) => throwError(() => {
|
|
16
|
+
try {
|
|
17
|
+
const dataJson = JSON.parse(message.data);
|
|
18
|
+
if (isErrorResponse(dataJson)) {
|
|
19
|
+
return parseErrorResponse(dataJson);
|
|
20
|
+
}
|
|
21
|
+
return new Error(`Data stream error`, { cause: dataJson });
|
|
22
|
+
}
|
|
23
|
+
catch { /* ignore json parse errors */ }
|
|
24
|
+
return new Error(`Data stream error`, { cause: message.data });
|
|
25
|
+
})));
|
|
26
|
+
return merge(stream$, error$).pipe(finalize(() => eventSource.close()));
|
|
14
27
|
}
|
|
15
28
|
}
|
|
@@ -6,7 +6,7 @@ import type { Transaction } from '../orm/server/transaction.js';
|
|
|
6
6
|
import { Transactional } from '../orm/server/transactional.js';
|
|
7
7
|
import type { OneOrMany, Record, UndefinableJson } from '../types/types.js';
|
|
8
8
|
import { TaskQueueEnqueueBatch } from './enqueue-batch.js';
|
|
9
|
-
import type { ProcessBatchWorker, ProcessWorker, TaskData, TaskDefinitionMap, TaskOfType, TaskProcessResultPayload, TaskResult,
|
|
9
|
+
import type { ProcessBatchWorker, ProcessWorker, TaskData, TaskDefinitionMap, TaskOfType, TaskProcessResultPayload, TaskResult, TasksResults, TasksStates, TaskState, TaskTypes } from './types.js';
|
|
10
10
|
export declare class TaskProcessResult<Result = unknown> {
|
|
11
11
|
readonly payload: TaskProcessResultPayload<Result>;
|
|
12
12
|
private constructor();
|
|
@@ -175,7 +175,9 @@ export type TaskQueueArgument = string | (QueueConfig & {
|
|
|
175
175
|
namespace: string;
|
|
176
176
|
});
|
|
177
177
|
export declare const defaultQueueConfig: Required<QueueConfig>;
|
|
178
|
-
export declare abstract class TaskQueue<Definitions extends TaskDefinitionMap = TaskDefinitionMap> extends Transactional
|
|
178
|
+
export declare abstract class TaskQueue<Definitions extends TaskDefinitionMap = TaskDefinitionMap> extends Transactional<QueueConfig & {
|
|
179
|
+
namespace: string;
|
|
180
|
+
}> implements Resolvable<TaskQueueArgument> {
|
|
179
181
|
readonly [resolveArgumentType]: TaskQueueArgument;
|
|
180
182
|
protected readonly config: QueueConfig & {
|
|
181
183
|
namespace: string;
|
|
@@ -304,6 +306,9 @@ export declare abstract class TaskQueue<Definitions extends TaskDefinitionMap =
|
|
|
304
306
|
types?: Type[];
|
|
305
307
|
forceDequeue?: boolean;
|
|
306
308
|
}, handler: ProcessBatchWorker<Definitions, Type>): void;
|
|
309
|
+
protected getTransactionalContextData(): QueueConfig & {
|
|
310
|
+
namespace: string;
|
|
311
|
+
};
|
|
307
312
|
private processWorker;
|
|
308
313
|
private processBatchWorker;
|
|
309
314
|
}
|
package/task-queue/task-queue.js
CHANGED
|
@@ -79,7 +79,7 @@ export const defaultQueueConfig = {
|
|
|
79
79
|
idempotencyWindow: millisecondsPerMinute * 60,
|
|
80
80
|
};
|
|
81
81
|
export class TaskQueue extends Transactional {
|
|
82
|
-
config = (() => { const arg = injectArgument(this); return isString(arg) ? { namespace: arg } : arg; })();
|
|
82
|
+
config = this.transactionalContextData ?? (() => { const arg = injectArgument(this); return isString(arg) ? { namespace: arg } : arg; })();
|
|
83
83
|
logger = inject(Logger, `TaskQueue:${this.config.namespace}`);
|
|
84
84
|
batch() {
|
|
85
85
|
return new TaskQueueEnqueueBatch(this);
|
|
@@ -94,6 +94,9 @@ export class TaskQueue extends Transactional {
|
|
|
94
94
|
void this.processBatchWorker(batchSize, cancellationSignal, handler, { types, forceDequeue });
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
getTransactionalContextData() {
|
|
98
|
+
return this.config;
|
|
99
|
+
}
|
|
97
100
|
async processWorker(cancellationSignal, handler, options) {
|
|
98
101
|
await this.processBatchWorker(1, cancellationSignal, async (batchContext) => {
|
|
99
102
|
const task = batchContext.tasks[0];
|
|
@@ -15,16 +15,17 @@ export type IntegrationTestOptions = {
|
|
|
15
15
|
};
|
|
16
16
|
logLevels?: Record<string, LogLevel>;
|
|
17
17
|
modules?: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
circuitBreaker?: boolean;
|
|
21
|
-
rateLimiter?: boolean;
|
|
18
|
+
api?: boolean;
|
|
19
|
+
audit?: boolean;
|
|
22
20
|
authentication?: boolean;
|
|
21
|
+
circuitBreaker?: boolean;
|
|
23
22
|
keyValueStore?: boolean;
|
|
24
|
-
audit?: boolean;
|
|
25
23
|
lock?: boolean;
|
|
24
|
+
messageBus?: boolean;
|
|
25
|
+
notification?: boolean;
|
|
26
|
+
rateLimiter?: boolean;
|
|
26
27
|
signals?: boolean;
|
|
27
|
-
|
|
28
|
+
taskQueue?: boolean;
|
|
28
29
|
test?: boolean;
|
|
29
30
|
webServer?: boolean;
|
|
30
31
|
};
|
|
@@ -15,6 +15,7 @@ import { configurePostgresLock, migratePostgresLockSchema } from '../lock/postgr
|
|
|
15
15
|
import { ConsoleLogTransport, DEFAULT_LOG_LEVEL, LogFormatter, LogLevel, LogManager, LogTransport, PrettyPrintLogFormatter } from '../logger/index.js';
|
|
16
16
|
import { configureLocalMessageBus } from '../message-bus/index.js';
|
|
17
17
|
import { configureWebServerModule, WebServerModule } from '../module/modules/web-server.module.js';
|
|
18
|
+
import { configureNotification, migrateNotificationSchema } from '../notification/server/index.js';
|
|
18
19
|
import { configureOrm, Database } from '../orm/server/index.js';
|
|
19
20
|
import { configurePostgresRateLimiter, migratePostgresRateLimiterSchema } from '../rate-limit/postgres/module.js';
|
|
20
21
|
import { configureDefaultSignalsImplementation } from '../signals/implementation/configure.js';
|
|
@@ -63,7 +64,7 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
63
64
|
await database.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(options.orm.schema)}`);
|
|
64
65
|
}
|
|
65
66
|
// 7. Optional Modules
|
|
66
|
-
if (options.modules?.messageBus ?? options.modules?.taskQueue ?? options.modules?.authentication ?? options.modules?.test) {
|
|
67
|
+
if (options.modules?.messageBus ?? options.modules?.taskQueue ?? options.modules?.authentication ?? options.modules?.test ?? options.modules?.notification) {
|
|
67
68
|
configureLocalMessageBus({ injector });
|
|
68
69
|
}
|
|
69
70
|
if (options.modules?.taskQueue) {
|
|
@@ -86,7 +87,7 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
86
87
|
configurePostgresLock({ injector });
|
|
87
88
|
await runInInjectionContext(injector, migratePostgresLockSchema);
|
|
88
89
|
}
|
|
89
|
-
if (options.modules?.signals ?? options.modules?.authentication ?? options.modules?.test) {
|
|
90
|
+
if (options.modules?.signals ?? options.modules?.authentication ?? options.modules?.test ?? options.modules?.notification) {
|
|
90
91
|
configureDefaultSignalsImplementation();
|
|
91
92
|
}
|
|
92
93
|
if (options.modules?.audit ?? options.modules?.authentication) {
|
|
@@ -101,6 +102,10 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
101
102
|
});
|
|
102
103
|
await runInInjectionContext(injector, migrateAuthenticationSchema);
|
|
103
104
|
}
|
|
105
|
+
if (options.modules?.notification) {
|
|
106
|
+
configureNotification({ injector });
|
|
107
|
+
await runInInjectionContext(injector, migrateNotificationSchema);
|
|
108
|
+
}
|
|
104
109
|
if (options.modules?.api ?? options.modules?.authentication) {
|
|
105
110
|
configureNodeHttpServer({ trustedProxiesCount: 0, injector });
|
|
106
111
|
configureApiServer({ controllers: [AuthenticationApiController], injector });
|