@cheetah.js/orm 0.1.85 → 0.1.87
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/dist/SqlBuilder.d.ts
CHANGED
|
@@ -43,6 +43,12 @@ export declare class SqlBuilder<T> {
|
|
|
43
43
|
executeAndReturnFirst(): Promise<T | undefined>;
|
|
44
44
|
executeAndReturnFirstOrFail(): Promise<T>;
|
|
45
45
|
executeAndReturnAll(): Promise<T[]>;
|
|
46
|
+
private hasOneToManyJoinedJoin;
|
|
47
|
+
private processOneToManyJoinedResult;
|
|
48
|
+
private attachOneToManyRelations;
|
|
49
|
+
private removeDuplicatesByPrimaryKey;
|
|
50
|
+
private getPrimaryKeyName;
|
|
51
|
+
private getPrimaryKeyNameForEntity;
|
|
46
52
|
executeCount(): Promise<number>;
|
|
47
53
|
private logExecution;
|
|
48
54
|
inTransaction<T>(callback: (builder: SqlBuilder<T>) => Promise<T>): Promise<T>;
|
package/dist/SqlBuilder.js
CHANGED
|
@@ -165,11 +165,17 @@ class SqlBuilder {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
async executeAndReturnFirst() {
|
|
168
|
-
|
|
168
|
+
const hasOneToManyJoinedJoin = this.hasOneToManyJoinedJoin();
|
|
169
|
+
if (!hasOneToManyJoinedJoin) {
|
|
170
|
+
this.statements.limit = 1;
|
|
171
|
+
}
|
|
169
172
|
const result = await this.execute();
|
|
170
173
|
if (result.query.rows.length === 0) {
|
|
171
174
|
return undefined;
|
|
172
175
|
}
|
|
176
|
+
if (hasOneToManyJoinedJoin) {
|
|
177
|
+
return this.processOneToManyJoinedResult(result.query.rows);
|
|
178
|
+
}
|
|
173
179
|
const entities = result.query.rows[0];
|
|
174
180
|
const model = await this.modelTransformer.transform(this.model, this.statements, entities);
|
|
175
181
|
this.afterHooks(model);
|
|
@@ -177,11 +183,21 @@ class SqlBuilder {
|
|
|
177
183
|
return model;
|
|
178
184
|
}
|
|
179
185
|
async executeAndReturnFirstOrFail() {
|
|
180
|
-
|
|
186
|
+
const hasOneToManyJoinedJoin = this.hasOneToManyJoinedJoin();
|
|
187
|
+
if (!hasOneToManyJoinedJoin) {
|
|
188
|
+
this.statements.limit = 1;
|
|
189
|
+
}
|
|
181
190
|
const result = await this.execute();
|
|
182
191
|
if (result.query.rows.length === 0) {
|
|
183
192
|
throw new Error('Result not found');
|
|
184
193
|
}
|
|
194
|
+
if (hasOneToManyJoinedJoin) {
|
|
195
|
+
const model = await this.processOneToManyJoinedResult(result.query.rows);
|
|
196
|
+
if (!model) {
|
|
197
|
+
throw new Error('Result not found');
|
|
198
|
+
}
|
|
199
|
+
return model;
|
|
200
|
+
}
|
|
185
201
|
const entities = result.query.rows[0];
|
|
186
202
|
const model = await this.modelTransformer.transform(this.model, this.statements, entities);
|
|
187
203
|
this.afterHooks(model);
|
|
@@ -203,6 +219,70 @@ class SqlBuilder {
|
|
|
203
219
|
}
|
|
204
220
|
return results;
|
|
205
221
|
}
|
|
222
|
+
hasOneToManyJoinedJoin() {
|
|
223
|
+
if (!this.statements.join || this.statements.join.length === 0) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
if (this.statements.strategy !== 'joined') {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return this.statements.join.some(join => {
|
|
230
|
+
const relationship = this.entity.relations.find(rel => rel.propertyKey === join.joinProperty);
|
|
231
|
+
return relationship?.relation === 'one-to-many';
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
async processOneToManyJoinedResult(rows) {
|
|
235
|
+
const primaryKey = this.getPrimaryKeyName();
|
|
236
|
+
const alias = this.statements.alias;
|
|
237
|
+
const primaryKeyColumn = `${alias}_${primaryKey}`;
|
|
238
|
+
const firstRowPrimaryKeyValue = rows[0][primaryKeyColumn];
|
|
239
|
+
const relatedRows = rows.filter(row => row[primaryKeyColumn] === firstRowPrimaryKeyValue);
|
|
240
|
+
const model = this.modelTransformer.transform(this.model, this.statements, relatedRows[0]);
|
|
241
|
+
this.afterHooks(model);
|
|
242
|
+
this.attachOneToManyRelations(model, relatedRows);
|
|
243
|
+
return model;
|
|
244
|
+
}
|
|
245
|
+
attachOneToManyRelations(model, rows) {
|
|
246
|
+
if (!this.statements.join) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
for (const join of this.statements.join) {
|
|
250
|
+
const relationship = this.entity.relations.find(rel => rel.propertyKey === join.joinProperty);
|
|
251
|
+
if (relationship?.relation === 'one-to-many') {
|
|
252
|
+
const joinedModels = rows.map(row => this.modelTransformer.transform(join.joinEntity, { alias: join.joinAlias }, row));
|
|
253
|
+
const uniqueModels = this.removeDuplicatesByPrimaryKey(joinedModels, join.joinEntity);
|
|
254
|
+
model[join.joinProperty] = uniqueModels;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
removeDuplicatesByPrimaryKey(models, entityClass) {
|
|
259
|
+
const entity = this.entityStorage.get(entityClass);
|
|
260
|
+
if (!entity) {
|
|
261
|
+
return models;
|
|
262
|
+
}
|
|
263
|
+
const primaryKey = this.getPrimaryKeyNameForEntity(entity);
|
|
264
|
+
const seen = new Set();
|
|
265
|
+
const unique = [];
|
|
266
|
+
for (const model of models) {
|
|
267
|
+
const id = model[primaryKey];
|
|
268
|
+
if (id && !seen.has(id)) {
|
|
269
|
+
seen.add(id);
|
|
270
|
+
unique.push(model);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return unique;
|
|
274
|
+
}
|
|
275
|
+
getPrimaryKeyName() {
|
|
276
|
+
return this.getPrimaryKeyNameForEntity(this.entity);
|
|
277
|
+
}
|
|
278
|
+
getPrimaryKeyNameForEntity(entity) {
|
|
279
|
+
for (const prop in entity.properties) {
|
|
280
|
+
if (entity.properties[prop].options.isPrimary) {
|
|
281
|
+
return prop;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return 'id';
|
|
285
|
+
}
|
|
206
286
|
async executeCount() {
|
|
207
287
|
const result = await this.execute();
|
|
208
288
|
if (result.query.rows.length === 0) {
|
|
@@ -19,27 +19,28 @@ class SqlJoinManager {
|
|
|
19
19
|
const relationshipNames = relationshipPath.split('.');
|
|
20
20
|
let currentEntity = this.entity;
|
|
21
21
|
let currentAlias = this.statements.alias;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
relationshipNames.forEach((relationshipName, index) => {
|
|
25
|
-
const relationship = currentEntity.relations.find(rel => rel.propertyKey === relationshipName);
|
|
22
|
+
for (const relationshipName of relationshipNames) {
|
|
23
|
+
const relationship = currentEntity.relations.find((rel) => rel.propertyKey === relationshipName);
|
|
26
24
|
if (!relationship) {
|
|
27
|
-
throw new Error(`Relationship "${relationshipName}" not found in entity`);
|
|
25
|
+
throw new Error(`Relationship "${relationshipName}" not found in entity "${currentEntity.tableName}"`);
|
|
28
26
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
statement = this.statements.strategy === 'joined' ? this.statements.join : this.statements.selectJoin;
|
|
39
|
-
currentAlias = statement[statement.length - 1][nameAliasProperty];
|
|
27
|
+
const statement = this.statements.strategy === 'joined'
|
|
28
|
+
? this.statements.join
|
|
29
|
+
: this.statements.selectJoin;
|
|
30
|
+
const nameAliasProperty = this.statements.strategy === 'joined' ? 'joinAlias' : 'alias';
|
|
31
|
+
const existingJoin = statement?.find((j) => j.joinProperty === relationshipName && j.originAlias === currentAlias);
|
|
32
|
+
if (existingJoin) {
|
|
33
|
+
currentAlias = existingJoin[nameAliasProperty];
|
|
34
|
+
currentEntity = this.entityStorage.get(relationship.entity());
|
|
35
|
+
continue;
|
|
40
36
|
}
|
|
37
|
+
this.applyJoin(relationship, {}, currentAlias);
|
|
38
|
+
const newStatement = this.statements.strategy === 'joined'
|
|
39
|
+
? this.statements.join
|
|
40
|
+
: this.statements.selectJoin;
|
|
41
|
+
currentAlias = newStatement[newStatement.length - 1][nameAliasProperty];
|
|
41
42
|
currentEntity = this.entityStorage.get(relationship.entity());
|
|
42
|
-
}
|
|
43
|
+
}
|
|
43
44
|
}
|
|
44
45
|
applyJoin(relationShip, value, alias) {
|
|
45
46
|
const { tableName, schema } = this.getTableName();
|
|
@@ -45,30 +45,30 @@ export declare abstract class Repository<T extends BaseEntity> {
|
|
|
45
45
|
* });
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
|
-
find(options: RepositoryFindOptions<T>): Promise<T[]>;
|
|
48
|
+
find<Hint extends string = never>(options: RepositoryFindOptions<T, Hint>): Promise<T[]>;
|
|
49
49
|
/**
|
|
50
50
|
* Finds a single entity matching the given criteria.
|
|
51
51
|
* Returns undefined if not found.
|
|
52
52
|
*/
|
|
53
|
-
findOne(options: RepositoryFindOneOptions<T>): Promise<T | undefined>;
|
|
53
|
+
findOne<Hint extends string = never>(options: RepositoryFindOneOptions<T, Hint>): Promise<T | undefined>;
|
|
54
54
|
/**
|
|
55
55
|
* Finds a single entity matching the given criteria.
|
|
56
56
|
* Throws an error if not found.
|
|
57
57
|
*/
|
|
58
|
-
findOneOrFail(options: RepositoryFindOneOptions<T>): Promise<T>;
|
|
58
|
+
findOneOrFail<Hint extends string = never>(options: RepositoryFindOneOptions<T, Hint>): Promise<T>;
|
|
59
59
|
/**
|
|
60
60
|
* Finds all entities with optional filtering.
|
|
61
61
|
*/
|
|
62
|
-
findAll(options?: Omit<RepositoryFindOptions<T>, 'where'>): Promise<T[]>;
|
|
62
|
+
findAll<Hint extends string = never>(options?: Omit<RepositoryFindOptions<T>, 'where'>): Promise<T[]>;
|
|
63
63
|
/**
|
|
64
64
|
* Finds an entity by its primary key.
|
|
65
65
|
*/
|
|
66
|
-
findById(id: number | string): Promise<T | undefined>;
|
|
66
|
+
findById<Hint extends string = never>(id: number | string, options?: Omit<RepositoryFindOneOptions<T, Hint>, 'where'>): Promise<T | undefined>;
|
|
67
67
|
/**
|
|
68
68
|
* Finds an entity by its primary key.
|
|
69
69
|
* Throws an error if not found.
|
|
70
70
|
*/
|
|
71
|
-
findByIdOrFail(id: number | string): Promise<T>;
|
|
71
|
+
findByIdOrFail<Hint extends string = never>(id: number | string, options?: Omit<RepositoryFindOneOptions<T, Hint>, 'where'>): Promise<T>;
|
|
72
72
|
/**
|
|
73
73
|
* Creates a new entity.
|
|
74
74
|
*/
|
|
@@ -112,10 +112,10 @@ export declare abstract class Repository<T extends BaseEntity> {
|
|
|
112
112
|
/**
|
|
113
113
|
* Find options for repository queries.
|
|
114
114
|
*/
|
|
115
|
-
export type RepositoryFindOptions<T> = FindOptions<T> & {
|
|
115
|
+
export type RepositoryFindOptions<T, Hint extends string = never> = FindOptions<T, Hint> & {
|
|
116
116
|
where?: FilterQuery<T>;
|
|
117
117
|
};
|
|
118
118
|
/**
|
|
119
119
|
* Find one options for repository queries.
|
|
120
120
|
*/
|
|
121
|
-
export type RepositoryFindOneOptions<T> = Omit<RepositoryFindOptions<T>, 'limit' | 'offset'>;
|
|
121
|
+
export type RepositoryFindOneOptions<T, Hint extends string = never> = Omit<RepositoryFindOptions<T, Hint>, 'limit' | 'offset'>;
|
|
@@ -98,15 +98,15 @@ class Repository {
|
|
|
98
98
|
/**
|
|
99
99
|
* Finds an entity by its primary key.
|
|
100
100
|
*/
|
|
101
|
-
async findById(id) {
|
|
102
|
-
return this.findOne({ where: { id } });
|
|
101
|
+
async findById(id, options) {
|
|
102
|
+
return this.findOne({ where: { id }, ...options });
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* Finds an entity by its primary key.
|
|
106
106
|
* Throws an error if not found.
|
|
107
107
|
*/
|
|
108
|
-
async findByIdOrFail(id) {
|
|
109
|
-
return this.findOneOrFail({ where: { id } });
|
|
108
|
+
async findByIdOrFail(id, options) {
|
|
109
|
+
return this.findOneOrFail({ where: { id }, ...options });
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* Creates a new entity.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cheetah.js/orm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.87",
|
|
4
4
|
"description": "A simple ORM for Cheetah.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"bun",
|
|
56
56
|
"value-object"
|
|
57
57
|
],
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "f7ded7894ef2218a131a9275756477b43d0ae992"
|
|
59
59
|
}
|