@tstdl/base 0.93.87 → 0.93.89

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 (314) hide show
  1. package/ai/genkit/helpers.d.ts +3 -1
  2. package/ai/genkit/helpers.js +3 -3
  3. package/api/server/gateway.d.ts +3 -0
  4. package/api/server/gateway.js +15 -4
  5. package/api/server/middlewares/catch-error.middleware.js +2 -4
  6. package/api/server/middlewares/cors.middleware.js +2 -3
  7. package/api/server/middlewares/csrf.middleware.d.ts +41 -0
  8. package/api/server/middlewares/csrf.middleware.js +108 -0
  9. package/api/server/middlewares/index.d.ts +1 -0
  10. package/api/server/middlewares/index.js +1 -0
  11. package/api/server/module.d.ts +8 -2
  12. package/api/server/module.js +14 -8
  13. package/api/server/tests/csrf.middleware.test.js +91 -0
  14. package/audit/drizzle/{0000_bored_stick.sql → 0000_lumpy_thunderball.sql} +3 -3
  15. package/audit/drizzle/meta/0000_snapshot.json +4 -4
  16. package/audit/drizzle/meta/_journal.json +2 -9
  17. package/audit/module.d.ts +4 -1
  18. package/audit/module.js +3 -2
  19. package/audit/schemas.d.ts +1 -1
  20. package/audit/types.d.ts +1 -1
  21. package/audit/types.js +1 -1
  22. package/authentication/client/authentication.service.d.ts +14 -1
  23. package/authentication/client/authentication.service.js +82 -23
  24. package/authentication/client/http-client.middleware.d.ts +6 -0
  25. package/authentication/client/http-client.middleware.js +36 -0
  26. package/authentication/client/module.js +8 -2
  27. package/authentication/models/service-account.model.d.ts +2 -2
  28. package/authentication/models/service-account.model.js +10 -5
  29. package/authentication/models/subject.model.d.ts +19 -5
  30. package/authentication/models/subject.model.js +25 -29
  31. package/authentication/models/system-account.model.d.ts +3 -2
  32. package/authentication/models/system-account.model.js +11 -5
  33. package/authentication/models/user.model.d.ts +2 -11
  34. package/authentication/models/user.model.js +5 -16
  35. package/authentication/server/authentication-api-request-token.provider.d.ts +0 -2
  36. package/authentication/server/authentication-api-request-token.provider.js +3 -11
  37. package/authentication/server/authentication.api-controller.d.ts +1 -2
  38. package/authentication/server/authentication.api-controller.js +8 -9
  39. package/authentication/server/authentication.audit.d.ts +3 -2
  40. package/authentication/server/authentication.service.d.ts +27 -1
  41. package/authentication/server/authentication.service.js +67 -18
  42. package/authentication/server/drizzle/{0000_normal_paper_doll.sql → 0000_soft_tag.sql} +25 -32
  43. package/authentication/server/drizzle/meta/0000_snapshot.json +180 -205
  44. package/authentication/server/drizzle/meta/_journal.json +2 -2
  45. package/authentication/server/helper.js +9 -2
  46. package/authentication/server/module.d.ts +4 -1
  47. package/authentication/server/module.js +9 -5
  48. package/authentication/server/schemas.d.ts +2 -1
  49. package/authentication/server/schemas.js +2 -2
  50. package/authentication/server/subject.service.d.ts +14 -8
  51. package/authentication/server/subject.service.js +86 -84
  52. package/authentication/tests/authentication-ancillary.service.test.d.ts +1 -0
  53. package/authentication/tests/authentication-ancillary.service.test.js +13 -0
  54. package/authentication/tests/authentication-secret-requirements.validator.test.d.ts +1 -0
  55. package/authentication/tests/authentication-secret-requirements.validator.test.js +29 -0
  56. package/authentication/tests/authentication.api-controller.test.d.ts +1 -0
  57. package/authentication/tests/authentication.api-controller.test.js +88 -0
  58. package/authentication/tests/authentication.api-request-token.provider.test.d.ts +1 -0
  59. package/authentication/tests/authentication.api-request-token.provider.test.js +48 -0
  60. package/authentication/tests/authentication.client-middleware.test.d.ts +1 -0
  61. package/authentication/tests/authentication.client-middleware.test.js +23 -0
  62. package/authentication/tests/authentication.client-service.test.d.ts +1 -0
  63. package/authentication/tests/authentication.client-service.test.js +70 -0
  64. package/authentication/tests/authentication.service.test.d.ts +1 -0
  65. package/authentication/tests/authentication.service.test.js +186 -0
  66. package/authentication/tests/authentication.test-ancillary-service.d.ts +9 -0
  67. package/authentication/tests/authentication.test-ancillary-service.js +27 -0
  68. package/authentication/tests/helper.test.d.ts +1 -0
  69. package/authentication/tests/helper.test.js +107 -0
  70. package/authentication/tests/secret-requirements.error.test.d.ts +1 -0
  71. package/authentication/tests/secret-requirements.error.test.js +14 -0
  72. package/authentication/tests/subject.service.test.d.ts +1 -0
  73. package/authentication/tests/subject.service.test.js +140 -0
  74. package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +1 -1
  75. package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
  76. package/circuit-breaker/postgres/module.d.ts +7 -1
  77. package/circuit-breaker/postgres/module.js +8 -6
  78. package/circuit-breaker/tests/circuit-breaker.test.js +2 -22
  79. package/document-management/api/document-management.api.js +2 -6
  80. package/document-management/server/services/document-validation.service.js +6 -5
  81. package/document-management/server/services/document-workflow.service.js +5 -5
  82. package/document-management/service-models/document-folders.view-model.d.ts +5 -2
  83. package/document-management/service-models/document-folders.view-model.js +42 -9
  84. package/document-management/service-models/enriched/enriched-document-management-data.view.js +1 -1
  85. package/examples/document-management/main.js +4 -4
  86. package/http/client/adapters/undici.adapter.d.ts +7 -5
  87. package/http/client/adapters/undici.adapter.js +13 -10
  88. package/http/client/module.d.ts +3 -1
  89. package/http/client/module.js +8 -9
  90. package/http/server/http-server.d.ts +2 -0
  91. package/http/server/node/module.d.ts +6 -2
  92. package/http/server/node/module.js +6 -4
  93. package/http/server/node/node-http-server.d.ts +2 -0
  94. package/http/server/node/node-http-server.js +7 -0
  95. package/http/types.d.ts +1 -1
  96. package/key-value-store/postgres/module.d.ts +7 -1
  97. package/key-value-store/postgres/module.js +7 -3
  98. package/lock/postgres/lock.js +0 -1
  99. package/lock/postgres/module.d.ts +7 -1
  100. package/lock/postgres/module.js +9 -5
  101. package/logger/formatter.d.ts +2 -0
  102. package/logger/formatters/json.js +2 -2
  103. package/logger/formatters/pretty-print.js +8 -10
  104. package/logger/logger.d.ts +1 -1
  105. package/logger/logger.js +15 -12
  106. package/message-bus/local/module.d.ts +5 -2
  107. package/message-bus/local/module.js +5 -4
  108. package/module/module.d.ts +2 -1
  109. package/module/module.js +3 -0
  110. package/module/modules/web-server.module.d.ts +11 -6
  111. package/module/modules/web-server.module.js +15 -10
  112. package/orm/decorators.d.ts +24 -1
  113. package/orm/decorators.js +40 -4
  114. package/orm/query/base.d.ts +17 -17
  115. package/orm/query/base.js +1 -1
  116. package/orm/repository.types.d.ts +45 -1
  117. package/orm/schemas/tsvector.js +1 -1
  118. package/orm/server/drizzle/schema-converter.d.ts +3 -1
  119. package/orm/server/drizzle/schema-converter.js +120 -14
  120. package/orm/server/index.d.ts +1 -0
  121. package/orm/server/index.js +1 -0
  122. package/orm/server/module.d.ts +4 -2
  123. package/orm/server/module.js +6 -5
  124. package/orm/server/query-converter.d.ts +6 -3
  125. package/orm/server/query-converter.js +32 -20
  126. package/orm/server/repository-config.d.ts +8 -0
  127. package/orm/server/repository-config.js +8 -0
  128. package/orm/server/repository.d.ts +117 -43
  129. package/orm/server/repository.js +757 -253
  130. package/orm/server/transaction.d.ts +4 -2
  131. package/orm/server/transaction.js +14 -5
  132. package/orm/server/transactional.d.ts +6 -2
  133. package/orm/server/transactional.js +39 -9
  134. package/orm/server/types.d.ts +2 -0
  135. package/orm/sqls/case-when.d.ts +3 -3
  136. package/orm/sqls/case-when.js +2 -2
  137. package/orm/sqls/sqls.d.ts +31 -5
  138. package/orm/sqls/sqls.js +69 -6
  139. package/orm/tests/data-types.test.d.ts +1 -0
  140. package/orm/tests/data-types.test.js +39 -0
  141. package/orm/tests/decorators.test.d.ts +1 -0
  142. package/orm/tests/decorators.test.js +77 -0
  143. package/orm/tests/encryption.test.d.ts +1 -0
  144. package/orm/tests/encryption.test.js +34 -0
  145. package/orm/tests/query-complex.test.d.ts +1 -0
  146. package/orm/tests/query-complex.test.js +203 -0
  147. package/orm/tests/query-converter-complex.test.d.ts +1 -0
  148. package/orm/tests/query-converter-complex.test.js +126 -0
  149. package/orm/tests/query-converter.test.d.ts +1 -0
  150. package/orm/tests/query-converter.test.js +123 -0
  151. package/orm/tests/repository-advanced.test.d.ts +1 -0
  152. package/orm/tests/repository-advanced.test.js +232 -0
  153. package/orm/tests/repository-attributes.test.d.ts +1 -0
  154. package/orm/tests/repository-attributes.test.js +99 -0
  155. package/orm/tests/repository-comprehensive.test.d.ts +1 -0
  156. package/orm/tests/repository-comprehensive.test.js +187 -0
  157. package/orm/tests/repository-coverage.test.d.ts +1 -0
  158. package/orm/tests/repository-coverage.test.js +303 -0
  159. package/orm/tests/repository-cti-complex.test.d.ts +1 -0
  160. package/orm/tests/repository-cti-complex.test.js +170 -0
  161. package/orm/tests/repository-cti-embedded.test.d.ts +1 -0
  162. package/orm/tests/repository-cti-embedded.test.js +188 -0
  163. package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
  164. package/orm/tests/repository-cti-extensive.test.js +308 -0
  165. package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
  166. package/orm/tests/repository-cti-mapping.test.js +121 -0
  167. package/orm/tests/repository-cti-search.test.d.ts +1 -0
  168. package/orm/tests/repository-cti-search.test.js +152 -0
  169. package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
  170. package/orm/tests/repository-cti-soft-delete.test.js +115 -0
  171. package/orm/tests/repository-cti-transactions.test.d.ts +1 -0
  172. package/orm/tests/repository-cti-transactions.test.js +126 -0
  173. package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
  174. package/orm/tests/repository-cti-upsert-many.test.js +127 -0
  175. package/orm/tests/repository-cti.test.d.ts +1 -0
  176. package/orm/tests/repository-cti.test.js +456 -0
  177. package/orm/tests/repository-edge-cases.test.d.ts +1 -0
  178. package/orm/tests/repository-edge-cases.test.js +216 -0
  179. package/orm/tests/repository-expiration.test.d.ts +1 -0
  180. package/orm/tests/repository-expiration.test.js +153 -0
  181. package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
  182. package/orm/tests/repository-extra-coverage.test.js +546 -0
  183. package/orm/tests/repository-mapping.test.d.ts +1 -0
  184. package/orm/tests/repository-mapping.test.js +71 -0
  185. package/orm/tests/repository-regression.test.d.ts +1 -0
  186. package/orm/tests/repository-regression.test.js +330 -0
  187. package/orm/tests/repository-search-coverage.test.d.ts +1 -0
  188. package/orm/tests/repository-search-coverage.test.js +129 -0
  189. package/orm/tests/repository-search.test.d.ts +1 -0
  190. package/orm/tests/repository-search.test.js +116 -0
  191. package/orm/tests/repository-soft-delete.test.d.ts +1 -0
  192. package/orm/tests/repository-soft-delete.test.js +143 -0
  193. package/orm/tests/repository-transactions-nested.test.d.ts +1 -0
  194. package/orm/tests/repository-transactions-nested.test.js +202 -0
  195. package/orm/tests/repository-types.test.d.ts +1 -0
  196. package/orm/tests/repository-types.test.js +218 -0
  197. package/orm/tests/schema-converter.test.d.ts +1 -0
  198. package/orm/tests/schema-converter.test.js +81 -0
  199. package/orm/tests/schema-generation.test.d.ts +1 -0
  200. package/orm/tests/schema-generation.test.js +127 -0
  201. package/orm/tests/sql-helpers.test.d.ts +1 -0
  202. package/orm/tests/sql-helpers.test.js +67 -0
  203. package/orm/tests/transaction-safety.test.d.ts +1 -0
  204. package/orm/tests/transaction-safety.test.js +81 -0
  205. package/orm/tests/transactional.test.d.ts +1 -0
  206. package/orm/tests/transactional.test.js +224 -0
  207. package/orm/tests/utils.test.d.ts +1 -0
  208. package/orm/tests/utils.test.js +70 -0
  209. package/orm/utils.d.ts +7 -0
  210. package/orm/utils.js +26 -6
  211. package/package.json +12 -7
  212. package/pool/pool.js +1 -1
  213. package/rate-limit/index.d.ts +2 -0
  214. package/rate-limit/index.js +2 -0
  215. package/rate-limit/postgres/drizzle/0000_watery_rage.sql +7 -0
  216. package/{queue → rate-limit}/postgres/drizzle/meta/0000_snapshot.json +14 -39
  217. package/rate-limit/postgres/drizzle/meta/_journal.json +13 -0
  218. package/{queue → rate-limit}/postgres/drizzle.config.js +1 -1
  219. package/rate-limit/postgres/index.d.ts +4 -0
  220. package/rate-limit/postgres/index.js +4 -0
  221. package/rate-limit/postgres/module.d.ts +12 -0
  222. package/rate-limit/postgres/module.js +28 -0
  223. package/rate-limit/postgres/postgres-rate-limiter.d.ts +9 -0
  224. package/rate-limit/postgres/postgres-rate-limiter.js +56 -0
  225. package/rate-limit/postgres/rate-limit.model.d.ts +8 -0
  226. package/rate-limit/postgres/rate-limit.model.js +35 -0
  227. package/rate-limit/postgres/rate-limiter.provider.d.ts +6 -0
  228. package/rate-limit/postgres/rate-limiter.provider.js +21 -0
  229. package/rate-limit/postgres/schemas.d.ts +3 -0
  230. package/rate-limit/postgres/schemas.js +4 -0
  231. package/rate-limit/provider.d.ts +9 -0
  232. package/rate-limit/provider.js +2 -0
  233. package/rate-limit/rate-limiter.d.ts +35 -0
  234. package/rate-limit/rate-limiter.js +3 -0
  235. package/rate-limit/tests/postgres-rate-limiter.test.d.ts +1 -0
  236. package/rate-limit/tests/postgres-rate-limiter.test.js +92 -0
  237. package/signals/implementation/configure.d.ts +3 -0
  238. package/signals/implementation/configure.js +3 -0
  239. package/sse/data-stream-source.d.ts +1 -1
  240. package/sse/data-stream-source.js +6 -6
  241. package/task-queue/enqueue-batch.d.ts +17 -0
  242. package/task-queue/enqueue-batch.js +24 -0
  243. package/{queue → task-queue}/index.d.ts +1 -1
  244. package/{queue → task-queue}/index.js +1 -1
  245. package/task-queue/postgres/drizzle/0000_thin_black_panther.sql +74 -0
  246. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +592 -0
  247. package/task-queue/postgres/drizzle/meta/_journal.json +13 -0
  248. package/task-queue/postgres/drizzle.config.d.ts +2 -0
  249. package/task-queue/postgres/drizzle.config.js +11 -0
  250. package/task-queue/postgres/index.d.ts +4 -0
  251. package/task-queue/postgres/index.js +4 -0
  252. package/task-queue/postgres/module.d.ts +12 -0
  253. package/task-queue/postgres/module.js +28 -0
  254. package/task-queue/postgres/schemas.d.ts +16 -0
  255. package/task-queue/postgres/schemas.js +8 -0
  256. package/task-queue/postgres/task-queue.d.ts +83 -0
  257. package/task-queue/postgres/task-queue.js +1054 -0
  258. package/task-queue/postgres/task-queue.provider.d.ts +7 -0
  259. package/{queue/postgres/queue.provider.js → task-queue/postgres/task-queue.provider.js} +8 -8
  260. package/task-queue/postgres/task.model.d.ts +39 -0
  261. package/task-queue/postgres/task.model.js +178 -0
  262. package/{queue → task-queue}/provider.d.ts +3 -3
  263. package/task-queue/provider.js +2 -0
  264. package/{queue → task-queue}/task-context.d.ts +7 -7
  265. package/{queue → task-queue}/task-context.js +8 -8
  266. package/{queue/queue.d.ts → task-queue/task-queue.d.ts} +128 -59
  267. package/task-queue/task-queue.js +200 -0
  268. package/task-queue/tests/complex.test.d.ts +1 -0
  269. package/task-queue/tests/complex.test.js +299 -0
  270. package/task-queue/tests/dependencies.test.d.ts +1 -0
  271. package/task-queue/tests/dependencies.test.js +174 -0
  272. package/task-queue/tests/queue.test.d.ts +1 -0
  273. package/task-queue/tests/queue.test.js +334 -0
  274. package/task-queue/tests/worker.test.d.ts +1 -0
  275. package/task-queue/tests/worker.test.js +163 -0
  276. package/test1.js +1 -1
  277. package/test4.js +2 -2
  278. package/unit-test/index.d.ts +1 -0
  279. package/unit-test/index.js +1 -0
  280. package/unit-test/integration-setup.d.ts +55 -0
  281. package/unit-test/integration-setup.js +182 -0
  282. package/utils/patterns.d.ts +3 -0
  283. package/utils/patterns.js +6 -1
  284. package/audit/drizzle/0001_previous_network.sql +0 -2
  285. package/audit/drizzle/meta/0001_snapshot.json +0 -195
  286. package/queue/enqueue-batch.d.ts +0 -17
  287. package/queue/enqueue-batch.js +0 -18
  288. package/queue/postgres/drizzle/0000_zippy_moondragon.sql +0 -11
  289. package/queue/postgres/drizzle/0001_certain_wild_pack.sql +0 -2
  290. package/queue/postgres/drizzle/0002_dear_meggan.sql +0 -2
  291. package/queue/postgres/drizzle/0003_tricky_venom.sql +0 -30
  292. package/queue/postgres/drizzle/meta/0001_snapshot.json +0 -103
  293. package/queue/postgres/drizzle/meta/0002_snapshot.json +0 -90
  294. package/queue/postgres/drizzle/meta/0003_snapshot.json +0 -288
  295. package/queue/postgres/drizzle/meta/_journal.json +0 -34
  296. package/queue/postgres/index.d.ts +0 -4
  297. package/queue/postgres/index.js +0 -4
  298. package/queue/postgres/module.d.ts +0 -9
  299. package/queue/postgres/module.js +0 -29
  300. package/queue/postgres/queue.d.ts +0 -60
  301. package/queue/postgres/queue.js +0 -681
  302. package/queue/postgres/queue.provider.d.ts +0 -7
  303. package/queue/postgres/schemas.d.ts +0 -14
  304. package/queue/postgres/schemas.js +0 -6
  305. package/queue/postgres/task.model.d.ts +0 -24
  306. package/queue/postgres/task.model.js +0 -115
  307. package/queue/provider.js +0 -2
  308. package/queue/queue.js +0 -131
  309. package/queue/tests/queue.test.js +0 -623
  310. package/test3.d.ts +0 -1
  311. package/test3.js +0 -47
  312. /package/{queue/tests/queue.test.d.ts → api/server/tests/csrf.middleware.test.d.ts} +0 -0
  313. /package/circuit-breaker/postgres/drizzle/{0000_hard_shocker.sql → 0000_cooing_korath.sql} +0 -0
  314. /package/{queue → rate-limit}/postgres/drizzle.config.d.ts +0 -0
@@ -4,58 +4,91 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { and, asc, count, desc, eq, inArray, isNull as isSqlNull, isSQLWrapper, lte, or, SQL, sql } from 'drizzle-orm';
7
+ import { and, asc, count, desc, eq, getTableName, inArray, isNull as isSqlNull, isSQLWrapper, lte, or, SQL, sql } from 'drizzle-orm';
8
8
  import { match, P } from 'ts-pattern';
9
9
  import { CancellationSignal } from '../../cancellation/token.js';
10
10
  import { NotFoundError } from '../../errors/not-found.error.js';
11
11
  import { Singleton } from '../../injector/decorators.js';
12
12
  import { inject, injectArgument } from '../../injector/inject.js';
13
13
  import { afterResolve, resolveArgumentType } from '../../injector/interfaces.js';
14
+ import { reflectionRegistry } from '../../reflection/index.js';
14
15
  import { distinct, toArray } from '../../utils/array/array.js';
15
16
  import { mapAsync } from '../../utils/async-iterable-helpers/map.js';
16
17
  import { toArrayAsync } from '../../utils/async-iterable-helpers/to-array.js';
17
18
  import { importSymmetricKey } from '../../utils/cryptography.js';
18
- import { assignDeep, fromEntries, objectEntries } from '../../utils/object/object.js';
19
+ import { assignDeep, fromEntries, objectEntries, objectKeys } from '../../utils/object/object.js';
19
20
  import { toSnakeCase } from '../../utils/string/index.js';
20
21
  import { cancelableTimeout } from '../../utils/timing.js';
21
22
  import { tryIgnoreAsync } from '../../utils/try-ignore.js';
22
- import { assertDefined, assertDefinedPass, isArray, isBoolean, isDefined, isFunction, isInstanceOf, isString, isUndefined } from '../../utils/type-guards.js';
23
+ import { assertDefined, assertDefinedPass, isArray, isBoolean, isDefined, isFunction, isInstanceOf, isNullOrUndefined, isString, isUndefined } from '../../utils/type-guards.js';
23
24
  import { typeExtends } from '../../utils/type/index.js';
24
25
  import { millisecondsPerSecond } from '../../utils/units.js';
25
26
  import { Entity } from '../entity.js';
26
27
  import { distance, isSimilar, isStrictWordSimilar, isWordSimilar, TRANSACTION_TIMESTAMP, tsHeadline, tsRankCd } from '../sqls/index.js';
27
- import { getColumnDefinitions, getColumnDefinitionsMap, getDrizzleTableFromType } from './drizzle/schema-converter.js';
28
+ import { getInheritanceMetadata, isChildEntity } from '../utils.js';
29
+ import { getColumnDefinitions, getColumnDefinitionsMap, getDrizzleTableFromType, getTableColumnDefinitions, isTableOwning } from './drizzle/schema-converter.js';
28
30
  import { convertQuery, getTsQuery, getTsVector, resolveTargetColumn } from './query-converter.js';
31
+ import { EntityRepositoryConfig } from './repository-config.js';
29
32
  import { ENCRYPTION_SECRET } from './tokens.js';
30
- import { getTransactionalContextData, injectTransactional, injectTransactionalAsync, isInTransactionalContext, Transactional } from './transactional.js';
33
+ import { injectTransactional, injectTransactionalAsync, isInTransactionalContext, Transactional, tryGetTransactionalContextData } from './transactional.js';
31
34
  const searchScoreColumn = '__tsl_score';
32
35
  const searchDistanceColumn = '__tsl_distance';
33
36
  const searchHighlightColumn = '__tsl_highlight';
34
37
  const searchHighlightPositionsColumn = '__tsl_highlight_positions';
35
38
  export const repositoryType = Symbol('repositoryType');
36
- /**
37
- * Configuration class for EntityRepository.
38
- * Specifies the database schema to be used.
39
- */
40
- export class EntityRepositoryConfig {
41
- /** The name of the database schema. */
42
- schema;
43
- }
44
39
  const entityTypeToken = Symbol('EntityType');
45
40
  let EntityRepository = class EntityRepository extends Transactional {
46
- #context = isInTransactionalContext() ? getTransactionalContextData(this) : {};
41
+ #context = (isInTransactionalContext() ? tryGetTransactionalContextData(this) : undefined) ?? {};
47
42
  #encryptionSecret = isInTransactionalContext() ? this.#context.encryptionSecret : inject(ENCRYPTION_SECRET, undefined, { optional: true });
48
43
  #cancellationSignal = isInTransactionalContext() ? undefined : inject(CancellationSignal);
49
44
  #transformContext = this.#context.transformContext;
50
- type = this.#context.type ?? injectArgument(this, { optional: true }) ?? assertDefinedPass(this.constructor[entityTypeToken], 'Missing entity type.');
45
+ type = assertDefinedPass(this.#context.type ?? this.constructor[entityTypeToken] ?? injectArgument(this, { optional: true }), 'Missing entity type.');
51
46
  typeName = this.type.entityName ?? this.type.name;
52
- #table = this.#context.table ?? getDrizzleTableFromType(this.type, inject(EntityRepositoryConfig, undefined, { optional: true })?.schema);
53
- #tableWithMetadata = this.#table;
47
+ #schema = inject(EntityRepositoryConfig, undefined, { optional: true })?.schema;
48
+ #table = this.#context.table ?? getDrizzleTableFromType(this.type, this.#schema);
49
+ #tableColumnDefinitions = getTableColumnDefinitions(this.#table);
50
+ #isChild = isChildEntity(this.type);
51
+ #tablesChain = this.getTablesChain(this.type);
52
+ #baseTableInfo = this.#tablesChain[0];
53
+ #baseTable = this.#baseTableInfo.table;
54
+ #baseTableWithMetadata = this.#baseTable;
54
55
  #columnDefinitions = this.#context.columnDefinitions ?? getColumnDefinitions(this.#table);
55
56
  #columnDefinitionsMap = this.#context.columnDefinitionsMap ?? getColumnDefinitionsMap(this.#table);
56
- #upsertManyExcludedMapping = fromEntries(this.#columnDefinitions
57
+ #joinedTables = this.#tablesChain.filter((info) => info.table != this.#table).map((info) => info.table);
58
+ #inheritanceMetadata = getInheritanceMetadata(this.type);
59
+ #subclasses = this.#inheritanceMetadata?.subclasses ?? [];
60
+ #subclassTablesInfo = this.#subclasses.map((subclass) => {
61
+ const table = getDrizzleTableFromType(subclass, this.#schema);
62
+ const tablesChain = this.getTablesChain(subclass);
63
+ return {
64
+ type: subclass,
65
+ table,
66
+ tablesChain,
67
+ columnDefinitions: getColumnDefinitions(table),
68
+ discriminatorValue: reflectionRegistry.getMetadata(subclass)?.data.tryGet('orm')?.childEntity?.discriminatorValue, // eslint-disable-line @typescript-eslint/no-non-null-asserted-optional-chain
69
+ };
70
+ });
71
+ #discriminatorInfo = (() => {
72
+ if (!this.#isChild) {
73
+ return undefined;
74
+ }
75
+ const inheritanceMetadata = getInheritanceMetadata(this.#baseTableInfo.type);
76
+ const childEntityMetadata = reflectionRegistry.getMetadata(this.type)?.data.tryGet('orm')?.childEntity;
77
+ if (isDefined(inheritanceMetadata) && isDefined(childEntityMetadata) && isString(inheritanceMetadata.discriminatorColumn)) {
78
+ return {
79
+ columnName: inheritanceMetadata.discriminatorColumn,
80
+ value: childEntityMetadata.discriminatorValue,
81
+ };
82
+ }
83
+ return undefined;
84
+ })();
85
+ #defaultSelection = fromEntries(this.#columnDefinitions.map((column) => [column.name, this.resolveTargetColumn(column)]));
86
+ #upsertManyExcludedMapping = fromEntries(this.#tableColumnDefinitions
57
87
  .filter((column) => column.name != this.#table.id.name)
58
88
  .map((column) => [column.name, sql `excluded.${sql.identifier(this.getColumn(column).name)}`]));
89
+ #expirationColumns = this.#columnDefinitions.filter((column) => isDefined(column.reflectionData?.expirationField));
90
+ #softExpirationColumns = this.#expirationColumns.filter((column) => column.reflectionData.expirationField.mode == 'soft');
91
+ #hardExpirationColumns = this.#expirationColumns.filter((column) => column.reflectionData.expirationField.mode == 'hard');
59
92
  hasMetadata = typeExtends(this.type, Entity);
60
93
  /**
61
94
  * Gets the Drizzle table definition for the entity type.
@@ -68,23 +101,30 @@ let EntityRepository = class EntityRepository extends Transactional {
68
101
  void this.expirationLoop();
69
102
  }
70
103
  }
104
+ /**
105
+ * Processes expired entities (soft or hard delete) based on metadata.
106
+ * Exposed primarily for testing, but can be used in other scenarios as needed.
107
+ */
108
+ async processExpirations() {
109
+ if (this.#expirationColumns.length == 0) {
110
+ return;
111
+ }
112
+ const softDeletionQuery = or(...this.#softExpirationColumns.map((column) => lte(sql `${this.resolveTargetColumn(column)} + INTERVAL '${sql.raw(String(column.reflectionData.expirationField.after))} ms'`, TRANSACTION_TIMESTAMP)));
113
+ const hardDeletionQuery = or(...this.#hardExpirationColumns.map((column) => lte(sql `${this.resolveTargetColumn(column)} + INTERVAL '${sql.raw(String(column.reflectionData.expirationField.after))} ms'`, TRANSACTION_TIMESTAMP)));
114
+ if (isDefined(softDeletionQuery)) {
115
+ await tryIgnoreAsync(async () => await this.deleteManyByQuery(softDeletionQuery));
116
+ }
117
+ if (isDefined(hardDeletionQuery)) {
118
+ await tryIgnoreAsync(async () => await this.hardDeleteManyByQuery(hardDeletionQuery));
119
+ }
120
+ }
71
121
  async expirationLoop() {
72
- const expirationColumns = this.#columnDefinitions.filter((column) => isDefined(column.reflectionData?.expirationField));
73
- const softExpirationColumns = expirationColumns.filter((column) => column.reflectionData.expirationField.mode == 'soft');
74
- const hardExpirationColumns = expirationColumns.filter((column) => column.reflectionData.expirationField.mode == 'hard');
75
- if ((softExpirationColumns.length + hardExpirationColumns.length) == 0) {
122
+ if (this.#expirationColumns.length == 0) {
76
123
  return;
77
124
  }
78
- const softDeletionQuery = or(...softExpirationColumns.map((column) => lte(sql `${this.resolveTargetColumn(column)} + INTERVAL '${sql.raw(String(column.reflectionData.expirationField.after))} ms'`, TRANSACTION_TIMESTAMP)));
79
- const hardDeletionQuery = or(...hardExpirationColumns.map((column) => lte(sql `${this.resolveTargetColumn(column)} + INTERVAL '${sql.raw(String(column.reflectionData.expirationField.after))} ms'`, TRANSACTION_TIMESTAMP)));
80
125
  assertDefined(this.#cancellationSignal);
81
126
  while (this.#cancellationSignal.isUnset) {
82
- if (isDefined(softDeletionQuery)) {
83
- await tryIgnoreAsync(async () => await this.deleteManyByQuery(softDeletionQuery));
84
- }
85
- if (isDefined(hardDeletionQuery)) {
86
- await tryIgnoreAsync(async () => await this.hardDeleteManyByQuery(hardDeletionQuery));
87
- }
127
+ await this.processExpirations();
88
128
  await cancelableTimeout(30 * millisecondsPerSecond, this.#cancellationSignal);
89
129
  }
90
130
  }
@@ -114,9 +154,9 @@ let EntityRepository = class EntityRepository extends Transactional {
114
154
  const rawScore = (scoreOption != false) ? tsRankCd(tsvector, tsquery, isBoolean(rank) ? undefined : rank) : undefined;
115
155
  const score = (isFunction(scoreOption) ? scoreOption(rawScore) : rawScore)?.as(searchScoreColumn);
116
156
  const vectorClause = sql `${tsvector} @@ ${tsquery}`;
117
- const selection = fromEntries(this.#columnDefinitions.map((column) => [column.name, this.resolveTargetColumn(column)]));
157
+ const extraSelection = {};
118
158
  if (isDefined(score)) {
119
- selection[searchScoreColumn] = score;
159
+ extraSelection[searchScoreColumn] = score;
120
160
  }
121
161
  if (isDefined(highlight)) {
122
162
  const { source, ...headlineOptions } = (isString(highlight) || isInstanceOf(highlight, SQL))
@@ -125,19 +165,14 @@ let EntityRepository = class EntityRepository extends Transactional {
125
165
  const document = match(source)
126
166
  .with(P.instanceOf(SQL), (s) => s)
127
167
  .otherwise((paths) => {
128
- const columns = this.getColumns(paths);
168
+ const columns = this.getColumns(toArray(paths));
129
169
  return sql.join(columns, sql ` || ' ' || `);
130
170
  });
131
- selection[searchHighlightColumn] = tsHeadline(languageSql, document, tsquery, headlineOptions).as(searchHighlightColumn);
171
+ extraSelection[searchHighlightColumn] = tsHeadline(languageSql, document, tsquery, headlineOptions).as(searchHighlightColumn);
132
172
  }
133
- const whereClause = isDefined(filter)
134
- ? and(this.convertQuery(filter), vectorClause)
135
- : vectorClause;
136
- let dbQuery = this
137
- .applySelect(this.session, selection, options.distinct)
138
- .from(this.#table)
139
- .where(whereClause)
140
- .$dynamic();
173
+ const whereClause = and(this.convertQuery(filter ?? {}), vectorClause);
174
+ let dbQuery = this.createBaseQuery(options, extraSelection);
175
+ dbQuery = dbQuery.where(whereClause);
141
176
  if (isDefined(options.offset)) {
142
177
  dbQuery = dbQuery.offset(options.offset);
143
178
  }
@@ -160,7 +195,7 @@ let EntityRepository = class EntityRepository extends Transactional {
160
195
  }
161
196
  dbQuery = dbQuery.orderBy(...orderByExpressions);
162
197
  const rows = await dbQuery;
163
- return await this._mapSearchResults(rows);
198
+ return await this._mapSearchResults(rows, undefined, { includeSubclasses: options.includeSubclasses });
164
199
  }
165
200
  async trigramSearch(options) {
166
201
  const { query: { $trigram: trigramOptions }, rank, filter } = options;
@@ -177,17 +212,13 @@ let EntityRepository = class EntityRepository extends Transactional {
177
212
  : isSimilar(query, searchExpression);
178
213
  const distanceColumn = distance(query, searchExpression).as(searchDistanceColumn);
179
214
  const score = sql `1 - ${distance(query, searchExpression)}`.as(searchScoreColumn);
180
- const selection = fromEntries(this.#columnDefinitions.map((column) => [column.name, this.resolveTargetColumn(column)]));
181
- selection[searchDistanceColumn] = distanceColumn;
182
- selection[searchScoreColumn] = score;
183
- const whereClause = isDefined(filter)
184
- ? and(this.convertQuery(filter), trigramClause)
185
- : trigramClause;
186
- let dbQuery = this
187
- .applySelect(this.session, selection, options.distinct)
188
- .from(this.#table)
189
- .where(whereClause)
190
- .$dynamic();
215
+ const extraSelection = {
216
+ [searchDistanceColumn]: distanceColumn,
217
+ [searchScoreColumn]: score,
218
+ };
219
+ const whereClause = and(this.convertQuery(filter ?? {}), trigramClause);
220
+ let dbQuery = this.createBaseQuery(options, extraSelection);
221
+ dbQuery = dbQuery.where(whereClause);
191
222
  if (isDefined(options.offset)) {
192
223
  dbQuery = dbQuery.offset(options.offset);
193
224
  }
@@ -204,18 +235,18 @@ let EntityRepository = class EntityRepository extends Transactional {
204
235
  }
205
236
  dbQuery = dbQuery.orderBy(...orderByExpressions);
206
237
  const rows = await dbQuery;
207
- return await this._mapSearchResults(rows, (rawDistance) => 1 - rawDistance);
238
+ return await this._mapSearchResults(rows, (rawDistance) => 1 - rawDistance, { includeSubclasses: options.includeSubclasses });
208
239
  }
209
240
  async paradeDbSearch(options) {
210
241
  const { query, score: scoreOption = true, highlight, filter } = options;
211
242
  const keyField = this.#table.id;
212
243
  const paradeNativeQuery = this.convertQuery(query);
213
- const selection = fromEntries(this.#columnDefinitions.map((column) => [column.name, this.resolveTargetColumn(column)]));
244
+ const extraSelection = {};
214
245
  let score;
215
246
  if (scoreOption) {
216
247
  const rawScore = sql `pdb.score(${keyField})`;
217
248
  score = (isFunction(scoreOption) ? scoreOption(rawScore) : rawScore).as(searchScoreColumn);
218
- selection[searchScoreColumn] = score;
249
+ extraSelection[searchScoreColumn] = score;
219
250
  }
220
251
  if (isDefined(highlight)) {
221
252
  const { source, includePositions, ...highlightOptionsAny } = (isString(highlight) || isInstanceOf(highlight, SQL))
@@ -229,19 +260,16 @@ let EntityRepository = class EntityRepository extends Transactional {
229
260
  .map(([key, value]) => sql `${sql.raw(toSnakeCase(key))} => ${sql `${value}`}`);
230
261
  const snippetParameters = sql.join([sourceSql, ...highlightParts], sql.raw(', '));
231
262
  const highlightSql = sql `pdb.${sql.raw(functionName)}(${snippetParameters})`;
232
- selection[searchHighlightColumn] = highlightSql.as(searchHighlightColumn);
263
+ extraSelection[searchHighlightColumn] = highlightSql.as(searchHighlightColumn);
233
264
  if (includePositions == true) {
234
- selection[searchHighlightPositionsColumn] = sql `array_to_json(pdb.snippet_positions(${sourceSql}))`.as(searchHighlightPositionsColumn);
265
+ extraSelection[searchHighlightPositionsColumn] = sql `array_to_json(pdb.snippet_positions(${sourceSql}))`.as(searchHighlightPositionsColumn);
235
266
  }
236
267
  }
237
268
  const whereClause = isDefined(filter)
238
269
  ? and(this.convertQuery(filter), paradeNativeQuery)
239
270
  : paradeNativeQuery;
240
- let dbQuery = this
241
- .applySelect(this.session, selection, options.distinct)
242
- .from(this.#table)
243
- .where(whereClause)
244
- .$dynamic();
271
+ let dbQuery = this.createBaseQuery(options, extraSelection);
272
+ dbQuery = dbQuery.where(whereClause);
245
273
  if (isDefined(options.offset)) {
246
274
  dbQuery = dbQuery.offset(options.offset);
247
275
  }
@@ -264,7 +292,7 @@ let EntityRepository = class EntityRepository extends Transactional {
264
292
  }
265
293
  dbQuery = dbQuery.orderBy(...orderByExpressions);
266
294
  const rows = await dbQuery;
267
- return await this._mapSearchResults(rows);
295
+ return await this._mapSearchResults(rows, undefined, { includeSubclasses: options.includeSubclasses });
268
296
  }
269
297
  /**
270
298
  * Performs a full-text search and returns entities ranked by relevance.
@@ -283,11 +311,12 @@ let EntityRepository = class EntityRepository extends Transactional {
283
311
  * Loads a single entity by its ID.
284
312
  * Throws `NotFoundError` if the entity is not found.
285
313
  * @param id The ID of the entity to load.
314
+ * @param options Optional loading options (e.g., withDeleted).
286
315
  * @returns A promise that resolves to the loaded entity.
287
316
  * @throws {NotFoundError} If the entity with the given ID is not found.
288
317
  */
289
- async load(id) {
290
- const entity = await this.tryLoad(id);
318
+ async load(id, options) {
319
+ const entity = await this.tryLoad(id, options);
291
320
  if (isUndefined(entity)) {
292
321
  throw new NotFoundError(`${this.typeName} ${id} not found.`);
293
322
  }
@@ -297,10 +326,11 @@ let EntityRepository = class EntityRepository extends Transactional {
297
326
  * Tries to load a single entity by its ID.
298
327
  * Returns `undefined` if the entity is not found.
299
328
  * @param id The ID of the entity to load.
329
+ * @param options Optional loading options (e.g., withDeleted).
300
330
  * @returns A promise that resolves to the loaded entity or `undefined` if not found.
301
331
  */
302
- async tryLoad(id) {
303
- return await this.tryLoadByQuery(eq(this.#table.id, id));
332
+ async tryLoad(id, options) {
333
+ return await this.tryLoadByQuery(eq(this.#table.id, id), options);
304
334
  }
305
335
  /**
306
336
  * Loads a single entity based on a query.
@@ -325,12 +355,11 @@ let EntityRepository = class EntityRepository extends Transactional {
325
355
  * @returns A promise that resolves to the loaded entity or `undefined` if not found.
326
356
  */
327
357
  async tryLoadByQuery(query, options) {
328
- const sqlQuery = this.convertQuery(query);
329
- let dbQuery = this.session.select()
330
- .from(this.#table)
358
+ const sqlQuery = this.convertQuery(query, options);
359
+ let dbQuery = this.createBaseQuery(options);
360
+ dbQuery = dbQuery
331
361
  .where(sqlQuery)
332
- .limit(1)
333
- .$dynamic();
362
+ .limit(1);
334
363
  if (isDefined(options?.offset)) {
335
364
  dbQuery = dbQuery.offset(options.offset);
336
365
  }
@@ -341,7 +370,7 @@ let EntityRepository = class EntityRepository extends Transactional {
341
370
  if (isUndefined(row)) {
342
371
  return undefined;
343
372
  }
344
- return await this.mapToEntity(row);
373
+ return await this.mapToEntity(row, { includeSubclasses: options?.includeSubclasses });
345
374
  }
346
375
  /**
347
376
  * Loads multiple entities by their IDs.
@@ -350,7 +379,7 @@ let EntityRepository = class EntityRepository extends Transactional {
350
379
  * @returns A promise that resolves to an array of loaded entities.
351
380
  */
352
381
  async loadMany(ids, options) {
353
- if (ids.length === 0) {
382
+ if (ids.length == 0) {
354
383
  return [];
355
384
  }
356
385
  return await this.loadManyByQuery(inArray(this.#table.id, ids), options);
@@ -372,11 +401,9 @@ let EntityRepository = class EntityRepository extends Transactional {
372
401
  * @returns A promise that resolves to an array of loaded entities.
373
402
  */
374
403
  async loadManyByQuery(query, options) {
375
- const sqlQuery = this.convertQuery(query);
376
- let dbQuery = this.applySelect(this.session, options?.distinct)
377
- .from(this.#table)
378
- .where(sqlQuery)
379
- .$dynamic();
404
+ const sqlQuery = this.convertQuery(query, options);
405
+ let dbQuery = this.createBaseQuery(options);
406
+ dbQuery = dbQuery.where(sqlQuery);
380
407
  if (isDefined(options?.offset)) {
381
408
  dbQuery = dbQuery.offset(options.offset);
382
409
  }
@@ -387,7 +414,7 @@ let EntityRepository = class EntityRepository extends Transactional {
387
414
  dbQuery = dbQuery.orderBy(...this.convertOrderBy(options.order));
388
415
  }
389
416
  const rows = await dbQuery;
390
- return await this.mapManyToEntity(rows);
417
+ return await this.mapManyToEntity(rows, { includeSubclasses: options?.includeSubclasses });
391
418
  }
392
419
  /**
393
420
  * Loads multiple entities based on a query and returns them as an async iterable cursor.
@@ -418,47 +445,56 @@ let EntityRepository = class EntityRepository extends Transactional {
418
445
  }
419
446
  /**
420
447
  * Counts the total number of entities of the repository's type.
448
+ * @param options Optional counting options (e.g., withDeleted).
421
449
  * @returns A promise that resolves to the total count.
422
450
  */
423
- async count() {
424
- const dbQuery = this.session
425
- .select({ count: count() })
426
- .from(this.#table);
427
- const [result] = await dbQuery;
428
- return assertDefinedPass(result).count;
451
+ async count(options) {
452
+ return await this.countByQuery({}, options);
429
453
  }
430
454
  /**
431
455
  * Counts the number of entities matching a query.
432
456
  * @param query The query to filter entities.
457
+ * @param options Optional counting options (e.g., withDeleted).
433
458
  * @returns A promise that resolves to the count of matching entities.
434
459
  */
435
- async countByQuery(query) {
436
- const sqlQuery = this.convertQuery(query);
437
- const dbQuery = this.session
460
+ async countByQuery(query, options) {
461
+ const sqlQuery = this.convertQuery(query, options);
462
+ let dbQuery = this.session
438
463
  .select({ count: count() })
439
464
  .from(this.#table)
440
- .where(sqlQuery);
465
+ .$dynamic();
466
+ dbQuery = this.applyJoins(dbQuery);
467
+ dbQuery = dbQuery.where(sqlQuery);
468
+ if (isDefined(options?.for)) {
469
+ const lock = isString(options.for) ? { mode: options.for } : options.for;
470
+ dbQuery = dbQuery.for(lock.mode, { skipLocked: lock.skipLocked, noWait: lock.noWait });
471
+ }
441
472
  const [result] = await dbQuery;
442
473
  return assertDefinedPass(result).count;
443
474
  }
444
475
  /**
445
476
  * Checks if an entity with the given ID exists.
446
477
  * @param id The ID of the entity to check.
478
+ * @param options Optional counting options (e.g., withDeleted).
447
479
  * @returns A promise that resolves to `true` if the entity exists, `false` otherwise.
448
480
  */
449
- async has(id) {
450
- return await this.hasByQuery(eq(this.#table.id, id));
481
+ async has(id, options) {
482
+ return await this.hasByQuery(eq(this.#table.id, id), options);
451
483
  }
452
484
  /**
453
485
  * Checks if any entity matches the given query.
454
486
  * @param query The query to filter entities.
487
+ * @param options Optional counting options (e.g., withDeleted).
455
488
  * @returns A promise that resolves to `true` if at least one entity matches the query, `false` otherwise.
456
489
  */
457
- async hasByQuery(query) {
458
- const sqlQuery = this.convertQuery(query);
459
- const result = await this.session
490
+ async hasByQuery(query, options) {
491
+ const sqlQuery = this.convertQuery(query, options);
492
+ let dbQuery = this.session
460
493
  .select({ value: sql `1` })
461
494
  .from(this.#table)
495
+ .$dynamic();
496
+ dbQuery = this.applyJoins(dbQuery);
497
+ const result = await dbQuery
462
498
  .where(sqlQuery)
463
499
  .limit(1);
464
500
  return result.length > 0;
@@ -470,7 +506,7 @@ let EntityRepository = class EntityRepository extends Transactional {
470
506
  */
471
507
  async hasAll(ids) {
472
508
  const uniqueIds = distinct(ids);
473
- if (uniqueIds.length === 0) {
509
+ if (uniqueIds.length == 0) {
474
510
  return false;
475
511
  }
476
512
  const count = await this.countByQuery(inArray(this.#table.id, uniqueIds));
@@ -493,18 +529,80 @@ let EntityRepository = class EntityRepository extends Transactional {
493
529
  }
494
530
  return await this.mapToEntity(row);
495
531
  }
532
+ async insertCTI(entity) {
533
+ return await this.transaction(async (transaction) => {
534
+ const transformContext = await this.getTransformContext();
535
+ const rows = [];
536
+ let generatedId;
537
+ for (const { table, columnDefinitions } of this.#tablesChain) {
538
+ let columns = await this._mapToTableInsertColumns(entity, transformContext, table, columnDefinitions);
539
+ if (isDefined(generatedId)) {
540
+ columns = { ...columns, id: generatedId };
541
+ }
542
+ const [row] = await transaction.pgTransaction
543
+ .insert(table)
544
+ .values(columns)
545
+ .returning();
546
+ if (isUndefined(generatedId)) {
547
+ generatedId = row.id;
548
+ }
549
+ rows.push(row);
550
+ }
551
+ // Merge rows for mapping
552
+ const mergedRow = fromEntries(rows.flatMap((row, index) => {
553
+ const tableName = getTableName(this.#tablesChain[index].table);
554
+ return [[tableName, row]];
555
+ }));
556
+ return await this.mapToEntity(mergedRow);
557
+ });
558
+ }
559
+ async insertManyCTI(entities) {
560
+ return await this.transaction(async (transaction) => {
561
+ const transformContext = await this.getTransformContext();
562
+ const results = {};
563
+ let generatedIds;
564
+ for (const { table, columnDefinitions } of this.#tablesChain) {
565
+ const values = await Promise.all(entities.map(async (entity, index) => {
566
+ const columns = await this._mapToTableInsertColumns(entity, transformContext, table, columnDefinitions);
567
+ if (isDefined(generatedIds)) {
568
+ return { ...columns, id: generatedIds[index] };
569
+ }
570
+ return columns;
571
+ }));
572
+ const rows = await transaction.pgTransaction
573
+ .insert(table)
574
+ .values(values)
575
+ .returning();
576
+ if (isUndefined(generatedIds)) {
577
+ generatedIds = rows.map((row) => row.id);
578
+ }
579
+ results[getTableName(table)] = rows;
580
+ }
581
+ // Merge rows by index
582
+ const mergedRows = entities.map((_, index) => {
583
+ return fromEntries(this.#tablesChain.map(({ table }) => {
584
+ const tableName = getTableName(table);
585
+ return [tableName, results[tableName][index]];
586
+ }));
587
+ });
588
+ return await this.mapManyToEntity(mergedRows);
589
+ });
590
+ }
496
591
  /**
497
592
  * Inserts a new entity into the database.
498
593
  * @param entity The entity to insert.
499
594
  * @returns A promise that resolves to the inserted entity.
500
595
  */
501
596
  async insert(entity) {
502
- const columns = await this.mapToInsertColumns(entity);
503
- const [row] = await this.session
504
- .insert(this.#table)
505
- .values(columns)
506
- .returning();
507
- return await this.mapToEntity(row);
597
+ if (!this.#isChild) {
598
+ const columns = await this.mapToInsertColumns(entity);
599
+ const [row] = await this.session
600
+ .insert(this.#table)
601
+ .values(columns)
602
+ .returning();
603
+ return await this.mapToEntity(row);
604
+ }
605
+ return await this.insertCTI(entity);
508
606
  }
509
607
  /**
510
608
  * Inserts multiple new entities into the database.
@@ -512,12 +610,15 @@ let EntityRepository = class EntityRepository extends Transactional {
512
610
  * @returns A promise that resolves to an array of the inserted entities.
513
611
  */
514
612
  async insertMany(entities) {
515
- if (entities.length === 0) {
613
+ if (entities.length == 0) {
516
614
  return [];
517
615
  }
518
- const columns = await this.mapManyToInsertColumns(entities);
519
- const rows = await this.session.insert(this.#table).values(columns).returning();
520
- return await this.mapManyToEntity(rows);
616
+ if (!this.#isChild) {
617
+ const columns = await this.mapManyToInsertColumns(entities);
618
+ const rows = await this.session.insert(this.#table).values(columns).returning();
619
+ return await this.mapManyToEntity(rows);
620
+ }
621
+ return await this.insertManyCTI(entities);
521
622
  }
522
623
  /**
523
624
  * Inserts an entity if it does not already exist based on the target columns.
@@ -545,7 +646,7 @@ let EntityRepository = class EntityRepository extends Transactional {
545
646
  * @returns A promise that resolves to the inserted or existing entities.
546
647
  */
547
648
  async insertManyIfNotExists(target, entities) {
548
- if (entities.length === 0) {
649
+ if (entities.length == 0) {
549
650
  return [];
550
651
  }
551
652
  const targetColumns = toArray(target).map((path) => this.getColumn(path));
@@ -559,99 +660,324 @@ let EntityRepository = class EntityRepository extends Transactional {
559
660
  }
560
661
  /**
561
662
  * Inserts an entity or updates it if a conflict occurs based on the target columns.
663
+ * Throws an error if the entity is not returned (e.g. because of a condition in `wheres`).
562
664
  * @param target The column(s) to use for conflict detection.
563
665
  * @param entity The entity to insert.
564
666
  * @param update Optional update to apply if a conflict occurs. Defaults to the inserted entity's values.
667
+ * @param wheres Optional conditions for the conflict target and the update.
565
668
  * @returns A promise that resolves to the inserted or updated entity.
566
669
  */
567
- async upsert(target, entity, update) {
568
- const targetColumns = toArray(target).map((path) => this.getColumn(path));
569
- const columns = await this.mapToInsertColumns(entity);
570
- const mappedUpdate = await this.mapUpdate(update ?? entity);
571
- const [row] = await this.session
572
- .insert(this.#table)
573
- .values(columns)
574
- .onConflictDoUpdate({
575
- target: targetColumns,
576
- set: mappedUpdate,
577
- })
578
- .returning();
579
- return await this.mapToEntity(row);
670
+ async upsert(target, entity, update, wheres) {
671
+ const result = await this.tryUpsert(target, entity, update, wheres);
672
+ if (isUndefined(result)) {
673
+ throw new Error(`${this.typeName} upsert failed to return a row.`);
674
+ }
675
+ return result;
676
+ }
677
+ async tryUpsertCTI(target, entity, update, wheres) {
678
+ return await this.transaction(async (transaction) => {
679
+ const transformContext = await this.getTransformContext();
680
+ const results = {};
681
+ let generatedId = entity.id;
682
+ for (const { table, columnDefinitions } of this.#tablesChain) {
683
+ const tableColumnNames = columnDefinitions.map((def) => def.name);
684
+ const tableColumnNamesSet = new Set(tableColumnNames);
685
+ const filteredTarget = isDefined(wheres?.target) ? this.filterQuery(wheres.target, tableColumnNamesSet) : undefined;
686
+ const targetQuery = isDefined(filteredTarget) ? this._convertQuery(filteredTarget, table) : undefined;
687
+ const filteredSet = isDefined(wheres?.set) ? this.filterQuery(wheres.set, tableColumnNamesSet) : undefined;
688
+ const setQuery = isDefined(filteredSet) ? this._convertQuery(filteredSet, table) : undefined;
689
+ const columns = await this._mapToTableInsertColumns(entity, transformContext, table, columnDefinitions);
690
+ const mappedUpdate = await this._mapToTableUpdate(update ?? entity, transformContext, table, columnDefinitions);
691
+ const targetPaths = toArray(target);
692
+ const isTargetInTable = targetPaths.every((path) => {
693
+ const def = this.#columnDefinitionsMap.get(path);
694
+ return isDefined(def) && tableColumnNames.includes(def.name);
695
+ });
696
+ const tableTarget = isTargetInTable
697
+ ? targetPaths.map((path) => resolveTargetColumn(path, table, this.#columnDefinitionsMap))
698
+ : [table.id];
699
+ const [row] = await transaction.pgTransaction
700
+ .insert(table)
701
+ .values({ ...columns, ...(isDefined(generatedId) ? { id: generatedId } : {}) })
702
+ .onConflictDoUpdate({
703
+ target: tableTarget,
704
+ set: mappedUpdate,
705
+ targetWhere: targetQuery,
706
+ setWhere: setQuery,
707
+ })
708
+ .returning();
709
+ if (isUndefined(row)) {
710
+ return undefined;
711
+ }
712
+ if (isUndefined(generatedId)) {
713
+ generatedId = row['id'];
714
+ }
715
+ results[getTableName(table)] = row;
716
+ }
717
+ return await this.mapToEntity(results);
718
+ });
719
+ }
720
+ /**
721
+ * Tries to insert an entity or update it if a conflict occurs based on the target columns.
722
+ * Returns `undefined` if the entity is not returned (e.g. because of a condition in `wheres`).
723
+ * @param target The column(s) to use for conflict detection.
724
+ * @param entity The entity to insert.
725
+ * @param update Optional update to apply if a conflict occurs. Defaults to the inserted entity's values.
726
+ * @param wheres Optional conditions for the conflict target and the update.
727
+ * @returns A promise that resolves to the inserted or updated entity or `undefined`.
728
+ */
729
+ async tryUpsert(target, entity, update, wheres) {
730
+ const targetQuery = isDefined(wheres?.target) ? this.convertQuery(wheres.target) : undefined;
731
+ const setQuery = isDefined(wheres?.set) ? this.convertQuery(wheres.set) : undefined;
732
+ if (!this.#isChild) {
733
+ const targetColumns = toArray(target).map((path) => this.getColumn(path));
734
+ const columns = await this.mapToInsertColumns(entity);
735
+ const mappedUpdate = await this.mapUpdate(update ?? entity);
736
+ const [row] = await this.session
737
+ .insert(this.#table)
738
+ .values(columns)
739
+ .onConflictDoUpdate({
740
+ target: targetColumns,
741
+ set: mappedUpdate,
742
+ targetWhere: targetQuery,
743
+ setWhere: setQuery,
744
+ })
745
+ .returning();
746
+ if (isUndefined(row)) {
747
+ return undefined;
748
+ }
749
+ return await this.mapToEntity(row);
750
+ }
751
+ return await this.tryUpsertCTI(target, entity, update, wheres);
752
+ }
753
+ async upsertManyCTI(target, entities, update, wheres) {
754
+ return await this.transaction(async (transaction) => {
755
+ const transformContext = await this.getTransformContext();
756
+ const results = {};
757
+ let generatedIds = entities.map((e) => e.id).filter(isDefined);
758
+ if (generatedIds.length != entities.length) {
759
+ generatedIds = undefined;
760
+ }
761
+ for (const { table, columnDefinitions } of this.#tablesChain) {
762
+ const tableColumnNames = columnDefinitions.map((def) => def.name);
763
+ const tableColumnNamesSet = new Set(tableColumnNames);
764
+ const filteredTarget = isDefined(wheres?.target) ? this.filterQuery(wheres.target, tableColumnNamesSet) : undefined;
765
+ const targetQuery = isDefined(filteredTarget) ? this._convertQuery(filteredTarget, table) : undefined;
766
+ const filteredSet = isDefined(wheres?.set) ? this.filterQuery(wheres.set, tableColumnNamesSet) : undefined;
767
+ const setQuery = isDefined(filteredSet) ? this._convertQuery(filteredSet, table) : undefined;
768
+ const values = await Promise.all(entities.map(async (entity, index) => {
769
+ const columns = await this._mapToTableInsertColumns(entity, transformContext, table, columnDefinitions);
770
+ if (isDefined(generatedIds)) {
771
+ return { ...columns, id: generatedIds[index] };
772
+ }
773
+ return columns;
774
+ }));
775
+ const mappedUpdate = isDefined(update)
776
+ ? await this._mapToTableUpdate(update, transformContext, table, columnDefinitions)
777
+ : {
778
+ ...fromEntries(columnDefinitions.filter((column) => column.name != table.id.name).map((column) => [column.name, sql `excluded.${sql.identifier(resolveTargetColumn(column, table, this.#columnDefinitionsMap).name)}`])),
779
+ ...((table == this.#baseTable) ? this._getMetadataUpdate(update) : undefined),
780
+ };
781
+ const targetPaths = toArray(target);
782
+ const isTargetInTable = targetPaths.every((path) => {
783
+ const def = this.#columnDefinitionsMap.get(path);
784
+ return isDefined(def) && tableColumnNames.includes(def.name);
785
+ });
786
+ const tableTarget = isTargetInTable
787
+ ? targetPaths.map((path) => resolveTargetColumn(path, table, this.#columnDefinitionsMap))
788
+ : [table.id];
789
+ const rows = await transaction.pgTransaction
790
+ .insert(table)
791
+ .values(values)
792
+ .onConflictDoUpdate({
793
+ target: tableTarget,
794
+ set: mappedUpdate,
795
+ targetWhere: targetQuery,
796
+ setWhere: setQuery,
797
+ })
798
+ .returning();
799
+ if (isUndefined(generatedIds)) {
800
+ generatedIds = rows.map((row) => row.id);
801
+ }
802
+ results[getTableName(table)] = rows;
803
+ }
804
+ // Merge rows by index
805
+ // We must ensure that we only include indices that were returned in ALL tables (consistent results)
806
+ // For upsertMany with conditions, some rows might be skipped in some tables.
807
+ // However, if it's skipped in the first table, it won't have an ID for the subsequent ones if it was a new ID.
808
+ // This is quite complex for polymorphic upsertMany with conditions.
809
+ // Assuming for now that conditions are consistent or only on the base table.
810
+ const tableNames = this.#tablesChain.map(({ table }) => getTableName(table));
811
+ const firstTableName = tableNames[0];
812
+ // Use IDs from the first table to find matching rows in other tables
813
+ const firstTableRows = results[firstTableName];
814
+ const mergedRows = [];
815
+ for (const row of firstTableRows) {
816
+ const id = row['id'];
817
+ const mergedRow = { [firstTableName]: row };
818
+ let allTablesMatched = true;
819
+ for (let i = 1; i < tableNames.length; i++) {
820
+ const tableName = tableNames[i];
821
+ const matchingRow = results[tableName].find(r => r['id'] === id);
822
+ if (isDefined(matchingRow)) {
823
+ mergedRow[tableName] = matchingRow;
824
+ }
825
+ else {
826
+ allTablesMatched = false;
827
+ break;
828
+ }
829
+ }
830
+ if (allTablesMatched) {
831
+ mergedRows.push(mergedRow);
832
+ }
833
+ }
834
+ return await this.mapManyToEntity(mergedRows);
835
+ });
580
836
  }
581
837
  /**
582
838
  * Inserts multiple entities or updates them if a conflict occurs based on the target columns.
583
839
  * @param target The column(s) to use for conflict detection.
584
840
  * @param entities An array of entities to insert.
585
841
  * @param update Optional update to apply if a conflict occurs. Defaults to the inserted entity's values.
842
+ * @param wheres Optional conditions for the conflict target and the update.
586
843
  * @returns A promise that resolves to an array of the inserted or updated entities.
587
844
  */
588
- async upsertMany(target, entities, update) {
589
- if (entities.length === 0) {
845
+ async upsertMany(target, entities, update, wheres) {
846
+ if (entities.length == 0) {
590
847
  return [];
591
848
  }
592
- const targetColumns = toArray(target).map((path) => this.getColumn(path));
593
- const columns = await this.mapManyToInsertColumns(entities);
594
- const mappedUpdate = isDefined(update)
595
- ? await this.mapUpdate(update)
596
- : {
597
- ...this.#upsertManyExcludedMapping,
598
- ...this._getMetadataUpdate(update),
599
- };
600
- const rows = await this.session
601
- .insert(this.#table)
602
- .values(columns)
603
- .onConflictDoUpdate({
604
- target: targetColumns,
605
- set: mappedUpdate,
606
- })
607
- .returning();
608
- return await this.mapManyToEntity(rows);
849
+ const targetQuery = isDefined(wheres?.target) ? this.convertQuery(wheres.target) : undefined;
850
+ const setQuery = isDefined(wheres?.set) ? this.convertQuery(wheres.set) : undefined;
851
+ if (!this.#isChild) {
852
+ const targetColumns = toArray(target).map((path) => this.getColumn(path));
853
+ const columns = await this.mapManyToInsertColumns(entities);
854
+ const mappedUpdate = isDefined(update)
855
+ ? await this.mapUpdate(update)
856
+ : {
857
+ ...this.#upsertManyExcludedMapping,
858
+ ...this._getMetadataUpdate(update),
859
+ };
860
+ const rows = await this.session
861
+ .insert(this.#table)
862
+ .values(columns)
863
+ .onConflictDoUpdate({
864
+ target: targetColumns,
865
+ set: mappedUpdate,
866
+ targetWhere: targetQuery,
867
+ setWhere: setQuery,
868
+ })
869
+ .returning();
870
+ return await this.mapManyToEntity(rows);
871
+ }
872
+ return await this.upsertManyCTI(target, entities, update, wheres);
609
873
  }
610
874
  /**
611
875
  * Updates an entity by its ID.
612
876
  * Throws `NotFoundError` if the entity is not found.
613
877
  * @param id The ID of the entity to update.
614
878
  * @param update The update to apply to the entity.
879
+ * @param options Optional update options.
615
880
  * @returns A promise that resolves to the updated entity.
616
881
  * @throws {NotFoundError} If the entity with the given ID is not found.
617
882
  */
618
- async update(id, update) {
619
- const entity = await this.tryUpdate(id, update);
883
+ async update(id, update, options) {
884
+ const entity = await this.tryUpdate(id, update, options);
620
885
  if (isUndefined(entity)) {
621
886
  throw new NotFoundError(`${this.typeName} ${id} not found.`);
622
887
  }
623
888
  return entity;
624
889
  }
890
+ async tryUpdateCTI(id, update, options) {
891
+ return await this.transaction(async (transaction) => {
892
+ const transformContext = await this.getTransformContext();
893
+ const results = {};
894
+ for (const { table, columnDefinitions } of this.#tablesChain) {
895
+ const mappedUpdate = await this._mapToTableUpdate(update, transformContext, table, columnDefinitions);
896
+ const sqlQuery = (table == this.#baseTable) ? this.convertQuery(eq(table.id, id), options) : eq(table.id, id);
897
+ if (objectKeys(mappedUpdate).length > 0) {
898
+ const [row] = await transaction.pgTransaction
899
+ .update(table)
900
+ .set(mappedUpdate)
901
+ .where(sqlQuery)
902
+ .returning();
903
+ if (isUndefined(row)) {
904
+ return undefined;
905
+ }
906
+ results[getTableName(table)] = row;
907
+ }
908
+ else {
909
+ const [row] = await transaction.pgTransaction
910
+ .select()
911
+ .from(table)
912
+ .where(sqlQuery)
913
+ .limit(1);
914
+ if (isUndefined(row)) {
915
+ return undefined;
916
+ }
917
+ results[getTableName(table)] = row;
918
+ }
919
+ }
920
+ const mergedRow = fromEntries(this.#tablesChain.map(({ table }) => {
921
+ const tableName = getTableName(table);
922
+ return [tableName, results[tableName]];
923
+ }));
924
+ return await this.mapToEntity(mergedRow);
925
+ });
926
+ }
927
+ async updateManyCTI(query, update) {
928
+ return await this.transaction(async (transaction) => {
929
+ const ids = await this.withTransaction(transaction).loadManyByQuery(query).then((entities) => entities.map((entity) => entity.id));
930
+ if (ids.length == 0) {
931
+ return [];
932
+ }
933
+ const transformContext = await this.getTransformContext();
934
+ for (const { table, columnDefinitions } of this.#tablesChain) {
935
+ const mappedUpdate = await this._mapToTableUpdate(update, transformContext, table, columnDefinitions);
936
+ if (objectEntries(mappedUpdate).length > 0) {
937
+ await transaction.pgTransaction
938
+ .update(table)
939
+ .set(mappedUpdate)
940
+ .where(inArray(table.id, ids));
941
+ }
942
+ }
943
+ return await this.loadMany(ids);
944
+ });
945
+ }
625
946
  /**
626
947
  * Tries to update an entity by its ID.
627
948
  * Returns `undefined` if the entity is not found.
628
949
  * @param id The ID of the entity to update.
629
950
  * @param update The update to apply to the entity.
951
+ * @param options Optional update options.
630
952
  * @returns A promise that resolves to the updated entity or `undefined` if not found.
631
953
  */
632
- async tryUpdate(id, update) {
633
- const sqlQuery = this.convertQuery(eq(this.#table.id, id));
634
- const mappedUpdate = await this.mapUpdate(update);
635
- const [row] = await this.session
636
- .update(this.#table)
637
- .set(mappedUpdate)
638
- .where(sqlQuery)
639
- .returning();
640
- if (isUndefined(row)) {
641
- return undefined;
954
+ async tryUpdate(id, update, options) {
955
+ if (!this.#isChild) {
956
+ const sqlQuery = this.convertQuery(eq(this.#table.id, id), options);
957
+ const mappedUpdate = await this.mapUpdate(update);
958
+ const [row] = await this.session
959
+ .update(this.#table)
960
+ .set(mappedUpdate)
961
+ .where(sqlQuery)
962
+ .returning();
963
+ if (isUndefined(row)) {
964
+ return undefined;
965
+ }
966
+ return await this.mapToEntity(row);
642
967
  }
643
- return await this.mapToEntity(row);
968
+ return await this.tryUpdateCTI(id, update, options);
644
969
  }
645
970
  /**
646
971
  * Updates a single entity matching a query.
647
972
  * Throws `NotFoundError` if no entity matches the query.
648
973
  * @param query The query to filter entities.
649
974
  * @param update The update to apply to the entity.
975
+ * @param options Optional update options.
650
976
  * @returns A promise that resolves to the updated entity.
651
977
  * @throws {NotFoundError} If no entity matches the query.
652
978
  */
653
- async updateByQuery(query, update) {
654
- const entity = await this.tryUpdateByQuery(query, update);
979
+ async updateByQuery(query, update, options) {
980
+ const entity = await this.tryUpdateByQuery(query, update, options);
655
981
  if (isUndefined(entity)) {
656
982
  throw new NotFoundError(`${this.typeName} not found.`);
657
983
  }
@@ -662,20 +988,30 @@ let EntityRepository = class EntityRepository extends Transactional {
662
988
  * Returns `undefined` if no entity matches the query.
663
989
  * @param query The query to filter entities.
664
990
  * @param update The update to apply to the entity.
991
+ * @param options Optional update options.
665
992
  * @returns A promise that resolves to the updated entity or `undefined` if not found.
666
993
  */
667
- async tryUpdateByQuery(query, update) {
668
- const mappedUpdate = await this.mapUpdate(update);
669
- const idQuery = this.getIdLimitQuery(query).for('update');
670
- const [row] = await this.session
671
- .update(this.#table)
672
- .set(mappedUpdate)
673
- .where(inArray(this.#table.id, idQuery))
674
- .returning();
675
- if (isUndefined(row)) {
676
- return undefined;
994
+ async tryUpdateByQuery(query, update, options) {
995
+ if (!this.#isChild) {
996
+ const mappedUpdate = await this.mapUpdate(update);
997
+ const idQuery = this.getIdLimitQuery(query, options).for('update');
998
+ const [row] = await this.session
999
+ .update(this.#table)
1000
+ .set(mappedUpdate)
1001
+ .where(inArray(this.#table.id, idQuery))
1002
+ .returning();
1003
+ if (isUndefined(row)) {
1004
+ return undefined;
1005
+ }
1006
+ return await this.mapToEntity(row);
677
1007
  }
678
- return await this.mapToEntity(row);
1008
+ return await this.transaction(async () => {
1009
+ const id = await this.tryLoadByQuery(query, options).then((entity) => entity?.id);
1010
+ if (isUndefined(id)) {
1011
+ return undefined;
1012
+ }
1013
+ return await this.tryUpdate(id, update, options);
1014
+ });
679
1015
  }
680
1016
  /**
681
1017
  * Updates multiple entities by their IDs.
@@ -684,7 +1020,7 @@ let EntityRepository = class EntityRepository extends Transactional {
684
1020
  * @returns A promise that resolves to an array of the updated entities.
685
1021
  */
686
1022
  async updateMany(ids, update) {
687
- if (ids.length === 0) {
1023
+ if (ids.length == 0) {
688
1024
  return [];
689
1025
  }
690
1026
  return await this.updateManyByQuery(inArray(this.#table.id, ids), update);
@@ -696,25 +1032,29 @@ let EntityRepository = class EntityRepository extends Transactional {
696
1032
  * @returns A promise that resolves to an array of the updated entities.
697
1033
  */
698
1034
  async updateManyByQuery(query, update) {
699
- const sqlQuery = this.convertQuery(query);
700
- const mappedUpdate = await this.mapUpdate(update);
701
- const rows = await this.session
702
- .update(this.#table)
703
- .set(mappedUpdate)
704
- .where(sqlQuery)
705
- .returning();
706
- return await this.mapManyToEntity(rows);
1035
+ if (!this.#isChild) {
1036
+ const sqlQuery = this.convertQuery(query);
1037
+ const mappedUpdate = await this.mapUpdate(update);
1038
+ const rows = await this.session
1039
+ .update(this.#table)
1040
+ .set(mappedUpdate)
1041
+ .where(sqlQuery)
1042
+ .returning();
1043
+ return await this.mapManyToEntity(rows);
1044
+ }
1045
+ return await this.updateManyCTI(query, update);
707
1046
  }
708
1047
  /**
709
1048
  * Deletes an entity by its ID (soft delete if metadata is available).
710
1049
  * Throws `NotFoundError` if the entity is not found.
711
1050
  * @param id The ID of the entity to delete.
1051
+ * @param options Optional delete options.
712
1052
  * @param metadataUpdate Optional metadata update to apply during soft delete.
713
1053
  * @returns A promise that resolves to the deleted entity.
714
1054
  * @throws {NotFoundError} If the entity with the given ID is not found.
715
1055
  */
716
- async delete(id, metadataUpdate) {
717
- const entity = await this.tryDelete(id, metadataUpdate);
1056
+ async delete(id, options, metadataUpdate) {
1057
+ const entity = await this.tryDelete(id, options, metadataUpdate);
718
1058
  if (isUndefined(entity)) {
719
1059
  throw new NotFoundError(`${this.typeName} ${id} not found.`);
720
1060
  }
@@ -724,16 +1064,17 @@ let EntityRepository = class EntityRepository extends Transactional {
724
1064
  * Tries to delete an entity by its ID (soft delete if metadata is available).
725
1065
  * Returns `undefined` if the entity is not found.
726
1066
  * @param id The ID of the entity to delete.
1067
+ * @param options Optional delete options.
727
1068
  * @param metadataUpdate Optional metadata update to apply during soft delete.
728
1069
  * @returns A promise that resolves to the deleted entity or `undefined` if not found.
729
1070
  */
730
- async tryDelete(id, metadataUpdate) {
1071
+ async tryDelete(id, options, metadataUpdate) {
731
1072
  if (!this.hasMetadata) {
732
- return await this.tryHardDelete(id);
1073
+ return await this.tryHardDelete(id, options);
733
1074
  }
734
- const sqlQuery = and(isSqlNull(this.#tableWithMetadata.deleteTimestamp), this.convertQuery(eq(this.#table.id, id)));
1075
+ const sqlQuery = and(isSqlNull(this.#baseTableWithMetadata.deleteTimestamp), this.convertQuery(eq(this.#baseTable.id, id), options));
735
1076
  const [row] = await this.session
736
- .update(this.#tableWithMetadata)
1077
+ .update(this.#baseTableWithMetadata)
737
1078
  .set({
738
1079
  deleteTimestamp: TRANSACTION_TIMESTAMP,
739
1080
  attributes: this.getAttributesUpdate(metadataUpdate?.attributes),
@@ -749,12 +1090,13 @@ let EntityRepository = class EntityRepository extends Transactional {
749
1090
  * Deletes a single entity matching a query (soft delete if metadata is available).
750
1091
  * Throws `NotFoundError` if no entity matches the query.
751
1092
  * @param query The query to filter entities.
1093
+ * @param options Optional delete options.
752
1094
  * @param metadataUpdate Optional metadata update to apply during soft delete.
753
1095
  * @returns A promise that resolves to the deleted entity.
754
1096
  * @throws {NotFoundError} If no entity matches the query.
755
1097
  */
756
- async deleteByQuery(query, metadataUpdate) {
757
- const entity = await this.tryDeleteByQuery(query, metadataUpdate);
1098
+ async deleteByQuery(query, options, metadataUpdate) {
1099
+ const entity = await this.tryDeleteByQuery(query, options, metadataUpdate);
758
1100
  if (isUndefined(entity)) {
759
1101
  throw new NotFoundError(`${this.typeName} not found.`);
760
1102
  }
@@ -764,17 +1106,18 @@ let EntityRepository = class EntityRepository extends Transactional {
764
1106
  * Tries to delete a single entity matching a query (soft delete if metadata is available).
765
1107
  * Returns `undefined` if no entity matches the query.
766
1108
  * @param query The query to filter entities.
1109
+ * @param options Optional delete options.
767
1110
  * @param metadataUpdate Optional metadata update to apply during soft delete.
768
1111
  * @returns A promise that resolves to the deleted entity or `undefined` if not found.
769
1112
  */
770
- async tryDeleteByQuery(query, metadataUpdate) {
1113
+ async tryDeleteByQuery(query, options, metadataUpdate) {
771
1114
  if (!this.hasMetadata) {
772
- return await this.tryHardDeleteByQuery(query);
1115
+ return await this.tryHardDeleteByQuery(query, options);
773
1116
  }
774
- const idQuery = this.getIdLimitQuery(query).for('update');
775
- const sqlQuery = and(isSqlNull(this.#tableWithMetadata.deleteTimestamp), inArray(this.#table.id, idQuery));
1117
+ const idQuery = this.getIdLimitQuery(query, options).for('update');
1118
+ const sqlQuery = and(isSqlNull(this.#baseTableWithMetadata.deleteTimestamp), inArray(this.#baseTable.id, idQuery));
776
1119
  const [row] = await this.session
777
- .update(this.#tableWithMetadata)
1120
+ .update(this.#baseTableWithMetadata)
778
1121
  .set({
779
1122
  deleteTimestamp: TRANSACTION_TIMESTAMP,
780
1123
  attributes: this.getAttributesUpdate(metadataUpdate?.attributes),
@@ -789,28 +1132,30 @@ let EntityRepository = class EntityRepository extends Transactional {
789
1132
  /**
790
1133
  * Deletes multiple entities by their IDs (soft delete if metadata is available).
791
1134
  * @param ids An array of entity IDs to delete.
1135
+ * @param options Optional delete options.
792
1136
  * @param metadataUpdate Optional metadata update to apply during soft delete.
793
1137
  * @returns A promise that resolves to an array of the deleted entities.
794
1138
  */
795
- async deleteMany(ids, metadataUpdate) {
796
- if (ids.length === 0) {
1139
+ async deleteMany(ids, options, metadataUpdate) {
1140
+ if (ids.length == 0) {
797
1141
  return [];
798
1142
  }
799
- return await this.deleteManyByQuery(inArray(this.#table.id, ids), metadataUpdate);
1143
+ return await this.deleteManyByQuery(inArray(this.#table.id, ids), options, metadataUpdate);
800
1144
  }
801
1145
  /**
802
1146
  * Deletes multiple entities matching a query (soft delete if metadata is available). Already deleted entities are ignored.
803
1147
  * @param query The query to filter entities.
1148
+ * @param options Optional delete options.
804
1149
  * @param metadataUpdate Optional metadata update to apply during soft delete.
805
1150
  * @returns A promise that resolves to an array of the deleted entities.
806
1151
  */
807
- async deleteManyByQuery(query, metadataUpdate) {
1152
+ async deleteManyByQuery(query, options, metadataUpdate) {
808
1153
  if (!this.hasMetadata) {
809
- return await this.hardDeleteManyByQuery(query);
1154
+ return await this.hardDeleteManyByQuery(query, options);
810
1155
  }
811
- const sqlQuery = and(isSqlNull(this.#tableWithMetadata.deleteTimestamp), this.convertQuery(query));
1156
+ const sqlQuery = and(isSqlNull(this.#baseTableWithMetadata.deleteTimestamp), this.convertQuery(query, options));
812
1157
  const rows = await this.session
813
- .update(this.#tableWithMetadata)
1158
+ .update(this.#baseTableWithMetadata)
814
1159
  .set({
815
1160
  deleteTimestamp: TRANSACTION_TIMESTAMP,
816
1161
  attributes: this.getAttributesUpdate(metadataUpdate?.attributes),
@@ -823,11 +1168,12 @@ let EntityRepository = class EntityRepository extends Transactional {
823
1168
  * Hard deletes an entity by its ID (removes from the database).
824
1169
  * Throws `NotFoundError` if the entity is not found.
825
1170
  * @param id The ID of the entity to hard delete.
1171
+ * @param options Optional delete options.
826
1172
  * @returns A promise that resolves to the hard deleted entity.
827
1173
  * @throws {NotFoundError} If the entity with the given ID is not found.
828
1174
  */
829
- async hardDelete(id) {
830
- const result = await this.tryHardDelete(id);
1175
+ async hardDelete(id, options) {
1176
+ const result = await this.tryHardDelete(id, options);
831
1177
  if (!result) {
832
1178
  throw new NotFoundError(`${this.typeName} ${id} not found.`);
833
1179
  }
@@ -837,12 +1183,13 @@ let EntityRepository = class EntityRepository extends Transactional {
837
1183
  * Tries to hard delete an entity by its ID (removes from the database).
838
1184
  * Returns `undefined` if the entity is not found.
839
1185
  * @param id The ID of the entity to hard delete.
1186
+ * @param options Optional delete options.
840
1187
  * @returns A promise that resolves to the hard deleted entity or `undefined` if not found.
841
1188
  */
842
- async tryHardDelete(id) {
843
- const sqlQuery = this.convertQuery(eq(this.#table.id, id));
1189
+ async tryHardDelete(id, options) {
1190
+ const sqlQuery = this.convertQuery(eq(this.#baseTable.id, id), { withDeleted: true, ...options });
844
1191
  const [row] = await this.session
845
- .delete(this.#table)
1192
+ .delete(this.#baseTable)
846
1193
  .where(sqlQuery)
847
1194
  .returning();
848
1195
  if (isUndefined(row)) {
@@ -854,11 +1201,12 @@ let EntityRepository = class EntityRepository extends Transactional {
854
1201
  * Hard deletes a single entity matching a query (removes from the database).
855
1202
  * Throws `NotFoundError` if no entity matches the query.
856
1203
  * @param query The query to filter entities.
1204
+ * @param options Optional delete options.
857
1205
  * @returns A promise that resolves to the hard deleted entity.
858
1206
  * @throws {NotFoundError} If no entity matches the query.
859
1207
  */
860
- async hardDeleteByQuery(query) {
861
- const result = await this.tryHardDeleteByQuery(query);
1208
+ async hardDeleteByQuery(query, options) {
1209
+ const result = await this.tryHardDeleteByQuery(query, options);
862
1210
  if (!result) {
863
1211
  throw new NotFoundError(`${this.typeName} not found.`);
864
1212
  }
@@ -868,13 +1216,14 @@ let EntityRepository = class EntityRepository extends Transactional {
868
1216
  * Tries to hard delete a single entity matching a query (removes from the database).
869
1217
  * Returns `undefined` if no entity matches the query.
870
1218
  * @param query The query to filter entities.
1219
+ * @param options Optional delete options.
871
1220
  * @returns A promise that resolves to the hard deleted entity or `undefined` if not found.
872
1221
  */
873
- async tryHardDeleteByQuery(query) {
874
- const idQuery = this.getIdLimitQuery(query).for('update');
875
- const sqlQuery = inArray(this.#table.id, idQuery);
1222
+ async tryHardDeleteByQuery(query, options) {
1223
+ const idQuery = this.getIdLimitQuery(query, { withDeleted: true, ...options });
1224
+ const sqlQuery = inArray(this.#baseTable.id, idQuery);
876
1225
  const [row] = await this.session
877
- .delete(this.#table)
1226
+ .delete(this.#baseTable)
878
1227
  .where(sqlQuery)
879
1228
  .returning();
880
1229
  if (isUndefined(row)) {
@@ -885,23 +1234,25 @@ let EntityRepository = class EntityRepository extends Transactional {
885
1234
  /**
886
1235
  * Hard deletes multiple entities by their IDs (removes from the database).
887
1236
  * @param ids An array of entity IDs to hard delete.
1237
+ * @param options Optional delete options.
888
1238
  * @returns A promise that resolves to an array of the hard deleted entities.
889
1239
  */
890
- async hardDeleteMany(ids) {
891
- if (ids.length === 0) {
1240
+ async hardDeleteMany(ids, options) {
1241
+ if (ids.length == 0) {
892
1242
  return [];
893
1243
  }
894
- return await this.hardDeleteManyByQuery(inArray(this.#table.id, ids));
1244
+ return await this.hardDeleteManyByQuery(inArray(this.#table.id, ids), options);
895
1245
  }
896
1246
  /**
897
1247
  * Hard deletes multiple entities matching a query (removes from the database).
898
1248
  * @param query The query to filter entities.
1249
+ * @param options Optional delete options.
899
1250
  * @returns A promise that resolves to an array of the hard deleted entities.
900
1251
  */
901
- async hardDeleteManyByQuery(query) {
902
- const sqlQuery = this.convertQuery(query);
1252
+ async hardDeleteManyByQuery(query, options) {
1253
+ const sqlQuery = this.convertQuery(query, { withDeleted: true, ...options });
903
1254
  const rows = await this.session
904
- .delete(this.#table)
1255
+ .delete(this.#baseTable)
905
1256
  .where(sqlQuery)
906
1257
  .returning();
907
1258
  return await this.mapManyToEntity(rows);
@@ -968,25 +1319,56 @@ let EntityRepository = class EntityRepository extends Transactional {
968
1319
  if (!this.hasMetadata || (options?.withDeleted == true)) {
969
1320
  return sql;
970
1321
  }
971
- return and(isSqlNull(this.#tableWithMetadata.deleteTimestamp), sql);
1322
+ return and(isSqlNull(this.#baseTableWithMetadata.deleteTimestamp), sql);
1323
+ }
1324
+ _convertQuery(query, table, options) {
1325
+ let sql = convertQuery(query, table, this.#columnDefinitionsMap, { ignorePgTable: true });
1326
+ if (!this.hasMetadata || (options?.withDeleted == true) || (table != this.#baseTable)) {
1327
+ return sql;
1328
+ }
1329
+ return and(isSqlNull(this.#baseTableWithMetadata.deleteTimestamp), sql);
1330
+ }
1331
+ filterQuery(query, tableColumnNames) {
1332
+ if ((query instanceof SQL) || isSQLWrapper(query)) {
1333
+ return query;
1334
+ }
1335
+ const filtered = {};
1336
+ let hasEntries = false;
1337
+ for (const [key, value] of objectEntries(query)) {
1338
+ const columnDef = this.#columnDefinitionsMap.get(key);
1339
+ if (isDefined(columnDef) && tableColumnNames.has(columnDef.name)) {
1340
+ filtered[key] = value;
1341
+ hasEntries = true;
1342
+ }
1343
+ else if (key == '$and' || key == '$or' || key == '$nor') {
1344
+ const subQueries = value.map((q) => this.filterQuery(q, tableColumnNames)).filter(isDefined);
1345
+ if (subQueries.length > 0) {
1346
+ filtered[key] = subQueries;
1347
+ hasEntries = true;
1348
+ }
1349
+ }
1350
+ }
1351
+ return hasEntries ? filtered : undefined;
972
1352
  }
973
1353
  /**
974
1354
  * Maps multiple database rows to an array of entities.
975
1355
  * @param columns An array of database rows.
1356
+ * @param options Optional options, including `includeSubclasses` for polymorphic loading.
976
1357
  * @returns A promise that resolves to an array of entities.
977
1358
  */
978
- async mapManyToEntity(columns) {
1359
+ async mapManyToEntity(columns, options) {
979
1360
  const transformContext = await this.getTransformContext();
980
- return await this._mapManyToEntity(columns, transformContext);
1361
+ return await this._mapManyToEntity(columns, transformContext, options?.includeSubclasses);
981
1362
  }
982
1363
  /**
983
1364
  * Maps a single database row to an entity.
984
1365
  * @param columns A database row.
1366
+ * @param options Optional options, including `includeSubclasses` for polymorphic loading.
985
1367
  * @returns A promise that resolves to an entity.
986
1368
  */
987
- async mapToEntity(columns) {
1369
+ async mapToEntity(columns, options) {
988
1370
  const transformContext = await this.getTransformContext();
989
- return await this._mapToEntity(columns, transformContext);
1371
+ return await this._mapToEntity(columns, transformContext, options?.includeSubclasses);
990
1372
  }
991
1373
  /**
992
1374
  * Maps multiple entity-like objects to database column values for insertion or update.
@@ -1004,7 +1386,7 @@ let EntityRepository = class EntityRepository extends Transactional {
1004
1386
  */
1005
1387
  async mapToColumns(obj) {
1006
1388
  const transformContext = await this.getTransformContext();
1007
- return await this._mapToColumns(obj, transformContext);
1389
+ return await this._mapToTableColumns(obj, transformContext);
1008
1390
  }
1009
1391
  /**
1010
1392
  * Maps multiple new entity objects to database column values for insertion.
@@ -1037,12 +1419,16 @@ let EntityRepository = class EntityRepository extends Transactional {
1037
1419
  * Gets a Drizzle select query for the ID of a single entity matching the provided query, limited to 1 result.
1038
1420
  * Useful for subqueries in update/delete operations targeting a single entity.
1039
1421
  * @param query The query to filter entities.
1422
+ * @param options Optional options (e.g., withDeleted).
1040
1423
  * @returns A Drizzle select query for the entity ID.
1041
1424
  */
1042
- getIdLimitQuery(query) {
1043
- const sqlQuery = this.convertQuery(query);
1044
- return this.session.select({ id: this.#table.id })
1425
+ getIdLimitQuery(query, options) {
1426
+ const sqlQuery = this.convertQuery(query, options);
1427
+ let dbQuery = this.session.select({ id: this.#table.id })
1045
1428
  .from(this.#table)
1429
+ .$dynamic();
1430
+ dbQuery = this.applyJoins(dbQuery);
1431
+ return dbQuery
1046
1432
  .where(sqlQuery)
1047
1433
  .limit(1);
1048
1434
  }
@@ -1054,7 +1440,7 @@ let EntityRepository = class EntityRepository extends Transactional {
1054
1440
  .with(false, () => applyTo.select(selection))
1055
1441
  .with(true, () => applyTo.selectDistinct(selection))
1056
1442
  .otherwise((targets) => {
1057
- const ons = targets.map((target) => resolveTargetColumn(target, this.#table, this.#columnDefinitionsMap));
1443
+ const ons = targets.map((target) => this.resolveTargetColumn(target));
1058
1444
  return applyTo.selectDistinctOn(ons, selection);
1059
1445
  });
1060
1446
  return selectBuilder;
@@ -1066,39 +1452,97 @@ let EntityRepository = class EntityRepository extends Transactional {
1066
1452
  if (attributes instanceof SQL) {
1067
1453
  return attributes;
1068
1454
  }
1069
- return sql `${this.#tableWithMetadata.attributes} || ${JSON.stringify(attributes)}::jsonb`;
1070
- }
1071
- async _mapManyToEntity(columns, transformContext) {
1072
- return await toArrayAsync(mapAsync(columns, async (column) => await this._mapToEntity(column, transformContext)));
1073
- }
1074
- async _mapToEntity(columns, transformContext) {
1075
- const entity = new this.type();
1076
- for (const def of this.#columnDefinitions) {
1077
- const rawValue = columns[def.name];
1455
+ return sql `${this.#baseTableWithMetadata.attributes} || ${JSON.stringify(attributes)}::jsonb`;
1456
+ }
1457
+ applyJoins(dbQuery) {
1458
+ return this.#joinedTables.reduce((query, joinedTable) => query.innerJoin(joinedTable, eq(this.#table.id, joinedTable.id)), dbQuery);
1459
+ }
1460
+ getTablesChain(type) {
1461
+ const chain = [];
1462
+ let currentType = type;
1463
+ while (true) {
1464
+ const table = getDrizzleTableFromType(currentType, this.#schema);
1465
+ const columnDefinitions = getTableColumnDefinitions(table);
1466
+ chain.unshift({ table, type: currentType, columnDefinitions });
1467
+ const parentType = reflectionRegistry.getMetadata(currentType)?.parent;
1468
+ if (isNullOrUndefined(parentType) || !isTableOwning(parentType)) {
1469
+ break;
1470
+ }
1471
+ currentType = parentType;
1472
+ }
1473
+ return chain;
1474
+ }
1475
+ async _mapManyToEntity(columns, transformContext, includeSubclasses) {
1476
+ return await toArrayAsync(mapAsync(columns, async (column) => await this._mapToEntity(column, transformContext, includeSubclasses)));
1477
+ }
1478
+ async _mapToEntity(columns, transformContext, includeSubclasses) {
1479
+ const subclasses = (includeSubclasses == true) ? this.#subclasses : (isArray(includeSubclasses) ? includeSubclasses : []);
1480
+ let type = this.type;
1481
+ if (isDefined(this.#inheritanceMetadata)) {
1482
+ const discriminatorColumnName = this.#inheritanceMetadata.discriminatorColumn;
1483
+ const discriminatorValue = columns[discriminatorColumnName];
1484
+ if (isDefined(discriminatorValue)) {
1485
+ const subclassInfo = this.#subclassTablesInfo.find((info) => info.discriminatorValue == discriminatorValue);
1486
+ if (isDefined(subclassInfo) && subclasses.includes(subclassInfo.type)) {
1487
+ type = subclassInfo.type;
1488
+ }
1489
+ const isCompatible = !this.#isChild
1490
+ || (discriminatorValue == this.#discriminatorInfo?.value)
1491
+ || this.#subclassTablesInfo.some((info) => info.discriminatorValue == discriminatorValue);
1492
+ if (!isCompatible) {
1493
+ throw new Error(`Discriminator mismatch: loaded "${discriminatorValue}" but expected "${this.#discriminatorInfo?.value}" or one of its subclasses.`);
1494
+ }
1495
+ }
1496
+ }
1497
+ const entity = new (type)();
1498
+ const columnDefinitions = (type == this.type)
1499
+ ? this.#columnDefinitions
1500
+ : getColumnDefinitions(getDrizzleTableFromType(type, this.#schema));
1501
+ for (const def of columnDefinitions) {
1502
+ let rawValue = columns[def.name];
1503
+ if (isUndefined(rawValue) && isDefined(def.table)) {
1504
+ const tableName = getTableName(def.table);
1505
+ if (isDefined(columns[tableName])) {
1506
+ rawValue = columns[tableName][def.name];
1507
+ }
1508
+ else {
1509
+ // Check if it's a prefixed column from a polymorphic join
1510
+ const prefixedName = tableName + '_' + def.name;
1511
+ if (isDefined(columns[prefixedName])) {
1512
+ rawValue = columns[prefixedName];
1513
+ }
1514
+ }
1515
+ }
1078
1516
  const transformed = await def.fromDatabase(rawValue, transformContext);
1079
1517
  assignDeep(entity, def.objectPath, transformed);
1080
1518
  }
1081
1519
  return entity;
1082
1520
  }
1083
1521
  async _mapManyToColumns(objects, transformContext) {
1084
- return await toArrayAsync(mapAsync(objects, async (obj) => await this._mapToColumns(obj, transformContext)));
1522
+ return await toArrayAsync(mapAsync(objects, async (obj) => await this._mapToTableColumns(obj, transformContext)));
1085
1523
  }
1086
- async _mapToColumns(obj, transformContext) {
1524
+ async _mapToTableColumns(obj, transformContext, definitions = this.#tableColumnDefinitions) {
1087
1525
  const columns = {};
1088
- for (const def of this.#columnDefinitions) {
1526
+ for (const def of definitions) {
1089
1527
  const rawValue = def.dereferenceObjectPath(obj);
1090
1528
  columns[def.name] = await def.toDatabase(rawValue, transformContext);
1091
1529
  }
1092
1530
  return columns;
1093
1531
  }
1094
- async _mapManyToInsertColumns(objects, transformContext) {
1095
- return await toArrayAsync(mapAsync(objects, async (obj) => await this._mapToInsertColumns(obj, transformContext)));
1096
- }
1097
- async _mapToInsertColumns(obj, transformContext) {
1098
- const mapped = await this._mapToColumns(obj, transformContext);
1532
+ async _mapToTableInsertColumns(obj, transformContext, table, definitions) {
1533
+ const columns = await this._mapToTableColumns(obj, transformContext, definitions);
1534
+ // Inject discriminator if applicable
1535
+ if (isDefined(this.#discriminatorInfo)) {
1536
+ const { columnName, value } = this.#discriminatorInfo;
1537
+ const resolvedDefinitions = definitions ?? getTableColumnDefinitions(table);
1538
+ const hasDiscriminatorColumn = isDefined(resolvedDefinitions.find((def) => def.name == columnName));
1539
+ if (hasDiscriminatorColumn) {
1540
+ columns[columnName] = value;
1541
+ }
1542
+ }
1099
1543
  return {
1100
- ...mapped,
1101
- ...(this.hasMetadata
1544
+ ...columns,
1545
+ ...(((table == this.#baseTable) && this.hasMetadata)
1102
1546
  ? {
1103
1547
  revision: 1,
1104
1548
  revisionTimestamp: TRANSACTION_TIMESTAMP,
@@ -1107,29 +1551,88 @@ let EntityRepository = class EntityRepository extends Transactional {
1107
1551
  : undefined),
1108
1552
  };
1109
1553
  }
1110
- async _mapUpdate(update, transformContext) {
1554
+ async _mapManyToInsertColumns(objects, transformContext) {
1555
+ return await toArrayAsync(mapAsync(objects, async (obj) => await this._mapToInsertColumns(obj, transformContext)));
1556
+ }
1557
+ async _mapToInsertColumns(obj, transformContext) {
1558
+ return await this._mapToTableInsertColumns(obj, transformContext, this.#table);
1559
+ }
1560
+ async _mapToTableUpdate(update, transformContext, table, definitions = this.#tableColumnDefinitions) {
1111
1561
  const mappedUpdate = {};
1112
- for (const column of this.#columnDefinitions) {
1113
- const value = column.dereferenceObjectPath(update);
1114
- if (isUndefined(value)) {
1115
- continue;
1562
+ for (const columnDef of definitions) {
1563
+ const value = columnDef.dereferenceObjectPath(update);
1564
+ if (isDefined(value)) {
1565
+ mappedUpdate[columnDef.name] = await columnDef.toDatabase(value, transformContext);
1116
1566
  }
1117
- mappedUpdate[column.name] = await column.toDatabase(value, transformContext);
1118
1567
  }
1119
1568
  return {
1120
1569
  ...mappedUpdate,
1121
- ...this._getMetadataUpdate(update),
1570
+ ...((table == this.#baseTable) ? this._getMetadataUpdate(update) : undefined),
1122
1571
  };
1123
1572
  }
1573
+ async _mapUpdate(update, transformContext) {
1574
+ return await this._mapToTableUpdate(update, transformContext, this.#table);
1575
+ }
1124
1576
  _getMetadataUpdate(update) {
1125
1577
  return this.hasMetadata
1126
1578
  ? {
1127
1579
  attributes: this.getAttributesUpdate(update?.metadata?.attributes),
1128
- revision: sql `${this.#tableWithMetadata.revision} + 1`,
1580
+ revision: sql `${this.#baseTableWithMetadata.revision} + 1`,
1129
1581
  revisionTimestamp: TRANSACTION_TIMESTAMP,
1130
1582
  }
1131
1583
  : undefined;
1132
1584
  }
1585
+ prepareSubclassJoins(includeSubclassesOption) {
1586
+ const includeSubclasses = (includeSubclassesOption == true)
1587
+ ? this.#subclasses
1588
+ : (isArray(includeSubclassesOption) ? includeSubclassesOption : []);
1589
+ const additionalTablesToJoin = new Map();
1590
+ if (includeSubclasses.length > 0) {
1591
+ for (const subclassInfo of this.#subclassTablesInfo) {
1592
+ if (includeSubclasses.includes(subclassInfo.type)) {
1593
+ for (const chainInfo of subclassInfo.tablesChain) {
1594
+ if (!this.#tablesChain.some((t) => t.table == chainInfo.table)) {
1595
+ additionalTablesToJoin.set(chainInfo.table, chainInfo);
1596
+ }
1597
+ }
1598
+ }
1599
+ }
1600
+ }
1601
+ return { includeSubclasses, additionalTablesToJoin };
1602
+ }
1603
+ addSubclassColumnsToSelection(selection, additionalTablesToJoin) {
1604
+ for (const [table, info] of additionalTablesToJoin) {
1605
+ for (const column of info.columnDefinitions) {
1606
+ const tableColumn = table[column.name];
1607
+ if (isDefined(tableColumn)) {
1608
+ selection[getTableName(table) + '_' + column.name] = tableColumn;
1609
+ }
1610
+ }
1611
+ }
1612
+ }
1613
+ applySubclassJoins(dbQuery, additionalTablesToJoin) {
1614
+ if (additionalTablesToJoin.size > 0) {
1615
+ for (const [table] of additionalTablesToJoin) {
1616
+ dbQuery = dbQuery.leftJoin(table, eq(this.#table.id, table.id));
1617
+ }
1618
+ }
1619
+ return dbQuery;
1620
+ }
1621
+ createBaseQuery(options, extraSelection = {}) {
1622
+ const { additionalTablesToJoin } = this.prepareSubclassJoins(options?.includeSubclasses);
1623
+ const selection = { ...this.#defaultSelection, ...extraSelection };
1624
+ this.addSubclassColumnsToSelection(selection, additionalTablesToJoin);
1625
+ let dbQuery = this.applySelect(this.session, selection, options?.distinct)
1626
+ .from(this.#table)
1627
+ .$dynamic();
1628
+ dbQuery = this.applyJoins(dbQuery);
1629
+ dbQuery = this.applySubclassJoins(dbQuery, additionalTablesToJoin);
1630
+ if (isDefined(options?.for)) {
1631
+ const lock = isString(options.for) ? { mode: options.for } : options.for;
1632
+ dbQuery = dbQuery.for(lock.mode, { skipLocked: lock.skipLocked, noWait: lock.noWait });
1633
+ }
1634
+ return dbQuery;
1635
+ }
1133
1636
  async getTransformContext() {
1134
1637
  if (isUndefined(this.#transformContext)) {
1135
1638
  if (isUndefined(this.#encryptionSecret)) {
@@ -1142,12 +1645,13 @@ let EntityRepository = class EntityRepository extends Transactional {
1142
1645
  }
1143
1646
  return await this.#transformContext;
1144
1647
  }
1145
- async _mapSearchResults(rows, scoreTransformer = (s) => s) {
1648
+ async _mapSearchResults(rows, scoreTransformer = (s) => s, options) {
1146
1649
  const transformContext = await this.getTransformContext();
1147
1650
  return await toArrayAsync(mapAsync(rows, async (row) => {
1148
- const { [searchScoreColumn]: rawScore, [searchHighlightColumn]: highlight, [searchHighlightPositionsColumn]: highlightPositions, ...entityRow } = row;
1651
+ const { [searchScoreColumn]: rawScore, [searchHighlightColumn]: highlight, [searchHighlightPositionsColumn]: rawHighlightPositions, ...entityRow } = row;
1652
+ const highlightPositions = isString(rawHighlightPositions) ? JSON.parse(rawHighlightPositions) : rawHighlightPositions;
1149
1653
  return {
1150
- entity: await this._mapToEntity(entityRow, transformContext),
1654
+ entity: await this._mapToEntity(entityRow, transformContext, options?.includeSubclasses),
1151
1655
  score: scoreTransformer(rawScore),
1152
1656
  highlight: highlight,
1153
1657
  highlightPositions,