tina4-nodejs 3.2.1 → 3.5.0
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/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/packages/cli/src/bin.ts +13 -1
- package/packages/cli/src/commands/migrate.ts +19 -5
- package/packages/cli/src/commands/migrateCreate.ts +29 -28
- package/packages/cli/src/commands/migrateRollback.ts +59 -0
- package/packages/cli/src/commands/migrateStatus.ts +62 -0
- package/packages/core/public/js/tina4-dev-admin.min.js +1 -1
- package/packages/core/public/js/tina4js.min.js +47 -0
- package/packages/core/src/auth.ts +44 -10
- package/packages/core/src/devAdmin.ts +14 -16
- package/packages/core/src/index.ts +10 -3
- package/packages/core/src/middleware.ts +232 -2
- package/packages/core/src/queue.ts +127 -25
- package/packages/core/src/queueBackends/mongoBackend.ts +223 -0
- package/packages/core/src/request.ts +3 -3
- package/packages/core/src/router.ts +115 -51
- package/packages/core/src/server.ts +47 -3
- package/packages/core/src/session.ts +29 -1
- package/packages/core/src/sessionHandlers/databaseHandler.ts +134 -0
- package/packages/core/src/sessionHandlers/redisHandler.ts +230 -0
- package/packages/core/src/types.ts +12 -6
- package/packages/core/src/websocket.ts +11 -2
- package/packages/core/src/websocketConnection.ts +4 -2
- package/packages/frond/src/engine.ts +66 -1
- package/packages/orm/src/autoCrud.ts +17 -12
- package/packages/orm/src/baseModel.ts +99 -21
- package/packages/orm/src/database.ts +197 -69
- package/packages/orm/src/databaseResult.ts +207 -0
- package/packages/orm/src/index.ts +6 -3
- package/packages/orm/src/migration.ts +296 -71
- package/packages/orm/src/model.ts +1 -0
- package/packages/orm/src/types.ts +1 -0
|
@@ -8,11 +8,16 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
|
|
|
8
8
|
const routes: RouteDefinition[] = [];
|
|
9
9
|
|
|
10
10
|
for (const { definition } of models) {
|
|
11
|
-
const { tableName, fields, softDelete, tableFilter } = definition;
|
|
11
|
+
const { tableName, fields, softDelete, tableFilter, fieldMapping } = definition;
|
|
12
12
|
const basePath = `/api/${tableName}`;
|
|
13
|
+
const mapping = fieldMapping ?? {};
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
+
// Helper to get DB column name for a JS property name
|
|
16
|
+
const getDbCol = (prop: string): string => mapping[prop] ?? prop;
|
|
17
|
+
|
|
18
|
+
// Find primary key field (JS property name) and its DB column name
|
|
15
19
|
const pkField = Object.entries(fields).find(([, def]) => def.primaryKey)?.[0] ?? "id";
|
|
20
|
+
const pkColumn = getDbCol(pkField);
|
|
16
21
|
|
|
17
22
|
// Build extra WHERE conditions for soft delete and table filter
|
|
18
23
|
const extraConditions: string[] = [];
|
|
@@ -65,7 +70,7 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
|
|
|
65
70
|
},
|
|
66
71
|
handler: async (req: Tina4Request, res: Tina4Response) => {
|
|
67
72
|
const adapter = getAdapter();
|
|
68
|
-
const conditions = [`"${
|
|
73
|
+
const conditions = [`"${pkColumn}" = ?`, ...extraConditions];
|
|
69
74
|
const items = adapter.query(
|
|
70
75
|
`SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
|
|
71
76
|
[req.params.id],
|
|
@@ -112,7 +117,7 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
|
|
|
112
117
|
insertFields.push(["is_deleted", 0]);
|
|
113
118
|
}
|
|
114
119
|
|
|
115
|
-
const columns = insertFields.map(([k]) => `"${k}"`).join(", ");
|
|
120
|
+
const columns = insertFields.map(([k]) => `"${getDbCol(k)}"`).join(", ");
|
|
116
121
|
const placeholders = insertFields.map(() => "?").join(", ");
|
|
117
122
|
const values = insertFields.map(([, v]) => v);
|
|
118
123
|
|
|
@@ -123,7 +128,7 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
|
|
|
123
128
|
|
|
124
129
|
const id = result.lastInsertRowid;
|
|
125
130
|
const created = adapter.query(
|
|
126
|
-
`SELECT * FROM "${tableName}" WHERE "${
|
|
131
|
+
`SELECT * FROM "${tableName}" WHERE "${pkColumn}" = ?`,
|
|
127
132
|
[id],
|
|
128
133
|
);
|
|
129
134
|
|
|
@@ -155,7 +160,7 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
|
|
|
155
160
|
const adapter = getAdapter();
|
|
156
161
|
|
|
157
162
|
// Check exists (respect soft delete)
|
|
158
|
-
const conditions = [`"${
|
|
163
|
+
const conditions = [`"${pkColumn}" = ?`, ...extraConditions];
|
|
159
164
|
const existing = adapter.query(
|
|
160
165
|
`SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
|
|
161
166
|
[req.params.id],
|
|
@@ -172,16 +177,16 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
|
|
|
172
177
|
return;
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
const setClause = updateFields.map(([k]) => `"${k}" = ?`).join(", ");
|
|
180
|
+
const setClause = updateFields.map(([k]) => `"${getDbCol(k)}" = ?`).join(", ");
|
|
176
181
|
const values = [...updateFields.map(([, v]) => v), req.params.id];
|
|
177
182
|
|
|
178
183
|
adapter.execute(
|
|
179
|
-
`UPDATE "${tableName}" SET ${setClause} WHERE "${
|
|
184
|
+
`UPDATE "${tableName}" SET ${setClause} WHERE "${pkColumn}" = ?`,
|
|
180
185
|
values,
|
|
181
186
|
);
|
|
182
187
|
|
|
183
188
|
const updated = adapter.query(
|
|
184
|
-
`SELECT * FROM "${tableName}" WHERE "${
|
|
189
|
+
`SELECT * FROM "${tableName}" WHERE "${pkColumn}" = ?`,
|
|
185
190
|
[req.params.id],
|
|
186
191
|
);
|
|
187
192
|
|
|
@@ -200,7 +205,7 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
|
|
|
200
205
|
handler: async (req: Tina4Request, res: Tina4Response) => {
|
|
201
206
|
const adapter = getAdapter();
|
|
202
207
|
|
|
203
|
-
const conditions = [`"${
|
|
208
|
+
const conditions = [`"${pkColumn}" = ?`, ...extraConditions];
|
|
204
209
|
const existing = adapter.query(
|
|
205
210
|
`SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
|
|
206
211
|
[req.params.id],
|
|
@@ -212,13 +217,13 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
|
|
|
212
217
|
|
|
213
218
|
if (softDelete) {
|
|
214
219
|
adapter.execute(
|
|
215
|
-
`UPDATE "${tableName}" SET is_deleted = 1 WHERE "${
|
|
220
|
+
`UPDATE "${tableName}" SET is_deleted = 1 WHERE "${pkColumn}" = ?`,
|
|
216
221
|
[req.params.id],
|
|
217
222
|
);
|
|
218
223
|
res.json({ message: "Deleted (soft)", data: existing[0] });
|
|
219
224
|
} else {
|
|
220
225
|
adapter.execute(
|
|
221
|
-
`DELETE FROM "${tableName}" WHERE "${
|
|
226
|
+
`DELETE FROM "${tableName}" WHERE "${pkColumn}" = ?`,
|
|
222
227
|
[req.params.id],
|
|
223
228
|
);
|
|
224
229
|
res.json({ message: "Deleted", data: existing[0] });
|
|
@@ -15,6 +15,7 @@ import type { DatabaseAdapter, FieldDefinition, RelationshipDefinition } from ".
|
|
|
15
15
|
* static hasOne = [{ model: "Profile", foreignKey: "user_id" }];
|
|
16
16
|
* static hasMany = [{ model: "Post", foreignKey: "author_id" }];
|
|
17
17
|
* static _db = "secondary";
|
|
18
|
+
* static fieldMapping = { firstName: "first_name", lastName: "last_name" };
|
|
18
19
|
* }
|
|
19
20
|
*/
|
|
20
21
|
export class BaseModel {
|
|
@@ -27,6 +28,14 @@ export class BaseModel {
|
|
|
27
28
|
static belongsTo?: RelationshipDefinition[];
|
|
28
29
|
static _db?: string;
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Maps JS property names to database column names.
|
|
33
|
+
* Example: { firstName: "first_name" } means the JS property `firstName`
|
|
34
|
+
* corresponds to the database column `first_name`.
|
|
35
|
+
* Properties not listed here use the property name as-is.
|
|
36
|
+
*/
|
|
37
|
+
static fieldMapping: Record<string, string> = {};
|
|
38
|
+
|
|
30
39
|
/** Instance data */
|
|
31
40
|
[key: string]: unknown;
|
|
32
41
|
|
|
@@ -35,12 +44,52 @@ export class BaseModel {
|
|
|
35
44
|
|
|
36
45
|
constructor(data?: Record<string, unknown>) {
|
|
37
46
|
if (data) {
|
|
47
|
+
const ModelClass = this.constructor as typeof BaseModel;
|
|
48
|
+
const reverseMapping = ModelClass.getReverseMapping();
|
|
38
49
|
for (const [key, value] of Object.entries(data)) {
|
|
39
|
-
this
|
|
50
|
+
// If this DB column has a mapping, use the JS property name instead
|
|
51
|
+
const jsProp = reverseMapping[key] ?? key;
|
|
52
|
+
this[jsProp] = value;
|
|
40
53
|
}
|
|
41
54
|
}
|
|
42
55
|
}
|
|
43
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Get the database column name for a JS property.
|
|
59
|
+
* Returns the mapped column name, or the property name if no mapping exists.
|
|
60
|
+
*/
|
|
61
|
+
static getDbColumn(prop: string): string {
|
|
62
|
+
return this.fieldMapping[prop] ?? prop;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get all instance data converted to database column names.
|
|
67
|
+
* Uses fieldMapping to translate JS property names to DB column names.
|
|
68
|
+
*/
|
|
69
|
+
getDbData(): Record<string, unknown> {
|
|
70
|
+
const ModelClass = this.constructor as typeof BaseModel;
|
|
71
|
+
const result: Record<string, unknown> = {};
|
|
72
|
+
for (const key of Object.keys(ModelClass.fields)) {
|
|
73
|
+
if (this[key] !== undefined) {
|
|
74
|
+
const dbCol = ModelClass.getDbColumn(key);
|
|
75
|
+
result[dbCol] = this[key];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get the reverse mapping (DB column → JS property).
|
|
83
|
+
* Flips fieldMapping so that { firstName: "first_name" } becomes { first_name: "firstName" }.
|
|
84
|
+
*/
|
|
85
|
+
static getReverseMapping(): Record<string, string> {
|
|
86
|
+
const reverse: Record<string, string> = {};
|
|
87
|
+
for (const [jsProp, dbCol] of Object.entries(this.fieldMapping)) {
|
|
88
|
+
reverse[dbCol] = jsProp;
|
|
89
|
+
}
|
|
90
|
+
return reverse;
|
|
91
|
+
}
|
|
92
|
+
|
|
44
93
|
/**
|
|
45
94
|
* Get the database adapter for this model.
|
|
46
95
|
*/
|
|
@@ -52,12 +101,19 @@ export class BaseModel {
|
|
|
52
101
|
}
|
|
53
102
|
|
|
54
103
|
/**
|
|
55
|
-
* Get the primary key field name.
|
|
104
|
+
* Get the primary key field name (JS property name).
|
|
56
105
|
*/
|
|
57
106
|
protected static getPkField(): string {
|
|
58
107
|
return Object.entries(this.fields).find(([, def]) => def.primaryKey)?.[0] ?? "id";
|
|
59
108
|
}
|
|
60
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Get the primary key database column name (applies fieldMapping).
|
|
112
|
+
*/
|
|
113
|
+
protected static getPkColumn(): string {
|
|
114
|
+
return this.getDbColumn(this.getPkField());
|
|
115
|
+
}
|
|
116
|
+
|
|
61
117
|
/**
|
|
62
118
|
* Find a record by primary key.
|
|
63
119
|
* @param id Primary key value.
|
|
@@ -67,7 +123,8 @@ export class BaseModel {
|
|
|
67
123
|
const ModelClass = this as unknown as typeof BaseModel & (new (data?: Record<string, unknown>) => T);
|
|
68
124
|
const db = ModelClass.getDb();
|
|
69
125
|
const pk = ModelClass.getPkField();
|
|
70
|
-
|
|
126
|
+
const pkCol = ModelClass.getPkColumn();
|
|
127
|
+
let sql = `SELECT * FROM "${ModelClass.tableName}" WHERE "${pkCol}" = ?`;
|
|
71
128
|
|
|
72
129
|
if (ModelClass.softDelete) {
|
|
73
130
|
sql += ` AND is_deleted = 0`;
|
|
@@ -129,6 +186,7 @@ export class BaseModel {
|
|
|
129
186
|
const ModelClass = this.constructor as typeof BaseModel;
|
|
130
187
|
const db = ModelClass.getDb();
|
|
131
188
|
const pk = ModelClass.getPkField();
|
|
189
|
+
const pkCol = ModelClass.getPkColumn();
|
|
132
190
|
const pkValue = this[pk];
|
|
133
191
|
this._relCache = {}; // Clear relationship cache on save
|
|
134
192
|
|
|
@@ -139,17 +197,17 @@ export class BaseModel {
|
|
|
139
197
|
);
|
|
140
198
|
if (updateFields.length === 0) return;
|
|
141
199
|
|
|
142
|
-
const setClause = updateFields.map(([k]) => `"${k}" = ?`).join(", ");
|
|
200
|
+
const setClause = updateFields.map(([k]) => `"${ModelClass.getDbColumn(k)}" = ?`).join(", ");
|
|
143
201
|
const values = [...updateFields.map(([k]) => this[k]), pkValue];
|
|
144
202
|
|
|
145
|
-
db.execute(`UPDATE "${ModelClass.tableName}" SET ${setClause} WHERE "${
|
|
203
|
+
db.execute(`UPDATE "${ModelClass.tableName}" SET ${setClause} WHERE "${pkCol}" = ?`, values);
|
|
146
204
|
} else {
|
|
147
205
|
// Insert
|
|
148
206
|
const insertFields = Object.entries(ModelClass.fields).filter(
|
|
149
207
|
([name, def]) => !(def.primaryKey && def.autoIncrement) && this[name] !== undefined,
|
|
150
208
|
);
|
|
151
209
|
|
|
152
|
-
const columns = insertFields.map(([k]) => `"${k}"`).join(", ");
|
|
210
|
+
const columns = insertFields.map(([k]) => `"${ModelClass.getDbColumn(k)}"`).join(", ");
|
|
153
211
|
const placeholders = insertFields.map(() => "?").join(", ");
|
|
154
212
|
const values = insertFields.map(([k]) => this[k]);
|
|
155
213
|
|
|
@@ -171,6 +229,7 @@ export class BaseModel {
|
|
|
171
229
|
const ModelClass = this.constructor as typeof BaseModel;
|
|
172
230
|
const db = ModelClass.getDb();
|
|
173
231
|
const pk = ModelClass.getPkField();
|
|
232
|
+
const pkCol = ModelClass.getPkColumn();
|
|
174
233
|
const pkValue = this[pk];
|
|
175
234
|
|
|
176
235
|
if (pkValue === undefined || pkValue === null) {
|
|
@@ -179,13 +238,13 @@ export class BaseModel {
|
|
|
179
238
|
|
|
180
239
|
if (ModelClass.softDelete) {
|
|
181
240
|
db.execute(
|
|
182
|
-
`UPDATE "${ModelClass.tableName}" SET is_deleted = 1 WHERE "${
|
|
241
|
+
`UPDATE "${ModelClass.tableName}" SET is_deleted = 1 WHERE "${pkCol}" = ?`,
|
|
183
242
|
[pkValue],
|
|
184
243
|
);
|
|
185
244
|
this.is_deleted = 1;
|
|
186
245
|
} else {
|
|
187
246
|
db.execute(
|
|
188
|
-
`DELETE FROM "${ModelClass.tableName}" WHERE "${
|
|
247
|
+
`DELETE FROM "${ModelClass.tableName}" WHERE "${pkCol}" = ?`,
|
|
189
248
|
[pkValue],
|
|
190
249
|
);
|
|
191
250
|
}
|
|
@@ -317,7 +376,13 @@ export class BaseModel {
|
|
|
317
376
|
if (db.tableExists(this.tableName)) return;
|
|
318
377
|
|
|
319
378
|
if (typeof db.createTable === "function") {
|
|
320
|
-
|
|
379
|
+
// Remap field keys to DB column names if fieldMapping is defined
|
|
380
|
+
const mappedFields: Record<string, FieldDefinition> = {};
|
|
381
|
+
for (const [fieldName, def] of Object.entries(this.fields)) {
|
|
382
|
+
const dbCol = this.getDbColumn(fieldName);
|
|
383
|
+
mappedFields[dbCol] = def;
|
|
384
|
+
}
|
|
385
|
+
db.createTable(this.tableName, mappedFields);
|
|
321
386
|
} else {
|
|
322
387
|
// Fallback: build SQL manually
|
|
323
388
|
const typeMap: Record<string, string> = {
|
|
@@ -331,9 +396,10 @@ export class BaseModel {
|
|
|
331
396
|
};
|
|
332
397
|
|
|
333
398
|
const colDefs: string[] = [];
|
|
334
|
-
for (const [
|
|
399
|
+
for (const [fieldName, def] of Object.entries(this.fields)) {
|
|
400
|
+
const dbCol = this.getDbColumn(fieldName);
|
|
335
401
|
const sqlType = typeMap[def.type] || "TEXT";
|
|
336
|
-
const parts = [`"${
|
|
402
|
+
const parts = [`"${dbCol}" ${sqlType}`];
|
|
337
403
|
if (def.primaryKey) parts.push("PRIMARY KEY");
|
|
338
404
|
if (def.autoIncrement) parts.push("AUTOINCREMENT");
|
|
339
405
|
if (def.required && !def.primaryKey) parts.push("NOT NULL");
|
|
@@ -382,6 +448,7 @@ export class BaseModel {
|
|
|
382
448
|
const ModelClass = this.constructor as typeof BaseModel;
|
|
383
449
|
const db = ModelClass.getDb();
|
|
384
450
|
const pk = ModelClass.getPkField();
|
|
451
|
+
const pkCol = ModelClass.getPkColumn();
|
|
385
452
|
const pkValue = this[pk];
|
|
386
453
|
|
|
387
454
|
if (pkValue === undefined || pkValue === null) {
|
|
@@ -389,7 +456,7 @@ export class BaseModel {
|
|
|
389
456
|
}
|
|
390
457
|
|
|
391
458
|
db.execute(
|
|
392
|
-
`DELETE FROM "${ModelClass.tableName}" WHERE "${
|
|
459
|
+
`DELETE FROM "${ModelClass.tableName}" WHERE "${pkCol}" = ?`,
|
|
393
460
|
[pkValue],
|
|
394
461
|
);
|
|
395
462
|
}
|
|
@@ -405,6 +472,7 @@ export class BaseModel {
|
|
|
405
472
|
|
|
406
473
|
const db = ModelClass.getDb();
|
|
407
474
|
const pk = ModelClass.getPkField();
|
|
475
|
+
const pkCol = ModelClass.getPkColumn();
|
|
408
476
|
const pkValue = this[pk];
|
|
409
477
|
|
|
410
478
|
if (pkValue === undefined || pkValue === null) {
|
|
@@ -412,7 +480,7 @@ export class BaseModel {
|
|
|
412
480
|
}
|
|
413
481
|
|
|
414
482
|
db.execute(
|
|
415
|
-
`UPDATE "${ModelClass.tableName}" SET is_deleted = 0 WHERE "${
|
|
483
|
+
`UPDATE "${ModelClass.tableName}" SET is_deleted = 0 WHERE "${pkCol}" = ?`,
|
|
416
484
|
[pkValue],
|
|
417
485
|
);
|
|
418
486
|
this.is_deleted = 0;
|
|
@@ -570,15 +638,19 @@ export class BaseModel {
|
|
|
570
638
|
relatedClass: typeof BaseModel & (new (data?: Record<string, unknown>) => R),
|
|
571
639
|
foreignKey: string,
|
|
572
640
|
): R | null {
|
|
573
|
-
|
|
641
|
+
// foreignKey is a DB column name — resolve to JS property name on this model
|
|
642
|
+
const ModelClass = this.constructor as typeof BaseModel;
|
|
643
|
+
const reverseMap = ModelClass.getReverseMapping();
|
|
644
|
+
const fkProp = reverseMap[foreignKey] ?? foreignKey;
|
|
645
|
+
const fkValue = this[fkProp];
|
|
574
646
|
|
|
575
647
|
if (fkValue === undefined || fkValue === null) {
|
|
576
648
|
return null;
|
|
577
649
|
}
|
|
578
650
|
|
|
579
651
|
const db = relatedClass.getDb();
|
|
580
|
-
const
|
|
581
|
-
let sql = `SELECT * FROM "${relatedClass.tableName}" WHERE "${
|
|
652
|
+
const relatedPkCol = relatedClass.getPkColumn();
|
|
653
|
+
let sql = `SELECT * FROM "${relatedClass.tableName}" WHERE "${relatedPkCol}" = ?`;
|
|
582
654
|
if (relatedClass.softDelete) {
|
|
583
655
|
sql += ` AND is_deleted = 0`;
|
|
584
656
|
}
|
|
@@ -727,10 +799,12 @@ export class BaseModel {
|
|
|
727
799
|
relatedClass._eagerLoad(related, nested);
|
|
728
800
|
}
|
|
729
801
|
|
|
730
|
-
// Group by FK
|
|
802
|
+
// Group by FK — fk is a DB column name, resolve to JS property name on the related model
|
|
803
|
+
const relatedReverseMap = relatedClass.getReverseMapping();
|
|
804
|
+
const fkProp = relatedReverseMap[fk] ?? fk;
|
|
731
805
|
const grouped: Record<string, BaseModel[]> = {};
|
|
732
806
|
for (const record of related) {
|
|
733
|
-
const fkVal = String(record[
|
|
807
|
+
const fkVal = String(record[fkProp]);
|
|
734
808
|
if (!grouped[fkVal]) grouped[fkVal] = [];
|
|
735
809
|
grouped[fkVal].push(record);
|
|
736
810
|
}
|
|
@@ -745,16 +819,20 @@ export class BaseModel {
|
|
|
745
819
|
}
|
|
746
820
|
}
|
|
747
821
|
} else if (relType === "belongsTo") {
|
|
822
|
+
// fk is a DB column name on the current model — resolve to JS property name
|
|
823
|
+
const ownerReverseMap = ModelClass.getReverseMapping();
|
|
824
|
+
const fkProp = ownerReverseMap[fk] ?? fk;
|
|
748
825
|
const fkValues = [...new Set(
|
|
749
826
|
instances
|
|
750
|
-
.map((inst) => inst[
|
|
827
|
+
.map((inst) => inst[fkProp])
|
|
751
828
|
.filter((v) => v !== undefined && v !== null),
|
|
752
829
|
)];
|
|
753
830
|
if (fkValues.length === 0) continue;
|
|
754
831
|
|
|
755
832
|
const relatedPk = relatedClass.getPkField();
|
|
833
|
+
const relatedPkCol = relatedClass.getPkColumn();
|
|
756
834
|
const placeholders = fkValues.map(() => "?").join(",");
|
|
757
|
-
let sql = `SELECT * FROM "${relatedClass.tableName}" WHERE "${
|
|
835
|
+
let sql = `SELECT * FROM "${relatedClass.tableName}" WHERE "${relatedPkCol}" IN (${placeholders})`;
|
|
758
836
|
if (relatedClass.softDelete) {
|
|
759
837
|
sql += ` AND is_deleted = 0`;
|
|
760
838
|
}
|
|
@@ -772,7 +850,7 @@ export class BaseModel {
|
|
|
772
850
|
}
|
|
773
851
|
|
|
774
852
|
for (const inst of instances) {
|
|
775
|
-
const fkVal = inst[
|
|
853
|
+
const fkVal = inst[fkProp];
|
|
776
854
|
inst._relCache[relName] = fkVal !== undefined && fkVal !== null
|
|
777
855
|
? lookup[String(fkVal)] ?? null
|
|
778
856
|
: null;
|