@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.
Files changed (219) hide show
  1. package/ai/genkit/tests/multi-region.test.js +6 -6
  2. package/ai/index.d.ts +2 -6
  3. package/ai/index.js +2 -6
  4. package/ai/parser/index.d.ts +1 -0
  5. package/ai/parser/index.js +1 -0
  6. package/ai/parser/parser.d.ts +12 -0
  7. package/ai/parser/parser.js +28 -0
  8. package/ai/prompts/build.d.ts +21 -0
  9. package/ai/prompts/build.js +25 -0
  10. package/ai/prompts/index.d.ts +2 -0
  11. package/ai/prompts/index.js +2 -0
  12. package/ai/prompts/instructions-formatter.d.ts +9 -22
  13. package/ai/prompts/instructions-formatter.js +20 -7
  14. package/ai/prompts/instructions.js +1 -1
  15. package/ai/prompts/steering.d.ts +27 -0
  16. package/ai/prompts/steering.js +54 -0
  17. package/ai/tests/instructions-formatter.test.js +115 -0
  18. package/ai/tests/steering.test.js +37 -0
  19. package/application/application.d.ts +2 -1
  20. package/application/application.js +3 -0
  21. package/authentication/client/module.d.ts +1 -1
  22. package/authentication/client/module.js +4 -5
  23. package/authentication/tests/authentication-ancillary.service.test.js +1 -1
  24. package/authentication/tests/authentication.api-controller.test.js +3 -1
  25. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  26. package/authentication/tests/authentication.client-service.test.js +1 -1
  27. package/authentication/tests/authentication.service.test.js +1 -1
  28. package/authentication/tests/subject.service.test.js +1 -1
  29. package/circuit-breaker/tests/circuit-breaker.test.js +1 -1
  30. package/document-management/api/document-management.api.d.ts +16 -16
  31. package/document-management/api/document-management.api.js +12 -12
  32. package/document-management/models/ai-configuration.d.ts +59 -0
  33. package/document-management/models/ai-configuration.js +1 -0
  34. package/document-management/models/document-assignment-scope.model.js +2 -4
  35. package/document-management/models/document-assignment-task.model.js +2 -4
  36. package/document-management/models/document-collection-assignment.model.js +2 -4
  37. package/document-management/models/document-collection.model.js +2 -3
  38. package/document-management/models/document-content.model.d.ts +6 -0
  39. package/document-management/models/document-content.model.js +32 -0
  40. package/document-management/models/document-property-value.model.js +1 -2
  41. package/document-management/models/document-request-collection-assignment.model.js +2 -4
  42. package/document-management/models/document-request.model.js +2 -4
  43. package/document-management/models/document-tag-assignment.model.js +2 -3
  44. package/document-management/models/document-validation-execution-related-document.model.js +2 -4
  45. package/document-management/models/document-validation-execution.model.js +2 -5
  46. package/document-management/models/document-workflow.model.d.ts +2 -1
  47. package/document-management/models/document-workflow.model.js +4 -5
  48. package/document-management/models/document.model.js +2 -3
  49. package/document-management/models/index.d.ts +2 -0
  50. package/document-management/models/index.js +2 -0
  51. package/document-management/server/api/document-management.api.d.ts +7 -7
  52. package/document-management/server/api/document-management.api.js +9 -9
  53. package/document-management/server/configure.d.ts +4 -1
  54. package/document-management/server/configure.js +9 -4
  55. package/document-management/server/drizzle/{0000_silly_chimera.sql → 0000_curious_nighthawk.sql} +8 -28
  56. package/document-management/server/drizzle/meta/0000_snapshot.json +14 -286
  57. package/document-management/server/drizzle/meta/_journal.json +2 -2
  58. package/document-management/server/module.d.ts +2 -0
  59. package/document-management/server/module.js +1 -0
  60. package/document-management/server/schemas.d.ts +2 -1
  61. package/document-management/server/services/document-file.service.d.ts +6 -6
  62. package/document-management/server/services/document-file.service.js +7 -81
  63. package/document-management/server/services/document-management-ai-provider.service.d.ts +66 -0
  64. package/document-management/server/services/document-management-ai-provider.service.js +2 -0
  65. package/document-management/server/services/document-management-ai.service.d.ts +44 -7
  66. package/document-management/server/services/document-management-ai.service.js +332 -329
  67. package/document-management/server/services/document-validation.service.d.ts +1 -1
  68. package/document-management/server/services/document-workflow.service.d.ts +4 -3
  69. package/document-management/server/services/document-workflow.service.js +26 -9
  70. package/document-management/server/services/document.service.d.ts +7 -3
  71. package/document-management/server/services/document.service.js +13 -4
  72. package/document-management/server/services/index.d.ts +1 -0
  73. package/document-management/server/services/index.js +1 -0
  74. package/document-management/server/validators/ai-validation-executor.d.ts +419 -12
  75. package/document-management/server/validators/ai-validation-executor.js +51 -46
  76. package/document-management/server/validators/single-document-validation-executor.d.ts +1 -3
  77. package/document-management/server/validators/single-document-validation-executor.js +2 -4
  78. package/document-management/service-models/document.service-model.d.ts +3 -3
  79. package/document-management/service-models/document.service-model.js +1 -1
  80. package/document-management/tests/ai-config-hierarchy.test.d.ts +1 -0
  81. package/document-management/tests/ai-config-hierarchy.test.js +64 -0
  82. package/document-management/tests/ai-config-integration.test.d.ts +1 -0
  83. package/document-management/tests/ai-config-integration.test.js +125 -0
  84. package/document-management/tests/ai-config-merge.test.d.ts +1 -0
  85. package/document-management/tests/ai-config-merge.test.js +38 -0
  86. package/document-management/tests/document-management-ai-overrides.test.d.ts +1 -0
  87. package/document-management/tests/document-management-ai-overrides.test.js +64 -0
  88. package/document-management/tests/document-management-core.test.js +6 -6
  89. package/document-management/tests/document-management.api.test.js +5 -5
  90. package/document-management/tests/document-statistics.service.test.js +10 -6
  91. package/document-management/tests/document-validation-ai-overrides.test.d.ts +1 -0
  92. package/document-management/tests/document-validation-ai-overrides.test.js +85 -0
  93. package/document-management/tests/document.service.test.js +15 -11
  94. package/document-management/tests/enum-helpers.test.js +5 -5
  95. package/examples/document-management/ai-provider.d.ts +20 -0
  96. package/examples/document-management/ai-provider.js +74 -0
  97. package/examples/document-management/main.js +9 -6
  98. package/examples/injector/graph-example.d.ts +1 -0
  99. package/examples/injector/graph-example.js +340 -0
  100. package/injector/decorators.d.ts +4 -4
  101. package/injector/decorators.js +5 -6
  102. package/injector/forward-ref.d.ts +15 -0
  103. package/injector/forward-ref.js +20 -0
  104. package/injector/graph.d.ts +113 -0
  105. package/injector/graph.js +631 -0
  106. package/injector/index.d.ts +2 -0
  107. package/injector/index.js +2 -0
  108. package/injector/inject.d.ts +15 -15
  109. package/injector/injector.d.ts +101 -13
  110. package/injector/injector.js +103 -59
  111. package/injector/resolve-chain.d.ts +20 -6
  112. package/injector/resolve-chain.js +39 -14
  113. package/injector/tests/advanced.test.d.ts +1 -0
  114. package/injector/tests/advanced.test.js +116 -0
  115. package/injector/tests/async-init.test.d.ts +1 -0
  116. package/injector/tests/async-init.test.js +77 -0
  117. package/injector/tests/basic.test.d.ts +1 -0
  118. package/injector/tests/basic.test.js +114 -0
  119. package/injector/tests/hierarchical.test.d.ts +1 -0
  120. package/injector/tests/hierarchical.test.js +59 -0
  121. package/injector/tests/lifecycles.test.d.ts +1 -0
  122. package/injector/tests/lifecycles.test.js +109 -0
  123. package/injector/token.d.ts +2 -1
  124. package/injector/token.js +4 -1
  125. package/injector/type-info.d.ts +1 -5
  126. package/injector/types.d.ts +4 -10
  127. package/logger/tests/pretty-print.test.d.ts +1 -0
  128. package/logger/{formatters → tests}/pretty-print.test.js +1 -1
  129. package/logger/transports/console.d.ts +3 -2
  130. package/logger/transports/console.js +4 -3
  131. package/notification/api/notification.api.d.ts +26 -6
  132. package/notification/api/notification.api.js +15 -4
  133. package/notification/client/notification-client.d.ts +6 -0
  134. package/notification/client/notification-client.js +13 -3
  135. package/notification/models/in-app-notification.model.d.ts +9 -3
  136. package/notification/models/in-app-notification.model.js +32 -11
  137. package/notification/models/notification-log.model.js +2 -3
  138. package/notification/server/api/notification.api-controller.d.ts +1 -0
  139. package/notification/server/api/notification.api-controller.js +7 -1
  140. package/notification/server/drizzle/{0000_oval_rage.sql → 0000_wise_pyro.sql} +22 -4
  141. package/notification/server/drizzle/meta/0000_snapshot.json +249 -37
  142. package/notification/server/drizzle/meta/_journal.json +2 -2
  143. package/notification/server/module.d.ts +5 -0
  144. package/notification/server/module.js +6 -1
  145. package/notification/server/providers/in-app-channel-provider.js +1 -0
  146. package/notification/server/schemas.d.ts +3 -2
  147. package/notification/server/schemas.js +3 -2
  148. package/notification/server/services/notification.service.d.ts +11 -6
  149. package/notification/server/services/notification.service.js +138 -42
  150. package/notification/tests/notification-api.test.js +16 -6
  151. package/notification/tests/notification-client.test.d.ts +1 -0
  152. package/notification/tests/{unit/notification-client.test.js → notification-client.test.js} +5 -5
  153. package/notification/tests/notification-flow.test.js +45 -7
  154. package/notification/tests/notification-sse.service.test.js +1 -1
  155. package/notification/tests/notification-type.service.test.js +1 -1
  156. package/object-storage/s3/s3.object-storage.js +3 -0
  157. package/object-storage/s3/tests/s3.object-storage.integration.test.js +1 -1
  158. package/orm/server/drizzle/schema-converter.js +5 -3
  159. package/orm/tests/repository-attributes.test.js +10 -17
  160. package/orm/tests/repository-cti-mapping.test.js +2 -2
  161. package/orm/tests/repository-cti-soft-delete.test.js +1 -1
  162. package/orm/tests/repository-cti.test.js +19 -33
  163. package/orm/tests/repository-extra-coverage.test.js +1 -1
  164. package/orm/tests/repository-search.test.js +5 -2
  165. package/orm/tests/schema-converter.test.js +1 -0
  166. package/orm/tests/transaction-safety.test.js +1 -1
  167. package/package.json +7 -9
  168. package/rate-limit/tests/postgres-rate-limiter.test.js +6 -16
  169. package/renderer/d2.d.ts +77 -0
  170. package/renderer/d2.js +68 -0
  171. package/renderer/graphviz.d.ts +47 -0
  172. package/renderer/graphviz.js +58 -0
  173. package/renderer/index.d.ts +4 -0
  174. package/renderer/index.js +4 -0
  175. package/renderer/typst.d.ts +57 -0
  176. package/renderer/typst.js +62 -0
  177. package/rpc/adapters/readable-stream.adapter.d.ts +3 -0
  178. package/rpc/adapters/readable-stream.adapter.js +5 -1
  179. package/rpc/rpc.js +28 -3
  180. package/rpc/tests/rpc.integration.test.js +3 -1
  181. package/schema/schemas/nullable.js +1 -1
  182. package/task-queue/task-queue.d.ts +2 -0
  183. package/task-queue/task-queue.js +6 -2
  184. package/task-queue/tests/complex.test.js +1 -1
  185. package/task-queue/tests/dependencies.test.js +3 -3
  186. package/task-queue/tests/extensive-dependencies.test.js +1 -1
  187. package/task-queue/tests/queue.test.js +1 -1
  188. package/task-queue/tests/worker.test.js +4 -7
  189. package/test5.js +52 -8
  190. package/{unit-test → testing}/integration-setup.d.ts +1 -0
  191. package/{unit-test → testing}/integration-setup.js +13 -0
  192. package/utils/base64.d.ts +7 -0
  193. package/utils/base64.js +10 -1
  194. package/utils/noop.d.ts +7 -1
  195. package/utils/noop.js +7 -1
  196. package/ai/ai-file.service.d.ts +0 -57
  197. package/ai/ai-file.service.js +0 -233
  198. package/ai/ai-session.d.ts +0 -38
  199. package/ai/ai-session.js +0 -50
  200. package/ai/ai.service.d.ts +0 -126
  201. package/ai/ai.service.js +0 -481
  202. package/ai/functions.d.ts +0 -9
  203. package/ai/functions.js +0 -38
  204. package/ai/module.d.ts +0 -26
  205. package/ai/module.js +0 -25
  206. package/ai/types.d.ts +0 -229
  207. package/ai/types.js +0 -33
  208. package/latex/index.d.ts +0 -1
  209. package/latex/index.js +0 -1
  210. package/typst/index.d.ts +0 -1
  211. package/typst/index.js +0 -1
  212. package/typst/render.d.ts +0 -23
  213. package/typst/render.js +0 -32
  214. /package/{logger/formatters/pretty-print.test.d.ts → ai/tests/instructions-formatter.test.d.ts} +0 -0
  215. /package/{notification/tests/unit/notification-client.test.d.ts → ai/tests/steering.test.d.ts} +0 -0
  216. /package/{latex/render.d.ts → renderer/latex.d.ts} +0 -0
  217. /package/{latex/render.js → renderer/latex.js} +0 -0
  218. /package/{unit-test → testing}/index.d.ts +0 -0
  219. /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, isNotNull } from '../../../utils/type-guards.js';
18
- import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationPreference, NotificationPriority, NotificationStatus, toInAppNotificationView, WebPushSubscription } from '../../models/index.js';
19
- import { inAppNotification, notificationLog } from '../schemas.js';
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
- let afterNotification;
48
- if (options.after != null) {
49
- const inApp = await this.#inAppNotificationRepository.loadByQuery({ tenantId, userId, id: options.after });
50
- afterNotification = await this.#notificationLogRepository.loadByQuery({ tenantId, userId, id: inApp.logId });
51
- }
52
- const rows = await this.#notificationLogRepository.session
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: inAppNotification,
69
+ inApp: table,
56
70
  })
57
- .from(notificationLog)
58
- .innerJoin(inAppNotification, and(eq(inAppNotification.tenantId, notificationLog.tenantId), eq(inAppNotification.userId, notificationLog.userId), eq(inAppNotification.logId, notificationLog.id)))
59
- .where(and(eq(notificationLog.tenantId, tenantId), eq(notificationLog.userId, userId), options.includeArchived ? undefined : isNull(inAppNotification.archiveTimestamp), options.unreadOnly ? isNull(inAppNotification.readTimestamp) : undefined, isDefined(afterNotification)
60
- ? or(lt(notificationLog.timestamp, afterNotification.timestamp), and(eq(notificationLog.timestamp, afterNotification.timestamp), lt(notificationLog.id, afterNotification.id)))
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(notificationLog.timestamp), desc(notificationLog.id));
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 this.#inAppNotificationRepository.mapManyToEntity(inAppRows);
84
+ const inAppEntities = await repository.mapManyToEntity(inAppRows);
69
85
  const notificationViewDatas = await this.#notificationAncillaryService.getViewData(tenantId, notificationEntities);
70
- const views = notificationEntities.map((notification, index) => {
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
- await this.#inAppNotificationRepository.updateByQuery({ tenantId, id, userId }, { archiveTimestamp: TRANSACTION_TIMESTAMP });
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
- await this.#inAppNotificationRepository.updateManyByQuery({ tenantId, userId, archiveTimestamp: null }, { archiveTimestamp: TRANSACTION_TIMESTAMP });
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, lastNotificationId) {
115
- const lastTimestamp = await this.getTimestamp(tenantId, userId, lastNotificationId);
116
- const [unreadCount, missedNotifications, readAndArchived] = await Promise.all([
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: lastNotificationId, limit: 50 }),
119
- this.#inAppNotificationRepository.session
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), or(gt(inAppNotification.readTimestamp, lastTimestamp), gt(inAppNotification.archiveTimestamp, lastTimestamp))))
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
- const readIds = readAndArchived.filter((row) => isNotNull(row.readTimestamp) && (row.readTimestamp > lastTimestamp)).map((row) => row.id);
130
- const archiveIds = readAndArchived.filter((row) => isNotNull(row.archiveTimestamp) && (row.archiveTimestamp > lastTimestamp)).map((row) => row.id);
131
- return { unreadCount, missedNotifications, readIds, archiveIds };
132
- }
133
- async getTimestamp(tenantId, userId, inAppId) {
134
- const inApp = await this.#inAppNotificationRepository.loadByQuery({ tenantId, userId, id: inAppId });
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 { setupIntegrationTest, truncateTables } from '../../unit-test/index.js';
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 = '00000000-0000-0000-0000-000000000000';
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 truncateTables(database, 'authentication', ['user', 'subject']);
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, includeArchived: false };
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 '../../../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';
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 '../../unit-test/index.js';
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 = '00000000-0000-0000-0000-000000000000';
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, 'authentication', ['user', 'subject']);
67
- await truncateTables(database, 'notification', ['log', 'in_app', 'type', 'preference', 'web_push_subscription']);
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 (default excludes archived)
237
+ // List (excludes archived)
238
238
  list = await notificationService.listInApp(tenantId, user.id);
239
239
  expect(list).toHaveLength(0);
240
- // List (include archived)
241
- list = await notificationService.listInApp(tenantId, user.id, { includeArchived: true });
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 '../../unit-test/index.js';
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 '../../unit-test/index.js';
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 '../../../unit-test/index.js';
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 })).using(data.options?.using ?? 'btree', ...columns);
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
- return getIdentifier(tableName, columnsOrBaseName, 'idx', options);
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 { configureOrm, Database } from '../server/index.js';
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 db;
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 = new Injector('Test');
34
- configureOrm({
35
- repositoryConfig: { schema },
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].display_name).toBe('Visible');
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].internal_secret).toBe('Hidden');
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].delete_timestamp).not.toBeNull();
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
  });