stabilize-orm 1.0.6 → 1.0.7

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.
@@ -1,96 +0,0 @@
1
- import { DBClient } from "./client";
2
- import { Cache } from "./cache";
3
- import { type QueryHint } from "./types";
4
-
5
- export class QueryBuilder<T> {
6
- private table: string;
7
- private selectFields: string[] = ["*"];
8
- private joins: string[] = [];
9
- private whereConditions: string[] = [];
10
- private whereParams: any[] = [];
11
- private orderByClause: string | null = null;
12
- private limitValue: number | null = null;
13
- private offsetValue: number | null = null;
14
- private hints: QueryHint[] = [];
15
-
16
- constructor(table: string) {
17
- this.table = table;
18
- }
19
-
20
- select(...fields: string[]): QueryBuilder<T> {
21
- this.selectFields = fields.length > 0 ? fields : ["*"];
22
- return this;
23
- }
24
-
25
- where(condition: string, ...params: any[]): QueryBuilder<T> {
26
- this.whereConditions.push(condition);
27
- this.whereParams.push(...params);
28
- return this;
29
- }
30
-
31
- join(table: string, condition: string): QueryBuilder<T> {
32
- this.joins.push(`LEFT JOIN ${table} ON ${condition}`);
33
- return this;
34
- }
35
-
36
- orderBy(clause: string): QueryBuilder<T> {
37
- this.orderByClause = clause;
38
- return this;
39
- }
40
-
41
- limit(limit: number): QueryBuilder<T> {
42
- this.limitValue = limit;
43
- return this;
44
- }
45
-
46
- offset(offset: number): QueryBuilder<T> {
47
- this.offsetValue = offset;
48
- return this;
49
- }
50
-
51
- hint(hint: QueryHint): QueryBuilder<T> {
52
- this.hints.push(hint);
53
- return this;
54
- }
55
-
56
- build(): { query: string; params: any[] } {
57
- let query = `SELECT ${this.selectFields.join(", ")} FROM ${this.table}`;
58
- if (this.hints.length > 0) {
59
- const hintStr = this.hints.map((h) => `${h.type}(${h.value})`).join(" ");
60
- query = `SELECT ${hintStr} ${this.selectFields.join(", ")} FROM ${this.table}`;
61
- }
62
- if (this.joins.length > 0) {
63
- query += " " + this.joins.join(" ");
64
- }
65
- if (this.whereConditions.length > 0) {
66
- query += " WHERE " + this.whereConditions.join(" AND ");
67
- }
68
- if (this.orderByClause) {
69
- query += ` ORDER BY ${this.orderByClause}`;
70
- }
71
- if (this.limitValue !== null) {
72
- query += ` LIMIT ${this.limitValue}`;
73
- }
74
- if (this.offsetValue !== null) {
75
- query += ` OFFSET ${this.offsetValue}`;
76
- }
77
- return { query, params: this.whereParams };
78
- }
79
-
80
- async execute(
81
- client: DBClient,
82
- cache?: Cache,
83
- cacheKey?: string,
84
- ): Promise<T[]> {
85
- const { query, params } = this.build();
86
- if (cache && cacheKey) {
87
- const cached = await cache.get<T[]>(cacheKey);
88
- if (cached) return cached;
89
- }
90
- const results = await client.query<T>(query, params);
91
- if (cache && cacheKey && results.length > 0) {
92
- await cache.set(cacheKey, results);
93
- }
94
- return results;
95
- }
96
- }
package/src/repository.ts DELETED
@@ -1,565 +0,0 @@
1
- import { Cache } from "./cache";
2
- import { DBClient } from "./client";
3
- import { ConsoleLogger, type Logger } from "./logger";
4
- import { QueryBuilder } from "./query-builder";
5
- // DBConfig added to the type imports
6
- import {
7
- DBType,
8
- RelationType,
9
- StabilizeError,
10
- type CacheConfig,
11
- type DBConfig,
12
- } from "./types";
13
- import {
14
- ModelKey,
15
- ColumnKey,
16
- ValidatorKey,
17
- RelationKey,
18
- SoftDeleteKey,
19
- } from "./decorators";
20
-
21
- export class Repository<T> {
22
- // Client remains DBClient type
23
- private client: DBClient;
24
- private cache: Cache | null;
25
- private table: string;
26
- private columns: Record<string, { name: string; type: string }>;
27
- private validators: Record<string, string[]>;
28
- private relations: Record<
29
- string,
30
- {
31
- type: RelationType;
32
- targetModel: () => any;
33
- foreignKey?: string;
34
- inverseKey?: string;
35
- joinTable?: string;
36
- }
37
- >;
38
- private softDeleteField: string | null;
39
- private logger: Logger;
40
-
41
- constructor(
42
- client: DBClient,
43
- model: new (...args: any[]) => T,
44
- cacheConfig: CacheConfig = { enabled: false, ttl: 60 },
45
- logger: Logger = new ConsoleLogger(),
46
- ) {
47
- this.client = client;
48
- this.cache = cacheConfig.enabled ? new Cache(cacheConfig, logger) : null;
49
- this.table = Reflect.getMetadata(ModelKey, model) || "";
50
- this.columns = Reflect.getMetadata(ColumnKey, model.prototype) || {};
51
- this.validators = Reflect.getMetadata(ValidatorKey, model.prototype) || {};
52
- this.relations = Reflect.getMetadata(RelationKey, model.prototype) || {};
53
- this.softDeleteField =
54
- Reflect.getMetadata(SoftDeleteKey, model.prototype) || null;
55
- this.logger = logger;
56
- }
57
-
58
- private getDBType(): DBType {
59
- return (this.client as any).config.type;
60
- }
61
-
62
- private validate(entity: Partial<T>) {
63
- for (const [key, rules] of Object.entries(this.validators)) {
64
- const value = (entity as any)[key];
65
- if (
66
- rules.includes("required") &&
67
- (value === undefined || value === null)
68
- ) {
69
- throw new StabilizeError(
70
- `Field ${key} is required`,
71
- "VALIDATION_ERROR",
72
- );
73
- }
74
- if (rules.includes("unique")) {
75
- // Defer unique check to DB
76
- }
77
- }
78
- }
79
-
80
- find(): QueryBuilder<T> {
81
- const qb = new QueryBuilder<T>(this.table);
82
- if (this.softDeleteField) {
83
- qb.where(`${this.softDeleteField} IS NULL`);
84
- }
85
- return qb;
86
- }
87
-
88
- async findOne(
89
- id: number | string,
90
- options: { relations?: string[] } = {},
91
- ): Promise<T | null> {
92
- const start = performance.now();
93
- this.logger.logDebug(`Finding one ${this.table} with ID ${id}`);
94
- const queryBuilder = this.find().where("id = ?", id).limit(1);
95
- if (options.relations) {
96
- for (const rel of options.relations) {
97
- await this.loadRelation(queryBuilder, rel);
98
- }
99
- }
100
- const cacheKey = options.relations
101
- ? `findOne:${this.table}:${id}:${options.relations.join(",")}`
102
- : `findOne:${this.table}:${id}`;
103
- const results = await queryBuilder.execute(
104
- this.client,
105
- this.cache!,
106
- cacheKey,
107
- );
108
- this.logger.logDebug(
109
- `Found ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
110
- );
111
- return results[0] || null;
112
- }
113
-
114
- async create(
115
- entity: Partial<T>,
116
- options: { relations?: string[] } = {},
117
- ): Promise<T> {
118
- const start = performance.now();
119
- this.logger.logDebug(
120
- `Creating ${this.table} with data: ${JSON.stringify(entity)}`,
121
- );
122
- this.validate(entity);
123
- return await this.client.transaction(async () => {
124
- const keys = Object.keys(entity).filter((k) => this.columns[k]);
125
- const columnNames = keys.map((k) => this.columns[k]?.name).join(", ");
126
- const placeholders = keys.map(() => "?").join(", ");
127
- const params = keys.map((k) => (entity as any)[k]);
128
-
129
- let query = `INSERT INTO ${this.table} (${columnNames}) VALUES (${placeholders})`;
130
- let insertedResult: T[] | undefined;
131
- let id: number | string | undefined;
132
-
133
- const dbType = this.getDBType();
134
-
135
- if (dbType === DBType.Postgres) {
136
- query += " RETURNING *";
137
- insertedResult = await this.client.query<T>(query, params);
138
- id = (insertedResult?.[0] as any)?.id;
139
- } else {
140
- await this.client.query(query, params);
141
-
142
- if (dbType === DBType.SQLite) {
143
- id = (
144
- await this.client.query<{ id: number }>(
145
- "SELECT last_insert_rowid() as id",
146
- [],
147
- )
148
- )[0]?.id;
149
- } else if (dbType === DBType.MySQL) {
150
- id = (
151
- await this.client.query<{ id: number }>(
152
- "SELECT LAST_INSERT_ID() as id",
153
- [],
154
- )
155
- )[0]?.id;
156
- }
157
- }
158
-
159
- if (!id)
160
- throw new StabilizeError(
161
- "Failed to retrieve inserted ID",
162
- "INSERT_ERROR",
163
- );
164
-
165
- const cacheKeys = [`find:${this.table}`, `findOne:${this.table}:${id}`];
166
-
167
- const queryBuilder = this.find().where("id = ?", id);
168
- if (options.relations) {
169
- for (const rel of options.relations) {
170
- await this.loadRelation(queryBuilder, rel);
171
- }
172
- }
173
-
174
- // If we are using write-through cache, we fetch the result now and cache it.
175
- if (this.cache) {
176
- await this.cache.invalidate(cacheKeys);
177
- if (this.cache.getStrategy() === "write-through") {
178
- // Fetch the full result for write-through cache, explicitly casting to T.
179
- const result = (insertedResult?.[0] ||
180
- (await queryBuilder.execute(this.client))[0]) as T;
181
- await this.cache.set(`findOne:${this.table}:${id}`, [result], 60);
182
- this.logger.logDebug(
183
- `Created ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
184
- );
185
- return result;
186
- }
187
- }
188
-
189
- const result = (insertedResult?.[0] ||
190
- (await queryBuilder.execute(this.client))[0]) as T;
191
- this.logger.logDebug(
192
- `Created ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
193
- );
194
- return result;
195
- });
196
- }
197
-
198
- async bulkCreate(
199
- entities: Partial<T>[],
200
- options: { relations?: string[]; batchSize?: number } = {},
201
- ): Promise<T[]> {
202
- const start = performance.now();
203
- this.logger.logDebug(
204
- `Bulk creating ${entities.length} ${this.table} entities`,
205
- );
206
- if (!entities.length) return [];
207
- const batchSize = options.batchSize || 1000;
208
- entities.forEach((entity) => this.validate(entity));
209
-
210
- const dbType = this.getDBType();
211
- const results: T[] = [];
212
-
213
- for (let i = 0; i < entities.length; i += batchSize) {
214
- const batch = entities.slice(i, i + batchSize);
215
- await this.client.transaction(async () => {
216
- const keys = Object.keys(batch[0]!).filter((k) => this.columns[k]);
217
- const columnNames = keys.map((k) => this.columns[k]?.name).join(", ");
218
- const placeholders = `(${keys.map(() => "?").join(", ")})`;
219
- let query = `INSERT INTO ${this.table} (${columnNames}) VALUES ${batch.map(() => placeholders).join(", ")}`;
220
- const params = batch.flatMap((entity) =>
221
- keys.map((k) => (entity as any)[k]),
222
- );
223
-
224
- let batchResults: T[] = [];
225
- let ids: (number | string)[] = [];
226
-
227
- if (dbType === DBType.Postgres) {
228
- // PostgreSQL supports RETURNING * for multiple inserts
229
- query += " RETURNING *";
230
- batchResults = await this.client.query<T>(query, params);
231
- ids = batchResults.map((r) => (r as any).id);
232
- } else {
233
- // SQLite/MySQL batch insert. ID retrieval remains complex.
234
- await this.client.query(query, params);
235
-
236
- // Unreliable ID retrieval kept for consistency, but should be reviewed for concurrency
237
- ids = (
238
- await this.client.query<{ id: number }>(
239
- `SELECT id FROM ${this.table} ORDER BY id DESC LIMIT ?`,
240
- [batch.length],
241
- )
242
- ).map((row) => row.id);
243
- }
244
-
245
- const cacheKeys = ids
246
- .map((id) => `findOne:${this.table}:${id}`)
247
- .concat(`find:${this.table}`);
248
-
249
- // If batchResults is empty (non-PostgreSQL), we need to fetch the full records.
250
- if (ids.length > 0 && batchResults.length === 0) {
251
- const queryBuilder = this.find().where(
252
- `id IN (${ids.map(() => "?").join(", ")})`,
253
- ...ids,
254
- );
255
- if (options.relations) {
256
- for (const rel of options.relations) {
257
- await this.loadRelation(queryBuilder, rel);
258
- }
259
- }
260
- batchResults = await queryBuilder.execute(this.client);
261
- }
262
-
263
- if (this.cache) {
264
- await this.cache.invalidate(cacheKeys);
265
- if (this.cache.getStrategy() === "write-through") {
266
- await this.cache.set(
267
- `bulkCreate:${this.table}:${ids.join(",")}`,
268
- batchResults,
269
- 60,
270
- );
271
- }
272
- }
273
- results.push(...batchResults);
274
- });
275
- }
276
- this.logger.logDebug(
277
- `Bulk created ${results.length} ${this.table} entities in ${(performance.now() - start).toFixed(2)}ms`,
278
- );
279
- return results;
280
- }
281
-
282
- async update(id: number | string, entity: Partial<T>): Promise<T> {
283
- const start = performance.now();
284
- this.logger.logDebug(`Updating ${this.table} with ID ${id}`);
285
- this.validate(entity);
286
- return await this.client.transaction(async () => {
287
- const keys = Object.keys(entity).filter((k) => this.columns[k]);
288
- const setClause = keys
289
- .map((k) => `${this.columns[k]?.name} = ?`)
290
- .join(", ");
291
- const query = `UPDATE ${this.table} SET ${setClause} WHERE id = ?${this.softDeleteField ? ` AND ${this.softDeleteField} IS NULL` : ""}`;
292
- const params = [...keys.map((k) => (entity as any)[k]), id];
293
- await this.client.query(query, params);
294
-
295
- let result: T;
296
-
297
- const cacheKeys = [`find:${this.table}`, `findOne:${this.table}:${id}`];
298
- if (this.cache) {
299
- await this.cache.invalidate(cacheKeys);
300
- if (this.cache.getStrategy() === "write-through") {
301
- const queryBuilder = this.find().where("id = ?", id);
302
- // Fetch the result and explicitly cast to T to satisfy the return type.
303
- result = (await queryBuilder.execute(this.client))[0] as T;
304
- await this.cache.set(`findOne:${this.table}:${id}`, [result], 60);
305
- this.logger.logDebug(
306
- `Updated ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
307
- );
308
- return result;
309
- }
310
- }
311
-
312
- // Fetch the result and explicitly cast to T. Logically, since we just updated the record, it must exist.
313
- result = (await this.findOne(id)) as T;
314
- this.logger.logDebug(
315
- `Updated ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
316
- );
317
- return result;
318
- });
319
- }
320
-
321
- async bulkUpdate(
322
- updates: { where: { condition: string; params: any[] }; set: Partial<T> }[],
323
- options: { batchSize?: number } = {},
324
- ): Promise<void> {
325
- const start = performance.now();
326
- this.logger.logDebug(
327
- `Bulk updating ${updates.length} ${this.table} entities`,
328
- );
329
- if (!updates.length) return;
330
- const batchSize = options.batchSize || 1000;
331
- updates.forEach((update) => this.validate(update.set));
332
-
333
- for (let i = 0; i < updates.length; i += batchSize) {
334
- const batch = updates.slice(i, i + batchSize);
335
- await this.client.transaction(async () => {
336
- for (const update of batch) {
337
- const keys = Object.keys(update.set).filter((k) => this.columns[k]);
338
- const setClause = keys
339
- .map((k) => `${this.columns[k]?.name} = ?`)
340
- .join(", ");
341
- const query = `UPDATE ${this.table} SET ${setClause} WHERE ${update.where.condition}${this.softDeleteField ? ` AND ${this.softDeleteField} IS NULL` : ""}`;
342
- const params = [
343
- ...keys.map((k) => (update.set as any)[k]),
344
- ...update.where.params,
345
- ];
346
- await this.client.query(query, params);
347
- }
348
- if (this.cache) {
349
- await this.cache.invalidatePattern(`find:${this.table}:*`);
350
- }
351
- });
352
- }
353
- this.logger.logDebug(
354
- `Bulk updated ${updates.length} ${this.table} entities in ${(performance.now() - start).toFixed(2)}ms`,
355
- );
356
- }
357
-
358
- async upsert(entity: Partial<T>, keys: string[]): Promise<T> {
359
- const start = performance.now();
360
- this.logger.logDebug(
361
- `Upserting ${this.table} with keys: ${keys.join(", ")}`,
362
- );
363
- this.validate(entity);
364
- return await this.client.transaction(async () => {
365
- const dbType = this.getDBType();
366
- const columns = Object.keys(entity).filter((k) => this.columns[k]);
367
- const columnNames = columns.map((k) => this.columns[k]?.name).join(", ");
368
- const placeholders = columns.map(() => "?").join(", ");
369
- const updateClause = columns
370
- .filter((c) => !keys.includes(c))
371
- .map((c) => `${this.columns[c]?.name} = ?`)
372
- .join(", ");
373
-
374
- let query: string;
375
-
376
- if (dbType === DBType.SQLite) {
377
- query = `INSERT INTO ${this.table} (${columnNames}) VALUES (${placeholders}) ON CONFLICT(${keys.map((k) => this.columns[k]!.name).join(", ")}) DO UPDATE SET ${updateClause}`;
378
- } else if (dbType === DBType.MySQL) {
379
- query = `INSERT INTO ${this.table} (${columnNames}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updateClause}`;
380
- } else {
381
- // PostgreSQL and default
382
- query = `INSERT INTO ${this.table} (${columnNames}) VALUES (${placeholders}) ON CONFLICT (${keys.map((k) => this.columns[k]!.name).join(", ")}) DO UPDATE SET ${updateClause} RETURNING *`;
383
- }
384
-
385
- const params = [
386
- ...columns.map((k) => (entity as any)[k]),
387
- ...columns
388
- .filter((c) => !keys.includes(c))
389
- .map((k) => (entity as any)[k]),
390
- ];
391
- const results = await this.client.query<T>(query, params);
392
-
393
- let id: number | string | undefined =
394
- (results[0] as any)?.id || (entity as any).id;
395
-
396
- // Fallback ID retrieval for non-PostgreSQL if the driver didn't return it
397
- if (!id && dbType === DBType.SQLite) {
398
- id = (
399
- await this.client.query<{ id: number }>(
400
- "SELECT last_insert_rowid() as id",
401
- [],
402
- )
403
- )[0]?.id;
404
- } else if (!id && dbType === DBType.MySQL) {
405
- id = (
406
- await this.client.query<{ id: number }>(
407
- "SELECT LAST_INSERT_ID() as id",
408
- [],
409
- )
410
- )[0]?.id;
411
- }
412
-
413
- if (!id)
414
- throw new StabilizeError(
415
- "Failed to retrieve upserted ID",
416
- "UPSERT_ERROR",
417
- );
418
-
419
- if (this.cache) {
420
- await this.cache.invalidatePattern(`find:${this.table}:*`);
421
- if (this.cache.getStrategy() === "write-through") {
422
- const queryBuilder = this.find().where("id = ?", id);
423
- const result = (await queryBuilder.execute(this.client))[0] as T;
424
- await this.cache.set(`findOne:${this.table}:${id}`, [result], 60);
425
- this.logger.logDebug(
426
- `Upserted ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
427
- );
428
- return result;
429
- }
430
- }
431
-
432
- const result = (await this.findOne(id)) as T;
433
- this.logger.logDebug(
434
- `Upserted ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
435
- );
436
- return result;
437
- });
438
- }
439
-
440
- async delete(id: number | string): Promise<void> {
441
- const start = performance.now();
442
- this.logger.logDebug(`Deleting ${this.table} with ID ${id}`);
443
- if (this.softDeleteField) {
444
- await this.client.query(
445
- `UPDATE ${this.table} SET ${this.softDeleteField} = ? WHERE id = ?`,
446
- [new Date().toISOString(), id],
447
- );
448
- } else {
449
- await this.client.query(`DELETE FROM ${this.table} WHERE id = ?`, [id]);
450
- }
451
- if (this.cache) {
452
- await this.cache.invalidate([
453
- `find:${this.table}`,
454
- `findOne:${this.table}:${id}`,
455
- ]);
456
- }
457
- this.logger.logDebug(
458
- `Deleted ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
459
- );
460
- }
461
-
462
- async bulkDelete(
463
- ids: (number | string)[],
464
- options: { batchSize?: number } = {},
465
- ): Promise<void> {
466
- const start = performance.now();
467
- this.logger.logDebug(`Bulk deleting ${ids.length} ${this.table} entities`);
468
- if (!ids.length) return;
469
- const batchSize = options.batchSize || 1000;
470
-
471
- for (let i = 0; i < ids.length; i += batchSize) {
472
- const batch = ids.slice(i, i + batchSize);
473
- await this.client.transaction(async () => {
474
- const placeholders = batch.map(() => "?").join(", ");
475
- const query = this.softDeleteField
476
- ? `UPDATE ${this.table} SET ${this.softDeleteField} = ? WHERE id IN (${placeholders})`
477
- : `DELETE FROM ${this.table} WHERE id IN (${placeholders})`;
478
- const params = this.softDeleteField
479
- ? [new Date().toISOString(), ...batch]
480
- : batch;
481
- await this.client.query(query, params);
482
- });
483
- }
484
- if (this.cache) {
485
- await this.cache.invalidatePattern(`find:${this.table}:*`);
486
- }
487
- this.logger.logDebug(
488
- `Bulk deleted ${ids.length} ${this.table} entities in ${(performance.now() - start).toFixed(2)}ms`,
489
- );
490
- }
491
-
492
- async recover(id: number | string): Promise<T> {
493
- const start = performance.now();
494
- this.logger.logDebug(`Recovering ${this.table} with ID ${id}`);
495
- if (!this.softDeleteField) {
496
- throw new StabilizeError(
497
- "Soft delete not enabled for this model",
498
- "RECOVER_ERROR",
499
- );
500
- }
501
- return await this.client.transaction(async () => {
502
- await this.client.query(
503
- `UPDATE ${this.table} SET ${this.softDeleteField} = NULL WHERE id = ?`,
504
- [id],
505
- );
506
- if (this.cache) {
507
- await this.cache.invalidate([
508
- `find:${this.table}`,
509
- `findOne:${this.table}:${id}`,
510
- ]);
511
- }
512
- const result = (await this.findOne(id))!;
513
- this.logger.logDebug(
514
- `Recovered ${this.table} with ID ${id} in ${(performance.now() - start).toFixed(2)}ms`,
515
- );
516
- return result;
517
- });
518
- }
519
-
520
- async rawQuery<T>(query: string, params: any[] = []): Promise<T[]> {
521
- const start = performance.now();
522
- this.logger.logDebug(`Executing raw query: ${query}`);
523
- const result = await this.client.query<T>(query, params);
524
- this.logger.logDebug(
525
- `Raw query completed in ${(performance.now() - start).toFixed(2)}ms`,
526
- );
527
- return result;
528
- }
529
-
530
- private async loadRelation(queryBuilder: QueryBuilder<T>, relation: string) {
531
- this.logger.logDebug(`Loading relation ${relation} for ${this.table}`);
532
- const rel = this.relations[relation];
533
- if (!rel)
534
- throw new StabilizeError(
535
- `Relation ${relation} not found`,
536
- "RELATION_ERROR",
537
- );
538
-
539
- const relatedTable = Reflect.getMetadata(ModelKey, rel.targetModel());
540
- if (
541
- rel.type === RelationType.OneToOne ||
542
- rel.type === RelationType.ManyToOne
543
- ) {
544
- queryBuilder.join(
545
- relatedTable,
546
- `${this.table}.${rel.foreignKey} = ${relatedTable}.id`,
547
- );
548
- } else if (rel.type === RelationType.OneToMany) {
549
- queryBuilder.join(
550
- relatedTable,
551
- `${relatedTable}.${rel.inverseKey} = ${this.table}.id`,
552
- );
553
- } else if (rel.type === RelationType.ManyToMany) {
554
- queryBuilder
555
- .join(
556
- rel.joinTable!,
557
- `${rel.joinTable}.${rel.foreignKey} = ${this.table}.id`,
558
- )
559
- .join(
560
- relatedTable,
561
- `${relatedTable}.id = ${rel.joinTable}.${rel.inverseKey}`,
562
- );
563
- }
564
- }
565
- }