@tstdl/base 0.93.123 → 0.93.126
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/ai/genkit/tests/multi-region.test.js +6 -6
- package/ai/index.d.ts +2 -6
- package/ai/index.js +2 -6
- package/ai/parser/index.d.ts +1 -0
- package/ai/parser/index.js +1 -0
- package/ai/parser/parser.d.ts +12 -0
- package/ai/parser/parser.js +28 -0
- package/ai/prompts/build.d.ts +21 -0
- package/ai/prompts/build.js +25 -0
- package/ai/prompts/index.d.ts +2 -0
- package/ai/prompts/index.js +2 -0
- package/ai/prompts/instructions-formatter.d.ts +9 -22
- package/ai/prompts/instructions-formatter.js +20 -7
- package/ai/prompts/instructions.js +1 -1
- package/ai/prompts/steering.d.ts +27 -0
- package/ai/prompts/steering.js +54 -0
- package/ai/tests/instructions-formatter.test.js +115 -0
- package/ai/tests/steering.test.js +37 -0
- package/application/application.d.ts +2 -1
- package/application/application.js +3 -0
- package/authentication/client/module.d.ts +1 -1
- package/authentication/client/module.js +4 -5
- package/authentication/tests/authentication-ancillary.service.test.js +1 -1
- package/authentication/tests/authentication.api-controller.test.js +3 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
- package/authentication/tests/authentication.client-service.test.js +1 -1
- package/authentication/tests/authentication.service.test.js +1 -1
- package/authentication/tests/subject.service.test.js +1 -1
- package/circuit-breaker/tests/circuit-breaker.test.js +1 -1
- package/document-management/api/document-management.api.d.ts +16 -16
- package/document-management/api/document-management.api.js +12 -12
- package/document-management/models/ai-configuration.d.ts +59 -0
- package/document-management/models/ai-configuration.js +1 -0
- package/document-management/models/document-assignment-scope.model.js +2 -4
- package/document-management/models/document-assignment-task.model.js +2 -4
- package/document-management/models/document-collection-assignment.model.js +2 -4
- package/document-management/models/document-collection.model.js +2 -3
- package/document-management/models/document-content.model.d.ts +6 -0
- package/document-management/models/document-content.model.js +32 -0
- package/document-management/models/document-property-value.model.js +1 -2
- package/document-management/models/document-request-collection-assignment.model.js +2 -4
- package/document-management/models/document-request.model.js +2 -4
- package/document-management/models/document-tag-assignment.model.js +2 -3
- package/document-management/models/document-validation-execution-related-document.model.js +2 -4
- package/document-management/models/document-validation-execution.model.js +2 -5
- package/document-management/models/document-workflow.model.d.ts +2 -1
- package/document-management/models/document-workflow.model.js +4 -5
- package/document-management/models/document.model.js +2 -3
- package/document-management/models/index.d.ts +2 -0
- package/document-management/models/index.js +2 -0
- package/document-management/server/api/document-management.api.d.ts +7 -7
- package/document-management/server/api/document-management.api.js +9 -9
- package/document-management/server/configure.d.ts +4 -1
- package/document-management/server/configure.js +9 -4
- package/document-management/server/drizzle/{0000_silly_chimera.sql → 0000_curious_nighthawk.sql} +8 -28
- package/document-management/server/drizzle/meta/0000_snapshot.json +14 -286
- package/document-management/server/drizzle/meta/_journal.json +2 -2
- package/document-management/server/module.d.ts +2 -0
- package/document-management/server/module.js +1 -0
- package/document-management/server/schemas.d.ts +2 -1
- package/document-management/server/services/document-file.service.d.ts +6 -6
- package/document-management/server/services/document-file.service.js +7 -81
- package/document-management/server/services/document-management-ai-provider.service.d.ts +66 -0
- package/document-management/server/services/document-management-ai-provider.service.js +2 -0
- package/document-management/server/services/document-management-ai.service.d.ts +44 -7
- package/document-management/server/services/document-management-ai.service.js +332 -329
- package/document-management/server/services/document-validation.service.d.ts +1 -1
- package/document-management/server/services/document-workflow.service.d.ts +4 -3
- package/document-management/server/services/document-workflow.service.js +26 -9
- package/document-management/server/services/document.service.d.ts +7 -3
- package/document-management/server/services/document.service.js +13 -4
- package/document-management/server/services/index.d.ts +1 -0
- package/document-management/server/services/index.js +1 -0
- package/document-management/server/validators/ai-validation-executor.d.ts +419 -12
- package/document-management/server/validators/ai-validation-executor.js +51 -46
- package/document-management/server/validators/single-document-validation-executor.d.ts +1 -3
- package/document-management/server/validators/single-document-validation-executor.js +2 -4
- package/document-management/service-models/document.service-model.d.ts +3 -3
- package/document-management/service-models/document.service-model.js +1 -1
- package/document-management/tests/ai-config-hierarchy.test.d.ts +1 -0
- package/document-management/tests/ai-config-hierarchy.test.js +64 -0
- package/document-management/tests/ai-config-integration.test.d.ts +1 -0
- package/document-management/tests/ai-config-integration.test.js +125 -0
- package/document-management/tests/ai-config-merge.test.d.ts +1 -0
- package/document-management/tests/ai-config-merge.test.js +38 -0
- package/document-management/tests/document-management-ai-overrides.test.d.ts +1 -0
- package/document-management/tests/document-management-ai-overrides.test.js +64 -0
- package/document-management/tests/document-management-core.test.js +6 -6
- package/document-management/tests/document-management.api.test.js +5 -5
- package/document-management/tests/document-statistics.service.test.js +10 -6
- package/document-management/tests/document-validation-ai-overrides.test.d.ts +1 -0
- package/document-management/tests/document-validation-ai-overrides.test.js +85 -0
- package/document-management/tests/document.service.test.js +15 -11
- package/document-management/tests/enum-helpers.test.js +5 -5
- package/examples/document-management/ai-provider.d.ts +20 -0
- package/examples/document-management/ai-provider.js +74 -0
- package/examples/document-management/main.js +9 -6
- package/examples/injector/graph-example.d.ts +1 -0
- package/examples/injector/graph-example.js +340 -0
- package/injector/decorators.d.ts +4 -4
- package/injector/decorators.js +5 -6
- package/injector/forward-ref.d.ts +15 -0
- package/injector/forward-ref.js +20 -0
- package/injector/graph.d.ts +113 -0
- package/injector/graph.js +631 -0
- package/injector/index.d.ts +2 -0
- package/injector/index.js +2 -0
- package/injector/inject.d.ts +15 -15
- package/injector/injector.d.ts +101 -13
- package/injector/injector.js +103 -59
- package/injector/resolve-chain.d.ts +20 -6
- package/injector/resolve-chain.js +39 -14
- package/injector/tests/advanced.test.d.ts +1 -0
- package/injector/tests/advanced.test.js +116 -0
- package/injector/tests/async-init.test.d.ts +1 -0
- package/injector/tests/async-init.test.js +77 -0
- package/injector/tests/basic.test.d.ts +1 -0
- package/injector/tests/basic.test.js +114 -0
- package/injector/tests/hierarchical.test.d.ts +1 -0
- package/injector/tests/hierarchical.test.js +59 -0
- package/injector/tests/lifecycles.test.d.ts +1 -0
- package/injector/tests/lifecycles.test.js +109 -0
- package/injector/token.d.ts +2 -1
- package/injector/token.js +4 -1
- package/injector/type-info.d.ts +1 -5
- package/injector/types.d.ts +4 -10
- package/logger/tests/pretty-print.test.d.ts +1 -0
- package/logger/{formatters → tests}/pretty-print.test.js +1 -1
- package/logger/transports/console.d.ts +3 -2
- package/logger/transports/console.js +4 -3
- package/notification/api/notification.api.d.ts +26 -6
- package/notification/api/notification.api.js +15 -4
- package/notification/client/notification-client.d.ts +6 -0
- package/notification/client/notification-client.js +13 -3
- package/notification/models/in-app-notification.model.d.ts +9 -3
- package/notification/models/in-app-notification.model.js +32 -11
- package/notification/models/notification-log.model.js +2 -3
- package/notification/server/api/notification.api-controller.d.ts +1 -0
- package/notification/server/api/notification.api-controller.js +7 -1
- package/notification/server/drizzle/{0000_oval_rage.sql → 0000_wise_pyro.sql} +22 -4
- package/notification/server/drizzle/meta/0000_snapshot.json +249 -37
- package/notification/server/drizzle/meta/_journal.json +2 -2
- package/notification/server/module.d.ts +5 -0
- package/notification/server/module.js +6 -1
- package/notification/server/providers/in-app-channel-provider.js +1 -0
- package/notification/server/schemas.d.ts +3 -2
- package/notification/server/schemas.js +3 -2
- package/notification/server/services/notification.service.d.ts +11 -6
- package/notification/server/services/notification.service.js +138 -42
- package/notification/tests/notification-api.test.js +16 -6
- package/notification/tests/notification-client.test.d.ts +1 -0
- package/notification/tests/{unit/notification-client.test.js → notification-client.test.js} +5 -5
- package/notification/tests/notification-flow.test.js +45 -7
- package/notification/tests/notification-sse.service.test.js +1 -1
- package/notification/tests/notification-type.service.test.js +1 -1
- package/object-storage/s3/s3.object-storage.js +3 -0
- package/object-storage/s3/tests/s3.object-storage.integration.test.js +1 -1
- package/orm/server/drizzle/schema-converter.js +5 -3
- package/orm/tests/repository-attributes.test.js +10 -17
- package/orm/tests/repository-cti-mapping.test.js +2 -2
- package/orm/tests/repository-cti-soft-delete.test.js +1 -1
- package/orm/tests/repository-cti.test.js +19 -33
- package/orm/tests/repository-extra-coverage.test.js +1 -1
- package/orm/tests/repository-search.test.js +5 -2
- package/orm/tests/schema-converter.test.js +1 -0
- package/orm/tests/transaction-safety.test.js +1 -1
- package/package.json +7 -9
- package/rate-limit/tests/postgres-rate-limiter.test.js +6 -16
- package/renderer/d2.d.ts +77 -0
- package/renderer/d2.js +68 -0
- package/renderer/graphviz.d.ts +47 -0
- package/renderer/graphviz.js +58 -0
- package/renderer/index.d.ts +4 -0
- package/renderer/index.js +4 -0
- package/renderer/typst.d.ts +57 -0
- package/renderer/typst.js +62 -0
- package/rpc/adapters/readable-stream.adapter.d.ts +3 -0
- package/rpc/adapters/readable-stream.adapter.js +5 -1
- package/rpc/rpc.js +28 -3
- package/rpc/tests/rpc.integration.test.js +3 -1
- package/schema/schemas/nullable.js +1 -1
- package/task-queue/task-queue.d.ts +2 -0
- package/task-queue/task-queue.js +6 -2
- package/task-queue/tests/complex.test.js +1 -1
- package/task-queue/tests/dependencies.test.js +3 -3
- package/task-queue/tests/extensive-dependencies.test.js +1 -1
- package/task-queue/tests/queue.test.js +1 -1
- package/task-queue/tests/worker.test.js +4 -7
- package/test5.js +52 -8
- package/{unit-test → testing}/integration-setup.d.ts +1 -0
- package/{unit-test → testing}/integration-setup.js +13 -0
- package/utils/base64.d.ts +7 -0
- package/utils/base64.js +10 -1
- package/utils/noop.d.ts +7 -1
- package/utils/noop.js +7 -1
- package/ai/ai-file.service.d.ts +0 -57
- package/ai/ai-file.service.js +0 -233
- package/ai/ai-session.d.ts +0 -38
- package/ai/ai-session.js +0 -50
- package/ai/ai.service.d.ts +0 -126
- package/ai/ai.service.js +0 -481
- package/ai/functions.d.ts +0 -9
- package/ai/functions.js +0 -38
- package/ai/module.d.ts +0 -26
- package/ai/module.js +0 -25
- package/ai/types.d.ts +0 -229
- package/ai/types.js +0 -33
- package/latex/index.d.ts +0 -1
- package/latex/index.js +0 -1
- package/typst/index.d.ts +0 -1
- package/typst/index.js +0 -1
- package/typst/render.d.ts +0 -23
- package/typst/render.js +0 -32
- /package/{logger/formatters/pretty-print.test.d.ts → ai/tests/instructions-formatter.test.d.ts} +0 -0
- /package/{notification/tests/unit/notification-client.test.d.ts → ai/tests/steering.test.d.ts} +0 -0
- /package/{latex/render.d.ts → renderer/latex.d.ts} +0 -0
- /package/{latex/render.js → renderer/latex.js} +0 -0
- /package/{unit-test → testing}/index.d.ts +0 -0
- /package/{unit-test → testing}/index.js +0 -0
|
@@ -6,26 +6,30 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
var _a;
|
|
8
8
|
var NotificationService_1;
|
|
9
|
-
import { and, desc, eq, gt, isNull, lt, or } from 'drizzle-orm';
|
|
9
|
+
import { and, desc, eq, gt, isNull, lt, or, sql } from 'drizzle-orm';
|
|
10
|
+
import { match, P } from 'ts-pattern';
|
|
10
11
|
import { BadRequestError } from '../../../errors/bad-request.error.js';
|
|
11
12
|
import { inject, Singleton } from '../../../injector/index.js';
|
|
12
13
|
import { Logger } from '../../../logger/logger.js';
|
|
13
|
-
import { TRANSACTION_TIMESTAMP } from '../../../orm/index.js';
|
|
14
|
+
import { getEntityIds, TRANSACTION_TIMESTAMP } from '../../../orm/index.js';
|
|
14
15
|
import { injectRepository, Transactional } from '../../../orm/server/index.js';
|
|
15
16
|
import { TaskQueue } from '../../../task-queue/task-queue.js';
|
|
16
17
|
import { tryIgnoreLogAsync } from '../../../utils/try-ignore.js';
|
|
17
|
-
import { isDefined,
|
|
18
|
-
import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationPreference, NotificationPriority, NotificationStatus, toInAppNotificationView, WebPushSubscription } from '../../models/index.js';
|
|
19
|
-
import {
|
|
18
|
+
import { isDefined, isUndefined } from '../../../utils/type-guards.js';
|
|
19
|
+
import { InAppNotification, InAppNotificationArchive, NotificationChannel, NotificationLogEntity, NotificationPreference, NotificationPriority, NotificationStatus, toInAppNotificationView, WebPushSubscription } from '../../models/index.js';
|
|
20
|
+
import { NotificationConfiguration } from '../module.js';
|
|
21
|
+
import { inAppNotification, inAppNotificationArchive, notificationLog } from '../schemas.js';
|
|
20
22
|
import { NotificationAncillaryService } from './notification-ancillary.service.js';
|
|
21
23
|
import { NotificationSseService } from './notification-sse.service.js';
|
|
22
24
|
let NotificationService = NotificationService_1 = class NotificationService extends Transactional {
|
|
23
25
|
#notificationLogRepository = injectRepository(NotificationLogEntity);
|
|
24
26
|
#inAppNotificationRepository = injectRepository(InAppNotification);
|
|
27
|
+
#inAppNotificationArchiveRepository = injectRepository(InAppNotificationArchive);
|
|
25
28
|
#preferenceRepository = injectRepository(NotificationPreference);
|
|
26
29
|
#webPushSubscriptionRepository = injectRepository(WebPushSubscription);
|
|
27
30
|
#notificationAncillaryService = inject(NotificationAncillaryService);
|
|
28
31
|
#taskQueue = inject((TaskQueue), 'notification');
|
|
32
|
+
#configuration = inject(NotificationConfiguration);
|
|
29
33
|
#sseService = inject(NotificationSseService);
|
|
30
34
|
#logger = inject(Logger, NotificationService_1.name);
|
|
31
35
|
async send(tenantId, userId, notification, options) {
|
|
@@ -44,36 +48,47 @@ let NotificationService = NotificationService_1 = class NotificationService exte
|
|
|
44
48
|
});
|
|
45
49
|
}
|
|
46
50
|
async listInApp(tenantId, userId, options = {}) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
return await this.#list(tenantId, userId, inAppNotification, this.#inAppNotificationRepository, options);
|
|
52
|
+
}
|
|
53
|
+
async listArchivedInApp(tenantId, userId, options = {}) {
|
|
54
|
+
return await this.#list(tenantId, userId, inAppNotificationArchive, this.#inAppNotificationArchiveRepository, options);
|
|
55
|
+
}
|
|
56
|
+
async #list(tenantId, userId, table, repository, options = {}) {
|
|
57
|
+
const db = this.#notificationLogRepository.session;
|
|
58
|
+
const { afterTimestamp, afterNotificationId } = await match(options.after)
|
|
59
|
+
.with(P.nullish, () => ({ afterTimestamp: undefined, afterNotificationId: undefined }))
|
|
60
|
+
.with(P.number, (value) => ({ afterTimestamp: value, afterNotificationId: undefined }))
|
|
61
|
+
.with(P.string, async (value) => {
|
|
62
|
+
const inApp = await repository.tryLoadByQuery({ tenantId, userId, id: value });
|
|
63
|
+
return { afterTimestamp: inApp?.timestamp, afterNotificationId: inApp?.logId };
|
|
64
|
+
})
|
|
65
|
+
.exhaustive();
|
|
66
|
+
const rows = await db
|
|
53
67
|
.select({
|
|
54
68
|
notification: notificationLog,
|
|
55
|
-
inApp:
|
|
69
|
+
inApp: table,
|
|
56
70
|
})
|
|
57
|
-
.from(
|
|
58
|
-
.innerJoin(
|
|
59
|
-
.where(and(eq(
|
|
60
|
-
?
|
|
71
|
+
.from(table)
|
|
72
|
+
.innerJoin(notificationLog, and(eq(notificationLog.tenantId, table.tenantId), eq(notificationLog.userId, table.userId), eq(notificationLog.id, table.logId)))
|
|
73
|
+
.where(and(eq(table.tenantId, tenantId), eq(table.userId, userId), options.unreadOnly ? isNull(table.readTimestamp) : undefined, isDefined(afterTimestamp)
|
|
74
|
+
? isDefined(afterNotificationId)
|
|
75
|
+
? or(lt(table.timestamp, afterTimestamp), and(eq(table.timestamp, afterTimestamp), lt(table.logId, afterNotificationId)))
|
|
76
|
+
: lt(table.timestamp, afterTimestamp)
|
|
61
77
|
: undefined))
|
|
62
78
|
.limit(options.limit ?? 50)
|
|
63
79
|
.offset(options.offset ?? 0)
|
|
64
|
-
.orderBy(desc(
|
|
80
|
+
.orderBy(desc(table.timestamp), desc(table.logId));
|
|
65
81
|
const inAppRows = rows.map((row) => row.inApp);
|
|
66
82
|
const notificationRows = rows.map((row) => row.notification);
|
|
67
83
|
const notificationEntities = await this.#notificationLogRepository.mapManyToEntity(notificationRows);
|
|
68
|
-
const inAppEntities = await
|
|
84
|
+
const inAppEntities = await repository.mapManyToEntity(inAppRows);
|
|
69
85
|
const notificationViewDatas = await this.#notificationAncillaryService.getViewData(tenantId, notificationEntities);
|
|
70
|
-
|
|
86
|
+
return notificationEntities.map((notification, index) => {
|
|
71
87
|
const inApp = inAppEntities[index];
|
|
72
88
|
const viewData = notificationViewDatas[index];
|
|
73
89
|
const payload = { ...notification.payload, ...viewData };
|
|
74
90
|
return toInAppNotificationView(inApp, { ...notification, payload });
|
|
75
91
|
});
|
|
76
|
-
return views;
|
|
77
92
|
}
|
|
78
93
|
async markRead(tenantId, userId, id) {
|
|
79
94
|
await this.#inAppNotificationRepository.updateByQuery({ tenantId, id, userId }, { readTimestamp: TRANSACTION_TIMESTAMP });
|
|
@@ -90,50 +105,131 @@ let NotificationService = NotificationService_1 = class NotificationService exte
|
|
|
90
105
|
});
|
|
91
106
|
}
|
|
92
107
|
async archive(tenantId, userId, id) {
|
|
93
|
-
|
|
108
|
+
const db = this.#inAppNotificationRepository.session;
|
|
109
|
+
const deletedRows = db.$with('deleted_rows').as(db.delete(inAppNotification)
|
|
110
|
+
.where(and(eq(inAppNotification.tenantId, tenantId), eq(inAppNotification.id, id), eq(inAppNotification.userId, userId)))
|
|
111
|
+
.returning());
|
|
112
|
+
await db.with(deletedRows)
|
|
113
|
+
.insert(inAppNotificationArchive)
|
|
114
|
+
.select(db
|
|
115
|
+
.select({
|
|
116
|
+
id: deletedRows.id,
|
|
117
|
+
tenantId: deletedRows.tenantId,
|
|
118
|
+
userId: deletedRows.userId,
|
|
119
|
+
logId: deletedRows.logId,
|
|
120
|
+
timestamp: deletedRows.timestamp,
|
|
121
|
+
readTimestamp: deletedRows.readTimestamp,
|
|
122
|
+
archiveTimestamp: TRANSACTION_TIMESTAMP.as('archive_timestamp'),
|
|
123
|
+
})
|
|
124
|
+
.from(deletedRows));
|
|
94
125
|
await tryIgnoreLogAsync(this.#logger, async () => {
|
|
95
126
|
const unreadCount = await this.unreadCount(tenantId, userId);
|
|
96
127
|
await this.#sseService.dispatch(tenantId, userId, { archiveId: id, unreadCount });
|
|
97
128
|
});
|
|
98
129
|
}
|
|
130
|
+
async archiveByQuery(tenantId, query) {
|
|
131
|
+
const db = this.#inAppNotificationRepository.session;
|
|
132
|
+
const logQueryCondition = this.#notificationLogRepository.convertQuery(query);
|
|
133
|
+
const rowsToDelete = db.$with('rows_to_delete').as(db.select({ id: inAppNotification.id })
|
|
134
|
+
.from(inAppNotification)
|
|
135
|
+
.innerJoin(notificationLog, and(eq(inAppNotification.tenantId, notificationLog.tenantId), eq(inAppNotification.logId, notificationLog.id), eq(inAppNotification.userId, notificationLog.userId)))
|
|
136
|
+
.where(and(eq(inAppNotification.tenantId, tenantId), logQueryCondition)));
|
|
137
|
+
const deletedRows = db.$with('deleted_rows').as(db.delete(inAppNotification)
|
|
138
|
+
.where(sql `${inAppNotification.id} IN (SELECT id FROM ${rowsToDelete})`)
|
|
139
|
+
.returning());
|
|
140
|
+
await db.with(rowsToDelete, deletedRows)
|
|
141
|
+
.insert(inAppNotificationArchive)
|
|
142
|
+
.select(db
|
|
143
|
+
.select({
|
|
144
|
+
id: deletedRows.id,
|
|
145
|
+
tenantId: deletedRows.tenantId,
|
|
146
|
+
userId: deletedRows.userId,
|
|
147
|
+
logId: deletedRows.logId,
|
|
148
|
+
timestamp: deletedRows.timestamp,
|
|
149
|
+
readTimestamp: deletedRows.readTimestamp,
|
|
150
|
+
archiveTimestamp: TRANSACTION_TIMESTAMP.as('archive_timestamp'),
|
|
151
|
+
})
|
|
152
|
+
.from(deletedRows));
|
|
153
|
+
}
|
|
99
154
|
async archiveAll(tenantId, userId) {
|
|
100
|
-
|
|
155
|
+
const db = this.#inAppNotificationRepository.session;
|
|
156
|
+
const deletedRows = db.$with('deleted_rows').as(db.delete(inAppNotification)
|
|
157
|
+
.where(and(eq(inAppNotification.tenantId, tenantId), eq(inAppNotification.userId, userId)))
|
|
158
|
+
.returning());
|
|
159
|
+
await db.with(deletedRows)
|
|
160
|
+
.insert(inAppNotificationArchive)
|
|
161
|
+
.select(db
|
|
162
|
+
.select({
|
|
163
|
+
id: deletedRows.id,
|
|
164
|
+
tenantId: deletedRows.tenantId,
|
|
165
|
+
userId: deletedRows.userId,
|
|
166
|
+
logId: deletedRows.logId,
|
|
167
|
+
timestamp: deletedRows.timestamp,
|
|
168
|
+
readTimestamp: deletedRows.readTimestamp,
|
|
169
|
+
archiveTimestamp: TRANSACTION_TIMESTAMP.as('archive_timestamp'),
|
|
170
|
+
})
|
|
171
|
+
.from(deletedRows));
|
|
101
172
|
await tryIgnoreLogAsync(this.#logger, async () => {
|
|
102
173
|
const unreadCount = await this.unreadCount(tenantId, userId);
|
|
103
174
|
await this.#sseService.dispatch(tenantId, userId, { archiveAll: true, unreadCount });
|
|
104
175
|
});
|
|
105
176
|
}
|
|
177
|
+
async runAutoArchive() {
|
|
178
|
+
const autoArchiveAfter = this.#configuration.autoArchiveAfter;
|
|
179
|
+
if (isUndefined(autoArchiveAfter) || (autoArchiveAfter <= 0)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const threshold = new Date(Date.now() - autoArchiveAfter);
|
|
183
|
+
const db = this.#inAppNotificationRepository.session;
|
|
184
|
+
const rowsToDelete = db.$with('rows_to_delete').as(db.select({ id: inAppNotification.id })
|
|
185
|
+
.from(inAppNotification)
|
|
186
|
+
.where(lt(inAppNotification.timestamp, threshold.getTime())));
|
|
187
|
+
const deletedRows = db.$with('deleted_rows').as(db.delete(inAppNotification)
|
|
188
|
+
.where(sql `${inAppNotification.id} IN (SELECT id FROM ${rowsToDelete})`)
|
|
189
|
+
.returning());
|
|
190
|
+
await db.with(rowsToDelete, deletedRows)
|
|
191
|
+
.insert(inAppNotificationArchive)
|
|
192
|
+
.select(db
|
|
193
|
+
.select({
|
|
194
|
+
id: deletedRows.id,
|
|
195
|
+
tenantId: deletedRows.tenantId,
|
|
196
|
+
userId: deletedRows.userId,
|
|
197
|
+
logId: deletedRows.logId,
|
|
198
|
+
timestamp: deletedRows.timestamp,
|
|
199
|
+
readTimestamp: deletedRows.readTimestamp,
|
|
200
|
+
archiveTimestamp: TRANSACTION_TIMESTAMP.as('archive_timestamp'),
|
|
201
|
+
})
|
|
202
|
+
.from(deletedRows));
|
|
203
|
+
}
|
|
106
204
|
async unreadCount(tenantId, userId) {
|
|
107
205
|
return await this.#inAppNotificationRepository.countByQuery({
|
|
108
206
|
tenantId,
|
|
109
207
|
userId,
|
|
110
208
|
readTimestamp: null,
|
|
111
|
-
archiveTimestamp: null,
|
|
112
209
|
});
|
|
113
210
|
}
|
|
114
|
-
async getCatchupData(tenantId, userId,
|
|
115
|
-
const
|
|
116
|
-
const [unreadCount, missedNotifications,
|
|
211
|
+
async getCatchupData(tenantId, userId, stateTimestamp) {
|
|
212
|
+
const db = this.#notificationLogRepository.session;
|
|
213
|
+
const [unreadCount, missedNotifications, readRows, archiveRows] = await Promise.all([
|
|
117
214
|
this.unreadCount(tenantId, userId),
|
|
118
|
-
this.listInApp(tenantId, userId, { after:
|
|
119
|
-
|
|
120
|
-
.select({
|
|
121
|
-
id: inAppNotification.id,
|
|
122
|
-
readTimestamp: inAppNotification.readTimestamp,
|
|
123
|
-
archiveTimestamp: inAppNotification.archiveTimestamp,
|
|
124
|
-
})
|
|
215
|
+
this.listInApp(tenantId, userId, { after: stateTimestamp, limit: 50 }),
|
|
216
|
+
db.select({ id: inAppNotification.id })
|
|
125
217
|
.from(inAppNotification)
|
|
126
|
-
.where(and(eq(inAppNotification.tenantId, tenantId), eq(inAppNotification.userId, userId),
|
|
218
|
+
.where(and(eq(inAppNotification.tenantId, tenantId), eq(inAppNotification.userId, userId), gt(inAppNotification.readTimestamp, stateTimestamp)))
|
|
219
|
+
.orderBy(desc(inAppNotification.readTimestamp))
|
|
220
|
+
.limit(50),
|
|
221
|
+
db.select({ id: inAppNotificationArchive.id })
|
|
222
|
+
.from(inAppNotificationArchive)
|
|
223
|
+
.where(and(eq(inAppNotificationArchive.tenantId, tenantId), eq(inAppNotificationArchive.userId, userId), gt(inAppNotificationArchive.archiveTimestamp, stateTimestamp)))
|
|
224
|
+
.orderBy(desc(inAppNotificationArchive.archiveTimestamp))
|
|
127
225
|
.limit(50),
|
|
128
226
|
]);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const log = await this.#notificationLogRepository.loadByQuery({ tenantId, userId, id: inApp.logId });
|
|
136
|
-
return log.timestamp;
|
|
227
|
+
return {
|
|
228
|
+
unreadCount,
|
|
229
|
+
missedNotifications,
|
|
230
|
+
readIds: getEntityIds(readRows),
|
|
231
|
+
archiveIds: getEntityIds(archiveRows),
|
|
232
|
+
};
|
|
137
233
|
}
|
|
138
234
|
async getPreferences(tenantId, userId) {
|
|
139
235
|
return await this.#preferenceRepository.loadManyByQuery({ tenantId, userId });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Subject } from 'rxjs';
|
|
2
|
-
import { beforeAll, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
3
|
import { SubjectService } from '../../authentication/server/subject.service.js';
|
|
4
|
-
import {
|
|
4
|
+
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
5
5
|
import { NotificationChannel } from '../models/index.js';
|
|
6
6
|
import { NotificationApiController } from '../server/api/notification.api-controller.js';
|
|
7
7
|
import { NotificationSseService } from '../server/services/notification-sse.service.js';
|
|
@@ -15,7 +15,7 @@ describe('Notification API (Integration)', () => {
|
|
|
15
15
|
let sseService;
|
|
16
16
|
let subjectService;
|
|
17
17
|
const schema = 'notification';
|
|
18
|
-
const tenantId =
|
|
18
|
+
const tenantId = crypto.randomUUID();
|
|
19
19
|
let userId;
|
|
20
20
|
beforeAll(async () => {
|
|
21
21
|
({ injector, database } = await setupIntegrationTest({
|
|
@@ -27,7 +27,7 @@ describe('Notification API (Integration)', () => {
|
|
|
27
27
|
rateLimiter: true,
|
|
28
28
|
},
|
|
29
29
|
}));
|
|
30
|
-
await
|
|
30
|
+
await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
|
|
31
31
|
controller = injector.resolve(NotificationApiController);
|
|
32
32
|
notificationService = injector.resolve(NotificationService);
|
|
33
33
|
sseService = injector.resolve(NotificationSseService);
|
|
@@ -41,16 +41,20 @@ describe('Notification API (Integration)', () => {
|
|
|
41
41
|
});
|
|
42
42
|
userId = user.id;
|
|
43
43
|
});
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
vi.clearAllMocks();
|
|
46
|
+
});
|
|
44
47
|
const createMockContext = (params = {}) => ({
|
|
45
48
|
parameters: params,
|
|
46
49
|
abortSignal: new AbortController().signal,
|
|
50
|
+
serverSentEvents: {},
|
|
47
51
|
getToken: async () => ({
|
|
48
52
|
payload: {
|
|
49
53
|
tenant: tenantId,
|
|
50
54
|
subject: userId,
|
|
51
55
|
},
|
|
52
56
|
}),
|
|
53
|
-
});
|
|
57
|
+
}); // Cast to any for controller compatibility if definition is strict
|
|
54
58
|
test('stream should register sse client', async () => {
|
|
55
59
|
const registerSpy = vi.spyOn(sseService, 'register');
|
|
56
60
|
const subject = new Subject();
|
|
@@ -74,10 +78,16 @@ describe('Notification API (Integration)', () => {
|
|
|
74
78
|
});
|
|
75
79
|
test('listInApp should call service', async () => {
|
|
76
80
|
const listInAppSpy = vi.spyOn(notificationService, 'listInApp').mockResolvedValue([]);
|
|
77
|
-
const params = { limit: 10, offset: 0
|
|
81
|
+
const params = { limit: 10, offset: 0 };
|
|
78
82
|
await controller.listInApp(createMockContext(params));
|
|
79
83
|
expect(listInAppSpy).toHaveBeenCalledWith(tenantId, userId, params);
|
|
80
84
|
});
|
|
85
|
+
test('listArchivedInApp should call service', async () => {
|
|
86
|
+
const listArchivedInAppSpy = vi.spyOn(notificationService, 'listArchivedInApp').mockResolvedValue([]);
|
|
87
|
+
const params = { limit: 10, offset: 0 };
|
|
88
|
+
await controller.listArchivedInApp(createMockContext(params));
|
|
89
|
+
expect(listArchivedInAppSpy).toHaveBeenCalledWith(tenantId, userId, params);
|
|
90
|
+
});
|
|
81
91
|
test('markRead should call service', async () => {
|
|
82
92
|
const markReadSpy = vi.spyOn(notificationService, 'markRead').mockResolvedValue();
|
|
83
93
|
const params = { id: 'notif-id' };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
-
import { AuthenticationClientService } from '
|
|
3
|
-
import { Injector, runInInjectionContext } from '
|
|
4
|
-
import { NotificationApiClient } from '
|
|
5
|
-
import { NotificationClient } from '
|
|
6
|
-
import { configureDefaultSignalsImplementation } from '
|
|
2
|
+
import { AuthenticationClientService } from '../../authentication/client/authentication.service.js';
|
|
3
|
+
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
4
|
+
import { NotificationApiClient } from '../../notification/api/index.js';
|
|
5
|
+
import { NotificationClient } from '../../notification/client/notification-client.js';
|
|
6
|
+
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
7
7
|
import { BehaviorSubject, of, Subject } from 'rxjs';
|
|
8
8
|
describe('NotificationClient', () => {
|
|
9
9
|
let injector;
|
|
@@ -9,7 +9,7 @@ import { SubjectService } from '../../authentication/server/subject.service.js';
|
|
|
9
9
|
import { runInInjectionContext, Singleton } from '../../injector/index.js';
|
|
10
10
|
import { MailService } from '../../mail/mail.service.js';
|
|
11
11
|
import { injectRepository } from '../../orm/server/index.js';
|
|
12
|
-
import { setupIntegrationTest, truncateTables } from '../../
|
|
12
|
+
import { clearTenantData, setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
13
13
|
import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationStatus, WebPushSubscription } from '../models/index.js';
|
|
14
14
|
import { configureNotification } from '../server/module.js';
|
|
15
15
|
import { EmailChannelProvider } from '../server/providers/email-channel-provider.js';
|
|
@@ -34,7 +34,7 @@ describe('Notification Flow (Integration)', () => {
|
|
|
34
34
|
let subjectService;
|
|
35
35
|
let mailServiceMock;
|
|
36
36
|
const schema = 'notification';
|
|
37
|
-
const tenantId =
|
|
37
|
+
const tenantId = crypto.randomUUID();
|
|
38
38
|
beforeAll(async () => {
|
|
39
39
|
({ injector, database } = await setupIntegrationTest({
|
|
40
40
|
orm: { schema },
|
|
@@ -63,8 +63,8 @@ describe('Notification Flow (Integration)', () => {
|
|
|
63
63
|
worker.registerProvider(NotificationChannel.InApp, injector.resolve(InAppChannelProvider));
|
|
64
64
|
});
|
|
65
65
|
beforeEach(async () => {
|
|
66
|
-
await truncateTables(database, '
|
|
67
|
-
await
|
|
66
|
+
await truncateTables(database, schema, ['log', 'in_app', 'in_app_archive', 'type', 'preference', 'web_push_subscription']);
|
|
67
|
+
await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
|
|
68
68
|
vi.clearAllMocks();
|
|
69
69
|
});
|
|
70
70
|
test('should execute full notification flow with escalation', async () => {
|
|
@@ -234,11 +234,11 @@ describe('Notification Flow (Integration)', () => {
|
|
|
234
234
|
expect(list[0].readTimestamp).not.toBeNull();
|
|
235
235
|
// Archive
|
|
236
236
|
await notificationService.archive(tenantId, user.id, list[0].id);
|
|
237
|
-
// List (
|
|
237
|
+
// List (excludes archived)
|
|
238
238
|
list = await notificationService.listInApp(tenantId, user.id);
|
|
239
239
|
expect(list).toHaveLength(0);
|
|
240
|
-
// List
|
|
241
|
-
list = await notificationService.
|
|
240
|
+
// List archived
|
|
241
|
+
list = await notificationService.listArchivedInApp(tenantId, user.id);
|
|
242
242
|
expect(list).toHaveLength(1);
|
|
243
243
|
expect(list[0].archiveTimestamp).not.toBeNull();
|
|
244
244
|
});
|
|
@@ -293,4 +293,42 @@ describe('Notification Flow (Integration)', () => {
|
|
|
293
293
|
expect(afterSecond[0].id).toBe(list[2].id);
|
|
294
294
|
});
|
|
295
295
|
});
|
|
296
|
+
test('should auto-archive old notifications', async () => {
|
|
297
|
+
await runInInjectionContext(injector, async () => {
|
|
298
|
+
const logRepo = injectRepository(NotificationLogEntity);
|
|
299
|
+
const inAppRepo = injectRepository(InAppNotification);
|
|
300
|
+
const user = await subjectService.createUser({ tenantId, email: 'auto@example.com', firstName: 'Auto', lastName: 'User' });
|
|
301
|
+
await typeService.initializeTypes({ test: { label: 'Auto Test' } });
|
|
302
|
+
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
|
|
303
|
+
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
304
|
+
await worker.deliver(logs[0].id);
|
|
305
|
+
// Verify active
|
|
306
|
+
expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(1);
|
|
307
|
+
// Manually update timestamp to be old (31 days ago)
|
|
308
|
+
const oldTimestamp = Date.now() - 31 * 24 * 60 * 60 * 1000;
|
|
309
|
+
await logRepo.updateByQuery({ id: logs[0].id }, { timestamp: oldTimestamp });
|
|
310
|
+
await inAppRepo.updateByQuery({ tenantId, logId: logs[0].id }, { timestamp: oldTimestamp });
|
|
311
|
+
await notificationService.runAutoArchive();
|
|
312
|
+
// Verify archived
|
|
313
|
+
expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(0);
|
|
314
|
+
expect(await notificationService.listArchivedInApp(tenantId, user.id)).toHaveLength(1);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
test('should archive all notifications for a user', async () => {
|
|
318
|
+
await runInInjectionContext(injector, async () => {
|
|
319
|
+
const logRepo = injectRepository(NotificationLogEntity);
|
|
320
|
+
const user = await subjectService.createUser({ tenantId, email: 'archiveall@example.com', firstName: 'Archive', lastName: 'All' });
|
|
321
|
+
await typeService.initializeTypes({ test: { label: 'Archive All Test' } });
|
|
322
|
+
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
|
|
323
|
+
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
|
|
324
|
+
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
325
|
+
for (const log of logs) {
|
|
326
|
+
await worker.deliver(log.id);
|
|
327
|
+
}
|
|
328
|
+
expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(2);
|
|
329
|
+
await notificationService.archiveAll(tenantId, user.id);
|
|
330
|
+
expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(0);
|
|
331
|
+
expect(await notificationService.listArchivedInApp(tenantId, user.id)).toHaveLength(2);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
296
334
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
import { runInInjectionContext } from '../../injector/index.js';
|
|
3
|
-
import { setupIntegrationTest } from '../../
|
|
3
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
4
4
|
import { NotificationSseService } from '../server/services/notification-sse.service.js';
|
|
5
5
|
describe('NotificationSseService', () => {
|
|
6
6
|
test('should register and publish to bus', async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
import { runInInjectionContext } from '../../injector/index.js';
|
|
3
|
-
import { setupIntegrationTest, truncateTables } from '../../
|
|
3
|
+
import { setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
4
4
|
import { NotificationTypeService } from '../server/services/notification-type.service.js';
|
|
5
5
|
describe('NotificationTypeService', () => {
|
|
6
6
|
test('should initialize types correctly', async () => {
|
|
@@ -56,6 +56,9 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
56
56
|
}));
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
|
+
if (this.isError(error, 'BucketAlreadyOwnedByYou', 'BucketAlreadyExists')) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
59
62
|
if (this.isBadRequestError(error)) {
|
|
60
63
|
throw new BadRequestError(`S3 request failed with 400 Bad Request. This often indicates an invalid bucket name ("${this.bucket}") or missing "forcePathStyle: true" for local S3 providers.`);
|
|
61
64
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
-
import { setupIntegrationTest } from '../../../
|
|
2
|
+
import { setupIntegrationTest } from '../../../testing/index.js';
|
|
3
3
|
import { readBinaryStream } from '../../../utils/stream/stream-reader.js';
|
|
4
4
|
import { configureS3ObjectStorage } from '../s3.object-storage-provider.js';
|
|
5
5
|
import { S3ObjectStorage } from '../s3.object-storage.js';
|
|
@@ -86,7 +86,8 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
|
|
|
86
86
|
return column;
|
|
87
87
|
});
|
|
88
88
|
const indexFn = (data.options?.unique == true) ? uniqueIndex : index;
|
|
89
|
-
let builder = indexFn(data.options?.name ?? getIndexName(tableName, columns, { naming: data.options?.naming
|
|
89
|
+
let builder = indexFn(data.options?.name ?? getIndexName(tableName, columns, { naming: data.options?.naming, partial: isDefined(data.options?.where) }))
|
|
90
|
+
.using(data.options?.using ?? 'btree', ...columns);
|
|
90
91
|
if (isDefined(data.options?.where)) {
|
|
91
92
|
const query = convertQuery(data.options.where(table), table, columnDefinitionsMap);
|
|
92
93
|
builder = builder.where(query.inlineParams());
|
|
@@ -198,7 +199,7 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
|
|
|
198
199
|
const paradeOptions = columnDef.reflectionData.paradeField;
|
|
199
200
|
return buildParadeCast(columnSql, paradeOptions);
|
|
200
201
|
});
|
|
201
|
-
const indexName = getIndexName(tableName, 'parade', { naming });
|
|
202
|
+
const indexName = getIndexName(tableName, 'parade', { naming, partial: isDefined(where) });
|
|
202
203
|
const indexColumns = [
|
|
203
204
|
table.id, // this orm always uses 'id' as key field as every entity has it
|
|
204
205
|
...classLevelColumnSqls,
|
|
@@ -468,7 +469,8 @@ function getPrimaryKeyName(tableName, columnsOrBaseName, options) {
|
|
|
468
469
|
return getIdentifier(tableName, columnsOrBaseName, 'pk', options);
|
|
469
470
|
}
|
|
470
471
|
function getIndexName(tableName, columnsOrBaseName, options) {
|
|
471
|
-
|
|
472
|
+
const suffix = (options?.partial == true) ? 'partial_idx' : 'idx';
|
|
473
|
+
return getIdentifier(tableName, columnsOrBaseName, suffix, options);
|
|
472
474
|
}
|
|
473
475
|
function getUniqueName(tableName, columnsOrBaseName, options) {
|
|
474
476
|
return getIdentifier(tableName, columnsOrBaseName, 'unique', options);
|
|
@@ -9,15 +9,16 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
};
|
|
10
10
|
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
11
11
|
import { StringProperty } from '../../schema/index.js';
|
|
12
|
+
import { dropTables, setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
12
13
|
import { sql } from 'drizzle-orm';
|
|
13
|
-
import { beforeAll, describe, expect, test } from 'vitest';
|
|
14
|
+
import { beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
14
15
|
import { Table } from '../decorators.js';
|
|
15
16
|
import { Entity } from '../entity.js';
|
|
16
|
-
import {
|
|
17
|
+
import { Database } from '../server/index.js';
|
|
17
18
|
import { injectRepository } from '../server/repository.js';
|
|
18
19
|
describe('ORM Repository Attributes (Integration)', () => {
|
|
19
20
|
let injector;
|
|
20
|
-
let
|
|
21
|
+
let database;
|
|
21
22
|
const schema = 'test_orm_attributes';
|
|
22
23
|
let AttributeEntity = class AttributeEntity extends Entity {
|
|
23
24
|
name;
|
|
@@ -30,17 +31,9 @@ describe('ORM Repository Attributes (Integration)', () => {
|
|
|
30
31
|
Table('attribute_entities', { schema })
|
|
31
32
|
], AttributeEntity);
|
|
32
33
|
beforeAll(async () => {
|
|
33
|
-
injector =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
connection: {
|
|
37
|
-
host: '127.0.0.1', port: 5432, user: 'tstdl', password: 'wf7rq6glrk5jykne', database: 'tstdl',
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
db = injector.resolve(Database);
|
|
41
|
-
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
42
|
-
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('attribute_entities')} CASCADE`);
|
|
43
|
-
await db.execute(sql `
|
|
34
|
+
({ injector, database } = await setupIntegrationTest({ orm: { schema } }));
|
|
35
|
+
await dropTables(database, schema, ['attribute_entities']);
|
|
36
|
+
await database.execute(sql `
|
|
44
37
|
CREATE TABLE ${sql.identifier(schema)}.${sql.identifier('attribute_entities')} (
|
|
45
38
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
46
39
|
name TEXT NOT NULL,
|
|
@@ -52,8 +45,10 @@ describe('ORM Repository Attributes (Integration)', () => {
|
|
|
52
45
|
)
|
|
53
46
|
`);
|
|
54
47
|
});
|
|
48
|
+
beforeEach(async () => {
|
|
49
|
+
await truncateTables(database, schema, ['attribute_entities']);
|
|
50
|
+
});
|
|
55
51
|
test('should support partial attribute updates', async () => {
|
|
56
|
-
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('attribute_entities')} CASCADE`);
|
|
57
52
|
await runInInjectionContext(injector, async () => {
|
|
58
53
|
const repository = injectRepository(AttributeEntity);
|
|
59
54
|
const e1 = await repository.insert(Object.assign(new AttributeEntity(), {
|
|
@@ -68,7 +63,6 @@ describe('ORM Repository Attributes (Integration)', () => {
|
|
|
68
63
|
});
|
|
69
64
|
});
|
|
70
65
|
test('should update attributes during soft delete', async () => {
|
|
71
|
-
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('attribute_entities')} CASCADE`);
|
|
72
66
|
await runInInjectionContext(injector, async () => {
|
|
73
67
|
const repository = injectRepository(AttributeEntity);
|
|
74
68
|
const e1 = await repository.insert(Object.assign(new AttributeEntity(), {
|
|
@@ -80,7 +74,6 @@ describe('ORM Repository Attributes (Integration)', () => {
|
|
|
80
74
|
});
|
|
81
75
|
});
|
|
82
76
|
test('should support querying by raw SQL on attributes', async () => {
|
|
83
|
-
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('attribute_entities')} CASCADE`);
|
|
84
77
|
await runInInjectionContext(injector, async () => {
|
|
85
78
|
const repository = injectRepository(AttributeEntity);
|
|
86
79
|
await repository.insert(Object.assign(new AttributeEntity(), {
|
|
@@ -96,9 +96,9 @@ describe('ORM Repository CTI Mapping (Integration)', () => {
|
|
|
96
96
|
expect(child.secret).toBe('Hidden');
|
|
97
97
|
// Verify DB column names
|
|
98
98
|
const { rows: parentRows } = await db.execute(sql `SELECT display_name FROM ${sql.identifier(schema)}.${sql.identifier('mapped_parents')} WHERE id = ${child.id}`);
|
|
99
|
-
expect(parentRows[0]
|
|
99
|
+
expect(parentRows[0]['display_name']).toBe('Visible');
|
|
100
100
|
const { rows: childRows } = await db.execute(sql `SELECT internal_secret FROM ${sql.identifier(schema)}.${sql.identifier('mapped_children')} WHERE id = ${child.id}`);
|
|
101
|
-
expect(childRows[0]
|
|
101
|
+
expect(childRows[0]['internal_secret']).toBe('Hidden');
|
|
102
102
|
// Load back
|
|
103
103
|
const loaded = await repo.load(child.id);
|
|
104
104
|
expect(loaded.displayName).toBe('Visible');
|
|
@@ -92,7 +92,7 @@ describe('ORM Repository CTI Soft Delete (Integration)', () => {
|
|
|
92
92
|
const entity = await repo.insert(Object.assign(new Subtype(), { baseName: 'B1', subData: 'S1' }));
|
|
93
93
|
await repo.delete(entity.id);
|
|
94
94
|
const { rows } = await db.execute(sql `SELECT delete_timestamp FROM ${sql.identifier(schema)}.${sql.identifier('bases')} WHERE id = ${entity.id}`);
|
|
95
|
-
expect(rows[0]
|
|
95
|
+
expect(rows[0]['delete_timestamp']).not.toBeNull();
|
|
96
96
|
expect(await repo.has(entity.id)).toBe(false);
|
|
97
97
|
expect(await repo.loadManyByQuery({ id: entity.id }).then((r) => r[0])).toBeUndefined();
|
|
98
98
|
});
|