objection 3.4.1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/lib/queryBuilder/InternalOptions.js +2 -0
- package/lib/queryBuilder/graph/GraphOptions.js +153 -6
- package/lib/queryBuilder/graph/GraphUpsert.js +35 -0
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +5 -1
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +3 -0
- package/lib/queryBuilder/operations/InsertGraphOperation.js +6 -1
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +434 -364
- package/lib/queryBuilder/parsers/relationExpressionParser.js +512 -547
- package/lib/relations/Relation.js +12 -2
- package/lib/relations/RelationFindOperation.js +6 -1
- package/lib/relations/RelationInsertOperation.js +13 -1
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +9 -0
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +5 -1
- package/lib/relations/manyToMany/ManyToManyRelation.js +7 -2
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +21 -1
- package/lib/utils/identifierMapping.js +17 -8
- package/lib/utils/objectUtils.js +77 -2
- package/package.json +5 -2
- package/typings/objection/index.d.ts +169 -22
- package/lib/utils/clone.js +0 -2329
|
@@ -83,6 +83,12 @@ class Relation {
|
|
|
83
83
|
return false;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Whether owner models may legitimately lack the owner join property at
|
|
87
|
+
// `index`, so that it isn't reported as missing (#132).
|
|
88
|
+
isOptionalOwnerProp(_index) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
clone() {
|
|
87
93
|
const relation = new this.constructor(this.name, this.ownerModelClass);
|
|
88
94
|
|
|
@@ -219,8 +225,12 @@ class Relation {
|
|
|
219
225
|
});
|
|
220
226
|
}
|
|
221
227
|
|
|
222
|
-
executeBeforeInsert(models, queryContext, result) {
|
|
223
|
-
return mapAfterAllReturn(
|
|
228
|
+
executeBeforeInsert(models, queryContext, result, owner) {
|
|
229
|
+
return mapAfterAllReturn(
|
|
230
|
+
models,
|
|
231
|
+
(model) => this.beforeInsert(model, queryContext, owner),
|
|
232
|
+
result,
|
|
233
|
+
);
|
|
224
234
|
}
|
|
225
235
|
|
|
226
236
|
createError(message) {
|
|
@@ -85,12 +85,17 @@ class RelationFindOperation extends FindOperation {
|
|
|
85
85
|
|
|
86
86
|
// Owner models without the join columns, e.g. loaded with a partial
|
|
87
87
|
// `select()`, silently get no related models (#1832). Only `undefined`
|
|
88
|
-
// is reported, as `null` is a legit value for a missing relation
|
|
88
|
+
// is reported, as `null` is a legit value for a missing relation, and
|
|
89
|
+
// owner props that may legitimately be unset are skipped (#132).
|
|
89
90
|
// TODO: Consider throwing an error instead in objection 4.0.
|
|
90
91
|
warnOnMissingOwnerProps() {
|
|
91
92
|
const { ownerProp } = this.relation;
|
|
92
93
|
|
|
93
94
|
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
95
|
+
if (this.relation.isOptionalOwnerProp(c)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
94
99
|
if (this.owner.modelArray.some((owner) => ownerProp.getProp(owner, c) === undefined)) {
|
|
95
100
|
deprecate(
|
|
96
101
|
`Fetching relation "${this.relation.name}" of ${this.relation.ownerModelClass.name}: ` +
|
|
@@ -11,9 +11,21 @@ class RelationInsertOperation extends InsertOperation {
|
|
|
11
11
|
this.assignResultToOwner = true;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
get ownerModel() {
|
|
15
|
+
// The owner model passed to the relation's `beforeInsert` hook. Only known
|
|
16
|
+
// if the query was started from a single model instance.
|
|
17
|
+
const { owner } = this;
|
|
18
|
+
return owner && owner.isModels && owner.modelArray.length === 1 ? owner.model : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
async onBefore2(builder, result) {
|
|
15
22
|
const queryContext = builder.context();
|
|
16
|
-
result = await this.relation.executeBeforeInsert(
|
|
23
|
+
result = await this.relation.executeBeforeInsert(
|
|
24
|
+
this.models,
|
|
25
|
+
queryContext,
|
|
26
|
+
result,
|
|
27
|
+
this.ownerModel,
|
|
28
|
+
);
|
|
17
29
|
return super.onBefore2(builder, result);
|
|
18
30
|
}
|
|
19
31
|
|
|
@@ -11,6 +11,15 @@ class BelongsToOneRelation extends Relation {
|
|
|
11
11
|
return true;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// The foreign key is unset when no related model was ever assigned, e.g. on
|
|
15
|
+
// models returned by `insert()` or created with `fromJson()`, where no
|
|
16
|
+
// related model is the right result. This can't be told apart from a
|
|
17
|
+
// foreign key left out of a partial `select()`. Foreign keys that are part
|
|
18
|
+
// of the id are always expected to be set (#132).
|
|
19
|
+
isOptionalOwnerProp(index) {
|
|
20
|
+
return !this.ownerModelClass.getIdPropertyArray().includes(this.ownerProp.props[index]);
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
insert(_, owner) {
|
|
15
24
|
return new BelongsToOneInsertOperation('insert', {
|
|
16
25
|
relation: this,
|
|
@@ -16,10 +16,13 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
16
16
|
constructor(...args) {
|
|
17
17
|
super(...args);
|
|
18
18
|
this.modifyFilterSubquery = null;
|
|
19
|
+
this.skipRelationFilter = false;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
// Updates of rows that are known to be related, like the patches of
|
|
23
|
+
// `upsertGraph()`, keep their own filters (#135).
|
|
21
24
|
get modifyMainQuery() {
|
|
22
|
-
return
|
|
25
|
+
return !this.skipRelationFilter;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
// At this point `builder` should only have the user's own wheres and joins. There can
|
|
@@ -108,6 +111,7 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
108
111
|
clone() {
|
|
109
112
|
const clone = super.clone();
|
|
110
113
|
clone.modifyFilterSubquery = this.modifyFilterSubquery;
|
|
114
|
+
clone.skipRelationFilter = this.skipRelationFilter;
|
|
111
115
|
return clone;
|
|
112
116
|
}
|
|
113
117
|
};
|
|
@@ -93,7 +93,8 @@ class ManyToManyRelation extends Relation {
|
|
|
93
93
|
|
|
94
94
|
// The modify operations can't tell the join rows apart from the related rows
|
|
95
95
|
// if the join table is also the related table, so they would modify the
|
|
96
|
-
// wrong rows (#1803).
|
|
96
|
+
// wrong rows (#1803). Inserting and relating would insert the join rows as
|
|
97
|
+
// new related rows (#133).
|
|
97
98
|
assertCanModifyJoinTable(builder, operationName) {
|
|
98
99
|
if (this.isJoinTableRelatedTable(builder)) {
|
|
99
100
|
throw this.createError(
|
|
@@ -188,7 +189,9 @@ class ManyToManyRelation extends Relation {
|
|
|
188
189
|
});
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
insert(
|
|
192
|
+
insert(builder, owner) {
|
|
193
|
+
this.assertCanModifyJoinTable(builder, 'insert');
|
|
194
|
+
|
|
192
195
|
return new ManyToManyInsertOperation('insert', {
|
|
193
196
|
relation: this,
|
|
194
197
|
owner,
|
|
@@ -218,6 +221,8 @@ class ManyToManyRelation extends Relation {
|
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
relate(builder, owner) {
|
|
224
|
+
this.assertCanModifyJoinTable(builder, 'relate');
|
|
225
|
+
|
|
221
226
|
return new ManyToManyRelateOperation('relate', {
|
|
222
227
|
relation: this,
|
|
223
228
|
owner,
|
|
@@ -18,6 +18,11 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
18
18
|
onAdd(builder, args) {
|
|
19
19
|
const obj = args[0];
|
|
20
20
|
|
|
21
|
+
// The modified rows are known to be related to the owner, so they are
|
|
22
|
+
// filtered by their own ids instead of a subquery that joins the join
|
|
23
|
+
// table. On MySQL, concurrent updates with that subquery deadlock (#135).
|
|
24
|
+
this.skipRelationFilter = builder.internalOptions().skipRelationFilter;
|
|
25
|
+
|
|
21
26
|
// Copy all extra properties to the `joinTablePatch` object.
|
|
22
27
|
for (const extra of this.relation.joinTableExtras) {
|
|
23
28
|
if (extra.aliasProp in obj) {
|
|
@@ -50,7 +55,9 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
50
55
|
.patch(this.joinTablePatch);
|
|
51
56
|
|
|
52
57
|
addWhereGroup(joinTableUpdateQuery, () =>
|
|
53
|
-
this.
|
|
58
|
+
this.skipRelationFilter
|
|
59
|
+
? this.applyIdFilterForJoinTable(joinTableUpdateQuery)
|
|
60
|
+
: this.applyModifyFilterForJoinTable(joinTableUpdateQuery),
|
|
54
61
|
);
|
|
55
62
|
addWhereGroup(joinTableUpdateQuery, () =>
|
|
56
63
|
joinTableUpdateQuery.modify(this.relation.joinTableModify),
|
|
@@ -63,6 +70,19 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
// Filters the join rows by the ids of the owner and the updated model.
|
|
74
|
+
applyIdFilterForJoinTable(builder) {
|
|
75
|
+
return builder
|
|
76
|
+
.whereInComposite(
|
|
77
|
+
this.relation.joinTableOwnerProp.refs(builder),
|
|
78
|
+
this.owner.getProps(this.relation),
|
|
79
|
+
)
|
|
80
|
+
.whereComposite(
|
|
81
|
+
this.relation.joinTableRelatedProp.refs(builder),
|
|
82
|
+
this.relation.relatedProp.getProps(this.model),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
/* istanbul ignore next */
|
|
67
87
|
applyModifyFilterForRelatedTable(builder) {
|
|
68
88
|
throw new Error('not implemented');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isObject } = require('./objectUtils');
|
|
3
|
+
const { isObject, isPlainObject } = require('./objectUtils');
|
|
4
4
|
|
|
5
5
|
// Super fast memoize for single argument functions.
|
|
6
6
|
function memoize(func) {
|
|
@@ -149,8 +149,9 @@ function mapLastPart(mapper, separator) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// Returns a function that takes an object as an input and maps the object's keys
|
|
152
|
-
// using `mapper
|
|
153
|
-
|
|
152
|
+
// using `mapper`, and its values using the optional `valueMapper`. If the input
|
|
153
|
+
// is not an object, the input is returned unchanged.
|
|
154
|
+
function keyMapper(mapper, valueMapper) {
|
|
154
155
|
return (obj) => {
|
|
155
156
|
if (!isObject(obj) || Array.isArray(obj)) {
|
|
156
157
|
return obj;
|
|
@@ -161,7 +162,7 @@ function keyMapper(mapper) {
|
|
|
161
162
|
|
|
162
163
|
for (let i = 0, l = keys.length; i < l; ++i) {
|
|
163
164
|
const key = keys[i];
|
|
164
|
-
out[mapper(key)] = obj[key];
|
|
165
|
+
out[mapper(key)] = valueMapper ? valueMapper(obj[key]) : obj[key];
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
return out;
|
|
@@ -189,10 +190,16 @@ function snakeCaseMappers(opt = {}) {
|
|
|
189
190
|
};
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
function knexIdentifierMappers({ parse, format, idSeparator = ':' } = {}) {
|
|
193
|
+
function knexIdentifierMappers({ parse, format, idSeparator = ':', mapNestedKeys = false } = {}) {
|
|
193
194
|
const formatId = memoize(mapLastPart(format, idSeparator));
|
|
194
195
|
const parseId = memoize(mapLastPart(parse, idSeparator));
|
|
195
196
|
const parseKeys = keyMapper(parseId);
|
|
197
|
+
// With `mapNestedKeys`, also map the keys of plain objects one level down, as
|
|
198
|
+
// in the rows knex returns with its `nestTables: true` option on MySQL:
|
|
199
|
+
// `{ tableName: { columnName: value } }`.
|
|
200
|
+
const parseRow = mapNestedKeys
|
|
201
|
+
? keyMapper(parseId, (value) => (isPlainObject(value) ? parseKeys(value) : value))
|
|
202
|
+
: parseKeys;
|
|
196
203
|
|
|
197
204
|
return {
|
|
198
205
|
wrapIdentifier(identifier, origWrap) {
|
|
@@ -204,12 +211,12 @@ function knexIdentifierMappers({ parse, format, idSeparator = ':' } = {}) {
|
|
|
204
211
|
const output = new Array(result.length);
|
|
205
212
|
|
|
206
213
|
for (let i = 0, l = result.length; i < l; ++i) {
|
|
207
|
-
output[i] =
|
|
214
|
+
output[i] = parseRow(result[i]);
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
return output;
|
|
211
218
|
} else {
|
|
212
|
-
return
|
|
219
|
+
return parseRow(result);
|
|
213
220
|
}
|
|
214
221
|
},
|
|
215
222
|
};
|
|
@@ -219,10 +226,11 @@ function knexSnakeCaseMappers(opt = {}) {
|
|
|
219
226
|
return knexIdentifierMappers({
|
|
220
227
|
parse: (str) => camelCase(str, opt),
|
|
221
228
|
format: (str) => snakeCase(str, opt),
|
|
229
|
+
mapNestedKeys: opt.mapNestedKeys,
|
|
222
230
|
});
|
|
223
231
|
}
|
|
224
232
|
|
|
225
|
-
function knexIdentifierMapping(colToProp) {
|
|
233
|
+
function knexIdentifierMapping(colToProp, { mapNestedKeys } = {}) {
|
|
226
234
|
const propToCol = Object.keys(colToProp).reduce((propToCol, column) => {
|
|
227
235
|
propToCol[colToProp[column]] = column;
|
|
228
236
|
return propToCol;
|
|
@@ -231,6 +239,7 @@ function knexIdentifierMapping(colToProp) {
|
|
|
231
239
|
return knexIdentifierMappers({
|
|
232
240
|
parse: (column) => colToProp[column] || column,
|
|
233
241
|
format: (prop) => propToCol[prop] || prop,
|
|
242
|
+
mapNestedKeys,
|
|
234
243
|
});
|
|
235
244
|
}
|
|
236
245
|
|
package/lib/utils/objectUtils.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { clone, cloneDeep } = require('./clone');
|
|
4
3
|
const SMALL_ARRAY_SIZE = 10;
|
|
5
4
|
|
|
6
5
|
function isEmpty(item) {
|
|
@@ -389,6 +388,83 @@ function mergeMaps(map1, map2) {
|
|
|
389
388
|
return map;
|
|
390
389
|
}
|
|
391
390
|
|
|
391
|
+
// Clones `value` deeply, like lodash's `cloneDeep()`: objects keep their
|
|
392
|
+
// prototype and get copies of their own enumerable properties, symbols
|
|
393
|
+
// included. Arrays, dates, regular expressions, maps, sets, buffers, typed
|
|
394
|
+
// arrays and array buffers are copied, circular references are kept.
|
|
395
|
+
// Anything else, like functions and errors, is kept as it is.
|
|
396
|
+
function cloneDeep(value, clones = new Map()) {
|
|
397
|
+
if (!isObject(value)) {
|
|
398
|
+
return value;
|
|
399
|
+
} else if (clones.has(value)) {
|
|
400
|
+
return clones.get(value);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const Ctor = value.constructor;
|
|
404
|
+
const tag = Object.prototype.toString.call(value);
|
|
405
|
+
let copy;
|
|
406
|
+
|
|
407
|
+
if (tag === '[object Object]') {
|
|
408
|
+
copy = Object.create(Object.getPrototypeOf(value));
|
|
409
|
+
clones.set(value, copy);
|
|
410
|
+
|
|
411
|
+
for (const key of Object.keys(value)) {
|
|
412
|
+
const item = cloneDeep(value[key], clones);
|
|
413
|
+
|
|
414
|
+
if (key === '__proto__') {
|
|
415
|
+
// Assigning it would set the prototype instead of the property.
|
|
416
|
+
Object.defineProperty(copy, key, {
|
|
417
|
+
value: item,
|
|
418
|
+
writable: true,
|
|
419
|
+
enumerable: true,
|
|
420
|
+
configurable: true,
|
|
421
|
+
});
|
|
422
|
+
} else {
|
|
423
|
+
copy[key] = item;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
for (const symbol of Object.getOwnPropertySymbols(value)) {
|
|
428
|
+
if (Object.prototype.propertyIsEnumerable.call(value, symbol)) {
|
|
429
|
+
copy[symbol] = cloneDeep(value[symbol], clones);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} else if (tag === '[object Array]') {
|
|
433
|
+
copy = new Ctor(value.length);
|
|
434
|
+
clones.set(value, copy);
|
|
435
|
+
|
|
436
|
+
for (let i = 0, l = value.length; i < l; ++i) {
|
|
437
|
+
copy[i] = cloneDeep(value[i], clones);
|
|
438
|
+
}
|
|
439
|
+
} else if (Buffer.isBuffer(value)) {
|
|
440
|
+
copy = Buffer.from(value);
|
|
441
|
+
} else if (tag === '[object DataView]') {
|
|
442
|
+
copy = new Ctor(value.buffer.slice(0), value.byteOffset, value.byteLength);
|
|
443
|
+
} else if (ArrayBuffer.isView(value)) {
|
|
444
|
+
copy = value.slice();
|
|
445
|
+
} else if (tag === '[object ArrayBuffer]') {
|
|
446
|
+
copy = value.slice(0);
|
|
447
|
+
} else if (tag === '[object Date]') {
|
|
448
|
+
copy = new Ctor(+value);
|
|
449
|
+
} else if (tag === '[object RegExp]') {
|
|
450
|
+
copy = new Ctor(value.source, value.flags);
|
|
451
|
+
copy.lastIndex = value.lastIndex;
|
|
452
|
+
} else if (tag === '[object Map]') {
|
|
453
|
+
copy = new Ctor();
|
|
454
|
+
clones.set(value, copy);
|
|
455
|
+
value.forEach((item, key) => copy.set(key, cloneDeep(item, clones)));
|
|
456
|
+
} else if (tag === '[object Set]') {
|
|
457
|
+
copy = new Ctor();
|
|
458
|
+
clones.set(value, copy);
|
|
459
|
+
value.forEach((item) => copy.add(cloneDeep(item, clones)));
|
|
460
|
+
} else {
|
|
461
|
+
return value;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
clones.set(value, copy);
|
|
465
|
+
return copy;
|
|
466
|
+
}
|
|
467
|
+
|
|
392
468
|
module.exports = {
|
|
393
469
|
isEmpty,
|
|
394
470
|
isString,
|
|
@@ -411,7 +487,6 @@ module.exports = {
|
|
|
411
487
|
values,
|
|
412
488
|
union,
|
|
413
489
|
chunk,
|
|
414
|
-
clone,
|
|
415
490
|
omit,
|
|
416
491
|
once,
|
|
417
492
|
last,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "objection",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "An SQL-friendly ORM for Node.js",
|
|
5
5
|
"main": "lib/objection.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
"test": "npm run eslint && mocha --slow 10 --timeout 15000 --reporter spec --recursive tests --exclude \"tests/unit/relations/files/**\" && npm run test:typings",
|
|
9
9
|
"test:fast": "mocha --slow 10 --timeout 15000 --reporter spec --recursive tests --bail --exclude \"tests/unit/relations/files/**\"",
|
|
10
10
|
"test:typings": "tsc && prettier --check \"typings/**/*.ts\" \"tests/ts/**/*.ts\"",
|
|
11
|
+
"db:up": "docker compose up -d --wait",
|
|
12
|
+
"db:down": "docker compose down -v",
|
|
13
|
+
"build:parsers": "npx --yes peggy@5.1.0 --format commonjs -o lib/queryBuilder/parsers/jsonFieldExpressionParser.js lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs && npx --yes peggy@5.1.0 --format commonjs -o lib/queryBuilder/parsers/relationExpressionParser.js lib/queryBuilder/parsers/relationExpressionParser.pegjs && prettier --write \"lib/queryBuilder/parsers/*Parser.js\"",
|
|
11
14
|
"prettier": "prettier --write \"{examples,lib,tests,testUtils,typings,doc}/**/*.{js,ts}\"",
|
|
12
|
-
"eslint": "eslint --format codeframe \"examples/**/*.js\" \"lib/**/*.js\" \"tests/**/*.js\"",
|
|
15
|
+
"eslint": "eslint --format codeframe \"examples/**/*.js\" \"lib/**/*.js\" \"tests/**/*.js\" \"testUtils/**/*.js\" \"*.js\"",
|
|
13
16
|
"docs:dev": "vitepress dev doc",
|
|
14
17
|
"docs:build": "vitepress build doc",
|
|
15
18
|
"docs:preview": "vitepress preview doc"
|
|
@@ -19,6 +19,11 @@ import { Knex } from 'knex';
|
|
|
19
19
|
// Export the entire Objection namespace.
|
|
20
20
|
export = Objection;
|
|
21
21
|
|
|
22
|
+
// Phantom brand of the relation types, used by `TypedRelationMappings`.
|
|
23
|
+
declare const relationKind: unique symbol;
|
|
24
|
+
// Phantom brand of `Generated` properties.
|
|
25
|
+
declare const generated: unique symbol;
|
|
26
|
+
|
|
22
27
|
declare namespace Objection {
|
|
23
28
|
const raw: RawFunction;
|
|
24
29
|
const val: ValueFunction;
|
|
@@ -462,6 +467,65 @@ declare namespace Objection {
|
|
|
462
467
|
: Expression<F>
|
|
463
468
|
: Expression<F>;
|
|
464
469
|
|
|
470
|
+
/**
|
|
471
|
+
* Marks a model property whose value the database generates, like an
|
|
472
|
+
* auto-incremented id, a timestamp or a column with a default value. It
|
|
473
|
+
* reads as T and accepts T, but `Insertable` and `InsertableGraph` treat
|
|
474
|
+
* the property as optional:
|
|
475
|
+
*
|
|
476
|
+
* id!: Generated<number>;
|
|
477
|
+
*/
|
|
478
|
+
export type Generated<T> = T extends null | undefined ? T : T & GeneratedBrand;
|
|
479
|
+
|
|
480
|
+
interface GeneratedBrand {
|
|
481
|
+
readonly [generated]?: true;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Insert keys are optional for optional, nullable and generated properties.
|
|
486
|
+
*/
|
|
487
|
+
type OptionalInsertPropertyNames<M> = {
|
|
488
|
+
[K in keyof M]-?: {} extends Pick<M, K>
|
|
489
|
+
? K
|
|
490
|
+
: null extends M[K]
|
|
491
|
+
? K
|
|
492
|
+
: typeof generated extends keyof NonNullable<M[K]>
|
|
493
|
+
? K
|
|
494
|
+
: never;
|
|
495
|
+
}[keyof M];
|
|
496
|
+
|
|
497
|
+
type InsertablePropertyNames<M> = Exclude<DataPropertyNames<M>, RelationPropertyNames<M>>;
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* The data to insert a model M with `insert()`. Unlike `PartialModelObject`,
|
|
501
|
+
* all properties are required except optional, nullable and `Generated`
|
|
502
|
+
* ones. Relation properties are left out.
|
|
503
|
+
*/
|
|
504
|
+
export type Insertable<M extends Model> = {
|
|
505
|
+
[K in Exclude<InsertablePropertyNames<M>, OptionalInsertPropertyNames<M>>]: Expression<M[K]>;
|
|
506
|
+
} & {
|
|
507
|
+
[K in Extract<InsertablePropertyNames<M>, OptionalInsertPropertyNames<M>>]?: Expression<M[K]>;
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Like `Insertable`, but for `insertGraph()`: relation properties are
|
|
512
|
+
* optional and hold the insertable graphs of the related models, or
|
|
513
|
+
* references to existing ones through `#dbRef` or `#ref`.
|
|
514
|
+
*/
|
|
515
|
+
export type InsertableGraph<M extends Model> = Insertable<M> &
|
|
516
|
+
Omit<GraphParameters, '#dbRef' | '#ref'> & {
|
|
517
|
+
[K in RelationPropertyNames<M>]?: InsertableGraphField<M[K]>;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
type InsertableGraphReference = { '#dbRef': MaybeCompositeId } | { '#ref': string };
|
|
521
|
+
|
|
522
|
+
type InsertableGraphField<F> =
|
|
523
|
+
NonNullable<F> extends (infer I extends Model)[]
|
|
524
|
+
? (InsertableGraph<I> | InsertableGraphReference)[]
|
|
525
|
+
: NonNullable<F> extends infer I extends Model
|
|
526
|
+
? InsertableGraph<I> | InsertableGraphReference | Extract<F, null>
|
|
527
|
+
: never;
|
|
528
|
+
|
|
465
529
|
/**
|
|
466
530
|
* Extracts the property names (excluding relations) of a model class.
|
|
467
531
|
*/
|
|
@@ -994,7 +1058,7 @@ declare namespace Objection {
|
|
|
994
1058
|
}
|
|
995
1059
|
|
|
996
1060
|
export interface InsertGraphOptions {
|
|
997
|
-
relate?: boolean | string[];
|
|
1061
|
+
relate?: boolean | string | string[];
|
|
998
1062
|
allowRefs?: boolean;
|
|
999
1063
|
}
|
|
1000
1064
|
|
|
@@ -1013,15 +1077,15 @@ declare namespace Objection {
|
|
|
1013
1077
|
}
|
|
1014
1078
|
|
|
1015
1079
|
export interface UpsertGraphOptions {
|
|
1016
|
-
relate?: boolean | string[];
|
|
1017
|
-
unrelate?: boolean | string[];
|
|
1018
|
-
insertMissing?: boolean | string[];
|
|
1019
|
-
update?: boolean | string[];
|
|
1020
|
-
noInsert?: boolean | string[];
|
|
1021
|
-
noUpdate?: boolean | string[];
|
|
1022
|
-
noDelete?: boolean | string[];
|
|
1023
|
-
noRelate?: boolean | string[];
|
|
1024
|
-
noUnrelate?: boolean | string[];
|
|
1080
|
+
relate?: boolean | string | string[];
|
|
1081
|
+
unrelate?: boolean | string | string[];
|
|
1082
|
+
insertMissing?: boolean | string | string[];
|
|
1083
|
+
update?: boolean | string | string[];
|
|
1084
|
+
noInsert?: boolean | string | string[];
|
|
1085
|
+
noUpdate?: boolean | string | string[];
|
|
1086
|
+
noDelete?: boolean | string | string[];
|
|
1087
|
+
noRelate?: boolean | string | string[];
|
|
1088
|
+
noUnrelate?: boolean | string | string[];
|
|
1025
1089
|
allowRefs?: boolean;
|
|
1026
1090
|
}
|
|
1027
1091
|
|
|
@@ -1488,6 +1552,11 @@ declare namespace Objection {
|
|
|
1488
1552
|
model: M,
|
|
1489
1553
|
context: QueryContext,
|
|
1490
1554
|
) => Promise<void> | void;
|
|
1555
|
+
type RelationMappingBeforeInsertHook<M extends Model> = (
|
|
1556
|
+
model: M,
|
|
1557
|
+
context: QueryContext,
|
|
1558
|
+
owner?: Model,
|
|
1559
|
+
) => Promise<void> | void;
|
|
1491
1560
|
type StringOrReferenceBuilder = string | ReferenceBuilder;
|
|
1492
1561
|
type RelationMappingColumnRef = StringOrReferenceBuilder | StringOrReferenceBuilder[];
|
|
1493
1562
|
|
|
@@ -1497,7 +1566,7 @@ declare namespace Objection {
|
|
|
1497
1566
|
join: RelationJoin;
|
|
1498
1567
|
modify?: Modifier<QueryBuilderType<M>>;
|
|
1499
1568
|
filter?: Modifier<QueryBuilderType<M>>;
|
|
1500
|
-
beforeInsert?:
|
|
1569
|
+
beforeInsert?: RelationMappingBeforeInsertHook<M>;
|
|
1501
1570
|
}
|
|
1502
1571
|
|
|
1503
1572
|
export interface RelationJoin {
|
|
@@ -1518,6 +1587,67 @@ declare namespace Objection {
|
|
|
1518
1587
|
|
|
1519
1588
|
export interface RelationType extends Constructor<Relation> {}
|
|
1520
1589
|
|
|
1590
|
+
// The relation types are branded so that `TypedRelationMappings` can tell
|
|
1591
|
+
// them apart. The brand is optional, so any `RelationType` is assignable.
|
|
1592
|
+
export interface BelongsToOneRelationType extends RelationType {
|
|
1593
|
+
readonly [relationKind]?: 'BelongsToOne';
|
|
1594
|
+
}
|
|
1595
|
+
export interface HasOneRelationType extends RelationType {
|
|
1596
|
+
readonly [relationKind]?: 'HasOne';
|
|
1597
|
+
}
|
|
1598
|
+
export interface HasManyRelationType extends RelationType {
|
|
1599
|
+
readonly [relationKind]?: 'HasMany';
|
|
1600
|
+
}
|
|
1601
|
+
export interface ManyToManyRelationType extends RelationType {
|
|
1602
|
+
readonly [relationKind]?: 'ManyToMany';
|
|
1603
|
+
}
|
|
1604
|
+
export interface HasOneThroughRelationType extends RelationType {
|
|
1605
|
+
readonly [relationKind]?: 'HasOneThrough';
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/**
|
|
1609
|
+
* Names of the properties of M that hold related models.
|
|
1610
|
+
*/
|
|
1611
|
+
type RelationPropertyNames<M> = {
|
|
1612
|
+
[K in keyof M]-?: NonNullable<M[K]> extends Model | Model[] ? K : never;
|
|
1613
|
+
}[keyof M];
|
|
1614
|
+
|
|
1615
|
+
type TypedModelClassSpecifier<M extends Model> =
|
|
1616
|
+
string | ModelConstructor<M> | (() => ModelConstructor<M>);
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* A `RelationMapping` for related models of type M with relation type R.
|
|
1620
|
+
*/
|
|
1621
|
+
export interface TypedRelationMapping<
|
|
1622
|
+
M extends Model,
|
|
1623
|
+
R extends RelationType = RelationType,
|
|
1624
|
+
> extends RelationMapping<M> {
|
|
1625
|
+
relation: R;
|
|
1626
|
+
modelClass: TypedModelClassSpecifier<M>;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
type TypedRelationMappingFor<T> =
|
|
1630
|
+
NonNullable<T> extends (infer I extends Model)[]
|
|
1631
|
+
? TypedRelationMapping<I, HasManyRelationType | ManyToManyRelationType>
|
|
1632
|
+
: NonNullable<T> extends infer I extends Model
|
|
1633
|
+
? TypedRelationMapping<
|
|
1634
|
+
I,
|
|
1635
|
+
BelongsToOneRelationType | HasOneRelationType | HasOneThroughRelationType
|
|
1636
|
+
>
|
|
1637
|
+
: never;
|
|
1638
|
+
|
|
1639
|
+
/**
|
|
1640
|
+
* Relation mappings checked against the relation properties of model M.
|
|
1641
|
+
* Use it with `satisfies`, so the inferred type of the mappings is kept:
|
|
1642
|
+
*
|
|
1643
|
+
* static relationMappings = {
|
|
1644
|
+
* pets: { ... },
|
|
1645
|
+
* } satisfies TypedRelationMappings<Person>;
|
|
1646
|
+
*/
|
|
1647
|
+
export type TypedRelationMappings<M extends Model> = [RelationPropertyNames<M>] extends [never]
|
|
1648
|
+
? Record<string, never>
|
|
1649
|
+
: { [K in RelationPropertyNames<M>]?: TypedRelationMappingFor<M[K]> };
|
|
1650
|
+
|
|
1521
1651
|
export interface Relation {
|
|
1522
1652
|
name: string;
|
|
1523
1653
|
ownerModelClass: typeof Model;
|
|
@@ -1528,6 +1658,7 @@ declare namespace Objection {
|
|
|
1528
1658
|
joinTable: string;
|
|
1529
1659
|
joinTableOwnerProp: RelationProperty;
|
|
1530
1660
|
joinTableRelatedProp: RelationProperty;
|
|
1661
|
+
isOneToOne(): boolean;
|
|
1531
1662
|
}
|
|
1532
1663
|
|
|
1533
1664
|
export interface RelationProperty {
|
|
@@ -1598,6 +1729,22 @@ declare namespace Objection {
|
|
|
1598
1729
|
* Only used by `snakeCaseMappers`.
|
|
1599
1730
|
*/
|
|
1600
1731
|
preserveJsonKeys?: boolean;
|
|
1732
|
+
/**
|
|
1733
|
+
* Also convert the keys of plain objects one level down in the results,
|
|
1734
|
+
* e.g. the rows of knex's `nestTables: true` option on MySQL. Also applies
|
|
1735
|
+
* to JSON columns parsed by the driver.
|
|
1736
|
+
* Only used by `knexSnakeCaseMappers`.
|
|
1737
|
+
*/
|
|
1738
|
+
mapNestedKeys?: boolean;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
export interface KnexIdentifierMappingOptions {
|
|
1742
|
+
/**
|
|
1743
|
+
* Also convert the keys of plain objects one level down in the results,
|
|
1744
|
+
* e.g. the rows of knex's `nestTables: true` option on MySQL. Also applies
|
|
1745
|
+
* to JSON columns parsed by the driver.
|
|
1746
|
+
*/
|
|
1747
|
+
mapNestedKeys?: boolean;
|
|
1601
1748
|
}
|
|
1602
1749
|
|
|
1603
1750
|
export interface ColumnNameMappers {
|
|
@@ -1619,7 +1766,7 @@ declare namespace Objection {
|
|
|
1619
1766
|
}
|
|
1620
1767
|
|
|
1621
1768
|
export interface KnexIdentifierMappingFactory {
|
|
1622
|
-
(colToProp: Record<string, string
|
|
1769
|
+
(colToProp: Record<string, string>, options?: KnexIdentifierMappingOptions): KnexMappers;
|
|
1623
1770
|
}
|
|
1624
1771
|
|
|
1625
1772
|
export type ValidationErrorType =
|
|
@@ -1732,11 +1879,11 @@ declare namespace Objection {
|
|
|
1732
1879
|
ref: ReferenceFunction;
|
|
1733
1880
|
fn: FunctionFunction;
|
|
1734
1881
|
|
|
1735
|
-
BelongsToOneRelation:
|
|
1736
|
-
HasOneRelation:
|
|
1737
|
-
HasManyRelation:
|
|
1738
|
-
ManyToManyRelation:
|
|
1739
|
-
HasOneThroughRelation:
|
|
1882
|
+
BelongsToOneRelation: BelongsToOneRelationType;
|
|
1883
|
+
HasOneRelation: HasOneRelationType;
|
|
1884
|
+
HasManyRelation: HasManyRelationType;
|
|
1885
|
+
ManyToManyRelation: ManyToManyRelationType;
|
|
1886
|
+
HasOneThroughRelation: HasOneThroughRelationType;
|
|
1740
1887
|
|
|
1741
1888
|
defaultGraphOptions?: GraphOptions;
|
|
1742
1889
|
|
|
@@ -1843,11 +1990,11 @@ declare namespace Objection {
|
|
|
1843
1990
|
static ref: ReferenceFunction;
|
|
1844
1991
|
static fn: FunctionFunction;
|
|
1845
1992
|
|
|
1846
|
-
static BelongsToOneRelation:
|
|
1847
|
-
static HasOneRelation:
|
|
1848
|
-
static HasManyRelation:
|
|
1849
|
-
static ManyToManyRelation:
|
|
1850
|
-
static HasOneThroughRelation:
|
|
1993
|
+
static BelongsToOneRelation: BelongsToOneRelationType;
|
|
1994
|
+
static HasOneRelation: HasOneRelationType;
|
|
1995
|
+
static HasManyRelation: HasManyRelationType;
|
|
1996
|
+
static ManyToManyRelation: ManyToManyRelationType;
|
|
1997
|
+
static HasOneThroughRelation: HasOneThroughRelationType;
|
|
1851
1998
|
|
|
1852
1999
|
static defaultGraphOptions?: GraphOptions;
|
|
1853
2000
|
|