orchid-orm 1.3.15 → 1.4.16
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/CHANGELOG.md +20 -0
- package/dist/index.d.ts +59 -54
- package/dist/index.esm.js +777 -547
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +776 -546
- package/dist/index.js.map +1 -1
- package/jest-setup.ts +4 -0
- package/package.json +8 -4
- package/src/appCodeUpdater/appCodeUpdater.ts +19 -0
- package/src/appCodeUpdater/fileChanges.ts +41 -0
- package/src/appCodeUpdater/testUtils.ts +31 -0
- package/src/appCodeUpdater/tsUtils.ts +137 -0
- package/src/appCodeUpdater/updateMainFile.test.ts +230 -0
- package/src/appCodeUpdater/updateMainFile.ts +163 -0
- package/src/appCodeUpdater/updateTableFile.ts +19 -0
- package/src/index.ts +5 -1
- package/src/orm.test.ts +13 -13
- package/src/orm.ts +21 -21
- package/src/relations/belongsTo.test.ts +1 -1
- package/src/relations/belongsTo.ts +291 -186
- package/src/relations/hasAndBelongsToMany.test.ts +1 -1
- package/src/relations/hasAndBelongsToMany.ts +292 -218
- package/src/relations/hasMany.test.ts +16 -10
- package/src/relations/hasMany.ts +243 -172
- package/src/relations/hasOne.test.ts +10 -10
- package/src/relations/hasOne.ts +211 -138
- package/src/relations/relations.ts +85 -77
- package/src/relations/utils.ts +154 -4
- package/src/repo.test.ts +29 -29
- package/src/repo.ts +6 -6
- package/src/{model.test.ts → table.test.ts} +15 -15
- package/src/{model.ts → table.ts} +17 -17
- package/src/test-utils/test-db.ts +15 -15
- package/src/test-utils/{test-models.ts → test-tables.ts} +42 -42
- package/src/transaction.test.ts +1 -1
- package/src/transaction.ts +4 -4
- package/src/utils.ts +9 -0
- package/tsconfig.json +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BelongsTo, BelongsToInfo, makeBelongsToMethod } from './belongsTo';
|
|
2
2
|
import { HasOne, HasOneInfo, makeHasOneMethod } from './hasOne';
|
|
3
|
-
import {
|
|
3
|
+
import { DbTable, Table, TableClass, TableClasses } from '../table';
|
|
4
4
|
import { OrchidORM } from '../orm';
|
|
5
5
|
import {
|
|
6
6
|
Query,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
defaultsKey,
|
|
14
14
|
relationQueryKey,
|
|
15
15
|
EmptyObject,
|
|
16
|
+
VirtualColumn,
|
|
16
17
|
} from 'pqb';
|
|
17
18
|
import { HasMany, HasManyInfo, makeHasManyMethod } from './hasMany';
|
|
18
19
|
import {
|
|
@@ -25,7 +26,7 @@ import { getSourceRelation, getThroughRelation } from './utils';
|
|
|
25
26
|
export interface RelationThunkBase {
|
|
26
27
|
type: string;
|
|
27
28
|
returns: 'one' | 'many';
|
|
28
|
-
fn():
|
|
29
|
+
fn(): TableClass;
|
|
29
30
|
options: BaseRelation['options'];
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -36,8 +37,7 @@ export type RelationThunks = Record<string, RelationThunk>;
|
|
|
36
37
|
export type RelationData = {
|
|
37
38
|
returns: 'one' | 'many';
|
|
38
39
|
method(params: Record<string, unknown>): Query;
|
|
39
|
-
|
|
40
|
-
nestedUpdate: BaseRelation['nestedUpdate'];
|
|
40
|
+
virtualColumn?: VirtualColumn;
|
|
41
41
|
joinQuery(fromQuery: Query, toQuery: Query): Query;
|
|
42
42
|
reverseJoin(fromQuery: Query, toQuery: Query): Query;
|
|
43
43
|
primaryKey: string;
|
|
@@ -45,16 +45,16 @@ export type RelationData = {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export type Relation<
|
|
48
|
-
T extends
|
|
48
|
+
T extends Table,
|
|
49
49
|
Relations extends RelationThunks,
|
|
50
50
|
K extends keyof Relations,
|
|
51
|
-
M extends Query =
|
|
51
|
+
M extends Query = DbTable<ReturnType<Relations[K]['fn']>>,
|
|
52
52
|
Info extends RelationInfo = RelationInfo<T, Relations, Relations[K]>,
|
|
53
53
|
> = {
|
|
54
54
|
type: Relations[K]['type'];
|
|
55
55
|
returns: Relations[K]['returns'];
|
|
56
56
|
key: K;
|
|
57
|
-
|
|
57
|
+
table: M;
|
|
58
58
|
query: M;
|
|
59
59
|
joinQuery(fromQuery: Query, toQuery: Query): Query;
|
|
60
60
|
defaults: Info['populate'];
|
|
@@ -63,19 +63,17 @@ export type Relation<
|
|
|
63
63
|
: M & {
|
|
64
64
|
[defaultsKey]: Record<Info['populate'], true>;
|
|
65
65
|
};
|
|
66
|
-
nestedInsert: BaseRelation['nestedInsert'];
|
|
67
|
-
nestedUpdate: BaseRelation['nestedUpdate'];
|
|
68
66
|
primaryKey: string;
|
|
69
67
|
options: Relations[K]['options'];
|
|
70
68
|
};
|
|
71
69
|
|
|
72
|
-
export type
|
|
70
|
+
export type RelationScopeOrTable<Relation extends RelationThunkBase> =
|
|
73
71
|
Relation['options']['scope'] extends (q: Query) => Query
|
|
74
72
|
? ReturnType<Relation['options']['scope']>
|
|
75
|
-
:
|
|
73
|
+
: DbTable<ReturnType<Relation['fn']>>;
|
|
76
74
|
|
|
77
75
|
export type RelationInfo<
|
|
78
|
-
T extends
|
|
76
|
+
T extends Table = Table,
|
|
79
77
|
Relations extends RelationThunks = RelationThunks,
|
|
80
78
|
Relation extends RelationThunk = RelationThunk,
|
|
81
79
|
> = Relation extends BelongsTo
|
|
@@ -89,11 +87,11 @@ export type RelationInfo<
|
|
|
89
87
|
: never;
|
|
90
88
|
|
|
91
89
|
export type MapRelation<
|
|
92
|
-
T extends
|
|
90
|
+
T extends Table,
|
|
93
91
|
Relations extends RelationThunks,
|
|
94
92
|
RelationName extends keyof Relations,
|
|
95
93
|
Relation extends RelationThunk = Relations[RelationName],
|
|
96
|
-
RelatedQuery extends Query =
|
|
94
|
+
RelatedQuery extends Query = RelationScopeOrTable<Relation>,
|
|
97
95
|
Info extends {
|
|
98
96
|
params: Record<string, unknown>;
|
|
99
97
|
populate: string;
|
|
@@ -116,7 +114,7 @@ export type MapRelation<
|
|
|
116
114
|
Info['chainedDelete']
|
|
117
115
|
>;
|
|
118
116
|
|
|
119
|
-
export type MapRelations<T extends
|
|
117
|
+
export type MapRelations<T extends Table> = 'relations' extends keyof T
|
|
120
118
|
? T['relations'] extends RelationThunks
|
|
121
119
|
? {
|
|
122
120
|
[K in keyof T['relations']]: MapRelation<T, T['relations'], K>;
|
|
@@ -127,48 +125,50 @@ export type MapRelations<T extends Model> = 'relations' extends keyof T
|
|
|
127
125
|
type ApplyRelationData = {
|
|
128
126
|
relationName: string;
|
|
129
127
|
relation: RelationThunk;
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
dbTable: DbTable<TableClass>;
|
|
129
|
+
otherDbTable: DbTable<TableClass>;
|
|
132
130
|
};
|
|
133
131
|
|
|
134
132
|
type DelayedRelations = Map<Query, Record<string, ApplyRelationData[]>>;
|
|
135
133
|
|
|
136
134
|
export const applyRelations = (
|
|
137
135
|
qb: Query,
|
|
138
|
-
|
|
139
|
-
result: OrchidORM<
|
|
136
|
+
tables: Record<string, Table>,
|
|
137
|
+
result: OrchidORM<TableClasses>,
|
|
140
138
|
) => {
|
|
141
|
-
const
|
|
139
|
+
const tableEntries = Object.entries(tables);
|
|
142
140
|
|
|
143
141
|
const delayedRelations: DelayedRelations = new Map();
|
|
144
142
|
|
|
145
|
-
for (const
|
|
146
|
-
const
|
|
143
|
+
for (const name in tables) {
|
|
144
|
+
const table = tables[name] as Table & {
|
|
147
145
|
relations?: RelationThunks;
|
|
148
146
|
};
|
|
149
|
-
if (!('relations' in
|
|
147
|
+
if (!('relations' in table) || typeof table.relations !== 'object')
|
|
150
148
|
continue;
|
|
151
149
|
|
|
152
|
-
const
|
|
153
|
-
for (const relationName in
|
|
154
|
-
const relation =
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
(pair) => pair[1] instanceof
|
|
150
|
+
const dbTable = result[name];
|
|
151
|
+
for (const relationName in table.relations) {
|
|
152
|
+
const relation = table.relations[relationName];
|
|
153
|
+
const otherTableClass = relation.fn();
|
|
154
|
+
const otherTable = tableEntries.find(
|
|
155
|
+
(pair) => pair[1] instanceof otherTableClass,
|
|
158
156
|
);
|
|
159
|
-
if (!
|
|
160
|
-
throw new Error(
|
|
157
|
+
if (!otherTable) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Cannot find table class for class ${otherTableClass.name}`,
|
|
160
|
+
);
|
|
161
161
|
}
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
if (!
|
|
165
|
-
throw new Error(`Cannot find
|
|
162
|
+
const otherTableName = otherTable[0];
|
|
163
|
+
const otherDbTable = result[otherTableName];
|
|
164
|
+
if (!otherDbTable)
|
|
165
|
+
throw new Error(`Cannot find table class by name ${otherTableName}`);
|
|
166
166
|
|
|
167
167
|
const data: ApplyRelationData = {
|
|
168
168
|
relationName,
|
|
169
169
|
relation,
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
dbTable,
|
|
171
|
+
otherDbTable,
|
|
172
172
|
};
|
|
173
173
|
|
|
174
174
|
const options = relation.options as { through?: string; source?: string };
|
|
@@ -176,9 +176,9 @@ export const applyRelations = (
|
|
|
176
176
|
typeof options.through === 'string' &&
|
|
177
177
|
typeof options.source === 'string'
|
|
178
178
|
) {
|
|
179
|
-
const throughRelation = getThroughRelation(
|
|
179
|
+
const throughRelation = getThroughRelation(dbTable, options.through);
|
|
180
180
|
if (!throughRelation) {
|
|
181
|
-
delayRelation(delayedRelations,
|
|
181
|
+
delayRelation(delayedRelations, dbTable, options.through, data);
|
|
182
182
|
continue;
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -189,7 +189,7 @@ export const applyRelations = (
|
|
|
189
189
|
if (!sourceRelation) {
|
|
190
190
|
delayRelation(
|
|
191
191
|
delayedRelations,
|
|
192
|
-
throughRelation.
|
|
192
|
+
throughRelation.table,
|
|
193
193
|
options.source,
|
|
194
194
|
data,
|
|
195
195
|
);
|
|
@@ -209,17 +209,17 @@ export const applyRelations = (
|
|
|
209
209
|
for (const item of value[key]) {
|
|
210
210
|
const { relation } = item;
|
|
211
211
|
|
|
212
|
-
if (item.
|
|
212
|
+
if (item.dbTable.relations[item.relationName as never]) continue;
|
|
213
213
|
|
|
214
|
-
const as = item.
|
|
214
|
+
const as = item.dbTable.definedAs;
|
|
215
215
|
let message = `Cannot define a \`${item.relationName}\` relation on \`${as}\``;
|
|
216
|
-
const
|
|
216
|
+
const table = result[as];
|
|
217
217
|
|
|
218
218
|
const { through, source } = relation.options as {
|
|
219
219
|
through: string;
|
|
220
220
|
source: string;
|
|
221
221
|
};
|
|
222
|
-
const throughRel =
|
|
222
|
+
const throughRel = table.relations[
|
|
223
223
|
through as never
|
|
224
224
|
] as unknown as BaseRelation;
|
|
225
225
|
|
|
@@ -228,10 +228,10 @@ export const applyRelations = (
|
|
|
228
228
|
} else if (
|
|
229
229
|
source &&
|
|
230
230
|
throughRel &&
|
|
231
|
-
!throughRel.
|
|
231
|
+
!throughRel.table.relations[source as never]
|
|
232
232
|
) {
|
|
233
233
|
message += `: cannot find \`${source}\` relation in \`${
|
|
234
|
-
(throughRel.
|
|
234
|
+
(throughRel.table as DbTable<TableClass>).definedAs
|
|
235
235
|
}\` required by the \`source\` option`;
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -243,50 +243,56 @@ export const applyRelations = (
|
|
|
243
243
|
|
|
244
244
|
const delayRelation = (
|
|
245
245
|
delayedRelations: DelayedRelations,
|
|
246
|
-
|
|
246
|
+
table: Query,
|
|
247
247
|
relationName: string,
|
|
248
248
|
data: ApplyRelationData,
|
|
249
249
|
) => {
|
|
250
|
-
let
|
|
251
|
-
if (!
|
|
252
|
-
|
|
253
|
-
delayedRelations.set(
|
|
250
|
+
let tableRelations = delayedRelations.get(table);
|
|
251
|
+
if (!tableRelations) {
|
|
252
|
+
tableRelations = {};
|
|
253
|
+
delayedRelations.set(table, tableRelations);
|
|
254
254
|
}
|
|
255
|
-
if (
|
|
256
|
-
|
|
255
|
+
if (tableRelations[relationName]) {
|
|
256
|
+
tableRelations[relationName].push(data);
|
|
257
257
|
} else {
|
|
258
|
-
|
|
258
|
+
tableRelations[relationName] = [data];
|
|
259
259
|
}
|
|
260
260
|
};
|
|
261
261
|
|
|
262
262
|
const applyRelation = (
|
|
263
263
|
qb: Query,
|
|
264
|
-
{ relationName, relation,
|
|
264
|
+
{ relationName, relation, dbTable, otherDbTable }: ApplyRelationData,
|
|
265
265
|
delayedRelations: DelayedRelations,
|
|
266
266
|
) => {
|
|
267
267
|
const query = (
|
|
268
268
|
relation.options.scope
|
|
269
|
-
? relation.options.scope(
|
|
270
|
-
: (
|
|
269
|
+
? relation.options.scope(otherDbTable)
|
|
270
|
+
: (otherDbTable as unknown as QueryWithTable)
|
|
271
271
|
).as(relationName);
|
|
272
272
|
|
|
273
273
|
const definedAs = (query as unknown as { definedAs?: string }).definedAs;
|
|
274
274
|
if (!definedAs) {
|
|
275
275
|
throw new Error(
|
|
276
|
-
`
|
|
276
|
+
`Table class for table ${query.table} is not attached to db instance`,
|
|
277
277
|
);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
const { type } = relation;
|
|
281
281
|
let data;
|
|
282
282
|
if (type === 'belongsTo') {
|
|
283
|
-
data = makeBelongsToMethod(relation, query);
|
|
283
|
+
data = makeBelongsToMethod(relation, relationName, query);
|
|
284
284
|
} else if (type === 'hasOne') {
|
|
285
|
-
data = makeHasOneMethod(
|
|
285
|
+
data = makeHasOneMethod(dbTable, relation, relationName, query);
|
|
286
286
|
} else if (type === 'hasMany') {
|
|
287
|
-
data = makeHasManyMethod(
|
|
287
|
+
data = makeHasManyMethod(dbTable, relation, relationName, query);
|
|
288
288
|
} else if (type === 'hasAndBelongsToMany') {
|
|
289
|
-
data = makeHasAndBelongsToManyMethod(
|
|
289
|
+
data = makeHasAndBelongsToManyMethod(
|
|
290
|
+
dbTable,
|
|
291
|
+
qb,
|
|
292
|
+
relation,
|
|
293
|
+
relationName,
|
|
294
|
+
query,
|
|
295
|
+
);
|
|
290
296
|
} else {
|
|
291
297
|
throw new Error(`Unknown relation type ${type}`);
|
|
292
298
|
}
|
|
@@ -295,50 +301,52 @@ const applyRelation = (
|
|
|
295
301
|
query._take();
|
|
296
302
|
}
|
|
297
303
|
|
|
298
|
-
|
|
304
|
+
if (data.virtualColumn) {
|
|
305
|
+
dbTable.shape[relationName] = data.virtualColumn;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
makeRelationQuery(dbTable, definedAs, relationName, data);
|
|
299
309
|
|
|
300
|
-
(
|
|
310
|
+
(dbTable.relations as Record<string, unknown>)[relationName] = {
|
|
301
311
|
type,
|
|
302
312
|
key: relationName,
|
|
303
|
-
|
|
313
|
+
table: otherDbTable,
|
|
304
314
|
query,
|
|
305
|
-
nestedInsert: data.nestedInsert,
|
|
306
|
-
nestedUpdate: data.nestedUpdate,
|
|
307
315
|
joinQuery: data.joinQuery,
|
|
308
316
|
primaryKey: data.primaryKey,
|
|
309
317
|
options: relation.options,
|
|
310
318
|
};
|
|
311
319
|
|
|
312
|
-
const
|
|
313
|
-
if (!
|
|
320
|
+
const tableRelations = delayedRelations.get(dbTable);
|
|
321
|
+
if (!tableRelations) return;
|
|
314
322
|
|
|
315
|
-
|
|
323
|
+
tableRelations[relationName]?.forEach((data) => {
|
|
316
324
|
applyRelation(qb, data, delayedRelations);
|
|
317
325
|
});
|
|
318
326
|
};
|
|
319
327
|
|
|
320
328
|
const makeRelationQuery = (
|
|
321
|
-
|
|
329
|
+
table: Query,
|
|
322
330
|
definedAs: string,
|
|
323
331
|
relationName: string,
|
|
324
332
|
data: RelationData,
|
|
325
333
|
) => {
|
|
326
|
-
Object.defineProperty(
|
|
334
|
+
Object.defineProperty(table, relationName, {
|
|
327
335
|
get() {
|
|
328
|
-
const
|
|
336
|
+
const toTable = this.db[definedAs].as(relationName) as Query;
|
|
329
337
|
|
|
330
338
|
if (data.returns === 'one') {
|
|
331
|
-
|
|
339
|
+
toTable._take();
|
|
332
340
|
}
|
|
333
341
|
|
|
334
342
|
const query = this.isSubQuery
|
|
335
|
-
?
|
|
336
|
-
:
|
|
343
|
+
? toTable
|
|
344
|
+
: toTable._whereExists(data.reverseJoin(this, toTable), (q) => q);
|
|
337
345
|
|
|
338
346
|
query.query[relationQueryKey] = {
|
|
339
347
|
relationName,
|
|
340
348
|
sourceQuery: this,
|
|
341
|
-
relationQuery:
|
|
349
|
+
relationQuery: toTable,
|
|
342
350
|
joinQuery: data.joinQuery,
|
|
343
351
|
};
|
|
344
352
|
|
package/src/relations/utils.ts
CHANGED
|
@@ -1,12 +1,162 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CreateCtx,
|
|
3
|
+
MaybeArray,
|
|
4
|
+
pushQueryValue,
|
|
5
|
+
Query,
|
|
6
|
+
QueryBase,
|
|
7
|
+
Relation,
|
|
8
|
+
UpdateCtx,
|
|
9
|
+
UpdateData,
|
|
10
|
+
WhereArg,
|
|
11
|
+
} from 'pqb';
|
|
12
|
+
import { HasOneNestedInsert, HasOneNestedUpdate } from './hasOne';
|
|
13
|
+
import { HasManyNestedInsert, HasManyNestedUpdate } from './hasMany';
|
|
2
14
|
|
|
3
|
-
export
|
|
4
|
-
|
|
15
|
+
export type NestedInsertOneItem = {
|
|
16
|
+
create?: Record<string, unknown>;
|
|
17
|
+
connect?: WhereArg<QueryBase>;
|
|
18
|
+
connectOrCreate?: {
|
|
19
|
+
where: WhereArg<QueryBase>;
|
|
20
|
+
create: Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type NestedInsertManyItems = {
|
|
25
|
+
create?: Record<string, unknown>[];
|
|
26
|
+
connect?: WhereArg<QueryBase>[];
|
|
27
|
+
connectOrCreate?: {
|
|
28
|
+
where: WhereArg<QueryBase>;
|
|
29
|
+
create: Record<string, unknown>;
|
|
30
|
+
}[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type NestedInsertItem = NestedInsertOneItem | NestedInsertManyItems;
|
|
34
|
+
|
|
35
|
+
export type NestedUpdateOneItem = {
|
|
36
|
+
disconnect?: boolean;
|
|
37
|
+
set?: WhereArg<QueryBase>;
|
|
38
|
+
delete?: boolean;
|
|
39
|
+
update?: UpdateData<Query>;
|
|
40
|
+
upsert?: {
|
|
41
|
+
update: UpdateData<Query>;
|
|
42
|
+
create: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
create: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type NestedUpdateManyItems = {
|
|
48
|
+
disconnect?: MaybeArray<WhereArg<QueryBase>>;
|
|
49
|
+
set?: MaybeArray<WhereArg<QueryBase>>;
|
|
50
|
+
delete?: MaybeArray<WhereArg<QueryBase>>;
|
|
51
|
+
update?: {
|
|
52
|
+
where: MaybeArray<WhereArg<QueryBase>>;
|
|
53
|
+
data: UpdateData<Query>;
|
|
54
|
+
};
|
|
55
|
+
create: Record<string, unknown>[];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type NestedUpdateItem = NestedUpdateOneItem | NestedUpdateManyItems;
|
|
59
|
+
|
|
60
|
+
export const getThroughRelation = (table: Query, through: string) => {
|
|
61
|
+
return (table.relations as Record<string, Relation>)[through];
|
|
5
62
|
};
|
|
6
63
|
|
|
7
64
|
export const getSourceRelation = (
|
|
8
65
|
throughRelation: Relation,
|
|
9
66
|
source: string,
|
|
10
67
|
) => {
|
|
11
|
-
return (throughRelation.
|
|
68
|
+
return (throughRelation.table.relations as Record<string, Relation>)[source];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const hasRelationHandleCreate = (
|
|
72
|
+
q: Query,
|
|
73
|
+
ctx: CreateCtx,
|
|
74
|
+
item: Record<string, unknown>,
|
|
75
|
+
rowIndex: number,
|
|
76
|
+
key: string,
|
|
77
|
+
primaryKey: string,
|
|
78
|
+
nestedInsert: HasOneNestedInsert | HasManyNestedInsert,
|
|
79
|
+
) => {
|
|
80
|
+
const value = item[key] as NestedInsertItem;
|
|
81
|
+
if (
|
|
82
|
+
(!value.create ||
|
|
83
|
+
(Array.isArray(value.create) && value.create.length === 0)) &&
|
|
84
|
+
(!value.connect ||
|
|
85
|
+
(Array.isArray(value.connect) && value.connect.length === 0)) &&
|
|
86
|
+
(!value.connectOrCreate ||
|
|
87
|
+
(Array.isArray(value.connectOrCreate) &&
|
|
88
|
+
value.connectOrCreate.length === 0))
|
|
89
|
+
)
|
|
90
|
+
return;
|
|
91
|
+
|
|
92
|
+
const store = ctx as unknown as {
|
|
93
|
+
hasRelation?: Record<string, [number, NestedInsertItem][]>;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (!store.hasRelation) store.hasRelation = {};
|
|
97
|
+
|
|
98
|
+
const values = [rowIndex, value] as [number, NestedInsertItem];
|
|
99
|
+
|
|
100
|
+
if (store.hasRelation[key]) {
|
|
101
|
+
store.hasRelation[key].push(values);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
q.query.wrapInTransaction = true;
|
|
106
|
+
ctx.returnTypeAll = true;
|
|
107
|
+
ctx.requiredReturning[primaryKey] = true;
|
|
108
|
+
|
|
109
|
+
const relationData = [values];
|
|
110
|
+
store.hasRelation[key] = relationData;
|
|
111
|
+
|
|
112
|
+
pushQueryValue(q, 'afterCreate', async (q: Query) => {
|
|
113
|
+
const { resultAll } = ctx;
|
|
114
|
+
return (nestedInsert as HasOneNestedInsert)(
|
|
115
|
+
q,
|
|
116
|
+
relationData.map(([rowIndex, data]) => [
|
|
117
|
+
resultAll[rowIndex],
|
|
118
|
+
data as NestedInsertOneItem,
|
|
119
|
+
]),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const hasRelationHandleUpdate = (
|
|
125
|
+
q: Query,
|
|
126
|
+
ctx: UpdateCtx,
|
|
127
|
+
set: Record<string, unknown>,
|
|
128
|
+
key: string,
|
|
129
|
+
primaryKey: string,
|
|
130
|
+
nestedUpdate: HasOneNestedUpdate | HasManyNestedUpdate,
|
|
131
|
+
) => {
|
|
132
|
+
const value = set[key] as NestedUpdateItem;
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
!value.set &&
|
|
136
|
+
!('upsert' in value) &&
|
|
137
|
+
(!value.disconnect ||
|
|
138
|
+
(Array.isArray(value.disconnect) && value.disconnect.length === 0)) &&
|
|
139
|
+
(!value.delete ||
|
|
140
|
+
(Array.isArray(value.delete) && value.delete.length === 0)) &&
|
|
141
|
+
(!value.update ||
|
|
142
|
+
(Array.isArray(value.update.where) && value.update.where.length === 0)) &&
|
|
143
|
+
(!value.create ||
|
|
144
|
+
(Array.isArray(value.create) && value.create.length === 0))
|
|
145
|
+
)
|
|
146
|
+
return;
|
|
147
|
+
|
|
148
|
+
if (!q.query.select?.includes('*') && !q.query.select?.includes(primaryKey)) {
|
|
149
|
+
q._select(primaryKey);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
q.query.wrapInTransaction = true;
|
|
153
|
+
ctx.returnTypeAll = true;
|
|
154
|
+
|
|
155
|
+
pushQueryValue(q, 'afterUpdate', (q: Query) => {
|
|
156
|
+
return (nestedUpdate as HasOneNestedUpdate)(
|
|
157
|
+
q,
|
|
158
|
+
ctx.resultAll,
|
|
159
|
+
value as NestedUpdateOneItem,
|
|
160
|
+
);
|
|
161
|
+
});
|
|
12
162
|
};
|
package/src/repo.test.ts
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
import { orchidORM } from './orm';
|
|
2
2
|
import { pgConfig } from './test-utils/test-db';
|
|
3
|
-
import {
|
|
3
|
+
import { createBaseTable } from './table';
|
|
4
4
|
import { assertType, expectSql } from './test-utils/test-utils';
|
|
5
5
|
import { columnTypes, QueryReturnType } from 'pqb';
|
|
6
6
|
import { createRepo } from './repo';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const BaseTable = createBaseTable({ columnTypes });
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class SomeTable extends BaseTable {
|
|
11
11
|
table = 'someTable';
|
|
12
12
|
columns = this.setColumns((t) => ({
|
|
13
13
|
id: t.serial().primaryKey(),
|
|
14
|
-
name: t.text(),
|
|
14
|
+
name: t.text(0, 100),
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
17
|
relations = {
|
|
18
|
-
|
|
18
|
+
other: this.hasMany(() => OtherTable, {
|
|
19
19
|
primaryKey: 'id',
|
|
20
20
|
foreignKey: 'someId',
|
|
21
21
|
}),
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
class
|
|
25
|
+
class OtherTable extends BaseTable {
|
|
26
26
|
table = 'otherTable';
|
|
27
27
|
columns = this.setColumns((t) => ({
|
|
28
28
|
id: t.serial().primaryKey(),
|
|
29
|
-
someId: t.integer().foreignKey(() =>
|
|
30
|
-
anotherId: t.integer().foreignKey(() =>
|
|
29
|
+
someId: t.integer().foreignKey(() => SomeTable, 'id'),
|
|
30
|
+
anotherId: t.integer().foreignKey(() => AnotherTable, 'id'),
|
|
31
31
|
}));
|
|
32
32
|
|
|
33
33
|
relations = {
|
|
34
|
-
|
|
34
|
+
some: this.belongsTo(() => SomeTable, {
|
|
35
35
|
primaryKey: 'id',
|
|
36
36
|
foreignKey: 'someId',
|
|
37
37
|
}),
|
|
38
|
-
|
|
38
|
+
another: this.belongsTo(() => AnotherTable, {
|
|
39
39
|
primaryKey: 'id',
|
|
40
40
|
foreignKey: 'anotherId',
|
|
41
41
|
}),
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
class
|
|
46
|
-
table = '
|
|
45
|
+
class AnotherTable extends BaseTable {
|
|
46
|
+
table = 'another';
|
|
47
47
|
columns = this.setColumns((t) => ({
|
|
48
48
|
id: t.serial().primaryKey(),
|
|
49
49
|
}));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const db = orchidORM(pgConfig, {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
some: SomeTable,
|
|
54
|
+
other: OtherTable,
|
|
55
|
+
another: AnotherTable,
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
describe('createRepo', () => {
|
|
59
59
|
describe('queryMethods', () => {
|
|
60
|
-
const repo = createRepo(db.
|
|
60
|
+
const repo = createRepo(db.some, {
|
|
61
61
|
queryMethods: {
|
|
62
62
|
one(q) {
|
|
63
63
|
return q.select('id');
|
|
@@ -71,7 +71,7 @@ describe('createRepo', () => {
|
|
|
71
71
|
},
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it('should accept user defined methods and allow to use them on the
|
|
74
|
+
it('should accept user defined methods and allow to use them on the table with chaining', async () => {
|
|
75
75
|
const q = repo.one().two().three(123).take();
|
|
76
76
|
|
|
77
77
|
assertType<Awaited<typeof q>, { id: number; name: string }>();
|
|
@@ -89,13 +89,13 @@ describe('createRepo', () => {
|
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
it('should have custom methods on relation queries inside of select', async () => {
|
|
92
|
-
const q = db.
|
|
93
|
-
|
|
92
|
+
const q = db.other.select('id', {
|
|
93
|
+
some: (q) => repo(q.some).one().two().three(123),
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
assertType<
|
|
97
97
|
Awaited<typeof q>,
|
|
98
|
-
{ id: number;
|
|
98
|
+
{ id: number; some: { id: number; name: string } | null }[]
|
|
99
99
|
>();
|
|
100
100
|
|
|
101
101
|
expectSql(
|
|
@@ -106,13 +106,13 @@ describe('createRepo', () => {
|
|
|
106
106
|
(
|
|
107
107
|
SELECT row_to_json("t".*)
|
|
108
108
|
FROM (
|
|
109
|
-
SELECT "
|
|
110
|
-
FROM "someTable" AS "
|
|
111
|
-
WHERE "
|
|
112
|
-
AND "
|
|
109
|
+
SELECT "some"."id", "some"."name"
|
|
110
|
+
FROM "someTable" AS "some"
|
|
111
|
+
WHERE "some"."id" = $1
|
|
112
|
+
AND "some"."id" = "otherTable"."someId"
|
|
113
113
|
LIMIT $2
|
|
114
114
|
) AS "t"
|
|
115
|
-
) AS "
|
|
115
|
+
) AS "some"
|
|
116
116
|
FROM "otherTable"
|
|
117
117
|
`,
|
|
118
118
|
[123, 1],
|
|
@@ -121,7 +121,7 @@ describe('createRepo', () => {
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
describe('queryOneMethods', () => {
|
|
124
|
-
const repo = createRepo(db.
|
|
124
|
+
const repo = createRepo(db.some, {
|
|
125
125
|
queryOneMethods: {
|
|
126
126
|
one(q) {
|
|
127
127
|
const type: Exclude<QueryReturnType, 'all'> = q.returnType;
|
|
@@ -140,7 +140,7 @@ describe('createRepo', () => {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
describe('queryWithWhereMethods', () => {
|
|
143
|
-
const repo = createRepo(db.
|
|
143
|
+
const repo = createRepo(db.some, {
|
|
144
144
|
queryWithWhereMethods: {
|
|
145
145
|
one(q) {
|
|
146
146
|
const hasWhere: true = q.hasWhere;
|
|
@@ -161,7 +161,7 @@ describe('createRepo', () => {
|
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
describe('queryOneWithWhere', () => {
|
|
164
|
-
const repo = createRepo(db.
|
|
164
|
+
const repo = createRepo(db.some, {
|
|
165
165
|
queryOneWithWhereMethods: {
|
|
166
166
|
one(q) {
|
|
167
167
|
const type: Exclude<QueryReturnType, 'all'> = q.returnType;
|
|
@@ -185,7 +185,7 @@ describe('createRepo', () => {
|
|
|
185
185
|
});
|
|
186
186
|
|
|
187
187
|
describe('methods', () => {
|
|
188
|
-
const repo = createRepo(db.
|
|
188
|
+
const repo = createRepo(db.some, {
|
|
189
189
|
methods: {
|
|
190
190
|
one(a: number, b: string) {
|
|
191
191
|
return a + b;
|