objection 4.0.0-rc.3 → 4.0.0-rc.4
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/lib/queryBuilder/InternalOptions.js +4 -0
- package/lib/queryBuilder/graph/GraphNode.js +8 -0
- package/lib/queryBuilder/graph/GraphPlan.js +13 -0
- package/lib/queryBuilder/graph/GraphSchedule.js +75 -37
- package/lib/queryBuilder/graph/GraphUpsert.js +2 -0
- package/lib/queryBuilder/graph/GraphWriter.js +99 -15
- package/lib/queryBuilder/graph/graphChecks.js +40 -3
- package/lib/queryBuilder/graph/graphKeys.js +83 -17
- package/lib/queryBuilder/jsonDialects/JsonDialect.js +6 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +5 -2
- package/lib/queryBuilder/operations/fieldExpressions.js +10 -7
- package/lib/relations/RelationProperty.js +6 -0
- package/package.json +3 -2
- package/typings/objection/index.d.ts +94 -93
|
@@ -6,6 +6,9 @@ export class InternalOptions {
|
|
|
6
6
|
this.skipRelationFilter = false;
|
|
7
7
|
// The join table extras that related updates write, all of them if null:
|
|
8
8
|
this.joinTableExtras = null;
|
|
9
|
+
// The field expressions that `upsertGraph()` writes keys in json columns of
|
|
10
|
+
// updated rows with:
|
|
11
|
+
this.fieldExpressionKeys = [];
|
|
9
12
|
this.debug = false;
|
|
10
13
|
this.schema = undefined;
|
|
11
14
|
}
|
|
@@ -18,6 +21,7 @@ export class InternalOptions {
|
|
|
18
21
|
copy.isInternalQuery = this.isInternalQuery;
|
|
19
22
|
copy.skipRelationFilter = this.skipRelationFilter;
|
|
20
23
|
copy.joinTableExtras = this.joinTableExtras;
|
|
24
|
+
copy.fieldExpressionKeys = this.fieldExpressionKeys;
|
|
21
25
|
copy.debug = this.debug;
|
|
22
26
|
copy.schema = this.schema;
|
|
23
27
|
|
|
@@ -191,6 +191,14 @@ export class GraphNode {
|
|
|
191
191
|
|
|
192
192
|
for (const node of nodes) {
|
|
193
193
|
if (node.isReference) {
|
|
194
|
+
// A `#ref` places its model in a relation, which a root doesn't have:
|
|
195
|
+
if (!node.parent) {
|
|
196
|
+
throw node.modelClass.createValidationError({
|
|
197
|
+
type: ValidationErrorType.InvalidGraph,
|
|
198
|
+
message: `the reference at ${formatPath(node.dataPathKey)} has no relation to place its model in`,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
194
202
|
node.referencedNode = getNode(node.reference);
|
|
195
203
|
}
|
|
196
204
|
|
|
@@ -91,6 +91,19 @@ export class GraphPlan {
|
|
|
91
91
|
);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Return true if the graph relates the row of `node` by the key of its
|
|
95
|
+
// BelongsToOneRelation alone, without an id or a reference.
|
|
96
|
+
isRelatedByKey(node) {
|
|
97
|
+
return (
|
|
98
|
+
this.actions.get(node) === 'relate' &&
|
|
99
|
+
node.relation.keySide === 'owner' &&
|
|
100
|
+
!node.hasId &&
|
|
101
|
+
!node.isReference &&
|
|
102
|
+
!node.isDbReference &&
|
|
103
|
+
node.propertyReferences.length === 0
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
// Return true if the model of the `#ref` model `node` writes what `node`
|
|
95
108
|
// would write to the row: The model is inserted, or it's written and its path
|
|
96
109
|
// allows any write that the path of `node` allows.
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { hasOwnerKey, hasSameColumns, hasSameKey } from './graphKeys.js';
|
|
2
2
|
import { groupBy } from '../../utils/objectUtils.js';
|
|
3
|
+
import { ValidationErrorType } from '../../model/ValidationError.js';
|
|
4
|
+
import { formatPath } from './GraphNode.js';
|
|
3
5
|
|
|
4
6
|
// Order the writes of a planned graph in steps that keep keys and constraints
|
|
5
7
|
// intact (#214), which `GraphWriter` writes:
|
|
6
8
|
// 1. Move the rows that the graph relates to existing owners of HasMany and
|
|
7
9
|
// HasOne relations, as they may still sit below rows that the next step
|
|
8
|
-
// deletes. Rows that wait for inserts are related in step 3
|
|
10
|
+
// deletes. Rows that wait for inserts or changed keys are related in step 3
|
|
11
|
+
// or 4.
|
|
9
12
|
// 2. Remove rows that reference their owners, before the inserts that may need
|
|
10
13
|
// their unique keys.
|
|
11
14
|
// 3. Insert the new rows in the order of their keys and references, then the
|
|
12
15
|
// keys deferred to break cycles and the join rows. Rows that move out of
|
|
13
16
|
// the slots of HasOneRelations that new rows go into are related in the
|
|
14
|
-
// same order, as the foreign key may be unique
|
|
17
|
+
// same order, as the foreign key may be unique, and so are the patches and
|
|
18
|
+
// relates that change the keys that these rows read, as a foreign key may
|
|
19
|
+
// reference them (#428).
|
|
15
20
|
// 4. Patch the changed rows, the other relates, and the keys of owners whose
|
|
16
21
|
// BelongsToOne rows were inserted, related or removed.
|
|
17
22
|
// 5. Unrelate the rows that their own to-many relation on their own key leaves
|
|
@@ -25,7 +30,8 @@ export class GraphSchedule {
|
|
|
25
30
|
this.plan = plan;
|
|
26
31
|
this.keys = keys;
|
|
27
32
|
// The nodes that each node waits for, through its keys, its references and
|
|
28
|
-
// the rows that move out of the slots that it goes into
|
|
33
|
+
// the rows that move out of the slots that it goes into. `isKeyChange`
|
|
34
|
+
// marks the patches and relates that change the keys it reads:
|
|
29
35
|
this.dependencies = createDependencies(plan, this.keys);
|
|
30
36
|
// Dependencies that aren't waited for, to break cycles. Deferred keys are
|
|
31
37
|
// written after the inserts:
|
|
@@ -36,9 +42,8 @@ export class GraphSchedule {
|
|
|
36
42
|
? []
|
|
37
43
|
: plan.activeNodes.filter((node) => this._isMovedFirst(node)),
|
|
38
44
|
);
|
|
39
|
-
// Relates
|
|
40
|
-
|
|
41
|
-
this.slotMovedNodes = this._getSlotMovedNodes();
|
|
45
|
+
// Relates and patches that aren't moved first but written in the inserts:
|
|
46
|
+
this.earlyNodes = this._getEarlyNodes();
|
|
42
47
|
this.steps = this._planSteps();
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -91,10 +96,9 @@ export class GraphSchedule {
|
|
|
91
96
|
activeNodes.filter((node) => {
|
|
92
97
|
const action = actions.get(node);
|
|
93
98
|
return (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
!this.slotMovedNodes.has(node))) &&
|
|
99
|
+
['patch', 'relate'].includes(action) &&
|
|
100
|
+
!this.movedNodes.has(node) &&
|
|
101
|
+
!this.earlyNodes.has(node) &&
|
|
98
102
|
// The row of a `#ref` model is written once, by its model:
|
|
99
103
|
!this._isWrittenByModel(node)
|
|
100
104
|
);
|
|
@@ -212,7 +216,25 @@ export class GraphSchedule {
|
|
|
212
216
|
}
|
|
213
217
|
}
|
|
214
218
|
} else if (!breakCycle()) {
|
|
215
|
-
|
|
219
|
+
// A patch that changes a key that the rows it waits for read can be
|
|
220
|
+
// written neither before nor after them:
|
|
221
|
+
const { node: changingNode } =
|
|
222
|
+
nodes
|
|
223
|
+
.filter((node) => !handledNodes.has(node))
|
|
224
|
+
.flatMap(getPendingDependencies)
|
|
225
|
+
.find(
|
|
226
|
+
({ node, dependent, isKeyChange }) => isKeyChange && isBlockedBy(node, dependent),
|
|
227
|
+
) || {};
|
|
228
|
+
|
|
229
|
+
if (changingNode) {
|
|
230
|
+
const { modelClass, dataPathKey } = changingNode;
|
|
231
|
+
throw modelClass.createValidationError({
|
|
232
|
+
type: ValidationErrorType.InvalidGraph,
|
|
233
|
+
message: `the graph changes a key of the ${modelClass.name} at ${formatPath(dataPathKey)} that rows read which it also waits for; write the key in a separate query first`,
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
throw new Error('the object graph contains cyclic references');
|
|
237
|
+
}
|
|
216
238
|
}
|
|
217
239
|
}
|
|
218
240
|
|
|
@@ -245,9 +267,10 @@ export class GraphSchedule {
|
|
|
245
267
|
|
|
246
268
|
// Return true if the HasMany or HasOne relate of `node` moves its row to an
|
|
247
269
|
// existing owner before the removals, see the steps above. `#ref` models may
|
|
248
|
-
// get their ids from the inserts, rows that wait for the inserts
|
|
249
|
-
//
|
|
250
|
-
// the removal of that row, or its move, as its key may be
|
|
270
|
+
// get their ids from the inserts, rows that wait for the inserts or for
|
|
271
|
+
// changed keys are written after them, and a relate into the slot of a HasOne
|
|
272
|
+
// row waits for the removal of that row, or its move, as its key may be
|
|
273
|
+
// unique.
|
|
251
274
|
_isMovedFirst(node, visitedNodes = new Set()) {
|
|
252
275
|
const { actions } = this.plan;
|
|
253
276
|
visitedNodes.add(node);
|
|
@@ -257,7 +280,7 @@ export class GraphSchedule {
|
|
|
257
280
|
!node.isReference &&
|
|
258
281
|
node.relation.keySide === 'related' &&
|
|
259
282
|
node.parent.existing &&
|
|
260
|
-
!this.
|
|
283
|
+
!this._waitsForWrites(node)
|
|
261
284
|
) {
|
|
262
285
|
const slotNode = getSlotNode(node);
|
|
263
286
|
const moverNode = getSlotMover(this.plan, node);
|
|
@@ -271,24 +294,32 @@ export class GraphSchedule {
|
|
|
271
294
|
}
|
|
272
295
|
}
|
|
273
296
|
|
|
274
|
-
// Find the relates
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
// as the foreign key may be unique
|
|
278
|
-
|
|
297
|
+
// Find the relates and patches that are written in the inserts, as the rows
|
|
298
|
+
// written there wait for them: The relates that move the rows out of the
|
|
299
|
+
// slots of HasOneRelations that new rows go into, along with the relates that
|
|
300
|
+
// move the rows out of their own slots, as the foreign key may be unique, and
|
|
301
|
+
// the patches and relates that change the keys that these rows or join rows
|
|
302
|
+
// read, as a foreign key may reference them (#428).
|
|
303
|
+
_getEarlyNodes() {
|
|
304
|
+
const { actions } = this.plan;
|
|
279
305
|
const nodes = new Set();
|
|
280
|
-
const
|
|
281
|
-
for (const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
306
|
+
const addEarlyNodes = (node) => {
|
|
307
|
+
for (const dependency of this.dependencies.get(node) || []) {
|
|
308
|
+
const { node: earlyNode, isSlotMove, isKeyChange } = dependency;
|
|
309
|
+
if (
|
|
310
|
+
(isSlotMove || isKeyChange) &&
|
|
311
|
+
!this.movedNodes.has(earlyNode) &&
|
|
312
|
+
!nodes.has(earlyNode)
|
|
313
|
+
) {
|
|
314
|
+
nodes.add(earlyNode);
|
|
315
|
+
addEarlyNodes(earlyNode);
|
|
285
316
|
}
|
|
286
317
|
}
|
|
287
318
|
};
|
|
288
319
|
|
|
289
320
|
for (const node of this.plan.activeNodes) {
|
|
290
|
-
if (
|
|
291
|
-
|
|
321
|
+
if (actions.get(node) === 'insert' || (node.relation?.keySide === 'join' && !node.current)) {
|
|
322
|
+
addEarlyNodes(node);
|
|
292
323
|
}
|
|
293
324
|
}
|
|
294
325
|
|
|
@@ -317,19 +348,21 @@ export class GraphSchedule {
|
|
|
317
348
|
}
|
|
318
349
|
|
|
319
350
|
// Return true if `node` waits for the keys or references of inserted models,
|
|
320
|
-
//
|
|
321
|
-
// that it waits for to get its
|
|
322
|
-
|
|
351
|
+
// or for keys that patches change (#428), also through the models that it
|
|
352
|
+
// references (#356), and through the models that it waits for to get its
|
|
353
|
+
// keys.
|
|
354
|
+
_waitsForWrites(node, visitedNodes = new Set()) {
|
|
323
355
|
const { actions } = this.plan;
|
|
324
356
|
visitedNodes.add(node);
|
|
325
357
|
return (this.dependencies.get(node) || []).some((dependency) => {
|
|
326
|
-
const { node: dependencyNode, relation } = dependency;
|
|
358
|
+
const { node: dependencyNode, relation, isKeyWait, isKeyChange } = dependency;
|
|
327
359
|
const referencedNode = dependencyNode.referencedNode || dependencyNode;
|
|
328
360
|
return (
|
|
329
361
|
actions.get(referencedNode) === 'insert' ||
|
|
330
|
-
|
|
362
|
+
isKeyChange ||
|
|
363
|
+
((!relation || isKeyWait) &&
|
|
331
364
|
!visitedNodes.has(referencedNode) &&
|
|
332
|
-
this.
|
|
365
|
+
this._waitsForWrites(referencedNode, visitedNodes))
|
|
333
366
|
);
|
|
334
367
|
});
|
|
335
368
|
}
|
|
@@ -458,18 +491,23 @@ function createDependencies(plan, keys) {
|
|
|
458
491
|
|
|
459
492
|
// A place waits for the inserts and `#ref{}` values that bind the slots of
|
|
460
493
|
// its keys. A dependency on the same row through the same key becomes such
|
|
461
|
-
// a wait, and stays deferrable to break cycles. The other waits aren't
|
|
462
|
-
for
|
|
494
|
+
// a wait, and stays deferrable to break cycles. The other waits aren't. A
|
|
495
|
+
// key change is waited for at the node that writes it, not at a `#ref`
|
|
496
|
+
// model of its row, which writes nothing:
|
|
497
|
+
for (const { node: waitedNode, prop, isKeyChange } of keys.waitsFor(node)) {
|
|
463
498
|
const dependency = (dependencies.get(node) || []).find(
|
|
464
499
|
(dependency) =>
|
|
465
|
-
|
|
500
|
+
(isKeyChange
|
|
501
|
+
? dependency.node === waitedNode
|
|
502
|
+
: getRow(dependency.node) === getRow(waitedNode)) &&
|
|
466
503
|
(!dependency.relation || getKeyProps(dependency, node).includes(prop)),
|
|
467
504
|
);
|
|
468
505
|
|
|
469
506
|
if (dependency) {
|
|
470
507
|
dependency.isKeyWait = true;
|
|
508
|
+
dependency.isKeyChange ||= isKeyChange;
|
|
471
509
|
} else {
|
|
472
|
-
addDependency(node, { node: waitedNode, isKeyWait: true });
|
|
510
|
+
addDependency(node, { node: waitedNode, isKeyWait: true, isKeyChange });
|
|
473
511
|
}
|
|
474
512
|
}
|
|
475
513
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
checkChangedIds,
|
|
10
10
|
checkGraph,
|
|
11
11
|
checkJoinTables,
|
|
12
|
+
checkKeyRelates,
|
|
12
13
|
checkMissingModels,
|
|
13
14
|
checkPlacements,
|
|
14
15
|
checkReadKeys,
|
|
@@ -100,6 +101,7 @@ export class GraphUpsert {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
checkChangedIds(builder, plan);
|
|
104
|
+
checkKeyRelates(builder, plan);
|
|
103
105
|
checkReferences(builder, plan);
|
|
104
106
|
checkJoinTables(builder, plan);
|
|
105
107
|
await new GraphWriter(new GraphSchedule(plan, keys)).write(builder);
|
|
@@ -2,6 +2,7 @@ import { getModel } from '../../model/getModel.js';
|
|
|
2
2
|
import { restoreOmitFromDatabaseJson } from '../../model/modelUtils.js';
|
|
3
3
|
import { valuesKey } from '../../model/modelValues.js';
|
|
4
4
|
import { UpdateOperation } from '../operations/UpdateOperation.js';
|
|
5
|
+
import { removeFieldExpressions } from '../operations/fieldExpressions.js';
|
|
5
6
|
import { getMaxRows, hasBatchInsert } from '../../utils/knexUtils.js';
|
|
6
7
|
import { isInternalProp } from '../../utils/internalPropUtils.js';
|
|
7
8
|
import {
|
|
@@ -125,14 +126,15 @@ export class GraphWriter {
|
|
|
125
126
|
await promiseMap(batches, (batch) => this._insertBatch(builder, batch), {
|
|
126
127
|
concurrency: getConcurrency(builder, nodes),
|
|
127
128
|
});
|
|
128
|
-
await this._patch(
|
|
129
|
-
builder,
|
|
130
|
-
nodes.filter((node) => this.schedule.slotMovedNodes.has(node)),
|
|
131
|
-
);
|
|
132
129
|
|
|
133
130
|
for (const node of references) {
|
|
134
131
|
resolveDependencies(this.schedule, node);
|
|
135
132
|
}
|
|
133
|
+
|
|
134
|
+
await this._patch(
|
|
135
|
+
builder,
|
|
136
|
+
[...nodes, ...references].filter((node) => this.schedule.earlyNodes.has(node)),
|
|
137
|
+
);
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
async _insertBatch(builder, nodes) {
|
|
@@ -275,6 +277,8 @@ export class GraphWriter {
|
|
|
275
277
|
} finally {
|
|
276
278
|
for (const [node, props] of omittedProps) {
|
|
277
279
|
restoreOmitFromDatabaseJson(node.obj, props);
|
|
280
|
+
// The keys of `setKeyPaths()` stay where a hook resolves the patch:
|
|
281
|
+
removeFieldExpressions(node.modelClass, node.obj);
|
|
278
282
|
}
|
|
279
283
|
}
|
|
280
284
|
}
|
|
@@ -284,6 +288,12 @@ export class GraphWriter {
|
|
|
284
288
|
_preparePatch(builder, node) {
|
|
285
289
|
const { referencedNode } = node;
|
|
286
290
|
|
|
291
|
+
// A row that the graph relates by its key alone isn't looked up, its key is
|
|
292
|
+
// only written to its owner:
|
|
293
|
+
if (this.plan.isRelatedByKey(node)) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
287
297
|
// The props that the graph fills into the model of a `#ref` model are only
|
|
288
298
|
// final now:
|
|
289
299
|
if (referencedNode) {
|
|
@@ -304,6 +314,8 @@ export class GraphWriter {
|
|
|
304
314
|
? node.relation.relatedProp.props
|
|
305
315
|
: []),
|
|
306
316
|
];
|
|
317
|
+
// The keys in json paths that the write sets or clears:
|
|
318
|
+
let pathKeys = new Map();
|
|
307
319
|
// Changing the id never makes sense with `upsertGraph()`. A many-to-many
|
|
308
320
|
// `#ref` model whose model writes its row only writes its join row:
|
|
309
321
|
const idProps = node.modelClass.getIdPropertyArray();
|
|
@@ -311,11 +323,15 @@ export class GraphWriter {
|
|
|
311
323
|
!!referencedNode && node.relation.keySide === 'join' && this.plan.isCoveredByModel(node);
|
|
312
324
|
const isWritable = (prop) =>
|
|
313
325
|
!idProps.includes(prop) &&
|
|
326
|
+
!pathKeys.has(prop) &&
|
|
314
327
|
!this.schedule.keys.isOnlyRead(node, prop) &&
|
|
315
328
|
(!isJoinRowOnly || node.relation.joinTableExtras.some((extra) => extra.aliasProp === prop));
|
|
316
329
|
|
|
317
330
|
const getPropsToUpdate = (changedProps) =>
|
|
318
|
-
(shouldPatch || shouldUpdate
|
|
331
|
+
(shouldPatch || shouldUpdate
|
|
332
|
+
? changedProps
|
|
333
|
+
: keyProps.flatMap((prop) => pathKeys.get(prop) || prop)
|
|
334
|
+
).filter(isWritable);
|
|
319
335
|
|
|
320
336
|
// Leave out the props that don't change, and return true if any do.
|
|
321
337
|
const handleUpdate = ({ changedProps, unchangedProps } = this._findChanges(node)) => {
|
|
@@ -335,21 +351,43 @@ export class GraphWriter {
|
|
|
335
351
|
// what to update. Otherwise, the hook may still change the model, and we
|
|
336
352
|
// check again after it runs, but only if the model has props to update, as
|
|
337
353
|
// relates are no updates (#2233, #2605).
|
|
338
|
-
const changes = this._findChanges(node);
|
|
339
|
-
const propsToUpdate = getPropsToUpdate(changes.changedProps);
|
|
340
354
|
const hasBeforeUpdate = hasHook(node.obj, 'beforeUpdate');
|
|
341
355
|
|
|
342
|
-
|
|
356
|
+
let changes = this._findChanges(node);
|
|
357
|
+
|
|
358
|
+
if (getPropsToUpdate(changes.changedProps).length === 0) {
|
|
343
359
|
return null;
|
|
344
|
-
} else if (!hasBeforeUpdate) {
|
|
345
|
-
handleUpdate(changes);
|
|
346
360
|
}
|
|
347
361
|
|
|
348
362
|
node.obj.$validate(null, {
|
|
349
363
|
dataPath: node.dataPathKey,
|
|
350
|
-
patch:
|
|
364
|
+
patch: !shouldUpdate,
|
|
351
365
|
});
|
|
352
366
|
|
|
367
|
+
// Write the keys in json columns as path updates where their column holds
|
|
368
|
+
// nothing else, which keeps its other values (#415). After the validation,
|
|
369
|
+
// as `update()` validates them as properties of their own:
|
|
370
|
+
pathKeys = setKeyPaths(node, [
|
|
371
|
+
...this.schedule.keys.getSetCells(node),
|
|
372
|
+
...this.plan
|
|
373
|
+
.getRemovedOwnerRelations(node)
|
|
374
|
+
.flatMap(({ ownerProp }) =>
|
|
375
|
+
ownerProp.props.map((_, index) => ({ prop: ownerProp, index })),
|
|
376
|
+
),
|
|
377
|
+
]);
|
|
378
|
+
|
|
379
|
+
if (pathKeys.size > 0) {
|
|
380
|
+
changes = this._findChanges(node);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const propsToUpdate = getPropsToUpdate(changes.changedProps);
|
|
384
|
+
|
|
385
|
+
if (propsToUpdate.length === 0) {
|
|
386
|
+
return null;
|
|
387
|
+
} else if (!hasBeforeUpdate) {
|
|
388
|
+
handleUpdate(changes);
|
|
389
|
+
}
|
|
390
|
+
|
|
353
391
|
// Rows that their owners relate by their keys can't be found through the
|
|
354
392
|
// relation until the owners are patched, so they're patched directly.
|
|
355
393
|
const { relation } = node;
|
|
@@ -360,17 +398,26 @@ export class GraphWriter {
|
|
|
360
398
|
|
|
361
399
|
return {
|
|
362
400
|
node,
|
|
363
|
-
|
|
401
|
+
shouldUpdate,
|
|
364
402
|
hasBeforeUpdate,
|
|
365
403
|
handleUpdate,
|
|
366
404
|
propsToUpdate,
|
|
405
|
+
fieldExpressionKeys: [...pathKeys.values()].flat(),
|
|
367
406
|
isPatchedThroughRelation,
|
|
368
407
|
};
|
|
369
408
|
}
|
|
370
409
|
|
|
371
410
|
_writePatch(
|
|
372
411
|
builder,
|
|
373
|
-
{
|
|
412
|
+
{
|
|
413
|
+
node,
|
|
414
|
+
shouldUpdate,
|
|
415
|
+
hasBeforeUpdate,
|
|
416
|
+
handleUpdate,
|
|
417
|
+
propsToUpdate,
|
|
418
|
+
fieldExpressionKeys,
|
|
419
|
+
isPatchedThroughRelation,
|
|
420
|
+
},
|
|
374
421
|
) {
|
|
375
422
|
const query = (
|
|
376
423
|
isPatchedThroughRelation
|
|
@@ -392,6 +439,8 @@ export class GraphWriter {
|
|
|
392
439
|
// Join rows are patched with the extras among the props to update. The
|
|
393
440
|
// extras of new join rows are written with them, see `_findChanges()`:
|
|
394
441
|
joinTableExtras: node.relation?.keySide === 'join' && !node.current ? [] : propsToUpdate,
|
|
442
|
+
// The keys in json columns, also in updates:
|
|
443
|
+
fieldExpressionKeys,
|
|
395
444
|
})
|
|
396
445
|
.copyFrom(builder, isReturningAll);
|
|
397
446
|
|
|
@@ -405,7 +454,8 @@ export class GraphWriter {
|
|
|
405
454
|
});
|
|
406
455
|
}
|
|
407
456
|
|
|
408
|
-
|
|
457
|
+
// Writes of only the keys under `noUpdate` are patches too:
|
|
458
|
+
return (shouldUpdate ? query.update(node.obj) : query.patch(node.obj))
|
|
409
459
|
.runAfter(async (result, query) => {
|
|
410
460
|
if (isObject(result) && result.$isObjectionModel) {
|
|
411
461
|
// Handle `returning('*')`.
|
|
@@ -554,7 +604,7 @@ function resolveDependencies(schedule, node) {
|
|
|
554
604
|
// Copy the keys and `#ref{}` values that `node` waits for into its model,
|
|
555
605
|
// after those that the graph fills into the models that it reads them from,
|
|
556
606
|
// e.g. the new key of an owner whose BelongsToOne row is replaced. Rows that
|
|
557
|
-
// move first only wait for rows that exist, see `GraphSchedule#
|
|
607
|
+
// move first only wait for rows that exist, see `GraphSchedule#_waitsForWrites()`.
|
|
558
608
|
function resolveFilledDependencies(schedule, node, visitedNodes = new Set()) {
|
|
559
609
|
visitedNodes.add(node);
|
|
560
610
|
|
|
@@ -746,6 +796,40 @@ async function fetchGeneratedKeys(builder, modelClass, nodes) {
|
|
|
746
796
|
}
|
|
747
797
|
}
|
|
748
798
|
|
|
799
|
+
// Add the changed keys of `cells` at the top level of json columns to `node` as
|
|
800
|
+
// field expressions where their column holds nothing but these keys, and return
|
|
801
|
+
// them by column. PostgreSQL and MySQL can't write nested paths whose parents
|
|
802
|
+
// are missing, so their columns are written whole.
|
|
803
|
+
function setKeyPaths(node, cells) {
|
|
804
|
+
const keysByColumn = new Map();
|
|
805
|
+
const pathCells = cells.filter(({ prop, index }) => prop.isJsonKey(index));
|
|
806
|
+
|
|
807
|
+
for (const [column, columnCells] of groupBy(pathCells, ({ prop, index }) => prop.props[index])) {
|
|
808
|
+
const keys = {};
|
|
809
|
+
|
|
810
|
+
for (const { prop, index } of columnCells) {
|
|
811
|
+
prop.setProp(keys, index, prop.getProp(node.obj, index));
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (jsonEquals(node.obj[column], keys[column])) {
|
|
815
|
+
const patch = {};
|
|
816
|
+
|
|
817
|
+
for (const { prop, index } of columnCells) {
|
|
818
|
+
const value = prop.getProp(node.obj, index);
|
|
819
|
+
|
|
820
|
+
if (!node.current || !nonStrictEquals(prop.getProp(node.current.obj, index), value)) {
|
|
821
|
+
prop.patch(patch, index, value);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
Object.assign(node.obj, patch);
|
|
826
|
+
keysByColumn.set(column, Object.keys(patch));
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return keysByColumn;
|
|
831
|
+
}
|
|
832
|
+
|
|
749
833
|
function hasOwnerKeyArray(relation) {
|
|
750
834
|
return relation.keySide === 'owner' && !relation.isOneToOne();
|
|
751
835
|
}
|
|
@@ -223,7 +223,7 @@ export function checkPlacements(builder, nodes) {
|
|
|
223
223
|
} else if (!nonStrictEquals(other.value, value)) {
|
|
224
224
|
throw builder.modelClass().createValidationError({
|
|
225
225
|
type: ValidationErrorType.InvalidGraph,
|
|
226
|
-
message: `the graph gives ${prop} of one ${name} two values, at ${formatPath(other.path)} and at ${formatPath(path)}`,
|
|
226
|
+
message: `the graph gives ${prop} of one ${name} two values, at ${formatPath(other.path())} and at ${formatPath(path())}`,
|
|
227
227
|
});
|
|
228
228
|
}
|
|
229
229
|
};
|
|
@@ -243,7 +243,8 @@ export function checkPlacements(builder, nodes) {
|
|
|
243
243
|
.map(([cols, rowId]) => `${cols}=${rowId}`),
|
|
244
244
|
].join(':');
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
// The data paths are only built for the errors:
|
|
247
|
+
const getPath = (node, key) => () => [node.dataPathKey, key].filter(Boolean).join('.');
|
|
247
248
|
const isSlot = (relation) => relation.keySide === 'related' && relation.isOneToOne();
|
|
248
249
|
|
|
249
250
|
// The props that the models give, except for the ids and the keys that the
|
|
@@ -405,7 +406,7 @@ export function checkPlacements(builder, nodes) {
|
|
|
405
406
|
const other = rows.get(otherRowId).get(relatedProp.props[0]);
|
|
406
407
|
throw builder.modelClass().createValidationError({
|
|
407
408
|
type: ValidationErrorType.InvalidGraph,
|
|
408
|
-
message: `the graph puts two ${relatedModelClass.name} rows in ${relation.name} of one ${owner.modelClass.name}, at ${formatPath(path)} and at ${formatPath(other.path)}`,
|
|
409
|
+
message: `the graph puts two ${relatedModelClass.name} rows in ${relation.name} of one ${owner.modelClass.name}, at ${formatPath(path())} and at ${formatPath(other.path())}`,
|
|
409
410
|
});
|
|
410
411
|
}
|
|
411
412
|
}
|
|
@@ -444,6 +445,42 @@ export function checkReferences(builder, plan) {
|
|
|
444
445
|
}
|
|
445
446
|
}
|
|
446
447
|
|
|
448
|
+
// A model that the graph relates by the key of its BelongsToOneRelation alone
|
|
449
|
+
// isn't looked up: only its key is written to its owner. So it can't give other
|
|
450
|
+
// props, unless they aren't written, relations or values that others refer to.
|
|
451
|
+
export function checkKeyRelates(builder, plan) {
|
|
452
|
+
const { graphOptions } = plan;
|
|
453
|
+
|
|
454
|
+
for (const node of plan.activeNodes.filter((node) => plan.isRelatedByKey(node))) {
|
|
455
|
+
const { modelClass, relation, obj } = node;
|
|
456
|
+
const relationNames = modelClass.getRelationNames();
|
|
457
|
+
const idProps = modelClass.getIdPropertyArray();
|
|
458
|
+
const isWritten = graphOptions.shouldPatch(node) || graphOptions.shouldUpdate(node);
|
|
459
|
+
const name = Object.keys(obj).find((key) => {
|
|
460
|
+
const value = obj[key];
|
|
461
|
+
return (
|
|
462
|
+
value !== undefined &&
|
|
463
|
+
!isInternalProp(key) &&
|
|
464
|
+
!relation.relatedProp.props.includes(key) &&
|
|
465
|
+
!(value === null && idProps.includes(key)) &&
|
|
466
|
+
(relationNames.includes(key) || isWritten)
|
|
467
|
+
);
|
|
468
|
+
});
|
|
469
|
+
const isReferenced =
|
|
470
|
+
node.propertyReferencingNodes.length > 0 ||
|
|
471
|
+
plan.nodes.some((other) => other.referencedNode === node);
|
|
472
|
+
|
|
473
|
+
if (name || isReferenced) {
|
|
474
|
+
throw builder.modelClass().createValidationError({
|
|
475
|
+
type: ValidationErrorType.InvalidGraph,
|
|
476
|
+
message: `the ${modelClass.name} at ${formatPath(node.dataPathKey)} is related by its key alone, so ${
|
|
477
|
+
name ? `its ${name} can't be written` : "it can't be referenced"
|
|
478
|
+
}`,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
447
484
|
// A `#ref{}` waits for the model it reads if the value only exists once that
|
|
448
485
|
// model is written. These waits follow from the graph alone, so their cycles
|
|
449
486
|
// are found before the first write.
|
|
@@ -18,7 +18,9 @@ export class GraphKeys {
|
|
|
18
18
|
this.activeNodes = new Set(plan.activeNodes);
|
|
19
19
|
this.rows = new Map();
|
|
20
20
|
this.nodesByRow = new Map();
|
|
21
|
-
|
|
21
|
+
// The cells in their order, and those of each row by their props:
|
|
22
|
+
this.cells = [];
|
|
23
|
+
this.cellsByRow = new Map();
|
|
22
24
|
// The props that each node only reads, and that other nodes write:
|
|
23
25
|
this.readProps = new Map();
|
|
24
26
|
// The cells that each place sets or reads:
|
|
@@ -153,13 +155,26 @@ export class GraphKeys {
|
|
|
153
155
|
.map(({ prop, index }) => prop.props[index]);
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
// Return the cells of the row of `node` that the graph sets, unless `node`
|
|
159
|
+
// only reads them:
|
|
160
|
+
getSetCells(node) {
|
|
161
|
+
const row = this._getRow(node);
|
|
162
|
+
return [...(this.placeCells.get(node) || [])].filter(
|
|
163
|
+
(cell) =>
|
|
164
|
+
cell.row === row &&
|
|
165
|
+
cell.setNodes.size > 0 &&
|
|
166
|
+
!this.isOnlyRead(node, cell.prop.props[cell.index]),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
156
170
|
// Return true if a place other than the places of the row of `node` through
|
|
157
171
|
// its BelongsToOne `relation` sets the key of the relation, e.g. the place
|
|
158
172
|
// that moves the row.
|
|
159
173
|
isSetByOtherPlaces(node, relation) {
|
|
160
174
|
const row = this._getRow(node);
|
|
175
|
+
const cells = this.cellsByRow.get(row);
|
|
161
176
|
return relation.ownerProp.props.some((_, i) =>
|
|
162
|
-
[...(
|
|
177
|
+
[...(cells?.get(relation.ownerProp.propDescription(i))?.placeNodes || [])].some(
|
|
163
178
|
(other) => !(other.relation === relation && this._getRow(other.parent) === row),
|
|
164
179
|
),
|
|
165
180
|
);
|
|
@@ -198,13 +213,23 @@ export class GraphKeys {
|
|
|
198
213
|
|
|
199
214
|
// Return true if a place of `node` sets the key column `prop` of its row:
|
|
200
215
|
isSet(node, prop, index) {
|
|
201
|
-
|
|
216
|
+
const cell = this.cellsByRow.get(this._getRow(node))?.get(prop.propDescription(index));
|
|
217
|
+
return !!cell?.setNodes.has(node);
|
|
202
218
|
}
|
|
203
219
|
|
|
204
|
-
// Return the nodes whose inserts
|
|
205
|
-
// cells that the place of `node` sets or reads
|
|
220
|
+
// Return the nodes whose inserts, `#ref{}` values or changes bind the slots
|
|
221
|
+
// of the cells that the place of `node` sets or reads, and whether they
|
|
222
|
+
// change them.
|
|
206
223
|
waitsFor(node) {
|
|
224
|
+
const { relation, parent } = node;
|
|
207
225
|
const waits = [];
|
|
226
|
+
const addWaits = (cell, nodes, isKeyChange) => {
|
|
227
|
+
for (const other of nodes) {
|
|
228
|
+
if (this.rows.get(other) !== this.rows.get(node)) {
|
|
229
|
+
waits.push({ node: other, prop: cell.prop.props[cell.index], isKeyChange });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
208
233
|
|
|
209
234
|
for (const cell of this.placeCells.get(node) || []) {
|
|
210
235
|
for (const anchorCell of this._getAnchorCells(cell.slot)) {
|
|
@@ -216,11 +241,21 @@ export class GraphKeys {
|
|
|
216
241
|
(this.insertRows.has(row) && this.plan.actions.get(other) === 'insert') ||
|
|
217
242
|
this._hasReference(other, prop, index),
|
|
218
243
|
);
|
|
244
|
+
addWaits(cell, anchorCell.waitedNodes, false);
|
|
219
245
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
246
|
+
// Only the places that set a cell write the changes of its slot:
|
|
247
|
+
if (cell.setNodes.has(node)) {
|
|
248
|
+
addWaits(cell, [this._getChangingNode(anchorCell)].filter(Boolean), true);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Join rows are inserted after all rows, but read the keys of their owners,
|
|
254
|
+
// which patches may change:
|
|
255
|
+
if (relation?.keySide === 'join') {
|
|
256
|
+
for (const cell of this._getCells(parent, relation.ownerProp)) {
|
|
257
|
+
for (const anchorCell of this._getAnchorCells(cell.slot)) {
|
|
258
|
+
addWaits(cell, [this._getChangingNode(anchorCell)].filter(Boolean), true);
|
|
224
259
|
}
|
|
225
260
|
}
|
|
226
261
|
}
|
|
@@ -228,6 +263,21 @@ export class GraphKeys {
|
|
|
228
263
|
return waits;
|
|
229
264
|
}
|
|
230
265
|
|
|
266
|
+
// Return the patch or relate that changes the value of an anchor cell, or
|
|
267
|
+
// may change it, when its row isn't loaded. A foreign key may reference the
|
|
268
|
+
// cell, so the rows that read the new value wait for it (#428). Ids don't
|
|
269
|
+
// change, see `checkChangedIds()`, and rows without ids are found by the
|
|
270
|
+
// values that the graph gives them.
|
|
271
|
+
_getChangingNode(cell) {
|
|
272
|
+
const { writtenValue, writingNode } = this._getGivenValues(cell);
|
|
273
|
+
return writingNode &&
|
|
274
|
+
!cell.isId &&
|
|
275
|
+
(this.insertRows.has(cell.row) || (writingNode.referencedNode || writingNode).obj.$hasId()) &&
|
|
276
|
+
!nonStrictEquals(writtenValue, this._getStoredValue(cell))
|
|
277
|
+
? writingNode
|
|
278
|
+
: null;
|
|
279
|
+
}
|
|
280
|
+
|
|
231
281
|
// The place of `node` gives `target` the key of `source`, for each column of
|
|
232
282
|
// the relation:
|
|
233
283
|
_addPlace(node, target, targetProp, source, sourceProp) {
|
|
@@ -244,8 +294,11 @@ export class GraphKeys {
|
|
|
244
294
|
sourceCells[i].readerNodes.add(source);
|
|
245
295
|
|
|
246
296
|
// A whole `#ref{}` value wins over the key of its place, also where the
|
|
247
|
-
// place lists the row again:
|
|
248
|
-
if (
|
|
297
|
+
// place lists the row again. Most graphs have none, so skip the keys:
|
|
298
|
+
if (
|
|
299
|
+
this.wholeReferences.size === 0 ||
|
|
300
|
+
!this.wholeReferences.has(this._getPlaceKey(target, cell.prop.props[cell.index]))
|
|
301
|
+
) {
|
|
249
302
|
cell.setNodes.add(target);
|
|
250
303
|
cell.placeNodes.add(node);
|
|
251
304
|
joinCells(cell, sourceCells[i]);
|
|
@@ -313,12 +366,19 @@ export class GraphKeys {
|
|
|
313
366
|
|
|
314
367
|
_getCell(node, prop, index) {
|
|
315
368
|
const row = this._getRow(node);
|
|
316
|
-
const
|
|
317
|
-
let
|
|
369
|
+
const description = prop.propDescription(index);
|
|
370
|
+
let cells = this.cellsByRow.get(row);
|
|
371
|
+
|
|
372
|
+
if (!cells) {
|
|
373
|
+
cells = new Map();
|
|
374
|
+
this.cellsByRow.set(row, cells);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let cell = cells.get(description);
|
|
318
378
|
|
|
319
379
|
if (!cell) {
|
|
320
380
|
cell = {
|
|
321
|
-
key,
|
|
381
|
+
key: this._getKey(row, prop, index),
|
|
322
382
|
row,
|
|
323
383
|
node,
|
|
324
384
|
prop,
|
|
@@ -330,7 +390,8 @@ export class GraphKeys {
|
|
|
330
390
|
slot: null,
|
|
331
391
|
};
|
|
332
392
|
cell.slot = { cells: [cell] };
|
|
333
|
-
this.cells.
|
|
393
|
+
this.cells.push(cell);
|
|
394
|
+
cells.set(description, cell);
|
|
334
395
|
}
|
|
335
396
|
|
|
336
397
|
return cell;
|
|
@@ -376,8 +437,13 @@ export class GraphKeys {
|
|
|
376
437
|
givenValues.value = value;
|
|
377
438
|
}
|
|
378
439
|
|
|
379
|
-
if (
|
|
440
|
+
if (
|
|
441
|
+
givenValues.writtenValue === undefined &&
|
|
442
|
+
action !== 'insert' &&
|
|
443
|
+
value !== undefined
|
|
444
|
+
) {
|
|
380
445
|
givenValues.writtenValue = value;
|
|
446
|
+
givenValues.writingNode = node;
|
|
381
447
|
}
|
|
382
448
|
|
|
383
449
|
if (givenValues.writtenValue !== undefined) {
|
|
@@ -524,7 +590,7 @@ export class GraphKeys {
|
|
|
524
590
|
// Throw for slots whose anchors, or whose rows without any, give different
|
|
525
591
|
// values that are known before the writes.
|
|
526
592
|
_checkSlots() {
|
|
527
|
-
const slots = new Set(
|
|
593
|
+
const slots = new Set(this.cells.map((cell) => cell.slot));
|
|
528
594
|
|
|
529
595
|
for (const slot of [...slots].filter(({ cells }) => cells.length > 1)) {
|
|
530
596
|
const anchorCells = this._getAnchorCells(slot);
|
|
@@ -17,6 +17,12 @@ export class JsonDialect {
|
|
|
17
17
|
return sql;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// The value that the path updates of a column start from, an empty one where
|
|
21
|
+
// the column is NULL, as `set()` leaves NULL as it is.
|
|
22
|
+
setBase(sql, access) {
|
|
23
|
+
return `COALESCE(${sql}, '${access[0].type === 'array' ? '[]' : '{}'}')`;
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
// Reference the bare column if there is no path, so that the database can use
|
|
21
27
|
// its indexes.
|
|
22
28
|
field(sql, access) {
|
|
@@ -70,8 +70,11 @@ export class UpdateOperation extends QueryBuilderOperation {
|
|
|
70
70
|
if (this.modelOptions.patch) {
|
|
71
71
|
return knexBuilder.update(convertFieldExpressionsToRaw(builder, json));
|
|
72
72
|
} else {
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const { fieldExpressionKeys } = builder.internalOptions();
|
|
74
|
+
checkFieldExpressions(builder.modelClass(), this.model, 'update', fieldExpressionKeys);
|
|
75
|
+
return knexBuilder.update(
|
|
76
|
+
fieldExpressionKeys.length > 0 ? convertFieldExpressionsToRaw(builder, json) : json,
|
|
77
|
+
);
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
|
|
@@ -7,13 +7,14 @@ import { ValidationErrorType } from '../../model/ValidationError.js';
|
|
|
7
7
|
import { getQueryPropKeys } from '../../model/modelQueryProps.js';
|
|
8
8
|
|
|
9
9
|
// Inserts and updates write whole values, so they can't write into json
|
|
10
|
-
// columns with field expressions like `'meta:color'
|
|
11
|
-
//
|
|
12
|
-
|
|
10
|
+
// columns with field expressions like `'meta:color'`, except for the
|
|
11
|
+
// `allowedKeys` of internal writes. Keys with a ':' that don't parse as field
|
|
12
|
+
// expressions are columns, as in the query properties.
|
|
13
|
+
export function checkFieldExpressions(modelClass, model, operation, allowedKeys = []) {
|
|
13
14
|
const keys = getFieldExpressionKeys(modelClass, [
|
|
14
15
|
...Object.keys(model),
|
|
15
16
|
...getQueryPropKeys(model),
|
|
16
|
-
]);
|
|
17
|
+
]).filter((key) => !allowedKeys.includes(key));
|
|
17
18
|
|
|
18
19
|
if (keys.length > 0) {
|
|
19
20
|
throw modelClass.createValidationError({
|
|
@@ -48,7 +49,7 @@ export function convertFieldExpressionsToRaw(builder, json) {
|
|
|
48
49
|
|
|
49
50
|
if (getFieldExpressionColumn(key, declaredColumns) !== null) {
|
|
50
51
|
// 'col:attr' : ref('other:lol') is transformed to
|
|
51
|
-
// "col" : raw(`jsonb_set("table"."col", '{attr}', to_jsonb("other"#>'{lol}'), true)`)
|
|
52
|
+
// "col" : raw(`jsonb_set(COALESCE("table"."col", '{}'), '{attr}', to_jsonb("other"#>'{lol}'), true)`)
|
|
52
53
|
// on Postgres, with the column qualified, as it is ambiguous with
|
|
53
54
|
// `excluded` in merge().
|
|
54
55
|
dialect.checkSupport();
|
|
@@ -82,9 +83,11 @@ export function convertFieldExpressionsToRaw(builder, json) {
|
|
|
82
83
|
val = JSON.stringify(val);
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
const { access } = parsed.parsedExpr;
|
|
87
|
+
const previous = convertedJson[parsed.column];
|
|
85
88
|
convertedJson[parsed.column] = knex.raw(
|
|
86
|
-
dialect.set('??',
|
|
87
|
-
[
|
|
89
|
+
dialect.set(previous ? '??' : dialect.setBase('??', access), access, valuePlaceholder),
|
|
90
|
+
[previous || `${builder.tableRef()}.${parsed.column}`, val],
|
|
88
91
|
);
|
|
89
92
|
} else {
|
|
90
93
|
convertedJson[key] = val;
|
|
@@ -126,6 +126,12 @@ export class RelationProperty {
|
|
|
126
126
|
return this._propSetters[index](obj, value);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// Returns true if the index:th column is a key at the top level of a json
|
|
130
|
+
// column.
|
|
131
|
+
isJsonKey(index) {
|
|
132
|
+
return this._refs[index].parsedExpr.access.length === 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
129
135
|
// Returns an instance of ReferenceBuilder that points to the index:th
|
|
130
136
|
// value of a row.
|
|
131
137
|
ref(builder, index) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "objection",
|
|
3
|
-
"version": "4.0.0-rc.
|
|
3
|
+
"version": "4.0.0-rc.4",
|
|
4
4
|
"description": "An SQL-friendly ORM for Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/objection.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "pnpm eslint && vitest run && pnpm test:typings",
|
|
18
18
|
"test:fast": "vitest run --bail 1",
|
|
19
19
|
"test:watch": "vitest",
|
|
20
|
-
"test:typings": "tsc && tsc -p tests/ts/strict && tsc -p tests/bench/typings && prettier --check \"typings/**/*.ts\" \"tests/ts/**/*.ts\" \"tests/bench/typings/*.ts\" \"tests/bench/typings/app/*.ts\"",
|
|
20
|
+
"test:typings": "tsc && tsc -p tests/ts/strict && tsc -p tests/bench/typings && node tests/ts/completions.js && prettier --check \"typings/**/*.ts\" \"tests/ts/**/*.ts\" \"tests/bench/typings/*.ts\" \"tests/bench/typings/app/*.ts\"",
|
|
21
21
|
"bench:typings": "node tests/bench/typings/bench.js",
|
|
22
22
|
"db:up": "docker compose up -d --wait",
|
|
23
23
|
"db:down": "docker compose --profile mssql down -v",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"sqlite3": "^6.0.1",
|
|
92
92
|
"tedious": "^20.3.7",
|
|
93
93
|
"typescript": "^7.0.0",
|
|
94
|
+
"typescript-5": "npm:typescript@5.8.3",
|
|
94
95
|
"vitepress": "1.6.4",
|
|
95
96
|
"vitest": "^4.1.11"
|
|
96
97
|
}
|
|
@@ -475,7 +475,7 @@ export type ItemType<T> = T extends Array<unknown> ? T[number] : T;
|
|
|
475
475
|
* Type for keys of non-function properties of T.
|
|
476
476
|
*/
|
|
477
477
|
export type NonFunctionPropertyNames<T> = {
|
|
478
|
-
[K in keyof T]
|
|
478
|
+
[K in keyof T]-?: T[K] extends Function ? never : K;
|
|
479
479
|
}[keyof T];
|
|
480
480
|
|
|
481
481
|
// The type-only members of models, which aren't data, also the keys of the
|
|
@@ -540,7 +540,7 @@ export type PartialModelObject<T extends Model> = {
|
|
|
540
540
|
? I[] | PartialModelObject<I>[]
|
|
541
541
|
: Expression<T[K]>
|
|
542
542
|
: NonNullable<T[K]> extends Model
|
|
543
|
-
? Expression<T[K]> | PartialModelObject<NonNullable<T[K]>> //
|
|
543
|
+
? Expression<T[K]> | PartialModelObject<NonNullable<T[K]>> // Nullable to-one relations
|
|
544
544
|
: Expression<T[K]>;
|
|
545
545
|
};
|
|
546
546
|
|
|
@@ -591,7 +591,7 @@ export type GraphParameters = {
|
|
|
591
591
|
export type PartialModelGraph<M, T = M & GraphParameters> = T extends any
|
|
592
592
|
? {
|
|
593
593
|
[K in DataPropertyNames<T>]?: null extends T[K]
|
|
594
|
-
? PartialModelGraphField<NonNullable<T[K]>> | null //
|
|
594
|
+
? PartialModelGraphField<NonNullable<T[K]>> | null // Strip null to recognize to-one relations
|
|
595
595
|
: PartialModelGraphField<T[K]>;
|
|
596
596
|
}
|
|
597
597
|
: never;
|
|
@@ -635,9 +635,10 @@ export type OptionalInsertPropertyNames<M> = {
|
|
|
635
635
|
export type InsertablePropertyNames<M> = Exclude<DataPropertyNames<M>, RelationPropertyNames<M>>;
|
|
636
636
|
|
|
637
637
|
/**
|
|
638
|
-
* The data
|
|
639
|
-
*
|
|
640
|
-
*
|
|
638
|
+
* The complete data of a new row of model M, to check what is passed to
|
|
639
|
+
* `insert()`, which itself takes any subset of the properties. Unlike
|
|
640
|
+
* `PartialModelObject`, all properties are required except optional, nullable
|
|
641
|
+
* and `Generated` ones. Relation properties are left out.
|
|
641
642
|
*/
|
|
642
643
|
export type Insertable<M extends Model> = {
|
|
643
644
|
[K in Exclude<InsertablePropertyNames<M>, OptionalInsertPropertyNames<M>>]: Expression<M[K]>;
|
|
@@ -646,9 +647,9 @@ export type Insertable<M extends Model> = {
|
|
|
646
647
|
};
|
|
647
648
|
|
|
648
649
|
/**
|
|
649
|
-
* Like `Insertable`, but for `insertGraph()`: relation
|
|
650
|
-
* optional and hold the insertable graphs of the related
|
|
651
|
-
* references to existing ones through `#dbRef` or `#ref`.
|
|
650
|
+
* Like `Insertable`, but for graphs passed to `insertGraph()`: relation
|
|
651
|
+
* properties are optional and hold the insertable graphs of the related
|
|
652
|
+
* models, or references to existing ones through `#dbRef` or `#ref`.
|
|
652
653
|
*/
|
|
653
654
|
export type InsertableGraph<M extends Model> = Insertable<M> &
|
|
654
655
|
Omit<GraphParameters, '#dbRef' | '#ref'> & {
|
|
@@ -667,38 +668,15 @@ export type InsertableGraphField<F> =
|
|
|
667
668
|
/**
|
|
668
669
|
* Extracts the property names (excluding relations) of a model class.
|
|
669
670
|
*/
|
|
670
|
-
export type ModelProps<T extends Model> =
|
|
671
|
-
{
|
|
672
|
-
[K in keyof T]?: Defined<T[K]> extends Model
|
|
673
|
-
? never
|
|
674
|
-
: Defined<T[K]> extends Array<infer I>
|
|
675
|
-
? I extends Model
|
|
676
|
-
? never
|
|
677
|
-
: K
|
|
678
|
-
: T[K] extends Function
|
|
679
|
-
? never
|
|
680
|
-
: K;
|
|
681
|
-
}[keyof T],
|
|
682
|
-
undefined | ModelTypeMembers
|
|
683
|
-
>;
|
|
671
|
+
export type ModelProps<T extends Model> = InsertablePropertyNames<T>;
|
|
684
672
|
|
|
685
673
|
/**
|
|
686
|
-
* Extracts the relation names of
|
|
674
|
+
* Extracts the relation names of a model class.
|
|
687
675
|
*/
|
|
688
|
-
export type ModelRelations<T extends Model> =
|
|
689
|
-
{
|
|
690
|
-
[K in keyof T]?: Defined<T[K]> extends Model
|
|
691
|
-
? K
|
|
692
|
-
: Defined<T[K]> extends Array<infer I>
|
|
693
|
-
? I extends Model
|
|
694
|
-
? K
|
|
695
|
-
: never
|
|
696
|
-
: never;
|
|
697
|
-
}[keyof T]
|
|
698
|
-
>;
|
|
676
|
+
export type ModelRelations<T extends Model> = RelationPropertyNames<T>;
|
|
699
677
|
|
|
700
678
|
/**
|
|
701
|
-
* Given a model property type, returns a query
|
|
679
|
+
* Given a model property type, returns a query builder type of the
|
|
702
680
|
* correct kind if the property is a model or a model array. Queries for
|
|
703
681
|
* to-one relations resolve to `undefined` when there's no related row.
|
|
704
682
|
*/
|
|
@@ -760,6 +738,18 @@ type Rebind<
|
|
|
760
738
|
*/
|
|
761
739
|
export type ExtraType<T extends { ExtraType: any }> = T['ExtraType'];
|
|
762
740
|
|
|
741
|
+
/**
|
|
742
|
+
* The extra type of queries in strict mode: the model class C that started the
|
|
743
|
+
* query, e.g. with `Person.query()`, and the modifiers P registered with
|
|
744
|
+
* `modifiers()`, to check the names of modifiers, and the id I of the class,
|
|
745
|
+
* for `findById()` and the other `*ById()` methods.
|
|
746
|
+
*/
|
|
747
|
+
export interface QueryExtra<C = unknown, P = unknown, I = MaybeCompositeId> {
|
|
748
|
+
modelClass: C;
|
|
749
|
+
registered: P;
|
|
750
|
+
id: I;
|
|
751
|
+
}
|
|
752
|
+
|
|
763
753
|
type WithExtra<QB extends AnyQueryBuilder, X> = Rebind<QB, ModelType<QB>, ResultType<QB>, X>;
|
|
764
754
|
|
|
765
755
|
// The narrowed query builders of `withGraph*()` start without the extra type
|
|
@@ -792,15 +782,16 @@ export type NumberQueryBuilder<QB extends AnyQueryBuilder> = QB['~number'];
|
|
|
792
782
|
*/
|
|
793
783
|
export type PageQueryBuilder<QB extends AnyQueryBuilder> = QB['~page'];
|
|
794
784
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
785
|
+
/**
|
|
786
|
+
* Strict mode checks the column names and values of queries against the
|
|
787
|
+
* models. Augment `TypeConfig` to opt in:
|
|
788
|
+
*
|
|
789
|
+
* declare module 'objection' {
|
|
790
|
+
* interface TypeConfig {
|
|
791
|
+
* strict: true;
|
|
792
|
+
* }
|
|
793
|
+
* }
|
|
794
|
+
*/
|
|
804
795
|
export interface TypeConfig {}
|
|
805
796
|
|
|
806
797
|
type IsStrict = TypeConfig extends { strict: true } ? true : false;
|
|
@@ -919,10 +910,11 @@ type CheckedColumn<M, C extends string> =
|
|
|
919
910
|
IsConcreteModel<M> extends true ? CheckColumn<M, C> : string;
|
|
920
911
|
|
|
921
912
|
/**
|
|
922
|
-
* A column argument: checked against model M in strict mode,
|
|
923
|
-
* loose mode,
|
|
913
|
+
* A column argument: checked against model M in strict mode, the model's
|
|
914
|
+
* properties in loose mode, for autocompletion. In loose mode, other columns
|
|
915
|
+
* go through the RefArg overloads.
|
|
924
916
|
*/
|
|
925
|
-
type ColumnArg<M, C extends string> = IfStrict<
|
|
917
|
+
type ColumnArg<M, C extends string> = IfStrict<InsertablePropertyNames<M>, CheckedColumn<M, C>>;
|
|
926
918
|
|
|
927
919
|
/**
|
|
928
920
|
* The value of a column of type T in conditions: `null` is always accepted,
|
|
@@ -1071,8 +1063,8 @@ type StrictSelectionObject<M, O> = {
|
|
|
1071
1063
|
/**
|
|
1072
1064
|
* The query builder QB with its result rows narrowed to the properties P in
|
|
1073
1065
|
* strict mode, next to the model's methods and relations, as after `select()`
|
|
1074
|
-
* and the aggregates, marked with SelectedBrand. Repeated selections add up.
|
|
1075
|
-
* that aren't checked, e.g. in generic code, aren't narrowed.
|
|
1066
|
+
* and the aggregates, marked with SelectedBrand. Repeated selections add up.
|
|
1067
|
+
* Queries that aren't checked, e.g. in generic code, aren't narrowed.
|
|
1076
1068
|
*/
|
|
1077
1069
|
type NarrowRows<QB extends AnyQueryBuilder, P, All extends boolean = false> = RowsQueryBuilder<
|
|
1078
1070
|
QB,
|
|
@@ -1188,18 +1180,6 @@ export type SelectedBrand<A> = { readonly [selected]: A };
|
|
|
1188
1180
|
*/
|
|
1189
1181
|
export type ExtractedBrand<A> = { readonly [extracted]: A };
|
|
1190
1182
|
|
|
1191
|
-
/**
|
|
1192
|
-
* The extra type of queries in strict mode: the model class C that started the
|
|
1193
|
-
* query, e.g. with `Person.query()`, and the modifiers P registered with
|
|
1194
|
-
* `modifiers()`, to check the names of modifiers, and the id I of the class,
|
|
1195
|
-
* for `findById()` and the other `*ById()` methods.
|
|
1196
|
-
*/
|
|
1197
|
-
export interface QueryExtra<C = unknown, P = unknown, I = MaybeCompositeId> {
|
|
1198
|
-
modelClass: C;
|
|
1199
|
-
registered: P;
|
|
1200
|
-
id: I;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
1183
|
// The results whose rows have `jsonExtract()` values, which `withGraph*()`
|
|
1204
1184
|
// keeps instead of converting the result kind:
|
|
1205
1185
|
type ExtractedRows =
|
|
@@ -1265,9 +1245,9 @@ type SelectedType<M, C extends string> = C extends `${infer Q}.${infer Col}`
|
|
|
1265
1245
|
: ColumnType<M, C>;
|
|
1266
1246
|
|
|
1267
1247
|
/**
|
|
1268
|
-
* The property of the column or alias C in the result rows:
|
|
1269
|
-
* converts them to camel case, with `upperCase: true`
|
|
1270
|
-
* of names in upper case.
|
|
1248
|
+
* The property of the column or alias C in the result rows:
|
|
1249
|
+
* `snakeCaseMappers()` converts them to camel case, with `upperCase: true`
|
|
1250
|
+
* after lowering the case of names in upper case.
|
|
1271
1251
|
*/
|
|
1272
1252
|
type ResultKey<M, C extends string> = M extends { ColumnNameMapping: 'snakeCase' }
|
|
1273
1253
|
? CamelCase<C>
|
|
@@ -1547,15 +1527,18 @@ export type JsonObjectOrFieldExpression = object | object[] | FieldExpression;
|
|
|
1547
1527
|
export type Selection<QB extends AnyQueryBuilder> = ColumnRef | AnyQueryBuilder | CallbackVoid<QB>;
|
|
1548
1528
|
|
|
1549
1529
|
export interface SelectMethod<out QB extends AnyQueryBuilder> {
|
|
1550
|
-
//
|
|
1551
|
-
|
|
1530
|
+
// The column overload comes first, for autocompletion, and last in strict
|
|
1531
|
+
// mode, so that errors are reported for it:
|
|
1532
|
+
<QBP extends QB, const C extends readonly [unknown, ...unknown[]]>(
|
|
1533
|
+
...columns: IfStrict<ModelProps<ModelType<QBP>>[], StrictSelections<ModelType<QBP>, C>>
|
|
1534
|
+
): IfStrict<QB, SelectQueryBuilder<QBP, C>>;
|
|
1552
1535
|
<QBP extends QB>(columns: IfStrict<ModelProps<ModelType<QBP>>, never>[]): QB;
|
|
1553
1536
|
|
|
1554
1537
|
<QBP extends QB>(...columns: IfStrict<Selection<QBP>, never>[]): QB;
|
|
1555
1538
|
<QBP extends QB>(columns: IfStrict<Selection<QBP>, never>[]): QB;
|
|
1556
1539
|
|
|
1557
|
-
//
|
|
1558
|
-
<QBP extends QB>(...args: IfStrict<
|
|
1540
|
+
// Allow anything else in loose mode, like `select(1)`:
|
|
1541
|
+
<QBP extends QB>(...args: IfStrict<{} | null | undefined, never>[]): QB;
|
|
1559
1542
|
|
|
1560
1543
|
<QBP extends QB, const C extends readonly unknown[] = []>(
|
|
1561
1544
|
...columns: StrictSelections<ModelType<QBP>, C>
|
|
@@ -1584,7 +1567,7 @@ export interface JsonExtraction {
|
|
|
1584
1567
|
|
|
1585
1568
|
// Extractions without an alias, or with several, add any properties.
|
|
1586
1569
|
export interface JsonExtract<out QB extends AnyQueryBuilder> {
|
|
1587
|
-
//
|
|
1570
|
+
// The column overloads come first, for autocompletion:
|
|
1588
1571
|
<QBP extends QB, const C extends string = never, const A extends string = string>(
|
|
1589
1572
|
column: ColumnArg<ModelType<QBP>, C>,
|
|
1590
1573
|
path: string,
|
|
@@ -1605,7 +1588,7 @@ export interface JsonExtract<out QB extends AnyQueryBuilder> {
|
|
|
1605
1588
|
}
|
|
1606
1589
|
|
|
1607
1590
|
export interface JsonSet<out QB extends AnyQueryBuilder> {
|
|
1608
|
-
//
|
|
1591
|
+
// The column overloads come first, for autocompletion:
|
|
1609
1592
|
<QBP extends QB, const C extends string = never>(
|
|
1610
1593
|
column: ColumnArg<ModelType<QBP>, C>,
|
|
1611
1594
|
path: string,
|
|
@@ -1617,7 +1600,7 @@ export interface JsonSet<out QB extends AnyQueryBuilder> {
|
|
|
1617
1600
|
}
|
|
1618
1601
|
|
|
1619
1602
|
export interface JsonInsert<out QB extends AnyQueryBuilder> {
|
|
1620
|
-
//
|
|
1603
|
+
// The column overloads come first, for autocompletion:
|
|
1621
1604
|
<QBP extends QB, const C extends string = never>(
|
|
1622
1605
|
column: ColumnArg<ModelType<QBP>, C>,
|
|
1623
1606
|
path: string,
|
|
@@ -1629,7 +1612,7 @@ export interface JsonInsert<out QB extends AnyQueryBuilder> {
|
|
|
1629
1612
|
}
|
|
1630
1613
|
|
|
1631
1614
|
export interface JsonRemove<out QB extends AnyQueryBuilder> {
|
|
1632
|
-
//
|
|
1615
|
+
// The column overloads come first, for autocompletion:
|
|
1633
1616
|
<QBP extends QB, const C extends string = never>(
|
|
1634
1617
|
column: ColumnArg<ModelType<QBP>, C>,
|
|
1635
1618
|
path: string,
|
|
@@ -1640,6 +1623,17 @@ export interface JsonRemove<out QB extends AnyQueryBuilder> {
|
|
|
1640
1623
|
}
|
|
1641
1624
|
|
|
1642
1625
|
export interface WhereMethod<out QB extends AnyQueryBuilder> {
|
|
1626
|
+
// The column overloads come first, for autocompletion:
|
|
1627
|
+
<QBP extends QB, const C extends string = never, const O extends Operator = Operator>(
|
|
1628
|
+
col: ColumnArg<ModelType<QBP>, C>,
|
|
1629
|
+
op: O,
|
|
1630
|
+
expr: OperatorValueArg<ModelType<QBP>, C, O>,
|
|
1631
|
+
): QB;
|
|
1632
|
+
<QBP extends QB, const C extends string = never>(
|
|
1633
|
+
col: ColumnArg<ModelType<QBP>, C>,
|
|
1634
|
+
expr: ValueArg<ModelType<QBP>, C>,
|
|
1635
|
+
): QB;
|
|
1636
|
+
|
|
1643
1637
|
(condition: boolean): QB;
|
|
1644
1638
|
(cb: CallbackVoid<QB>): QB;
|
|
1645
1639
|
(raw: Raw): QB;
|
|
@@ -1648,7 +1642,7 @@ export interface WhereMethod<out QB extends AnyQueryBuilder> {
|
|
|
1648
1642
|
(col: RefArg<QB>, op: Operator, expr: Expression<PrimitiveValue>): QB;
|
|
1649
1643
|
(col: RefArg<QB>, expr: Expression<PrimitiveValue>): QB;
|
|
1650
1644
|
|
|
1651
|
-
// The column overloads come last, so that errors are reported for them
|
|
1645
|
+
// The column overloads come last, so that errors are reported for them:
|
|
1652
1646
|
<QBP extends QB, const C extends string = never, const O extends Operator = Operator>(
|
|
1653
1647
|
col: ColumnArg<ModelType<QBP>, C>,
|
|
1654
1648
|
op: O,
|
|
@@ -1660,14 +1654,19 @@ export interface WhereMethod<out QB extends AnyQueryBuilder> {
|
|
|
1660
1654
|
): QB;
|
|
1661
1655
|
|
|
1662
1656
|
<QBP extends QB>(obj: IfStrict<PartialModelProps<ModelType<QBP>>, never>): QB;
|
|
1663
|
-
//
|
|
1664
|
-
//
|
|
1665
|
-
// tools can autocomplete using it.
|
|
1657
|
+
// Allow any keys in loose mode. The model's properties above only serve
|
|
1658
|
+
// autocompletion.
|
|
1666
1659
|
(obj: IfStrict<object, never>): QB;
|
|
1667
1660
|
<QBP extends QB, const O extends object = {}>(obj: O & StrictWhereObject<ModelType<QBP>, O>): QB;
|
|
1668
1661
|
}
|
|
1669
1662
|
|
|
1670
1663
|
export interface WhereLikeMethod<out QB extends AnyQueryBuilder> {
|
|
1664
|
+
// The column overload comes first, for autocompletion, and last, so that
|
|
1665
|
+
// errors are reported for it:
|
|
1666
|
+
<QBP extends QB, const C extends string = never>(
|
|
1667
|
+
col: ColumnArg<ModelType<QBP>, C>,
|
|
1668
|
+
expr: Expression<string>,
|
|
1669
|
+
): QB;
|
|
1671
1670
|
(col: RefArg<QB>, expr: Expression<string>): QB;
|
|
1672
1671
|
<QBP extends QB, const C extends string = never>(
|
|
1673
1672
|
col: ColumnArg<ModelType<QBP>, C>,
|
|
@@ -1688,13 +1687,12 @@ export interface WhereExistsMethod<out QB extends AnyQueryBuilder> {
|
|
|
1688
1687
|
}
|
|
1689
1688
|
|
|
1690
1689
|
export interface WhereInMethod<out QB extends AnyQueryBuilder> {
|
|
1691
|
-
//
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1690
|
+
// The column overload comes first, for autocompletion, and last in strict
|
|
1691
|
+
// mode, so that errors are reported for it:
|
|
1692
|
+
<QBP extends QB, const C extends string = never>(
|
|
1693
|
+
col: ColumnArg<ModelType<QBP>, C>,
|
|
1694
|
+
expr: IfStrict<Expression<PrimitiveValue> | CallbackVoid<QB>, WhereInValues<QB, QBP, C>>,
|
|
1695
1695
|
): QB;
|
|
1696
|
-
<QBP extends QB>(col: IfStrict<ModelProps<ModelType<QBP>>, never>, cb: CallbackVoid<QB>): QB;
|
|
1697
|
-
<QBP extends QB>(col: IfStrict<ModelProps<ModelType<QBP>>, never>, qb: AnyQueryBuilder): QB;
|
|
1698
1696
|
|
|
1699
1697
|
(col: RefArg<QB> | RefArg<QB>[], expr: readonly Expression<PrimitiveValue>[]): QB;
|
|
1700
1698
|
(col: RefArg<QB> | RefArg<QB>[], cb: CallbackVoid<QB>): QB;
|
|
@@ -1702,7 +1700,7 @@ export interface WhereInMethod<out QB extends AnyQueryBuilder> {
|
|
|
1702
1700
|
|
|
1703
1701
|
<QBP extends QB, const C extends string = never>(
|
|
1704
1702
|
col: StrictColumn<ModelType<QBP>, C>,
|
|
1705
|
-
expr:
|
|
1703
|
+
expr: WhereInValues<QB, QBP, C>,
|
|
1706
1704
|
): QB;
|
|
1707
1705
|
<QBP extends QB, const C extends readonly unknown[] = []>(
|
|
1708
1706
|
col: StrictColumns<ModelType<QBP>, C>,
|
|
@@ -1710,6 +1708,9 @@ export interface WhereInMethod<out QB extends AnyQueryBuilder> {
|
|
|
1710
1708
|
): QB;
|
|
1711
1709
|
}
|
|
1712
1710
|
|
|
1711
|
+
type WhereInValues<QB extends AnyQueryBuilder, QBP extends QB, C extends string> =
|
|
1712
|
+
readonly ValueArg<ModelType<QBP>, C>[] | Raw | AnyQueryBuilder | CallbackVoid<QB>;
|
|
1713
|
+
|
|
1713
1714
|
export interface WhereBetweenMethod<out QB extends AnyQueryBuilder> {
|
|
1714
1715
|
(column: RefArg<QB>, range: [Expression<PrimitiveValue>, Expression<PrimitiveValue>]): QB;
|
|
1715
1716
|
<QBP extends QB, const C extends string = never>(
|
|
@@ -1724,7 +1725,7 @@ export interface WhereNullMethod<out QB extends AnyQueryBuilder> {
|
|
|
1724
1725
|
}
|
|
1725
1726
|
|
|
1726
1727
|
export interface WhereColumnMethod<out QB extends AnyQueryBuilder> {
|
|
1727
|
-
//
|
|
1728
|
+
// The column overloads come first, for autocompletion:
|
|
1728
1729
|
<QBP extends QB, const C extends string = never, const D extends string = never>(
|
|
1729
1730
|
col1: ColumnArg<ModelType<QBP>, C>,
|
|
1730
1731
|
op: Operator,
|
|
@@ -1747,7 +1748,7 @@ export interface WhereColumnMethod<out QB extends AnyQueryBuilder> {
|
|
|
1747
1748
|
}
|
|
1748
1749
|
|
|
1749
1750
|
export interface WhereJsonObject<out QB extends AnyQueryBuilder> {
|
|
1750
|
-
//
|
|
1751
|
+
// The column overloads come first, for autocompletion:
|
|
1751
1752
|
<QBP extends QB, const C extends string = never>(
|
|
1752
1753
|
col: ColumnArg<ModelType<QBP>, C>,
|
|
1753
1754
|
value: any,
|
|
@@ -1757,7 +1758,7 @@ export interface WhereJsonObject<out QB extends AnyQueryBuilder> {
|
|
|
1757
1758
|
}
|
|
1758
1759
|
|
|
1759
1760
|
export interface WhereJsonPath<out QB extends AnyQueryBuilder> {
|
|
1760
|
-
//
|
|
1761
|
+
// The column overloads come first, for autocompletion:
|
|
1761
1762
|
<QBP extends QB, const C extends string = never>(
|
|
1762
1763
|
col: ColumnArg<ModelType<QBP>, C>,
|
|
1763
1764
|
jsonPath: string,
|
|
@@ -2114,7 +2115,7 @@ type WriteQueryBuilder<QB extends AnyQueryBuilder, M extends Model, R> = [R] ext
|
|
|
2114
2115
|
* The writes after `returning()` on single and maybe-single query builders,
|
|
2115
2116
|
* which resolve to one row.
|
|
2116
2117
|
*/
|
|
2117
|
-
interface ReturningWrites<QB extends AnyQueryBuilder, M extends Model, W> {
|
|
2118
|
+
export interface ReturningWrites<QB extends AnyQueryBuilder, M extends Model, W> {
|
|
2118
2119
|
update(update: PartialModelRow<M>): Rebind<QB, M, W>;
|
|
2119
2120
|
update(): Rebind<QB, M, W>;
|
|
2120
2121
|
patch(update: PartialModelPatch<M>): Rebind<QB, M, W>;
|
|
@@ -2239,7 +2240,8 @@ export interface GraphOptions {
|
|
|
2239
2240
|
maxBatchSize?: number;
|
|
2240
2241
|
/**
|
|
2241
2242
|
* Apply `limit()`, `offset()` and `first()` in graph modifiers to the related
|
|
2242
|
-
* items of each parent. Defaults to `true`. Only applies to
|
|
2243
|
+
* items of each parent. Defaults to `true`. Only applies to
|
|
2244
|
+
* `withGraphFetched()`.
|
|
2243
2245
|
*/
|
|
2244
2246
|
limitPerParent?: boolean;
|
|
2245
2247
|
}
|
|
@@ -2706,8 +2708,6 @@ export class QueryBuilder<
|
|
|
2706
2708
|
): Promise<R | FR>;
|
|
2707
2709
|
}
|
|
2708
2710
|
|
|
2709
|
-
export type X<T> = Promise<T>;
|
|
2710
|
-
|
|
2711
2711
|
export interface FetchGraphOptions {
|
|
2712
2712
|
transaction?: TransactionOrKnex;
|
|
2713
2713
|
skipFetched?: boolean;
|
|
@@ -3055,7 +3055,8 @@ export interface ConstructorFunctionType<T = any> extends PrototypeType<T> {
|
|
|
3055
3055
|
new (...args: any[]): T;
|
|
3056
3056
|
}
|
|
3057
3057
|
|
|
3058
|
-
//
|
|
3058
|
+
// Used internally to infer the generic static `this`, copied from:
|
|
3059
|
+
// https://github.com/microsoft/TypeScript/issues/5863#issuecomment-1483978415
|
|
3059
3060
|
export type ConstructorType<T = unknown, Static extends Record<string, any> = PrototypeType<T>> = (
|
|
3060
3061
|
ConstructorFunctionType<T> | PrototypeType<T>
|
|
3061
3062
|
) & {
|
|
@@ -3132,7 +3133,7 @@ export interface ModelClass<M extends Model> extends ModelConstructor<M> {
|
|
|
3132
3133
|
|
|
3133
3134
|
transaction<T>(callback: (trx: Transaction) => Promise<T>): Promise<T>;
|
|
3134
3135
|
transaction<T>(
|
|
3135
|
-
trxOrKnex: TransactionOrKnex,
|
|
3136
|
+
trxOrKnex: TransactionOrKnex | undefined,
|
|
3136
3137
|
callback: (trx: Transaction) => Promise<T>,
|
|
3137
3138
|
): Promise<T>;
|
|
3138
3139
|
|