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.
Files changed (38) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/index.d.ts +59 -54
  3. package/dist/index.esm.js +777 -547
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +776 -546
  6. package/dist/index.js.map +1 -1
  7. package/jest-setup.ts +4 -0
  8. package/package.json +8 -4
  9. package/src/appCodeUpdater/appCodeUpdater.ts +19 -0
  10. package/src/appCodeUpdater/fileChanges.ts +41 -0
  11. package/src/appCodeUpdater/testUtils.ts +31 -0
  12. package/src/appCodeUpdater/tsUtils.ts +137 -0
  13. package/src/appCodeUpdater/updateMainFile.test.ts +230 -0
  14. package/src/appCodeUpdater/updateMainFile.ts +163 -0
  15. package/src/appCodeUpdater/updateTableFile.ts +19 -0
  16. package/src/index.ts +5 -1
  17. package/src/orm.test.ts +13 -13
  18. package/src/orm.ts +21 -21
  19. package/src/relations/belongsTo.test.ts +1 -1
  20. package/src/relations/belongsTo.ts +291 -186
  21. package/src/relations/hasAndBelongsToMany.test.ts +1 -1
  22. package/src/relations/hasAndBelongsToMany.ts +292 -218
  23. package/src/relations/hasMany.test.ts +16 -10
  24. package/src/relations/hasMany.ts +243 -172
  25. package/src/relations/hasOne.test.ts +10 -10
  26. package/src/relations/hasOne.ts +211 -138
  27. package/src/relations/relations.ts +85 -77
  28. package/src/relations/utils.ts +154 -4
  29. package/src/repo.test.ts +29 -29
  30. package/src/repo.ts +6 -6
  31. package/src/{model.test.ts → table.test.ts} +15 -15
  32. package/src/{model.ts → table.ts} +17 -17
  33. package/src/test-utils/test-db.ts +15 -15
  34. package/src/test-utils/{test-models.ts → test-tables.ts} +42 -42
  35. package/src/transaction.test.ts +1 -1
  36. package/src/transaction.ts +4 -4
  37. package/src/utils.ts +9 -0
  38. package/tsconfig.json +1 -0
@@ -4,12 +4,10 @@ import {
4
4
  RelationThunkBase,
5
5
  RelationThunks,
6
6
  } from './relations';
7
- import { Model } from '../model';
7
+ import { Table } from '../table';
8
8
  import {
9
9
  addQueryOn,
10
10
  getQueryAs,
11
- HasManyNestedInsert,
12
- HasManyNestedUpdate,
13
11
  HasManyRelation,
14
12
  CreateData,
15
13
  JoinCallback,
@@ -21,8 +19,18 @@ import {
21
19
  WhereResult,
22
20
  InsertQueryData,
23
21
  isQueryReturnsAll,
22
+ VirtualColumn,
23
+ CreateCtx,
24
+ UpdateCtx,
24
25
  } from 'pqb';
25
- import { getSourceRelation, getThroughRelation } from './utils';
26
+ import {
27
+ getSourceRelation,
28
+ getThroughRelation,
29
+ hasRelationHandleCreate,
30
+ hasRelationHandleUpdate,
31
+ NestedInsertManyItems,
32
+ NestedUpdateManyItems,
33
+ } from './utils';
26
34
 
27
35
  export interface HasMany extends RelationThunkBase {
28
36
  type: 'hasMany';
@@ -31,7 +39,7 @@ export interface HasMany extends RelationThunkBase {
31
39
  }
32
40
 
33
41
  export type HasManyInfo<
34
- T extends Model,
42
+ T extends Table,
35
43
  Relations extends RelationThunks,
36
44
  Relation extends HasMany,
37
45
  > = {
@@ -56,8 +64,67 @@ export type HasManyInfo<
56
64
  chainedDelete: true;
57
65
  };
58
66
 
67
+ type State = {
68
+ query: Query;
69
+ primaryKey: string;
70
+ foreignKey: string;
71
+ };
72
+
73
+ export type HasManyNestedUpdate = (
74
+ query: Query,
75
+ data: Record<string, unknown>[],
76
+ relationData: NestedUpdateManyItems,
77
+ ) => Promise<void>;
78
+
79
+ export type HasManyNestedInsert = (
80
+ query: Query,
81
+ data: [
82
+ selfData: Record<string, unknown>,
83
+ relationData: NestedInsertManyItems,
84
+ ][],
85
+ ) => Promise<void>;
86
+
87
+ class HasManyVirtualColumn extends VirtualColumn {
88
+ private readonly nestedInsert: HasManyNestedInsert;
89
+ private readonly nestedUpdate: HasManyNestedUpdate;
90
+
91
+ constructor(private key: string, private state: State) {
92
+ super();
93
+ this.nestedInsert = nestedInsert(state);
94
+ this.nestedUpdate = nestedUpdate(state);
95
+ }
96
+
97
+ create(
98
+ q: Query,
99
+ ctx: CreateCtx,
100
+ item: Record<string, unknown>,
101
+ rowIndex: number,
102
+ ) {
103
+ hasRelationHandleCreate(
104
+ q,
105
+ ctx,
106
+ item,
107
+ rowIndex,
108
+ this.key,
109
+ this.state.primaryKey,
110
+ this.nestedInsert,
111
+ );
112
+ }
113
+
114
+ update(q: Query, ctx: UpdateCtx, set: Record<string, unknown>) {
115
+ hasRelationHandleUpdate(
116
+ q,
117
+ ctx,
118
+ set,
119
+ this.key,
120
+ this.state.primaryKey,
121
+ this.nestedUpdate,
122
+ );
123
+ }
124
+ }
125
+
59
126
  export const makeHasManyMethod = (
60
- model: Query,
127
+ table: Query,
61
128
  relation: HasMany,
62
129
  relationName: string,
63
130
  query: Query,
@@ -65,12 +132,12 @@ export const makeHasManyMethod = (
65
132
  if ('through' in relation.options) {
66
133
  const { through, source } = relation.options;
67
134
 
68
- type ModelWithQueryMethod = Record<
135
+ type TableWithQueryMethod = Record<
69
136
  string,
70
137
  (params: Record<string, unknown>) => Query
71
138
  >;
72
139
 
73
- const throughRelation = getThroughRelation(model, through);
140
+ const throughRelation = getThroughRelation(table, through);
74
141
  const sourceRelation = getSourceRelation(throughRelation, source);
75
142
  const sourceRelationQuery = sourceRelation.query.as(relationName);
76
143
  const sourceQuery = sourceRelation.joinQuery(
@@ -83,7 +150,7 @@ export const makeHasManyMethod = (
83
150
  return {
84
151
  returns: 'many',
85
152
  method: (params: Record<string, unknown>) => {
86
- const throughQuery = (model as unknown as ModelWithQueryMethod)[
153
+ const throughQuery = (table as unknown as TableWithQueryMethod)[
87
154
  through
88
155
  ](params);
89
156
 
@@ -92,8 +159,6 @@ export const makeHasManyMethod = (
92
159
  whereExistsCallback as unknown as JoinCallback<Query, Query>,
93
160
  );
94
161
  },
95
- nestedInsert: undefined,
96
- nestedUpdate: undefined,
97
162
  joinQuery(fromQuery, toQuery) {
98
163
  return toQuery.whereExists<Query, Query>(
99
164
  throughRelation.joinQuery(fromQuery, throughRelation.query),
@@ -123,6 +188,7 @@ export const makeHasManyMethod = (
123
188
  }
124
189
 
125
190
  const { primaryKey, foreignKey } = relation.options;
191
+ const state: State = { query, primaryKey, foreignKey };
126
192
 
127
193
  const fromQuerySelect = [{ selectAs: { [foreignKey]: primaryKey } }];
128
194
 
@@ -132,167 +198,7 @@ export const makeHasManyMethod = (
132
198
  const values = { [foreignKey]: params[primaryKey] };
133
199
  return query.where(values)._defaults(values);
134
200
  },
135
- nestedInsert: (async (q, data) => {
136
- const connect = data.filter(
137
- (
138
- item,
139
- ): item is [
140
- Record<string, unknown>,
141
- { connect: WhereArg<QueryBase>[] },
142
- ] => Boolean(item[1].connect),
143
- );
144
-
145
- const t = query.transacting(q);
146
-
147
- if (connect.length) {
148
- await Promise.all(
149
- connect.flatMap(([selfData, { connect }]) =>
150
- t
151
- .or<Query>(...connect)
152
- ._updateOrThrow({ [foreignKey]: selfData[primaryKey] }),
153
- ),
154
- );
155
- }
156
-
157
- const connectOrCreate = data.filter(
158
- (
159
- item,
160
- ): item is [
161
- Record<string, unknown>,
162
- {
163
- connectOrCreate: {
164
- where: WhereArg<QueryBase>;
165
- create: Record<string, unknown>;
166
- }[];
167
- },
168
- ] => Boolean(item[1].connectOrCreate),
169
- );
170
-
171
- let connected: number[];
172
- if (connectOrCreate.length) {
173
- connected = await Promise.all(
174
- connectOrCreate.flatMap(([selfData, { connectOrCreate }]) =>
175
- connectOrCreate.map((item) =>
176
- (
177
- t.where(item.where) as WhereResult<Query & { hasSelect: false }>
178
- )._update({
179
- [foreignKey]: selfData[primaryKey],
180
- }),
181
- ),
182
- ),
183
- );
184
- } else {
185
- connected = [];
186
- }
187
-
188
- let connectedI = 0;
189
- const create = data.filter(
190
- (
191
- item,
192
- ): item is [
193
- Record<string, unknown>,
194
- {
195
- create?: Record<string, unknown>[];
196
- connectOrCreate?: {
197
- where: WhereArg<QueryBase>;
198
- create: Record<string, unknown>;
199
- }[];
200
- },
201
- ] => {
202
- if (item[1].connectOrCreate) {
203
- const length = item[1].connectOrCreate.length;
204
- connectedI += length;
205
- for (let i = length; i > 0; i--) {
206
- if (connected[connectedI - i] === 0) return true;
207
- }
208
- }
209
- return Boolean(item[1].create);
210
- },
211
- );
212
-
213
- connectedI = 0;
214
- if (create.length) {
215
- await t._createMany(
216
- create.flatMap(
217
- ([selfData, { create = [], connectOrCreate = [] }]) => {
218
- return [
219
- ...create.map((item) => ({
220
- [foreignKey]: selfData[primaryKey],
221
- ...item,
222
- })),
223
- ...connectOrCreate
224
- .filter(() => connected[connectedI++] === 0)
225
- .map((item) => ({
226
- [foreignKey]: selfData[primaryKey],
227
- ...item.create,
228
- })),
229
- ];
230
- },
231
- ) as CreateData<Query>[],
232
- );
233
- }
234
- }) as HasManyNestedInsert,
235
- nestedUpdate: (async (q, data, params) => {
236
- if ((params.set || params.create) && isQueryReturnsAll(q)) {
237
- const key = params.set ? 'set' : 'create';
238
- throw new Error(`\`${key}\` option is not allowed in a batch update`);
239
- }
240
-
241
- const t = query.transacting(q);
242
- if (params.create) {
243
- await t._count()._createMany(
244
- params.create.map((create) => ({
245
- ...create,
246
- [foreignKey]: data[0][primaryKey],
247
- })),
248
- );
249
- delete t.query[toSqlCacheKey];
250
- }
251
-
252
- if (params.disconnect || params.set) {
253
- await t
254
- .where<Query>(
255
- getWhereForNestedUpdate(
256
- data,
257
- params.disconnect,
258
- primaryKey,
259
- foreignKey,
260
- ),
261
- )
262
- ._update({ [foreignKey]: null });
263
-
264
- if (params.set) {
265
- delete t.query[toSqlCacheKey];
266
- await t
267
- .where<Query>(
268
- Array.isArray(params.set)
269
- ? {
270
- OR: params.set,
271
- }
272
- : params.set,
273
- )
274
- ._update({ [foreignKey]: data[0][primaryKey] });
275
- }
276
- }
277
-
278
- if (params.delete || params.update) {
279
- delete t.query[toSqlCacheKey];
280
- const q = t._where(
281
- getWhereForNestedUpdate(
282
- data,
283
- params.delete || params.update?.where,
284
- primaryKey,
285
- foreignKey,
286
- ),
287
- );
288
-
289
- if (params.delete) {
290
- await q._delete();
291
- } else if (params.update) {
292
- await q._update<WhereResult<Query>>(params.update.data);
293
- }
294
- }
295
- }) as HasManyNestedUpdate,
201
+ virtualColumn: new HasManyVirtualColumn(relationName, state),
296
202
  joinQuery(fromQuery, toQuery) {
297
203
  return addQueryOn(toQuery, fromQuery, toQuery, foreignKey, primaryKey);
298
204
  },
@@ -328,3 +234,168 @@ const getWhereForNestedUpdate = (
328
234
  }
329
235
  return where;
330
236
  };
237
+
238
+ const nestedInsert = ({ query, primaryKey, foreignKey }: State) => {
239
+ return (async (q, data) => {
240
+ const connect = data.filter(
241
+ (
242
+ item,
243
+ ): item is [
244
+ Record<string, unknown>,
245
+ { connect: WhereArg<QueryBase>[] },
246
+ ] => Boolean(item[1].connect),
247
+ );
248
+
249
+ const t = query.transacting(q);
250
+
251
+ if (connect.length) {
252
+ await Promise.all(
253
+ connect.flatMap(([selfData, { connect }]) =>
254
+ t
255
+ .or<Query>(...connect)
256
+ ._updateOrThrow({ [foreignKey]: selfData[primaryKey] }),
257
+ ),
258
+ );
259
+ }
260
+
261
+ const connectOrCreate = data.filter(
262
+ (
263
+ item,
264
+ ): item is [
265
+ Record<string, unknown>,
266
+ {
267
+ connectOrCreate: {
268
+ where: WhereArg<QueryBase>;
269
+ create: Record<string, unknown>;
270
+ }[];
271
+ },
272
+ ] => Boolean(item[1].connectOrCreate),
273
+ );
274
+
275
+ let connected: number[];
276
+ if (connectOrCreate.length) {
277
+ connected = await Promise.all(
278
+ connectOrCreate.flatMap(([selfData, { connectOrCreate }]) =>
279
+ connectOrCreate.map((item) =>
280
+ (
281
+ t.where(item.where) as WhereResult<Query & { hasSelect: false }>
282
+ )._update({
283
+ [foreignKey]: selfData[primaryKey],
284
+ }),
285
+ ),
286
+ ),
287
+ );
288
+ } else {
289
+ connected = [];
290
+ }
291
+
292
+ let connectedI = 0;
293
+ const create = data.filter(
294
+ (
295
+ item,
296
+ ): item is [
297
+ Record<string, unknown>,
298
+ {
299
+ create?: Record<string, unknown>[];
300
+ connectOrCreate?: {
301
+ where: WhereArg<QueryBase>;
302
+ create: Record<string, unknown>;
303
+ }[];
304
+ },
305
+ ] => {
306
+ if (item[1].connectOrCreate) {
307
+ const length = item[1].connectOrCreate.length;
308
+ connectedI += length;
309
+ for (let i = length; i > 0; i--) {
310
+ if (connected[connectedI - i] === 0) return true;
311
+ }
312
+ }
313
+ return Boolean(item[1].create);
314
+ },
315
+ );
316
+
317
+ connectedI = 0;
318
+ if (create.length) {
319
+ await t._createMany(
320
+ create.flatMap(([selfData, { create = [], connectOrCreate = [] }]) => {
321
+ return [
322
+ ...create.map((item) => ({
323
+ [foreignKey]: selfData[primaryKey],
324
+ ...item,
325
+ })),
326
+ ...connectOrCreate
327
+ .filter(() => connected[connectedI++] === 0)
328
+ .map((item) => ({
329
+ [foreignKey]: selfData[primaryKey],
330
+ ...item.create,
331
+ })),
332
+ ];
333
+ }) as CreateData<Query>[],
334
+ );
335
+ }
336
+ }) as HasManyNestedInsert;
337
+ };
338
+
339
+ const nestedUpdate = ({ query, primaryKey, foreignKey }: State) => {
340
+ return (async (q, data, params) => {
341
+ if ((params.set || params.create) && isQueryReturnsAll(q)) {
342
+ const key = params.set ? 'set' : 'create';
343
+ throw new Error(`\`${key}\` option is not allowed in a batch update`);
344
+ }
345
+
346
+ const t = query.transacting(q);
347
+ if (params.create) {
348
+ await t._count()._createMany(
349
+ params.create.map((create) => ({
350
+ ...create,
351
+ [foreignKey]: data[0][primaryKey],
352
+ })),
353
+ );
354
+ delete t.query[toSqlCacheKey];
355
+ }
356
+
357
+ if (params.disconnect || params.set) {
358
+ await t
359
+ .where<Query>(
360
+ getWhereForNestedUpdate(
361
+ data,
362
+ params.disconnect,
363
+ primaryKey,
364
+ foreignKey,
365
+ ),
366
+ )
367
+ ._update({ [foreignKey]: null });
368
+
369
+ if (params.set) {
370
+ delete t.query[toSqlCacheKey];
371
+ await t
372
+ .where<Query>(
373
+ Array.isArray(params.set)
374
+ ? {
375
+ OR: params.set,
376
+ }
377
+ : params.set,
378
+ )
379
+ ._update({ [foreignKey]: data[0][primaryKey] });
380
+ }
381
+ }
382
+
383
+ if (params.delete || params.update) {
384
+ delete t.query[toSqlCacheKey];
385
+ const q = t._where(
386
+ getWhereForNestedUpdate(
387
+ data,
388
+ params.delete || params.update?.where,
389
+ primaryKey,
390
+ foreignKey,
391
+ ),
392
+ );
393
+
394
+ if (params.delete) {
395
+ await q._delete();
396
+ } else if (params.update) {
397
+ await q._update<WhereResult<Query>>(params.update.data);
398
+ }
399
+ }
400
+ }) as HasManyNestedUpdate;
401
+ };
@@ -8,7 +8,7 @@ import {
8
8
  useRelationCallback,
9
9
  useTestDatabase,
10
10
  } from '../test-utils/test-utils';
11
- import { User, Profile, Model } from '../test-utils/test-models';
11
+ import { User, Profile, BaseTable } from '../test-utils/test-tables';
12
12
  import { RelationQuery } from 'pqb';
13
13
  import { orchidORM } from '../orm';
14
14
 
@@ -1264,8 +1264,8 @@ describe('hasOne', () => {
1264
1264
  });
1265
1265
 
1266
1266
  describe('hasOne through', () => {
1267
- it('should resolve recursive situation when both models depends on each other', () => {
1268
- class Post extends Model {
1267
+ it('should resolve recursive situation when both tables depends on each other', () => {
1268
+ class Post extends BaseTable {
1269
1269
  table = 'post';
1270
1270
  columns = this.setColumns((t) => ({
1271
1271
  id: t.serial().primaryKey(),
@@ -1284,7 +1284,7 @@ describe('hasOne through', () => {
1284
1284
  };
1285
1285
  }
1286
1286
 
1287
- class Tag extends Model {
1287
+ class Tag extends BaseTable {
1288
1288
  table = 'tag';
1289
1289
  columns = this.setColumns((t) => ({
1290
1290
  id: t.serial().primaryKey(),
@@ -1303,7 +1303,7 @@ describe('hasOne through', () => {
1303
1303
  };
1304
1304
  }
1305
1305
 
1306
- class PostTag extends Model {
1306
+ class PostTag extends BaseTable {
1307
1307
  table = 'postTag';
1308
1308
  columns = this.setColumns((t) => ({
1309
1309
  postId: t.integer().foreignKey(() => Post, 'id'),
@@ -1341,7 +1341,7 @@ describe('hasOne through', () => {
1341
1341
  });
1342
1342
 
1343
1343
  it('should throw if through relation is not defined', () => {
1344
- class Post extends Model {
1344
+ class Post extends BaseTable {
1345
1345
  table = 'post';
1346
1346
  columns = this.setColumns((t) => ({
1347
1347
  id: t.serial().primaryKey(),
@@ -1355,7 +1355,7 @@ describe('hasOne through', () => {
1355
1355
  };
1356
1356
  }
1357
1357
 
1358
- class Tag extends Model {
1358
+ class Tag extends BaseTable {
1359
1359
  table = 'tag';
1360
1360
  columns = this.setColumns((t) => ({
1361
1361
  id: t.serial().primaryKey(),
@@ -1379,7 +1379,7 @@ describe('hasOne through', () => {
1379
1379
  });
1380
1380
 
1381
1381
  it('should throw if source relation is not defined', () => {
1382
- class Post extends Model {
1382
+ class Post extends BaseTable {
1383
1383
  table = 'post';
1384
1384
  columns = this.setColumns((t) => ({
1385
1385
  id: t.serial().primaryKey(),
@@ -1398,14 +1398,14 @@ describe('hasOne through', () => {
1398
1398
  };
1399
1399
  }
1400
1400
 
1401
- class Tag extends Model {
1401
+ class Tag extends BaseTable {
1402
1402
  table = 'tag';
1403
1403
  columns = this.setColumns((t) => ({
1404
1404
  id: t.serial().primaryKey(),
1405
1405
  }));
1406
1406
  }
1407
1407
 
1408
- class PostTag extends Model {
1408
+ class PostTag extends BaseTable {
1409
1409
  table = 'postTag';
1410
1410
  columns = this.setColumns((t) => ({
1411
1411
  postId: t.integer().foreignKey(() => Post, 'id'),