@cheetah.js/orm 0.1.45 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/SqlBuilder.d.ts +18 -33
  2. package/dist/SqlBuilder.js +102 -584
  3. package/dist/driver/bun-driver.base.d.ts +35 -1
  4. package/dist/driver/bun-driver.base.js +132 -0
  5. package/dist/driver/bun-mysql.driver.d.ts +13 -6
  6. package/dist/driver/bun-mysql.driver.js +47 -72
  7. package/dist/driver/bun-pg.driver.d.ts +12 -6
  8. package/dist/driver/bun-pg.driver.js +27 -59
  9. package/dist/driver/driver.interface.d.ts +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +1 -0
  12. package/dist/query/model-transformer.d.ts +21 -0
  13. package/dist/query/model-transformer.js +118 -0
  14. package/dist/query/sql-column-manager.d.ts +25 -0
  15. package/dist/query/sql-column-manager.js +124 -0
  16. package/dist/query/sql-condition-builder.d.ts +36 -0
  17. package/dist/query/sql-condition-builder.js +160 -0
  18. package/dist/query/sql-join-manager.d.ts +39 -0
  19. package/dist/query/sql-join-manager.js +224 -0
  20. package/dist/repository/Repository.d.ts +125 -0
  21. package/dist/repository/Repository.js +150 -0
  22. package/dist/utils/value-processor.d.ts +14 -0
  23. package/dist/utils/value-processor.js +84 -0
  24. package/package.json +3 -3
  25. package/build.ts +0 -14
  26. package/cheetah.config.ts +0 -14
  27. package/dist/SqlBuilder.js.map +0 -1
  28. package/dist/bun/index.js +0 -233434
  29. package/dist/bun/index.js.map +0 -336
  30. package/dist/common/email.vo.js.map +0 -1
  31. package/dist/common/uuid.js.map +0 -1
  32. package/dist/common/value-object.js.map +0 -1
  33. package/dist/constants.js.map +0 -1
  34. package/dist/decorators/entity.decorator.js.map +0 -1
  35. package/dist/decorators/enum.decorator.js.map +0 -1
  36. package/dist/decorators/event-hook.decorator.js.map +0 -1
  37. package/dist/decorators/index.decorator.js.map +0 -1
  38. package/dist/decorators/one-many.decorator.js.map +0 -1
  39. package/dist/decorators/primary-key.decorator.js.map +0 -1
  40. package/dist/decorators/property.decorator.js.map +0 -1
  41. package/dist/domain/base-entity.js.map +0 -1
  42. package/dist/domain/collection.js.map +0 -1
  43. package/dist/domain/entities.js.map +0 -1
  44. package/dist/domain/reference.js.map +0 -1
  45. package/dist/driver/bun-driver.base.js.map +0 -1
  46. package/dist/driver/bun-mysql.driver.js.map +0 -1
  47. package/dist/driver/bun-pg.driver.js.map +0 -1
  48. package/dist/driver/driver.interface.js.map +0 -1
  49. package/dist/driver/pg-driver.d.ts +0 -63
  50. package/dist/driver/pg-driver.js +0 -335
  51. package/dist/driver/pg-driver.js.map +0 -1
  52. package/dist/entry.js.map +0 -1
  53. package/dist/index.js.map +0 -1
  54. package/dist/orm.js.map +0 -1
  55. package/dist/orm.service.js.map +0 -1
  56. package/dist/utils.js.map +0 -1
@@ -0,0 +1,25 @@
1
+ import { Statement } from '../driver/driver.interface';
2
+ import { EntityStorage, Options } from '../domain/entities';
3
+ export declare class SqlColumnManager {
4
+ private entityStorage;
5
+ private statements;
6
+ private entity;
7
+ constructor(entityStorage: EntityStorage, statements: Statement<any>, entity: Options);
8
+ generateColumns(model: Function, updatedColumns: string[]): string[];
9
+ processUserColumns(columns: string[]): string[];
10
+ getColumnsForEntity(entity: Function, alias: string): string[];
11
+ discoverAlias(column: string, onlyAlias?: boolean): string | undefined;
12
+ private getJoinColumns;
13
+ private getPropertyColumns;
14
+ private getRelationColumns;
15
+ private extractAliases;
16
+ private filterValid;
17
+ private isNestedColumn;
18
+ private buildSimpleColumnAlias;
19
+ private buildNestedColumnAlias;
20
+ private validateJoinsExist;
21
+ private resolveNestedAlias;
22
+ private buildJoinMaps;
23
+ private findNextAlias;
24
+ private formatColumnWithAlias;
25
+ }
@@ -0,0 +1,124 @@
1
+ export class SqlColumnManager {
2
+ constructor(entityStorage, statements, entity) {
3
+ this.entityStorage = entityStorage;
4
+ this.statements = statements;
5
+ this.entity = entity;
6
+ }
7
+ generateColumns(model, updatedColumns) {
8
+ const baseColumns = this.getColumnsForEntity(model, this.statements.alias);
9
+ const joinColumns = this.getJoinColumns();
10
+ const allColumns = [...baseColumns, ...joinColumns];
11
+ return [...allColumns, ...updatedColumns];
12
+ }
13
+ processUserColumns(columns) {
14
+ const aliasedColumns = this.extractAliases(columns);
15
+ return this.filterValid(aliasedColumns);
16
+ }
17
+ getColumnsForEntity(entity, alias) {
18
+ const entityOptions = this.entityStorage.get(entity);
19
+ if (!entityOptions) {
20
+ throw new Error('Entity not found');
21
+ }
22
+ const propertyColumns = this.getPropertyColumns(entityOptions, alias);
23
+ const relationColumns = this.getRelationColumns(entityOptions, alias);
24
+ return [...propertyColumns, ...relationColumns];
25
+ }
26
+ discoverAlias(column, onlyAlias = false) {
27
+ if (!this.isNestedColumn(column)) {
28
+ return this.buildSimpleColumnAlias(column, this.statements.alias, onlyAlias);
29
+ }
30
+ return this.buildNestedColumnAlias(column, onlyAlias);
31
+ }
32
+ getJoinColumns() {
33
+ if (!this.statements.join) {
34
+ return [];
35
+ }
36
+ return this.statements.join.flatMap(join => this.getColumnsForEntity(join.joinEntity, join.joinAlias));
37
+ }
38
+ getPropertyColumns(entityOptions, alias) {
39
+ return Object.keys(entityOptions.properties).map(key => {
40
+ const columnName = entityOptions.properties[key].options.columnName;
41
+ return `${alias}."${columnName}" as "${alias}_${columnName}"`;
42
+ });
43
+ }
44
+ getRelationColumns(entityOptions, alias) {
45
+ if (!entityOptions.relations) {
46
+ return [];
47
+ }
48
+ return entityOptions.relations
49
+ .filter(relation => relation.relation === 'many-to-one')
50
+ .map(relation => `${alias}."${relation.columnName}" as "${alias}_${relation.columnName}"`);
51
+ }
52
+ extractAliases(columns) {
53
+ return columns
54
+ .map(column => this.discoverAlias(column))
55
+ .flat()
56
+ .filter((col) => col !== undefined);
57
+ }
58
+ filterValid(columns) {
59
+ return columns.filter(Boolean);
60
+ }
61
+ isNestedColumn(column) {
62
+ return column.includes('.');
63
+ }
64
+ buildSimpleColumnAlias(column, alias, onlyAlias) {
65
+ if (onlyAlias) {
66
+ return `${alias}."${column}"`;
67
+ }
68
+ return `${alias}."${column}" as ${alias}_${column}`;
69
+ }
70
+ buildNestedColumnAlias(column, onlyAlias) {
71
+ this.validateJoinsExist();
72
+ const parts = column.split('.');
73
+ const aliasInfo = this.resolveNestedAlias(parts);
74
+ if (!aliasInfo) {
75
+ return undefined;
76
+ }
77
+ return this.formatColumnWithAlias(aliasInfo.alias, parts[parts.length - 1], onlyAlias);
78
+ }
79
+ validateJoinsExist() {
80
+ if (!this.statements.join && !this.statements.selectJoin) {
81
+ throw new Error('Join not found');
82
+ }
83
+ }
84
+ resolveNestedAlias(parts) {
85
+ const joinMaps = this.buildJoinMaps();
86
+ let currentAlias = this.statements.alias;
87
+ for (let i = 0; i < parts.length; i++) {
88
+ const part = parts[i];
89
+ const isLastPart = i === parts.length - 1;
90
+ if (isLastPart) {
91
+ return { alias: currentAlias };
92
+ }
93
+ const nextAlias = this.findNextAlias(part, joinMaps, i === 0);
94
+ if (!nextAlias) {
95
+ return null;
96
+ }
97
+ currentAlias = nextAlias;
98
+ }
99
+ return { alias: currentAlias };
100
+ }
101
+ buildJoinMaps() {
102
+ const relationsMap = new Map(this.entity.relations.map(rel => [rel.propertyKey, rel]));
103
+ const joinMap = new Map();
104
+ this.statements.join?.forEach(join => joinMap.set(join.joinProperty, join));
105
+ const selectJoinMap = new Map();
106
+ this.statements.selectJoin?.forEach(join => selectJoinMap.set(join.joinProperty, join));
107
+ return { joinMap, selectJoinMap, relationsMap };
108
+ }
109
+ findNextAlias(part, maps, isFirstPart) {
110
+ if (maps.joinMap.has(part)) {
111
+ return maps.joinMap.get(part).joinAlias;
112
+ }
113
+ if (maps.selectJoinMap.has(part)) {
114
+ return null;
115
+ }
116
+ return null;
117
+ }
118
+ formatColumnWithAlias(alias, columnName, onlyAlias) {
119
+ if (onlyAlias) {
120
+ return `${alias}."${columnName}"`;
121
+ }
122
+ return `${alias}."${columnName}" as ${alias}_${columnName}`;
123
+ }
124
+ }
@@ -0,0 +1,36 @@
1
+ import { FilterQuery, Relationship, Statement } from '../driver/driver.interface';
2
+ import { EntityStorage } from '../domain/entities';
3
+ type ApplyJoinCallback = (relationship: Relationship<any>, value: FilterQuery<any>, alias: string) => string;
4
+ export declare class SqlConditionBuilder<T> {
5
+ private entityStorage;
6
+ private applyJoinCallback;
7
+ private statements;
8
+ private readonly OPERATORS;
9
+ private lastKeyNotOperator;
10
+ constructor(entityStorage: EntityStorage, applyJoinCallback: ApplyJoinCallback, statements: Statement<T>);
11
+ build(condition: FilterQuery<T>, alias: string, model: Function): string;
12
+ private processConditions;
13
+ private processEntry;
14
+ private handleRelationship;
15
+ private handleScalarValue;
16
+ private handleObjectValue;
17
+ private buildLogicalOperatorCondition;
18
+ private buildOperatorConditions;
19
+ private buildOperatorCondition;
20
+ private buildSimpleCondition;
21
+ private buildInCondition;
22
+ private buildNotInCondition;
23
+ private buildLikeCondition;
24
+ private buildComparisonCondition;
25
+ private buildNestedLogicalCondition;
26
+ private wrapWithLogicalOperator;
27
+ private extractValueFromValueObject;
28
+ private formatValue;
29
+ private findRelationship;
30
+ private isScalarValue;
31
+ private isArrayValue;
32
+ private isLogicalOperator;
33
+ private extractLogicalOperator;
34
+ private trackLastNonOperatorKey;
35
+ }
36
+ export {};
@@ -0,0 +1,160 @@
1
+ import { ValueObject } from '../common/value-object';
2
+ import { extendsFrom } from '../utils';
3
+ export class SqlConditionBuilder {
4
+ constructor(entityStorage, applyJoinCallback, statements) {
5
+ this.entityStorage = entityStorage;
6
+ this.applyJoinCallback = applyJoinCallback;
7
+ this.statements = statements;
8
+ this.OPERATORS = ['$eq', '$ne', '$in', '$nin', '$like', '$gt', '$gte', '$lt', '$lte', '$and', '$or'];
9
+ this.lastKeyNotOperator = '';
10
+ }
11
+ build(condition, alias, model) {
12
+ const sqlParts = this.processConditions(condition, alias, model);
13
+ if (sqlParts.length === 0) {
14
+ return '';
15
+ }
16
+ return this.wrapWithLogicalOperator(sqlParts, 'AND');
17
+ }
18
+ processConditions(condition, alias, model) {
19
+ const sqlParts = [];
20
+ for (let [key, value] of Object.entries(condition)) {
21
+ const extractedValue = this.extractValueFromValueObject(value);
22
+ const conditionSql = this.processEntry(key, extractedValue, alias, model);
23
+ if (conditionSql) {
24
+ sqlParts.push(conditionSql);
25
+ }
26
+ }
27
+ return sqlParts;
28
+ }
29
+ processEntry(key, value, alias, model) {
30
+ this.trackLastNonOperatorKey(key);
31
+ const relationship = this.findRelationship(key, model);
32
+ if (relationship) {
33
+ return this.handleRelationship(relationship, value, alias);
34
+ }
35
+ if (this.isScalarValue(value)) {
36
+ return this.handleScalarValue(key, value, alias);
37
+ }
38
+ if (this.isArrayValue(key, value)) {
39
+ return this.buildInCondition(key, value, alias);
40
+ }
41
+ return this.handleObjectValue(key, value, alias, model);
42
+ }
43
+ handleRelationship(relationship, value, alias) {
44
+ const sql = this.applyJoinCallback(relationship, value, alias);
45
+ if (this.statements.strategy === 'joined') {
46
+ return sql;
47
+ }
48
+ return '';
49
+ }
50
+ handleScalarValue(key, value, alias) {
51
+ if (key === '$eq') {
52
+ return this.buildSimpleCondition(this.lastKeyNotOperator, value, alias, '=');
53
+ }
54
+ return this.buildSimpleCondition(key, value, alias);
55
+ }
56
+ handleObjectValue(key, value, alias, model) {
57
+ if (this.isLogicalOperator(key)) {
58
+ return this.buildLogicalOperatorCondition(key, value, alias, model);
59
+ }
60
+ return this.buildOperatorConditions(key, value, alias, model);
61
+ }
62
+ buildLogicalOperatorCondition(key, value, alias, model) {
63
+ const conditions = value.map((cond) => this.build(cond, alias, model));
64
+ const operator = this.extractLogicalOperator(key);
65
+ return this.wrapWithLogicalOperator(conditions, operator);
66
+ }
67
+ buildOperatorConditions(key, value, alias, model) {
68
+ const parts = [];
69
+ for (const operator of this.OPERATORS) {
70
+ if (operator in value) {
71
+ const condition = this.buildOperatorCondition(key, operator, value[operator], alias, model);
72
+ parts.push(condition);
73
+ }
74
+ }
75
+ return parts.join(' AND ');
76
+ }
77
+ buildOperatorCondition(key, operator, value, alias, model) {
78
+ switch (operator) {
79
+ case '$eq':
80
+ return this.buildSimpleCondition(key, value, alias, '=');
81
+ case '$ne':
82
+ return this.buildSimpleCondition(key, value, alias, '!=');
83
+ case '$in':
84
+ return this.buildInCondition(key, value, alias);
85
+ case '$nin':
86
+ return this.buildNotInCondition(key, value, alias);
87
+ case '$like':
88
+ return this.buildLikeCondition(key, value, alias);
89
+ case '$gt':
90
+ return this.buildComparisonCondition(key, value, alias, '>');
91
+ case '$gte':
92
+ return this.buildComparisonCondition(key, value, alias, '>=');
93
+ case '$lt':
94
+ return this.buildComparisonCondition(key, value, alias, '<');
95
+ case '$lte':
96
+ return this.buildComparisonCondition(key, value, alias, '<=');
97
+ case '$and':
98
+ case '$or':
99
+ return this.buildNestedLogicalCondition(operator, value, alias, model);
100
+ default:
101
+ return '';
102
+ }
103
+ }
104
+ buildSimpleCondition(key, value, alias, operator = '=') {
105
+ const formattedValue = this.formatValue(value);
106
+ return `${alias}.${key} ${operator} ${formattedValue}`;
107
+ }
108
+ buildInCondition(key, values, alias) {
109
+ const formattedValues = values.map(val => this.formatValue(val)).join(', ');
110
+ return `${alias}.${key} IN (${formattedValues})`;
111
+ }
112
+ buildNotInCondition(key, values, alias) {
113
+ const formattedValues = values.map(val => this.formatValue(val)).join(', ');
114
+ return `${alias}.${key} NOT IN (${formattedValues})`;
115
+ }
116
+ buildLikeCondition(key, value, alias) {
117
+ return `${alias}.${key} LIKE '${value}'`;
118
+ }
119
+ buildComparisonCondition(key, value, alias, operator) {
120
+ return `${alias}.${key} ${operator} ${value}`;
121
+ }
122
+ buildNestedLogicalCondition(operator, value, alias, model) {
123
+ const conditions = value.map((cond) => this.build(cond, alias, model));
124
+ const logicalOp = this.extractLogicalOperator(operator);
125
+ return this.wrapWithLogicalOperator(conditions, logicalOp);
126
+ }
127
+ wrapWithLogicalOperator(conditions, operator) {
128
+ return `(${conditions.join(` ${operator} `)})`;
129
+ }
130
+ extractValueFromValueObject(value) {
131
+ if (extendsFrom(ValueObject, value?.constructor?.prototype)) {
132
+ return value.getValue();
133
+ }
134
+ return value;
135
+ }
136
+ formatValue(value) {
137
+ return (typeof value === 'string') ? `'${value}'` : value;
138
+ }
139
+ findRelationship(key, model) {
140
+ const entity = this.entityStorage.get(model);
141
+ return entity?.relations?.find(rel => rel.propertyKey === key);
142
+ }
143
+ isScalarValue(value) {
144
+ return typeof value !== 'object' || value === null;
145
+ }
146
+ isArrayValue(key, value) {
147
+ return !this.OPERATORS.includes(key) && Array.isArray(value);
148
+ }
149
+ isLogicalOperator(key) {
150
+ return ['$or', '$and'].includes(key);
151
+ }
152
+ extractLogicalOperator(key) {
153
+ return key.toUpperCase().replace('$', '');
154
+ }
155
+ trackLastNonOperatorKey(key) {
156
+ if (!this.OPERATORS.includes(key)) {
157
+ this.lastKeyNotOperator = key;
158
+ }
159
+ }
160
+ }
@@ -0,0 +1,39 @@
1
+ import { Statement, Relationship, FilterQuery, DriverInterface } from '../driver/driver.interface';
2
+ import { EntityStorage, Options } from '../domain/entities';
3
+ import { SqlConditionBuilder } from './sql-condition-builder';
4
+ import { SqlColumnManager } from './sql-column-manager';
5
+ import { ModelTransformer } from './model-transformer';
6
+ import { LoggerService } from '@cheetah.js/core';
7
+ export declare class SqlJoinManager<T> {
8
+ private entityStorage;
9
+ private statements;
10
+ private entity;
11
+ private model;
12
+ private driver;
13
+ private logger;
14
+ private conditionBuilder;
15
+ private columnManager;
16
+ private modelTransformer;
17
+ private getOriginalColumnsCallback;
18
+ private getAliasCallback;
19
+ constructor(entityStorage: EntityStorage, statements: Statement<T>, entity: Options, model: new () => T, driver: DriverInterface, logger: LoggerService, conditionBuilder: SqlConditionBuilder<T>, columnManager: SqlColumnManager, modelTransformer: ModelTransformer, getOriginalColumnsCallback: () => string[], getAliasCallback: (tableName: string) => string);
20
+ addJoinForRelationshipPath(relationshipPath: string): void;
21
+ applyJoin(relationShip: Relationship<any>, value: FilterQuery<any>, alias: string): string;
22
+ handleSelectJoin(entities: any, models: any): Promise<void>;
23
+ getPathForSelectJoin(selectJoin: Statement<any>): string[] | null;
24
+ private buildJoinOn;
25
+ private addJoinedJoin;
26
+ private addSelectJoin;
27
+ private processSelectJoin;
28
+ private getIds;
29
+ private updateJoinWhere;
30
+ private updateJoinColumns;
31
+ private attachJoinResults;
32
+ private setValueByPath;
33
+ private getPathForSelectJoinRecursive;
34
+ private findIdRecursively;
35
+ private getFkKey;
36
+ private getTableName;
37
+ private getPrimaryKey;
38
+ private formatValue;
39
+ }
@@ -0,0 +1,224 @@
1
+ export class SqlJoinManager {
2
+ constructor(entityStorage, statements, entity, model, driver, logger, conditionBuilder, columnManager, modelTransformer, getOriginalColumnsCallback, getAliasCallback) {
3
+ this.entityStorage = entityStorage;
4
+ this.statements = statements;
5
+ this.entity = entity;
6
+ this.model = model;
7
+ this.driver = driver;
8
+ this.logger = logger;
9
+ this.conditionBuilder = conditionBuilder;
10
+ this.columnManager = columnManager;
11
+ this.modelTransformer = modelTransformer;
12
+ this.getOriginalColumnsCallback = getOriginalColumnsCallback;
13
+ this.getAliasCallback = getAliasCallback;
14
+ }
15
+ addJoinForRelationshipPath(relationshipPath) {
16
+ const relationshipNames = relationshipPath.split('.');
17
+ let currentEntity = this.entity;
18
+ let currentAlias = this.statements.alias;
19
+ let statement = this.statements.strategy === 'joined' ? this.statements.join : this.statements.selectJoin;
20
+ let nameAliasProperty = this.statements.strategy === 'joined' ? 'joinAlias' : 'alias';
21
+ relationshipNames.forEach((relationshipName, index) => {
22
+ const relationship = currentEntity.relations.find(rel => rel.propertyKey === relationshipName);
23
+ if (!relationship) {
24
+ throw new Error(`Relationship "${relationshipName}" not found in entity`);
25
+ }
26
+ const isLastRelationship = index === relationshipNames.length - 1;
27
+ if (index === (relationshipNames.length - 2 >= 0 ? relationshipNames.length - 2 : 0)) {
28
+ const join = statement?.find(j => j.joinProperty === relationshipName);
29
+ if (join) {
30
+ currentAlias = join[nameAliasProperty];
31
+ }
32
+ }
33
+ if (relationship.relation === 'many-to-one' && isLastRelationship) {
34
+ this.applyJoin(relationship, {}, currentAlias);
35
+ statement = this.statements.strategy === 'joined' ? this.statements.join : this.statements.selectJoin;
36
+ currentAlias = statement[statement.length - 1][nameAliasProperty];
37
+ }
38
+ currentEntity = this.entityStorage.get(relationship.entity());
39
+ });
40
+ }
41
+ applyJoin(relationShip, value, alias) {
42
+ const { tableName, schema } = this.getTableName();
43
+ const { tableName: joinTableName, schema: joinSchema, hooks: joinHooks, } = this.entityStorage.get(relationShip.entity()) || {
44
+ tableName: relationShip.entity().name.toLowerCase(),
45
+ schema: 'public',
46
+ };
47
+ const originPrimaryKey = this.getPrimaryKey();
48
+ const joinAlias = this.getAliasCallback(joinTableName);
49
+ const joinWhere = this.conditionBuilder.build(value, joinAlias, relationShip.entity());
50
+ const on = this.buildJoinOn(relationShip, alias, joinAlias, originPrimaryKey);
51
+ if (this.statements.strategy === 'joined') {
52
+ this.addJoinedJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, on, alias, schema, tableName, joinHooks);
53
+ }
54
+ else {
55
+ this.addSelectJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, originPrimaryKey, alias, joinHooks);
56
+ }
57
+ return joinWhere;
58
+ }
59
+ async handleSelectJoin(entities, models) {
60
+ if (!this.statements.selectJoin || this.statements.selectJoin.length === 0) {
61
+ return;
62
+ }
63
+ for (const join of this.statements.selectJoin.reverse()) {
64
+ await this.processSelectJoin(join, entities, models);
65
+ }
66
+ return models;
67
+ }
68
+ getPathForSelectJoin(selectJoin) {
69
+ const path = this.getPathForSelectJoinRecursive(selectJoin);
70
+ return path.reverse();
71
+ }
72
+ buildJoinOn(relationShip, alias, joinAlias, originPrimaryKey) {
73
+ let on = '';
74
+ switch (relationShip.relation) {
75
+ case "one-to-many":
76
+ on = `${joinAlias}."${this.getFkKey(relationShip)}" = ${alias}."${originPrimaryKey}"`;
77
+ break;
78
+ case "many-to-one":
79
+ on = `${alias}."${relationShip.columnName}" = ${joinAlias}."${this.getFkKey(relationShip)}"`;
80
+ break;
81
+ }
82
+ return on;
83
+ }
84
+ addJoinedJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, on, alias, schema, tableName, joinHooks) {
85
+ this.statements.join = this.statements.join || [];
86
+ this.statements.join.push({
87
+ joinAlias: joinAlias,
88
+ joinTable: joinTableName,
89
+ joinSchema: joinSchema || 'public',
90
+ joinWhere: joinWhere,
91
+ joinProperty: relationShip.propertyKey,
92
+ originAlias: alias,
93
+ originSchema: schema,
94
+ originTable: tableName,
95
+ propertyKey: relationShip.propertyKey,
96
+ joinEntity: relationShip.entity(),
97
+ type: 'LEFT',
98
+ on,
99
+ originalEntity: relationShip.originalEntity,
100
+ hooks: joinHooks,
101
+ });
102
+ }
103
+ addSelectJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, originPrimaryKey, alias, joinHooks) {
104
+ this.statements.selectJoin = this.statements.selectJoin || [];
105
+ this.statements.selectJoin.push({
106
+ statement: 'select',
107
+ columns: this.getOriginalColumnsCallback().filter(column => column.startsWith(`${relationShip.propertyKey}`)).map(column => column.split('.')[1]) || [],
108
+ table: `"${joinSchema || 'public'}"."${joinTableName}"`,
109
+ alias: joinAlias,
110
+ where: joinWhere,
111
+ joinProperty: relationShip.propertyKey,
112
+ fkKey: this.getFkKey(relationShip),
113
+ primaryKey: originPrimaryKey,
114
+ originAlias: alias,
115
+ originProperty: relationShip.propertyKey,
116
+ joinEntity: relationShip.entity(),
117
+ originEntity: relationShip.originalEntity,
118
+ hooks: joinHooks,
119
+ });
120
+ }
121
+ async processSelectJoin(join, entities, models) {
122
+ let ids = this.getIds(join, entities, models);
123
+ if (Array.isArray(ids)) {
124
+ ids = ids.map((id) => this.formatValue(id)).join(', ');
125
+ }
126
+ this.updateJoinWhere(join, ids);
127
+ this.updateJoinColumns(join);
128
+ const child = await this.driver.executeStatement(join);
129
+ this.logger.debug(`SQL: ${child.sql} [${Date.now() - child.startTime}ms]`);
130
+ this.attachJoinResults(join, child, models);
131
+ }
132
+ getIds(join, entities, models) {
133
+ let ids = entities[`${join.originAlias}_${join.primaryKey}`];
134
+ if (typeof ids === 'undefined') {
135
+ const selectJoined = this.statements.selectJoin.find(j => j.joinEntity === join.originEntity);
136
+ if (!selectJoined) {
137
+ return undefined;
138
+ }
139
+ ids = this.findIdRecursively(models, selectJoined, join);
140
+ }
141
+ return ids;
142
+ }
143
+ updateJoinWhere(join, ids) {
144
+ if (join.where) {
145
+ join.where = `${join.where} AND ${join.alias}."${join.fkKey}" IN (${ids})`;
146
+ }
147
+ else {
148
+ join.where = `${join.alias}."${join.fkKey}" IN (${ids})`;
149
+ }
150
+ }
151
+ updateJoinColumns(join) {
152
+ if (join.columns && join.columns.length > 0) {
153
+ join.columns = join.columns.map((column) => `${join.alias}."${column}" as "${join.alias}_${column}"`);
154
+ }
155
+ else {
156
+ join.columns = this.columnManager.getColumnsForEntity(join.joinEntity, join.alias);
157
+ }
158
+ }
159
+ attachJoinResults(join, child, models) {
160
+ const property = this.entityStorage.get(this.model).relations.find((rel) => rel.propertyKey === join.joinProperty);
161
+ const values = child.query.rows.map((row) => this.modelTransformer.transform(join.joinEntity, join, row));
162
+ const path = this.getPathForSelectJoin(join);
163
+ this.setValueByPath(models, path, property?.type === Array ? [...values] : values[0]);
164
+ }
165
+ setValueByPath(obj, path, value) {
166
+ let currentObj = obj;
167
+ for (let i = 0; i < path.length - 1; i++) {
168
+ const key = path[i];
169
+ currentObj[key] = currentObj[key] || {};
170
+ currentObj = currentObj[key];
171
+ }
172
+ currentObj[path[path.length - 1]] = value;
173
+ }
174
+ getPathForSelectJoinRecursive(selectJoin) {
175
+ const originJoin = this.statements.selectJoin.find(j => j.joinEntity === selectJoin.originEntity);
176
+ let pathInJoin = [];
177
+ if (!originJoin) {
178
+ return [selectJoin.joinProperty];
179
+ }
180
+ if (originJoin.originEntity !== this.statements.originEntity) {
181
+ pathInJoin = this.getPathForSelectJoinRecursive(originJoin);
182
+ }
183
+ return [selectJoin.joinProperty, ...pathInJoin];
184
+ }
185
+ findIdRecursively(models, selectJoined, join) {
186
+ let ids = models[selectJoined.originProperty][join.primaryKey];
187
+ if (typeof ids === 'undefined') {
188
+ const nextSelectJoined = this.statements.selectJoin.find(j => j.joinEntity === selectJoined.originEntity);
189
+ if (nextSelectJoined) {
190
+ ids = this.findIdRecursively(models, nextSelectJoined, join);
191
+ }
192
+ }
193
+ return ids;
194
+ }
195
+ getFkKey(relationShip) {
196
+ if (typeof relationShip.fkKey === 'undefined') {
197
+ return 'id';
198
+ }
199
+ if (typeof relationShip.fkKey === 'string') {
200
+ return relationShip.fkKey;
201
+ }
202
+ const match = /\.(?<propriedade>[\w]+)/.exec(relationShip.fkKey.toString());
203
+ const propertyKey = match ? match.groups.propriedade : '';
204
+ const entity = this.entityStorage.get(relationShip.entity());
205
+ const property = Object.entries(entity.properties).find(([key, _value]) => key === propertyKey)?.[1];
206
+ return property.options.columnName;
207
+ }
208
+ getTableName() {
209
+ const tableName = this.entity.tableName || this.model.name.toLowerCase();
210
+ const schema = this.entity.schema || 'public';
211
+ return { tableName, schema };
212
+ }
213
+ getPrimaryKey() {
214
+ for (const prop in this.entity.properties) {
215
+ if (this.entity.properties[prop].options.isPrimary) {
216
+ return prop;
217
+ }
218
+ }
219
+ return 'id';
220
+ }
221
+ formatValue(value) {
222
+ return (typeof value === 'string') ? `'${value}'` : value;
223
+ }
224
+ }