@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,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,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,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,4 @@
1
+ import { NotificationLog } from '../models/notification-log.model.js';
2
+ export declare class TestNotification extends NotificationLog {
3
+ testField: string;
4
+ }
@@ -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;
@@ -6,4 +6,3 @@
6
6
  export * from './object-storage-provider.js';
7
7
  export * from './object-storage.js';
8
8
  export * from './object.js';
9
- export * from './s3/index.js';
@@ -6,4 +6,3 @@
6
6
  export * from './object-storage-provider.js';
7
7
  export * from './object-storage.js';
8
8
  export * from './object.js';
9
- export * from './s3/index.js';
@@ -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;
@@ -39,7 +39,6 @@ export class S3ObjectStorageProviderConfig {
39
39
  */
40
40
  secretKey;
41
41
  }
42
- export const bucketPerModule = Symbol('bucket per module');
43
42
  let S3ObjectStorageProvider = class S3ObjectStorageProvider extends ObjectStorageProvider {
44
43
  client;
45
44
  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, isInTransactionalContext: () => boolean;
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, isInTransactionalContext } = createContextProvider('Transactional');
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.97",
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.53",
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
@@ -2,7 +2,7 @@ import type { Logger } from '../logger/index.js';
2
2
  export type PoolOptions = {
3
3
  /**
4
4
  * Maximum number of instances
5
- * @default number of cpu cores
5
+ * @default half of cpu cores or 4
6
6
  */
7
7
  size?: number;
8
8
  /**
@@ -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
  }
@@ -5,6 +5,7 @@ export class CancelablePromise extends CustomPromise {
5
5
  #resolve;
6
6
  #reject;
7
7
  #pending = true;
8
+ [Symbol.toStringTag] = 'CancelablePromise';
8
9
  constructor(executor) {
9
10
  super();
10
11
  this.#resolve = (value) => {
@@ -4,6 +4,7 @@
4
4
  * @module Promise
5
5
  */
6
6
  export * from './cancelable-promise.js';
7
+ export * from './custom-promise.js';
7
8
  export * from './deferred-promise.js';
8
9
  export * from './lazy-promise.js';
9
10
  export * from './types.js';
package/promise/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * @module Promise
5
5
  */
6
6
  export * from './cancelable-promise.js';
7
+ export * from './custom-promise.js';
7
8
  export * from './deferred-promise.js';
8
9
  export * from './lazy-promise.js';
9
10
  export * from './types.js';
@@ -3,3 +3,4 @@ export * from './random-number-generator-function.js';
3
3
  export * from './random-number-generator.js';
4
4
  export * from './seeded-random-number-generator.js';
5
5
  export * from './sfc32.js';
6
+ export * from './utils.js';
@@ -3,3 +3,4 @@ export * from './random-number-generator-function.js';
3
3
  export * from './random-number-generator.js';
4
4
  export * from './seeded-random-number-generator.js';
5
5
  export * from './sfc32.js';
6
+ export * from './utils.js';
@@ -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
- return merge(data$, delta$).pipe(scan((data, message) => {
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), finalize(() => eventSource.close()));
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, TaskState, TaskTypes, TasksResults, TasksStates } from './types.js';
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 implements Resolvable<TaskQueueArgument> {
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
  }
@@ -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
- taskQueue?: boolean;
19
- messageBus?: boolean;
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
- api?: boolean;
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 });