@tstdl/base 0.93.87 → 0.93.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/genkit/helpers.d.ts +3 -1
- package/ai/genkit/helpers.js +3 -3
- package/api/server/gateway.d.ts +3 -0
- package/api/server/gateway.js +15 -4
- package/api/server/middlewares/catch-error.middleware.js +2 -4
- package/api/server/middlewares/cors.middleware.js +2 -3
- package/api/server/middlewares/csrf.middleware.d.ts +41 -0
- package/api/server/middlewares/csrf.middleware.js +108 -0
- package/api/server/middlewares/index.d.ts +1 -0
- package/api/server/middlewares/index.js +1 -0
- package/api/server/module.d.ts +8 -2
- package/api/server/module.js +14 -8
- package/api/server/tests/csrf.middleware.test.js +91 -0
- package/audit/drizzle/{0000_bored_stick.sql → 0000_lumpy_thunderball.sql} +3 -3
- package/audit/drizzle/meta/0000_snapshot.json +4 -4
- package/audit/drizzle/meta/_journal.json +2 -9
- package/audit/module.d.ts +4 -1
- package/audit/module.js +3 -2
- package/audit/schemas.d.ts +1 -1
- package/audit/types.d.ts +1 -1
- package/audit/types.js +1 -1
- package/authentication/client/authentication.service.d.ts +14 -1
- package/authentication/client/authentication.service.js +82 -23
- package/authentication/client/http-client.middleware.d.ts +6 -0
- package/authentication/client/http-client.middleware.js +36 -0
- package/authentication/client/module.js +8 -2
- package/authentication/models/service-account.model.d.ts +2 -2
- package/authentication/models/service-account.model.js +10 -5
- package/authentication/models/subject.model.d.ts +20 -5
- package/authentication/models/subject.model.js +34 -29
- package/authentication/models/system-account.model.d.ts +3 -2
- package/authentication/models/system-account.model.js +11 -5
- package/authentication/models/user.model.d.ts +2 -11
- package/authentication/models/user.model.js +5 -16
- package/authentication/server/authentication-api-request-token.provider.d.ts +0 -2
- package/authentication/server/authentication-api-request-token.provider.js +3 -11
- package/authentication/server/authentication.api-controller.d.ts +1 -2
- package/authentication/server/authentication.api-controller.js +8 -9
- package/authentication/server/authentication.audit.d.ts +3 -2
- package/authentication/server/authentication.service.d.ts +27 -1
- package/authentication/server/authentication.service.js +67 -18
- package/authentication/server/drizzle/{0000_normal_paper_doll.sql → 0000_soft_tag.sql} +25 -32
- package/authentication/server/drizzle/meta/0000_snapshot.json +180 -205
- package/authentication/server/drizzle/meta/_journal.json +2 -2
- package/authentication/server/helper.js +9 -2
- package/authentication/server/module.d.ts +4 -1
- package/authentication/server/module.js +9 -5
- package/authentication/server/schemas.d.ts +2 -1
- package/authentication/server/schemas.js +2 -2
- package/authentication/server/subject.service.d.ts +17 -11
- package/authentication/server/subject.service.js +86 -84
- package/authentication/tests/authentication-ancillary.service.test.d.ts +1 -0
- package/authentication/tests/authentication-ancillary.service.test.js +13 -0
- package/authentication/tests/authentication-secret-requirements.validator.test.d.ts +1 -0
- package/authentication/tests/authentication-secret-requirements.validator.test.js +29 -0
- package/authentication/tests/authentication.api-controller.test.d.ts +1 -0
- package/authentication/tests/authentication.api-controller.test.js +88 -0
- package/authentication/tests/authentication.api-request-token.provider.test.d.ts +1 -0
- package/authentication/tests/authentication.api-request-token.provider.test.js +48 -0
- package/authentication/tests/authentication.client-middleware.test.d.ts +1 -0
- package/authentication/tests/authentication.client-middleware.test.js +23 -0
- package/authentication/tests/authentication.client-service.test.d.ts +1 -0
- package/authentication/tests/authentication.client-service.test.js +70 -0
- package/authentication/tests/authentication.service.test.d.ts +1 -0
- package/authentication/tests/authentication.service.test.js +186 -0
- package/authentication/tests/authentication.test-ancillary-service.d.ts +9 -0
- package/authentication/tests/authentication.test-ancillary-service.js +27 -0
- package/authentication/tests/helper.test.d.ts +1 -0
- package/authentication/tests/helper.test.js +107 -0
- package/authentication/tests/secret-requirements.error.test.d.ts +1 -0
- package/authentication/tests/secret-requirements.error.test.js +14 -0
- package/authentication/tests/subject.service.test.d.ts +1 -0
- package/authentication/tests/subject.service.test.js +140 -0
- package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +1 -1
- package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
- package/circuit-breaker/postgres/module.d.ts +7 -1
- package/circuit-breaker/postgres/module.js +8 -6
- package/circuit-breaker/tests/circuit-breaker.test.js +2 -22
- package/document-management/api/document-management.api.js +2 -6
- package/document-management/server/services/document-validation.service.js +6 -5
- package/document-management/server/services/document-workflow.service.js +5 -5
- package/document-management/service-models/document-folders.view-model.d.ts +5 -2
- package/document-management/service-models/document-folders.view-model.js +42 -9
- package/document-management/service-models/enriched/enriched-document-management-data.view.js +1 -1
- package/examples/document-management/main.js +4 -4
- package/http/client/adapters/undici.adapter.d.ts +7 -5
- package/http/client/adapters/undici.adapter.js +13 -10
- package/http/client/module.d.ts +3 -1
- package/http/client/module.js +8 -9
- package/http/server/http-server.d.ts +2 -0
- package/http/server/node/module.d.ts +6 -2
- package/http/server/node/module.js +6 -4
- package/http/server/node/node-http-server.d.ts +2 -0
- package/http/server/node/node-http-server.js +7 -0
- package/http/types.d.ts +1 -1
- package/key-value-store/postgres/module.d.ts +7 -1
- package/key-value-store/postgres/module.js +7 -3
- package/lock/postgres/lock.js +0 -1
- package/lock/postgres/module.d.ts +7 -1
- package/lock/postgres/module.js +9 -5
- package/logger/formatter.d.ts +2 -0
- package/logger/formatters/json.js +2 -2
- package/logger/formatters/pretty-print.js +8 -10
- package/logger/logger.d.ts +1 -1
- package/logger/logger.js +15 -12
- package/message-bus/local/module.d.ts +5 -2
- package/message-bus/local/module.js +5 -4
- package/module/module.d.ts +2 -1
- package/module/module.js +3 -0
- package/module/modules/web-server.module.d.ts +11 -6
- package/module/modules/web-server.module.js +15 -10
- package/orm/decorators.d.ts +24 -1
- package/orm/decorators.js +40 -4
- package/orm/query/base.d.ts +17 -17
- package/orm/query/base.js +1 -1
- package/orm/repository.types.d.ts +45 -1
- package/orm/schemas/tsvector.js +1 -1
- package/orm/server/drizzle/schema-converter.d.ts +3 -1
- package/orm/server/drizzle/schema-converter.js +120 -14
- package/orm/server/index.d.ts +1 -0
- package/orm/server/index.js +1 -0
- package/orm/server/module.d.ts +4 -2
- package/orm/server/module.js +6 -5
- package/orm/server/query-converter.d.ts +6 -3
- package/orm/server/query-converter.js +32 -20
- package/orm/server/repository-config.d.ts +8 -0
- package/orm/server/repository-config.js +8 -0
- package/orm/server/repository.d.ts +117 -43
- package/orm/server/repository.js +757 -253
- package/orm/server/transaction.d.ts +4 -2
- package/orm/server/transaction.js +14 -5
- package/orm/server/transactional.d.ts +6 -2
- package/orm/server/transactional.js +39 -9
- package/orm/server/types.d.ts +2 -0
- package/orm/sqls/case-when.d.ts +3 -3
- package/orm/sqls/case-when.js +2 -2
- package/orm/sqls/sqls.d.ts +31 -5
- package/orm/sqls/sqls.js +69 -6
- package/orm/tests/data-types.test.d.ts +1 -0
- package/orm/tests/data-types.test.js +39 -0
- package/orm/tests/decorators.test.d.ts +1 -0
- package/orm/tests/decorators.test.js +77 -0
- package/orm/tests/encryption.test.d.ts +1 -0
- package/orm/tests/encryption.test.js +34 -0
- package/orm/tests/query-complex.test.d.ts +1 -0
- package/orm/tests/query-complex.test.js +203 -0
- package/orm/tests/query-converter-complex.test.d.ts +1 -0
- package/orm/tests/query-converter-complex.test.js +126 -0
- package/orm/tests/query-converter.test.d.ts +1 -0
- package/orm/tests/query-converter.test.js +123 -0
- package/orm/tests/repository-advanced.test.d.ts +1 -0
- package/orm/tests/repository-advanced.test.js +232 -0
- package/orm/tests/repository-attributes.test.d.ts +1 -0
- package/orm/tests/repository-attributes.test.js +99 -0
- package/orm/tests/repository-comprehensive.test.d.ts +1 -0
- package/orm/tests/repository-comprehensive.test.js +187 -0
- package/orm/tests/repository-coverage.test.d.ts +1 -0
- package/orm/tests/repository-coverage.test.js +303 -0
- package/orm/tests/repository-cti-complex.test.d.ts +1 -0
- package/orm/tests/repository-cti-complex.test.js +170 -0
- package/orm/tests/repository-cti-embedded.test.d.ts +1 -0
- package/orm/tests/repository-cti-embedded.test.js +188 -0
- package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
- package/orm/tests/repository-cti-extensive.test.js +308 -0
- package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
- package/orm/tests/repository-cti-mapping.test.js +121 -0
- package/orm/tests/repository-cti-search.test.d.ts +1 -0
- package/orm/tests/repository-cti-search.test.js +152 -0
- package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-cti-soft-delete.test.js +115 -0
- package/orm/tests/repository-cti-transactions.test.d.ts +1 -0
- package/orm/tests/repository-cti-transactions.test.js +126 -0
- package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
- package/orm/tests/repository-cti-upsert-many.test.js +127 -0
- package/orm/tests/repository-cti.test.d.ts +1 -0
- package/orm/tests/repository-cti.test.js +456 -0
- package/orm/tests/repository-edge-cases.test.d.ts +1 -0
- package/orm/tests/repository-edge-cases.test.js +216 -0
- package/orm/tests/repository-expiration.test.d.ts +1 -0
- package/orm/tests/repository-expiration.test.js +153 -0
- package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
- package/orm/tests/repository-extra-coverage.test.js +546 -0
- package/orm/tests/repository-mapping.test.d.ts +1 -0
- package/orm/tests/repository-mapping.test.js +71 -0
- package/orm/tests/repository-regression.test.d.ts +1 -0
- package/orm/tests/repository-regression.test.js +330 -0
- package/orm/tests/repository-search-coverage.test.d.ts +1 -0
- package/orm/tests/repository-search-coverage.test.js +129 -0
- package/orm/tests/repository-search.test.d.ts +1 -0
- package/orm/tests/repository-search.test.js +116 -0
- package/orm/tests/repository-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-soft-delete.test.js +143 -0
- package/orm/tests/repository-transactions-nested.test.d.ts +1 -0
- package/orm/tests/repository-transactions-nested.test.js +202 -0
- package/orm/tests/repository-types.test.d.ts +1 -0
- package/orm/tests/repository-types.test.js +218 -0
- package/orm/tests/schema-converter.test.d.ts +1 -0
- package/orm/tests/schema-converter.test.js +81 -0
- package/orm/tests/schema-generation.test.d.ts +1 -0
- package/orm/tests/schema-generation.test.js +127 -0
- package/orm/tests/sql-helpers.test.d.ts +1 -0
- package/orm/tests/sql-helpers.test.js +67 -0
- package/orm/tests/transaction-safety.test.d.ts +1 -0
- package/orm/tests/transaction-safety.test.js +81 -0
- package/orm/tests/transactional.test.d.ts +1 -0
- package/orm/tests/transactional.test.js +224 -0
- package/orm/tests/utils.test.d.ts +1 -0
- package/orm/tests/utils.test.js +70 -0
- package/orm/utils.d.ts +7 -0
- package/orm/utils.js +26 -6
- package/package.json +12 -7
- package/pool/pool.js +1 -1
- package/rate-limit/index.d.ts +2 -0
- package/rate-limit/index.js +2 -0
- package/rate-limit/postgres/drizzle/0000_watery_rage.sql +7 -0
- package/{queue → rate-limit}/postgres/drizzle/meta/0000_snapshot.json +14 -39
- package/rate-limit/postgres/drizzle/meta/_journal.json +13 -0
- package/{queue → rate-limit}/postgres/drizzle.config.js +1 -1
- package/rate-limit/postgres/index.d.ts +4 -0
- package/rate-limit/postgres/index.js +4 -0
- package/rate-limit/postgres/module.d.ts +12 -0
- package/rate-limit/postgres/module.js +28 -0
- package/rate-limit/postgres/postgres-rate-limiter.d.ts +9 -0
- package/rate-limit/postgres/postgres-rate-limiter.js +56 -0
- package/rate-limit/postgres/rate-limit.model.d.ts +8 -0
- package/rate-limit/postgres/rate-limit.model.js +35 -0
- package/rate-limit/postgres/rate-limiter.provider.d.ts +6 -0
- package/rate-limit/postgres/rate-limiter.provider.js +21 -0
- package/rate-limit/postgres/schemas.d.ts +3 -0
- package/rate-limit/postgres/schemas.js +4 -0
- package/rate-limit/provider.d.ts +9 -0
- package/rate-limit/provider.js +2 -0
- package/rate-limit/rate-limiter.d.ts +35 -0
- package/rate-limit/rate-limiter.js +3 -0
- package/rate-limit/tests/postgres-rate-limiter.test.d.ts +1 -0
- package/rate-limit/tests/postgres-rate-limiter.test.js +92 -0
- package/signals/implementation/configure.d.ts +3 -0
- package/signals/implementation/configure.js +3 -0
- package/sse/data-stream-source.d.ts +1 -1
- package/sse/data-stream-source.js +6 -6
- package/task-queue/enqueue-batch.d.ts +17 -0
- package/task-queue/enqueue-batch.js +24 -0
- package/{queue → task-queue}/index.d.ts +1 -1
- package/{queue → task-queue}/index.js +1 -1
- package/task-queue/postgres/drizzle/0000_thin_black_panther.sql +74 -0
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +592 -0
- package/task-queue/postgres/drizzle/meta/_journal.json +13 -0
- package/task-queue/postgres/drizzle.config.d.ts +2 -0
- package/task-queue/postgres/drizzle.config.js +11 -0
- package/task-queue/postgres/index.d.ts +4 -0
- package/task-queue/postgres/index.js +4 -0
- package/task-queue/postgres/module.d.ts +12 -0
- package/task-queue/postgres/module.js +28 -0
- package/task-queue/postgres/schemas.d.ts +16 -0
- package/task-queue/postgres/schemas.js +8 -0
- package/task-queue/postgres/task-queue.d.ts +83 -0
- package/task-queue/postgres/task-queue.js +1054 -0
- package/task-queue/postgres/task-queue.provider.d.ts +7 -0
- package/{queue/postgres/queue.provider.js → task-queue/postgres/task-queue.provider.js} +8 -8
- package/task-queue/postgres/task.model.d.ts +39 -0
- package/task-queue/postgres/task.model.js +178 -0
- package/{queue → task-queue}/provider.d.ts +3 -3
- package/task-queue/provider.js +2 -0
- package/{queue → task-queue}/task-context.d.ts +7 -7
- package/{queue → task-queue}/task-context.js +8 -8
- package/{queue/queue.d.ts → task-queue/task-queue.d.ts} +128 -59
- package/task-queue/task-queue.js +200 -0
- package/task-queue/tests/complex.test.d.ts +1 -0
- package/task-queue/tests/complex.test.js +299 -0
- package/task-queue/tests/dependencies.test.d.ts +1 -0
- package/task-queue/tests/dependencies.test.js +174 -0
- package/task-queue/tests/queue.test.d.ts +1 -0
- package/task-queue/tests/queue.test.js +334 -0
- package/task-queue/tests/worker.test.d.ts +1 -0
- package/task-queue/tests/worker.test.js +163 -0
- package/test1.js +1 -1
- package/test4.js +2 -2
- package/unit-test/index.d.ts +1 -0
- package/unit-test/index.js +1 -0
- package/unit-test/integration-setup.d.ts +55 -0
- package/unit-test/integration-setup.js +182 -0
- package/utils/patterns.d.ts +3 -0
- package/utils/patterns.js +6 -1
- package/audit/drizzle/0001_previous_network.sql +0 -2
- package/audit/drizzle/meta/0001_snapshot.json +0 -195
- package/queue/enqueue-batch.d.ts +0 -17
- package/queue/enqueue-batch.js +0 -18
- package/queue/postgres/drizzle/0000_zippy_moondragon.sql +0 -11
- package/queue/postgres/drizzle/0001_certain_wild_pack.sql +0 -2
- package/queue/postgres/drizzle/0002_dear_meggan.sql +0 -2
- package/queue/postgres/drizzle/0003_tricky_venom.sql +0 -30
- package/queue/postgres/drizzle/meta/0001_snapshot.json +0 -103
- package/queue/postgres/drizzle/meta/0002_snapshot.json +0 -90
- package/queue/postgres/drizzle/meta/0003_snapshot.json +0 -288
- package/queue/postgres/drizzle/meta/_journal.json +0 -34
- package/queue/postgres/index.d.ts +0 -4
- package/queue/postgres/index.js +0 -4
- package/queue/postgres/module.d.ts +0 -9
- package/queue/postgres/module.js +0 -29
- package/queue/postgres/queue.d.ts +0 -60
- package/queue/postgres/queue.js +0 -681
- package/queue/postgres/queue.provider.d.ts +0 -7
- package/queue/postgres/schemas.d.ts +0 -14
- package/queue/postgres/schemas.js +0 -6
- package/queue/postgres/task.model.d.ts +0 -24
- package/queue/postgres/task.model.js +0 -115
- package/queue/provider.js +0 -2
- package/queue/queue.js +0 -131
- package/queue/tests/queue.test.js +0 -623
- package/test3.d.ts +0 -1
- package/test3.js +0 -47
- /package/{queue/tests/queue.test.d.ts → api/server/tests/csrf.middleware.test.d.ts} +0 -0
- /package/circuit-breaker/postgres/drizzle/{0000_hard_shocker.sql → 0000_cooing_korath.sql} +0 -0
- /package/{queue → rate-limit}/postgres/drizzle.config.d.ts +0 -0
package/orm/server/repository.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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() ?
|
|
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 })
|
|
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
|
-
#
|
|
53
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
157
|
+
const extraSelection = {};
|
|
118
158
|
if (isDefined(score)) {
|
|
119
|
-
|
|
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
|
-
|
|
171
|
+
extraSelection[searchHighlightColumn] = tsHeadline(languageSql, document, tsquery, headlineOptions).as(searchHighlightColumn);
|
|
132
172
|
}
|
|
133
|
-
const whereClause =
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
263
|
+
extraSelection[searchHighlightColumn] = highlightSql.as(searchHighlightColumn);
|
|
233
264
|
if (includePositions == true) {
|
|
234
|
-
|
|
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
|
-
|
|
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.
|
|
330
|
-
|
|
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
|
|
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.
|
|
377
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
|
613
|
+
if (entities.length == 0) {
|
|
516
614
|
return [];
|
|
517
615
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
|
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
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
.
|
|
579
|
-
|
|
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
|
|
845
|
+
async upsertMany(target, entities, update, wheres) {
|
|
846
|
+
if (entities.length == 0) {
|
|
590
847
|
return [];
|
|
591
848
|
}
|
|
592
|
-
const
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
.
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
.
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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.
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
.
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
.
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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
|
|
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.#
|
|
1156
|
+
const sqlQuery = and(isSqlNull(this.#baseTableWithMetadata.deleteTimestamp), this.convertQuery(query, options));
|
|
812
1157
|
const rows = await this.session
|
|
813
|
-
.update(this.#
|
|
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.#
|
|
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.#
|
|
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)
|
|
875
|
-
const sqlQuery = inArray(this.#
|
|
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.#
|
|
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
|
|
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.#
|
|
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.#
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.#
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
return
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
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.
|
|
1522
|
+
return await toArrayAsync(mapAsync(objects, async (obj) => await this._mapToTableColumns(obj, transformContext)));
|
|
1085
1523
|
}
|
|
1086
|
-
async
|
|
1524
|
+
async _mapToTableColumns(obj, transformContext, definitions = this.#tableColumnDefinitions) {
|
|
1087
1525
|
const columns = {};
|
|
1088
|
-
for (const def of
|
|
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
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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
|
-
...
|
|
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
|
|
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
|
|
1113
|
-
const value =
|
|
1114
|
-
if (
|
|
1115
|
-
|
|
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.#
|
|
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]:
|
|
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,
|