@ditojs/server 2.0.5 → 2.1.1
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/package.json +11 -13
- package/src/app/Application.js +226 -179
- package/src/app/Validator.js +53 -43
- package/src/cli/console.js +6 -4
- package/src/cli/db/createMigration.js +59 -30
- package/src/cli/db/migrate.js +6 -4
- package/src/cli/db/reset.js +8 -5
- package/src/cli/db/rollback.js +6 -4
- package/src/cli/db/seed.js +2 -1
- package/src/cli/index.js +1 -1
- package/src/controllers/AdminController.js +106 -85
- package/src/controllers/CollectionController.js +37 -30
- package/src/controllers/Controller.js +83 -43
- package/src/controllers/ControllerAction.js +27 -15
- package/src/controllers/ModelController.js +4 -1
- package/src/controllers/RelationController.js +19 -21
- package/src/controllers/UsersController.js +3 -4
- package/src/decorators/parameters.js +3 -1
- package/src/decorators/scope.js +1 -1
- package/src/errors/ControllerError.js +2 -1
- package/src/errors/DatabaseError.js +20 -11
- package/src/graph/DitoGraphProcessor.js +48 -40
- package/src/graph/expression.js +6 -8
- package/src/graph/graph.js +20 -11
- package/src/lib/EventEmitter.js +12 -12
- package/src/middleware/handleConnectMiddleware.js +16 -10
- package/src/middleware/handleError.js +6 -5
- package/src/middleware/handleSession.js +33 -29
- package/src/middleware/handleUser.js +2 -2
- package/src/middleware/index.js +1 -0
- package/src/middleware/logRequests.js +3 -3
- package/src/middleware/setupRequestStorage.js +14 -0
- package/src/mixins/AssetMixin.js +62 -58
- package/src/mixins/SessionMixin.js +13 -10
- package/src/mixins/TimeStampedMixin.js +33 -29
- package/src/mixins/UserMixin.js +130 -116
- package/src/models/Model.js +245 -194
- package/src/models/definitions/filters.js +14 -13
- package/src/query/QueryBuilder.js +252 -195
- package/src/query/QueryFilters.js +3 -3
- package/src/query/QueryParameters.js +2 -2
- package/src/query/Registry.js +8 -10
- package/src/schema/keywords/_validate.js +10 -8
- package/src/schema/properties.test.js +247 -206
- package/src/schema/relations.js +42 -20
- package/src/schema/relations.test.js +36 -19
- package/src/services/Service.js +8 -14
- package/src/storage/S3Storage.js +5 -3
- package/src/storage/Storage.js +16 -14
- package/src/utils/function.js +7 -4
- package/src/utils/function.test.js +30 -6
- package/src/utils/object.test.js +5 -1
- package/types/index.d.ts +244 -257
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import objection from 'objection'
|
|
2
2
|
import {
|
|
3
|
-
isObject,
|
|
4
|
-
|
|
3
|
+
isObject,
|
|
4
|
+
isPlainObject,
|
|
5
|
+
isString,
|
|
6
|
+
isArray,
|
|
7
|
+
clone,
|
|
8
|
+
mapKeys,
|
|
9
|
+
getValueAtDataPath,
|
|
10
|
+
setValueAtDataPath,
|
|
11
|
+
parseDataPath
|
|
5
12
|
} from '@ditojs/utils'
|
|
6
13
|
import { QueryParameters } from './QueryParameters.js'
|
|
7
14
|
import { KnexHelper } from '../lib/index.js'
|
|
@@ -14,73 +21,32 @@ import { deprecate } from '../utils/deprecate.js'
|
|
|
14
21
|
const SYMBOL_ALL = Symbol('all')
|
|
15
22
|
|
|
16
23
|
export class QueryBuilder extends objection.QueryBuilder {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this._clearScopes(true)
|
|
27
|
-
}
|
|
24
|
+
#ignoreGraph = false
|
|
25
|
+
#graphAlgorithm = 'fetch'
|
|
26
|
+
#isJoinChildQuery = false
|
|
27
|
+
#scopes = { default: true } // Eager-apply the default scope
|
|
28
|
+
#allowScopes = null
|
|
29
|
+
#ignoreScopes = {}
|
|
30
|
+
#appliedScopes = {}
|
|
31
|
+
#allowFilters = null
|
|
32
|
+
#executeFirst = null // Part of a work-around for cyclic graphs
|
|
28
33
|
|
|
29
34
|
// @override
|
|
30
35
|
clone() {
|
|
31
36
|
const copy = super.clone()
|
|
32
|
-
copy
|
|
33
|
-
copy
|
|
34
|
-
copy
|
|
35
|
-
copy
|
|
36
|
-
copy
|
|
37
|
+
copy.#ignoreGraph = this.#ignoreGraph
|
|
38
|
+
copy.#graphAlgorithm = this.#graphAlgorithm
|
|
39
|
+
copy.#appliedScopes = { ...this.#appliedScopes }
|
|
40
|
+
copy.#allowFilters = this.#allowFilters ? { ...this.#allowFilters } : null
|
|
41
|
+
copy.#copyScopes(this)
|
|
37
42
|
return copy
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
// @override
|
|
41
46
|
async execute() {
|
|
42
|
-
|
|
43
|
-
// Only apply default scopes if this is a normal find query, meaning it
|
|
44
|
-
// does not define any write operations or special selects, e.g. `count`:
|
|
45
|
-
const isNormalFind = (
|
|
46
|
-
this.isFind() &&
|
|
47
|
-
!this.hasSpecialSelects()
|
|
48
|
-
)
|
|
49
|
-
// If this isn't a normal find query, ignore all graph operations,
|
|
50
|
-
// to not mess with special selects such as `count`, etc:
|
|
51
|
-
this._ignoreGraph = !isNormalFind
|
|
52
|
-
// All scopes in `_scopes` were already checked against `_allowScopes`.
|
|
53
|
-
// They themselves are allowed to apply / request other scopes that
|
|
54
|
-
// aren't listed, so clear `_applyScope` and restore again after:
|
|
55
|
-
const { _allowScopes } = this
|
|
56
|
-
this._allowScopes = null
|
|
57
|
-
const collectedScopes = {}
|
|
58
|
-
// Scopes can themselves request more scopes by calling `withScope()`
|
|
59
|
-
// In order to prevent that from causing problems while looping over
|
|
60
|
-
// `_scopes`, create a local copy of the entries, set `_scopes` to an
|
|
61
|
-
// empty object during iteration and check if there are new entries after
|
|
62
|
-
// one full loop. Keep doing this until there's nothing left, but keep
|
|
63
|
-
// track of all the applied scopes, so `_scopes` can be set to the the
|
|
64
|
-
// that in the end. This is needed for child queries, see `childQueryOf()`
|
|
65
|
-
let scopes = Object.entries(this._scopes)
|
|
66
|
-
while (scopes.length > 0) {
|
|
67
|
-
this._scopes = {}
|
|
68
|
-
for (const [scope, graph] of scopes) {
|
|
69
|
-
// Don't apply `default` scopes on anything else than a normal find
|
|
70
|
-
// query:
|
|
71
|
-
if (scope !== 'default' || isNormalFind) {
|
|
72
|
-
this._applyScope(scope, graph)
|
|
73
|
-
collectedScopes[scope] ||= graph
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
scopes = Object.entries(this._scopes)
|
|
77
|
-
}
|
|
78
|
-
this._scopes = collectedScopes
|
|
79
|
-
this._allowScopes = _allowScopes
|
|
80
|
-
this._ignoreGraph = false
|
|
81
|
-
}
|
|
47
|
+
this.#applyScopes()
|
|
82
48
|
// In case of cyclic graphs, run `_executeFirst()` now:
|
|
83
|
-
await this
|
|
49
|
+
await this.#executeFirst?.()
|
|
84
50
|
return super.execute()
|
|
85
51
|
}
|
|
86
52
|
|
|
@@ -100,13 +66,16 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
100
66
|
// @override
|
|
101
67
|
childQueryOf(query, options) {
|
|
102
68
|
super.childQueryOf(query, options)
|
|
69
|
+
this.#isJoinChildQuery = query.#graphAlgorithm === 'join'
|
|
103
70
|
if (this.isInternal()) {
|
|
104
71
|
// Internal queries shouldn't apply or inherit any scopes, not even the
|
|
105
72
|
// default scope.
|
|
106
|
-
this
|
|
73
|
+
this.#clearScopes(false)
|
|
107
74
|
} else {
|
|
108
75
|
// Inherit the graph scopes from the parent query.
|
|
109
|
-
this
|
|
76
|
+
this.#ignoreGraph = query.#ignoreGraph
|
|
77
|
+
this.#graphAlgorithm = query.#graphAlgorithm
|
|
78
|
+
this.#copyScopes(query, true)
|
|
110
79
|
}
|
|
111
80
|
return this
|
|
112
81
|
}
|
|
@@ -122,12 +91,12 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
122
91
|
for (const expr of scopes) {
|
|
123
92
|
if (expr) {
|
|
124
93
|
const { scope, graph } = getScope(expr)
|
|
125
|
-
if (this
|
|
94
|
+
if (this.#allowScopes && !this.#allowScopes[scope]) {
|
|
126
95
|
throw new QueryBuilderError(
|
|
127
96
|
`Query scope '${scope}' is not allowed.`
|
|
128
97
|
)
|
|
129
98
|
}
|
|
130
|
-
this
|
|
99
|
+
this.#scopes[scope] ||= graph
|
|
131
100
|
}
|
|
132
101
|
}
|
|
133
102
|
return this
|
|
@@ -136,52 +105,53 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
136
105
|
// Clear all scopes defined with `withScope()` statements, preserving the
|
|
137
106
|
// default scope.
|
|
138
107
|
clearWithScope() {
|
|
139
|
-
return this
|
|
108
|
+
return this.#clearScopes(true)
|
|
140
109
|
}
|
|
141
110
|
|
|
142
111
|
ignoreScope(...scopes) {
|
|
143
|
-
if (!this
|
|
144
|
-
this
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
112
|
+
if (!this.#ignoreScopes[SYMBOL_ALL]) {
|
|
113
|
+
this.#ignoreScopes =
|
|
114
|
+
scopes.length > 0
|
|
115
|
+
? {
|
|
116
|
+
...this.#ignoreScopes,
|
|
117
|
+
...createLookup(scopes)
|
|
118
|
+
}
|
|
119
|
+
: // Empty arguments = ignore all scopes
|
|
120
|
+
{
|
|
121
|
+
[SYMBOL_ALL]: true
|
|
122
|
+
}
|
|
153
123
|
}
|
|
154
124
|
return this
|
|
155
125
|
}
|
|
156
126
|
|
|
157
127
|
applyScope(...scopes) {
|
|
158
|
-
// When directly applying a scope, still merge it into `this
|
|
128
|
+
// When directly applying a scope, still merge it into `this.#scopes`,
|
|
159
129
|
// so it can still be passed on to forked child queries. This also handles
|
|
160
130
|
// the checks against `_allowScopes`.
|
|
161
131
|
this.withScope(...scopes)
|
|
162
132
|
for (const expr of scopes) {
|
|
163
133
|
if (expr) {
|
|
164
134
|
const { scope, graph } = getScope(expr)
|
|
165
|
-
this
|
|
135
|
+
this.#applyScope(scope, graph)
|
|
166
136
|
}
|
|
167
137
|
}
|
|
168
138
|
return this
|
|
169
139
|
}
|
|
170
140
|
|
|
171
141
|
allowScope(...scopes) {
|
|
172
|
-
this
|
|
142
|
+
this.#allowScopes ||= {
|
|
173
143
|
default: true // The default scope is always allowed.
|
|
174
144
|
}
|
|
175
145
|
for (const expr of scopes) {
|
|
176
146
|
if (expr) {
|
|
177
147
|
const { scope } = getScope(expr)
|
|
178
|
-
this
|
|
148
|
+
this.#allowScopes[scope] = true
|
|
179
149
|
}
|
|
180
150
|
}
|
|
181
151
|
}
|
|
182
152
|
|
|
183
153
|
clearAllowScope() {
|
|
184
|
-
this
|
|
154
|
+
this.#allowScopes = null
|
|
185
155
|
}
|
|
186
156
|
|
|
187
157
|
scope(...scopes) {
|
|
@@ -191,61 +161,111 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
191
161
|
}
|
|
192
162
|
|
|
193
163
|
mergeScope(...scopes) {
|
|
194
|
-
deprecate(
|
|
164
|
+
deprecate(
|
|
165
|
+
`QueryBuilder#mergeScope() is deprecated. Use #withScope() instead.`
|
|
166
|
+
)
|
|
195
167
|
|
|
196
168
|
return this.withScope(...scopes)
|
|
197
169
|
}
|
|
198
170
|
|
|
199
171
|
clearScope() {
|
|
200
|
-
deprecate(
|
|
172
|
+
deprecate(
|
|
173
|
+
`QueryBuilder#clearScope() is deprecated. Use #clearWithScope() or #ignoreScope() instead.`
|
|
174
|
+
)
|
|
201
175
|
|
|
202
176
|
return this.clearWithScope()
|
|
203
177
|
}
|
|
204
178
|
|
|
205
|
-
|
|
179
|
+
#clearScopes(addDefault) {
|
|
206
180
|
// `_scopes` is an object where the keys are the scopes and the values
|
|
207
181
|
// indicate if the scope should be eager-applied or not:
|
|
208
|
-
this
|
|
209
|
-
? { default: true } //
|
|
182
|
+
this.#scopes = addDefault
|
|
183
|
+
? { default: true } // Eager-apply the default scope
|
|
210
184
|
: {}
|
|
211
185
|
return this
|
|
212
186
|
}
|
|
213
187
|
|
|
214
|
-
|
|
188
|
+
#copyScopes(query, isChildQuery = false) {
|
|
215
189
|
const isSameModelClass = this.modelClass() === query.modelClass()
|
|
216
190
|
// Only copy `_allowScopes` and `_ignoreScopes` if it's for the same model.
|
|
217
191
|
if (isSameModelClass) {
|
|
218
|
-
this
|
|
219
|
-
this
|
|
192
|
+
this.#allowScopes = query.#allowScopes ? { ...query.#allowScopes } : null
|
|
193
|
+
this.#ignoreScopes = { ...query.#ignoreScopes }
|
|
220
194
|
}
|
|
221
195
|
const scopes = isChildQuery
|
|
222
|
-
// When copying scopes for child-queries, we also need to take the
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
: { ...query
|
|
196
|
+
? // When copying scopes for child-queries, we also need to take the
|
|
197
|
+
// already applied scopes into account and copy those too.
|
|
198
|
+
{ ...query.#appliedScopes, ...query.#scopes }
|
|
199
|
+
: { ...query.#scopes }
|
|
226
200
|
// If the target is a child query of a graph query, copy all scopes, graph
|
|
227
201
|
// and non-graph. If it is a child query of a related or eager query,
|
|
228
202
|
// copy only the graph scopes.
|
|
229
|
-
const copyAllScopes =
|
|
230
|
-
isSameModelClass &&
|
|
231
|
-
|
|
203
|
+
const copyAllScopes = (
|
|
204
|
+
isSameModelClass &&
|
|
205
|
+
isChildQuery &&
|
|
206
|
+
query.has(/GraphAndFetch$/)
|
|
207
|
+
)
|
|
208
|
+
this.#scopes = copyAllScopes
|
|
232
209
|
? scopes
|
|
233
210
|
: filterScopes(scopes, (scope, graph) => graph) // copy graph-scopes only.
|
|
234
211
|
}
|
|
235
212
|
|
|
236
|
-
|
|
237
|
-
if (!this
|
|
213
|
+
#applyScopes() {
|
|
214
|
+
if (!this.#ignoreScopes[SYMBOL_ALL]) {
|
|
215
|
+
// Only apply default scopes if this is a normal find query, meaning it
|
|
216
|
+
// does not define any write operations or special selects, e.g. `count`:
|
|
217
|
+
const isNormalFind = (
|
|
218
|
+
this.isFind() &&
|
|
219
|
+
!this.hasSpecialSelects()
|
|
220
|
+
)
|
|
221
|
+
// If this isn't a normal find query, ignore all graph operations,
|
|
222
|
+
// to not mess with special selects such as `count`, etc:
|
|
223
|
+
this.#ignoreGraph = !isNormalFind
|
|
224
|
+
// All scopes in `_scopes` were already checked against `_allowScopes`.
|
|
225
|
+
// They themselves are allowed to apply / request other scopes that
|
|
226
|
+
// aren't listed, so clear `_allowScopes` and restore again after:
|
|
227
|
+
const allowScopes = this.#allowScopes
|
|
228
|
+
this.#allowScopes = null
|
|
229
|
+
const collectedScopes = {}
|
|
230
|
+
// Scopes can themselves request more scopes by calling `withScope()`
|
|
231
|
+
// In order to prevent that from causing problems while looping over
|
|
232
|
+
// `_scopes`, create a local copy of the entries, set `_scopes` to an
|
|
233
|
+
// empty object during iteration and check if there are new entries after
|
|
234
|
+
// one full loop. Keep doing this until there's nothing left, but keep
|
|
235
|
+
// track of all the applied scopes, so `_scopes` can be set to the the
|
|
236
|
+
// that in the end. This is needed for child queries, see `childQueryOf()`
|
|
237
|
+
let scopes = Object.entries(this.#scopes)
|
|
238
|
+
while (scopes.length > 0) {
|
|
239
|
+
this.#scopes = {}
|
|
240
|
+
for (const [scope, graph] of scopes) {
|
|
241
|
+
// Don't apply `default` scopes on anything else than a normal find
|
|
242
|
+
// query:
|
|
243
|
+
if (isNormalFind || scope !== 'default') {
|
|
244
|
+
this.#applyScope(scope, graph)
|
|
245
|
+
collectedScopes[scope] ||= graph
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
scopes = Object.entries(this.#scopes)
|
|
249
|
+
}
|
|
250
|
+
this.#scopes = collectedScopes
|
|
251
|
+
this.#allowScopes = allowScopes
|
|
252
|
+
this.#ignoreGraph = false
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#applyScope(scope, graph) {
|
|
257
|
+
if (!this.#ignoreScopes[SYMBOL_ALL] && !this.#ignoreScopes[scope]) {
|
|
238
258
|
// Prevent multiple application of scopes. This can easily occur
|
|
239
259
|
// with the nesting and eager-application of graph-scopes, see below.
|
|
240
260
|
// NOTE: The boolean values indicate the `graph` settings, not whether the
|
|
241
261
|
// scopes were applied or not.
|
|
242
|
-
if (!(scope in this
|
|
262
|
+
if (!(scope in this.#appliedScopes)) {
|
|
243
263
|
// Only apply graph-scopes that are actually defined on the model:
|
|
244
264
|
const func = this.modelClass().getScope(scope)
|
|
245
265
|
if (func) {
|
|
246
266
|
func.call(this, this)
|
|
247
267
|
}
|
|
248
|
-
this
|
|
268
|
+
this.#appliedScopes[scope] = graph
|
|
249
269
|
}
|
|
250
270
|
if (graph) {
|
|
251
271
|
// Also bake the scope into any graph expression that may have been
|
|
@@ -258,7 +278,16 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
258
278
|
// re-applies itself to the result.
|
|
259
279
|
const name = `^${scope}`
|
|
260
280
|
const modifiers = {
|
|
261
|
-
[name]: query =>
|
|
281
|
+
[name]: query => {
|
|
282
|
+
query.withScope(name)
|
|
283
|
+
if (query.#isJoinChildQuery) {
|
|
284
|
+
// Join child queries are never executed, and need to apply
|
|
285
|
+
// their scopes manually. Note that it's OK to call this
|
|
286
|
+
// repeatedly, because `_appliedScopes` prevents multiple
|
|
287
|
+
// application of the same scope.
|
|
288
|
+
query.#applyScopes()
|
|
289
|
+
}
|
|
290
|
+
}
|
|
262
291
|
}
|
|
263
292
|
this.withGraph(
|
|
264
293
|
addGraphScope(this.modelClass(), expr, [name], modifiers, true)
|
|
@@ -269,7 +298,7 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
269
298
|
}
|
|
270
299
|
|
|
271
300
|
applyFilter(name, ...args) {
|
|
272
|
-
if (this
|
|
301
|
+
if (this.#allowFilters && !this.#allowFilters[name]) {
|
|
273
302
|
throw new QueryBuilderError(`Query filter '${name}' is not allowed.`)
|
|
274
303
|
}
|
|
275
304
|
const filter = this.modelClass().definition.filters[name]
|
|
@@ -281,9 +310,9 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
281
310
|
}
|
|
282
311
|
|
|
283
312
|
allowFilter(...filters) {
|
|
284
|
-
this
|
|
313
|
+
this.#allowFilters ||= {}
|
|
285
314
|
for (const filter of filters) {
|
|
286
|
-
this
|
|
315
|
+
this.#allowFilters[filter] = true
|
|
287
316
|
}
|
|
288
317
|
}
|
|
289
318
|
|
|
@@ -292,7 +321,7 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
292
321
|
// `_ignoreGraph` and `_graphAlgorithm`:
|
|
293
322
|
withGraph(expr, options = {}) {
|
|
294
323
|
// To make merging easier, keep the current algorithm if none is specified:
|
|
295
|
-
const { algorithm = this
|
|
324
|
+
const { algorithm = this.#graphAlgorithm } = options
|
|
296
325
|
const method = {
|
|
297
326
|
fetch: 'withGraphFetched',
|
|
298
327
|
join: 'withGraphJoined'
|
|
@@ -302,8 +331,8 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
302
331
|
`Graph algorithm '${algorithm}' is unsupported.`
|
|
303
332
|
)
|
|
304
333
|
}
|
|
305
|
-
if (!this
|
|
306
|
-
this
|
|
334
|
+
if (!this.#ignoreGraph) {
|
|
335
|
+
this.#graphAlgorithm = algorithm
|
|
307
336
|
super[method](expr, options)
|
|
308
337
|
}
|
|
309
338
|
return this
|
|
@@ -391,7 +420,8 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
391
420
|
// Support `restart` and `cascade` in PostgreSQL truncate queries.
|
|
392
421
|
return this.raw(
|
|
393
422
|
`truncate table ??${
|
|
394
|
-
restart ? ' restart identity' : ''
|
|
423
|
+
restart ? ' restart identity' : ''
|
|
424
|
+
}${
|
|
395
425
|
cascade ? ' cascade' : ''
|
|
396
426
|
}`,
|
|
397
427
|
this.modelClass().tableName
|
|
@@ -412,39 +442,39 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
412
442
|
// https://github.com/Vincit/objection.js/issues/101#issuecomment-200363667
|
|
413
443
|
upsert(data, options = {}) {
|
|
414
444
|
let mainQuery
|
|
415
|
-
return this
|
|
416
|
-
.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
? mainQuery[options.fetch ? 'insertAndFetch' : 'insert'](data)
|
|
432
|
-
// We can use the `mainQuery` we saved in runBefore() to fetch the
|
|
445
|
+
return this.runBefore((result, builder) => {
|
|
446
|
+
if (!builder.context().isMainQuery) {
|
|
447
|
+
// At this point the builder should only contain a bunch of `where*`
|
|
448
|
+
// operations. Store this query for later use in runAfter(). Also mark
|
|
449
|
+
// the query with `isMainQuery: true` so we can skip all this when
|
|
450
|
+
// this function is called for the `mainQuery`.
|
|
451
|
+
mainQuery = builder.clone().context({ isMainQuery: true })
|
|
452
|
+
// Call update() on the original query, turning it into an update.
|
|
453
|
+
builder[options.update ? 'update' : 'patch'](data)
|
|
454
|
+
}
|
|
455
|
+
return result
|
|
456
|
+
}).runAfter((result, builder) => {
|
|
457
|
+
if (!builder.context().isMainQuery) {
|
|
458
|
+
return result === 0
|
|
459
|
+
? mainQuery[options.fetch ? 'insertAndFetch' : 'insert'](data)
|
|
460
|
+
: // We can use the `mainQuery` we saved in runBefore() to fetch the
|
|
433
461
|
// inserted results. It is noteworthy that this query will return
|
|
434
462
|
// the wrong results if the update changed any of the columns the
|
|
435
463
|
// where operates with. This also returns all updated models.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
464
|
+
mainQuery.first()
|
|
465
|
+
}
|
|
466
|
+
return result
|
|
467
|
+
})
|
|
440
468
|
}
|
|
441
469
|
|
|
442
470
|
find(query, allowParam) {
|
|
443
471
|
if (!query) return this
|
|
444
472
|
const allowed = !allowParam
|
|
445
|
-
? QueryParameters.
|
|
446
|
-
// If it's already a lookup object just use it, otherwise convert it:
|
|
447
|
-
|
|
473
|
+
? QueryParameters.getAllowed()
|
|
474
|
+
: // If it's already a lookup object just use it, otherwise convert it:
|
|
475
|
+
isPlainObject(allowParam)
|
|
476
|
+
? allowParam
|
|
477
|
+
: createLookup(allowParam)
|
|
448
478
|
for (const [key, value] of Object.entries(query)) {
|
|
449
479
|
// Support array notation for multiple parameters, as sent by axios:
|
|
450
480
|
const param = key.endsWith('[]') ? key.slice(0, -2) : key
|
|
@@ -454,7 +484,8 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
454
484
|
const paramHandler = QueryParameters.get(param)
|
|
455
485
|
if (!paramHandler) {
|
|
456
486
|
throw new QueryBuilderError(
|
|
457
|
-
`Invalid query parameter '${param}' in '${key}=${value}'.`
|
|
487
|
+
`Invalid query parameter '${param}' in '${key}=${value}'.`
|
|
488
|
+
)
|
|
458
489
|
}
|
|
459
490
|
paramHandler(this, key, value)
|
|
460
491
|
}
|
|
@@ -500,18 +531,18 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
500
531
|
|
|
501
532
|
patchAndFetch(data) {
|
|
502
533
|
return isArray(data)
|
|
503
|
-
? this
|
|
534
|
+
? this.#upsertAndFetch(data)
|
|
504
535
|
: super.patchAndFetch(data)
|
|
505
536
|
}
|
|
506
537
|
|
|
507
538
|
updateAndFetch(data) {
|
|
508
539
|
return isArray(data)
|
|
509
|
-
? this
|
|
540
|
+
? this.#upsertAndFetch(data, { update: true })
|
|
510
541
|
: super.updateAndFetch(data)
|
|
511
542
|
}
|
|
512
543
|
|
|
513
544
|
upsertAndFetch(data) {
|
|
514
|
-
return this
|
|
545
|
+
return this.#upsertAndFetch(data, {
|
|
515
546
|
// Insert missing nodes only at root by allowing `insertMissing`,
|
|
516
547
|
// but set `noInsert` for all relations:
|
|
517
548
|
insertMissing: true,
|
|
@@ -519,7 +550,7 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
519
550
|
})
|
|
520
551
|
}
|
|
521
552
|
|
|
522
|
-
|
|
553
|
+
#upsertAndFetch(data, options) {
|
|
523
554
|
return this.upsertGraphAndFetch(data, {
|
|
524
555
|
fetchStrategy: 'OnlyNeeded',
|
|
525
556
|
noInset: true,
|
|
@@ -531,70 +562,111 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
531
562
|
}
|
|
532
563
|
|
|
533
564
|
insertDitoGraph(data, options) {
|
|
534
|
-
return this
|
|
535
|
-
|
|
565
|
+
return this.#handleDitoGraph(
|
|
566
|
+
'insertGraph',
|
|
567
|
+
data,
|
|
568
|
+
options,
|
|
569
|
+
insertDitoGraphOptions
|
|
570
|
+
)
|
|
536
571
|
}
|
|
537
572
|
|
|
538
573
|
insertDitoGraphAndFetch(data, options) {
|
|
539
|
-
return this
|
|
540
|
-
|
|
574
|
+
return this.#handleDitoGraph(
|
|
575
|
+
'insertGraphAndFetch',
|
|
576
|
+
data,
|
|
577
|
+
options,
|
|
578
|
+
insertDitoGraphOptions
|
|
579
|
+
)
|
|
541
580
|
}
|
|
542
581
|
|
|
543
582
|
upsertDitoGraph(data, options) {
|
|
544
|
-
return this
|
|
545
|
-
|
|
583
|
+
return this.#handleDitoGraph(
|
|
584
|
+
'upsertGraph',
|
|
585
|
+
data,
|
|
586
|
+
options,
|
|
587
|
+
upsertDitoGraphOptions
|
|
588
|
+
)
|
|
546
589
|
}
|
|
547
590
|
|
|
548
591
|
upsertDitoGraphAndFetch(data, options) {
|
|
549
|
-
return this
|
|
550
|
-
|
|
592
|
+
return this.#handleDitoGraph(
|
|
593
|
+
'upsertGraphAndFetch',
|
|
594
|
+
data,
|
|
595
|
+
options,
|
|
596
|
+
upsertDitoGraphOptions
|
|
597
|
+
)
|
|
551
598
|
}
|
|
552
599
|
|
|
553
600
|
patchDitoGraph(data, options) {
|
|
554
|
-
return this
|
|
555
|
-
|
|
601
|
+
return this.#handleDitoGraph(
|
|
602
|
+
'upsertGraph',
|
|
603
|
+
data,
|
|
604
|
+
options,
|
|
605
|
+
patchDitoGraphOptions
|
|
606
|
+
)
|
|
556
607
|
}
|
|
557
608
|
|
|
558
609
|
patchDitoGraphAndFetch(data, options) {
|
|
559
|
-
return this
|
|
560
|
-
|
|
610
|
+
return this.#handleDitoGraph(
|
|
611
|
+
'upsertGraphAndFetch',
|
|
612
|
+
data,
|
|
613
|
+
options,
|
|
614
|
+
patchDitoGraphOptions
|
|
615
|
+
)
|
|
561
616
|
}
|
|
562
617
|
|
|
563
618
|
updateDitoGraph(data, options) {
|
|
564
|
-
return this
|
|
565
|
-
|
|
619
|
+
return this.#handleDitoGraph(
|
|
620
|
+
'upsertGraph',
|
|
621
|
+
data,
|
|
622
|
+
options,
|
|
623
|
+
updateDitoGraphOptions
|
|
624
|
+
)
|
|
566
625
|
}
|
|
567
626
|
|
|
568
627
|
updateDitoGraphAndFetch(data, options) {
|
|
569
|
-
return this
|
|
570
|
-
|
|
628
|
+
return this.#handleDitoGraph(
|
|
629
|
+
'upsertGraphAndFetch',
|
|
630
|
+
data,
|
|
631
|
+
options,
|
|
632
|
+
updateDitoGraphOptions
|
|
633
|
+
)
|
|
571
634
|
}
|
|
572
635
|
|
|
573
636
|
upsertDitoGraphAndFetchById(id, data, options) {
|
|
574
637
|
this.context({ byId: id })
|
|
575
|
-
return this.upsertDitoGraphAndFetch(
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
638
|
+
return this.upsertDitoGraphAndFetch(
|
|
639
|
+
{
|
|
640
|
+
...data,
|
|
641
|
+
...this.modelClass().getReference(id)
|
|
642
|
+
},
|
|
643
|
+
options
|
|
644
|
+
)
|
|
579
645
|
}
|
|
580
646
|
|
|
581
647
|
patchDitoGraphAndFetchById(id, data, options) {
|
|
582
648
|
this.context({ byId: id })
|
|
583
|
-
return this.patchDitoGraphAndFetch(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
649
|
+
return this.patchDitoGraphAndFetch(
|
|
650
|
+
{
|
|
651
|
+
...data,
|
|
652
|
+
...this.modelClass().getReference(id)
|
|
653
|
+
},
|
|
654
|
+
options
|
|
655
|
+
)
|
|
587
656
|
}
|
|
588
657
|
|
|
589
658
|
updateDitoGraphAndFetchById(id, data, options) {
|
|
590
659
|
this.context({ byId: id })
|
|
591
|
-
return this.updateDitoGraphAndFetch(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
660
|
+
return this.updateDitoGraphAndFetch(
|
|
661
|
+
{
|
|
662
|
+
...data,
|
|
663
|
+
...this.modelClass().getReference(id)
|
|
664
|
+
},
|
|
665
|
+
options
|
|
666
|
+
)
|
|
595
667
|
}
|
|
596
668
|
|
|
597
|
-
|
|
669
|
+
#handleDitoGraph(method, data, options, defaultOptions) {
|
|
598
670
|
const handleGraph = data => {
|
|
599
671
|
const graphProcessor = new DitoGraphProcessor(
|
|
600
672
|
this.modelClass(),
|
|
@@ -615,10 +687,10 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
615
687
|
// `_upsertCyclicDitoGraphAndFetch()` needs to run asynchronously,
|
|
616
688
|
// but we can't do so here and `runBefore()` executes too late,
|
|
617
689
|
// so use `_executeFirst()` to work around it.
|
|
618
|
-
this
|
|
619
|
-
this
|
|
690
|
+
this.#executeFirst = async () => {
|
|
691
|
+
this.#executeFirst = null
|
|
620
692
|
handleGraph(
|
|
621
|
-
await this.clone()
|
|
693
|
+
await this.clone().#upsertCyclicDitoGraphAndFetch(data, options)
|
|
622
694
|
)
|
|
623
695
|
}
|
|
624
696
|
} else {
|
|
@@ -628,7 +700,7 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
628
700
|
return this
|
|
629
701
|
}
|
|
630
702
|
|
|
631
|
-
async
|
|
703
|
+
async #upsertCyclicDitoGraphAndFetch(data, options) {
|
|
632
704
|
// TODO: This is part of a workaround for the following Objection.js issue.
|
|
633
705
|
// Replace with a normal `upsertGraphAndFetch()` once it is fixed:
|
|
634
706
|
// https://github.com/Vincit/objection.js/issues/1482
|
|
@@ -697,7 +769,8 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
697
769
|
for (const method of mixinMethods) {
|
|
698
770
|
if (method in target) {
|
|
699
771
|
console.warn(
|
|
700
|
-
`There is already a property named '${method}' on '${target}'`
|
|
772
|
+
`There is already a property named '${method}' on '${target}'`
|
|
773
|
+
)
|
|
701
774
|
} else {
|
|
702
775
|
Object.defineProperty(target, method, {
|
|
703
776
|
value(...args) {
|
|
@@ -713,23 +786,6 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
713
786
|
|
|
714
787
|
KnexHelper.mixin(QueryBuilder.prototype)
|
|
715
788
|
|
|
716
|
-
// Override all deprecated eager methods to respect the `_ignoreGraph` flag,
|
|
717
|
-
// and also keep track of `_graphAlgorithm`, as required by `withGraph()`
|
|
718
|
-
// TODO: Remove once we move to Objection 3.0
|
|
719
|
-
for (const key of [
|
|
720
|
-
'eager', 'joinEager', 'naiveEager',
|
|
721
|
-
'mergeEager', 'mergeJoinEager', 'mergeNaiveEager'
|
|
722
|
-
]) {
|
|
723
|
-
const method = QueryBuilder.prototype[key]
|
|
724
|
-
QueryBuilder.prototype[key] = function(...args) {
|
|
725
|
-
if (!this._ignoreGraph) {
|
|
726
|
-
this._graphAlgorithm = /join/i.test(key) ? 'join' : 'fetch'
|
|
727
|
-
method.call(this, ...args)
|
|
728
|
-
}
|
|
729
|
-
return this
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
789
|
// Add conversion of identifiers to all `where` statements, as well as to
|
|
734
790
|
// `select` and `orderBy`, by detecting use of model properties and expanding
|
|
735
791
|
// them to `${tableRefFor(modelClass}.${propertyName}`, for unambiguous
|
|
@@ -762,16 +818,17 @@ for (const key of [
|
|
|
762
818
|
'groupBy', 'orderBy'
|
|
763
819
|
]) {
|
|
764
820
|
const method = QueryBuilder.prototype[key]
|
|
765
|
-
QueryBuilder.prototype[key] = function(...args) {
|
|
821
|
+
QueryBuilder.prototype[key] = function (...args) {
|
|
766
822
|
const modelClass = this.modelClass()
|
|
767
823
|
const { properties } = modelClass.definition
|
|
768
824
|
|
|
769
825
|
// Expands all identifiers known to the model to their extended versions.
|
|
770
826
|
const expandIdentifier = identifier => {
|
|
771
827
|
// Support expansion of identifiers with aliases, e.g. `name AS newName`
|
|
772
|
-
const alias =
|
|
828
|
+
const alias = (
|
|
773
829
|
isString(identifier) &&
|
|
774
830
|
identifier.match(/^\s*([a-z][\w_]+)(\s+AS\s+.*)$/i)
|
|
831
|
+
)
|
|
775
832
|
return alias
|
|
776
833
|
? `${expandIdentifier(alias[1])}${alias[2]}`
|
|
777
834
|
: identifier === '*' || identifier in properties
|
|
@@ -801,9 +858,11 @@ for (const key of [
|
|
|
801
858
|
}
|
|
802
859
|
|
|
803
860
|
function filterScopes(scopes, callback) {
|
|
804
|
-
return Object.fromEntries(
|
|
805
|
-
(
|
|
806
|
-
|
|
861
|
+
return Object.fromEntries(
|
|
862
|
+
Object.entries(scopes).filter(
|
|
863
|
+
([scope, graph]) => callback(scope, graph)
|
|
864
|
+
)
|
|
865
|
+
)
|
|
807
866
|
}
|
|
808
867
|
|
|
809
868
|
// The default options for insertDitoGraph(), upsertDitoGraph(),
|
|
@@ -842,7 +901,7 @@ function addGraphScope(modelClass, expr, scopes, modifiers, isRoot = false) {
|
|
|
842
901
|
// and if it's actually available in the model's list of modifiers.
|
|
843
902
|
for (const scope of scopes) {
|
|
844
903
|
if (
|
|
845
|
-
!expr.$modify
|
|
904
|
+
!expr.$modify.includes(scope) && (
|
|
846
905
|
modelClass.hasScope(scope) ||
|
|
847
906
|
modifiers[scope]
|
|
848
907
|
)
|
|
@@ -899,8 +958,6 @@ const mixinMethods = [
|
|
|
899
958
|
'clearWithScope',
|
|
900
959
|
|
|
901
960
|
'clear',
|
|
902
|
-
'pick',
|
|
903
|
-
'omit',
|
|
904
961
|
'select',
|
|
905
962
|
|
|
906
963
|
'insert',
|