@ditojs/server 2.0.5 → 2.1.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.
Files changed (53) hide show
  1. package/package.json +11 -13
  2. package/src/app/Application.js +226 -179
  3. package/src/app/Validator.js +53 -43
  4. package/src/cli/console.js +6 -4
  5. package/src/cli/db/createMigration.js +59 -30
  6. package/src/cli/db/migrate.js +6 -4
  7. package/src/cli/db/reset.js +8 -5
  8. package/src/cli/db/rollback.js +6 -4
  9. package/src/cli/db/seed.js +2 -1
  10. package/src/cli/index.js +1 -1
  11. package/src/controllers/AdminController.js +98 -84
  12. package/src/controllers/CollectionController.js +37 -30
  13. package/src/controllers/Controller.js +83 -43
  14. package/src/controllers/ControllerAction.js +27 -15
  15. package/src/controllers/ModelController.js +4 -1
  16. package/src/controllers/RelationController.js +19 -21
  17. package/src/controllers/UsersController.js +3 -4
  18. package/src/decorators/parameters.js +3 -1
  19. package/src/decorators/scope.js +1 -1
  20. package/src/errors/ControllerError.js +2 -1
  21. package/src/errors/DatabaseError.js +20 -11
  22. package/src/graph/DitoGraphProcessor.js +48 -40
  23. package/src/graph/expression.js +6 -8
  24. package/src/graph/graph.js +20 -11
  25. package/src/lib/EventEmitter.js +12 -12
  26. package/src/middleware/handleConnectMiddleware.js +16 -10
  27. package/src/middleware/handleError.js +6 -5
  28. package/src/middleware/handleSession.js +33 -29
  29. package/src/middleware/handleUser.js +2 -2
  30. package/src/middleware/index.js +1 -0
  31. package/src/middleware/logRequests.js +3 -3
  32. package/src/middleware/setupRequestStorage.js +14 -0
  33. package/src/mixins/AssetMixin.js +62 -58
  34. package/src/mixins/SessionMixin.js +13 -10
  35. package/src/mixins/TimeStampedMixin.js +33 -29
  36. package/src/mixins/UserMixin.js +130 -116
  37. package/src/models/Model.js +245 -194
  38. package/src/models/definitions/filters.js +14 -13
  39. package/src/query/QueryBuilder.js +252 -195
  40. package/src/query/QueryFilters.js +3 -3
  41. package/src/query/QueryParameters.js +2 -2
  42. package/src/query/Registry.js +8 -10
  43. package/src/schema/keywords/_validate.js +10 -8
  44. package/src/schema/properties.test.js +247 -206
  45. package/src/schema/relations.js +42 -20
  46. package/src/schema/relations.test.js +36 -19
  47. package/src/services/Service.js +8 -14
  48. package/src/storage/S3Storage.js +5 -3
  49. package/src/storage/Storage.js +16 -14
  50. package/src/utils/function.js +7 -4
  51. package/src/utils/function.test.js +30 -6
  52. package/src/utils/object.test.js +5 -1
  53. package/types/index.d.ts +244 -257
@@ -1,7 +1,14 @@
1
1
  import objection from 'objection'
2
2
  import {
3
- isObject, isPlainObject, isString, isArray, clone, mapKeys,
4
- getValueAtDataPath, setValueAtDataPath, parseDataPath
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
- constructor(modelClass) {
18
- super(modelClass)
19
- this._ignoreGraph = false
20
- this._graphAlgorithm = 'fetch'
21
- this._allowFilters = null
22
- this._allowScopes = null
23
- this._ignoreScopes = {}
24
- this._appliedScopes = {}
25
- this._executeFirst = null // Part of a work-around for cyclic graphs
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._ignoreGraph = this._ignoreGraph
33
- copy._graphAlgorithm = this._graphAlgorithm
34
- copy._appliedScopes = { ...this._appliedScopes }
35
- copy._allowFilters = this._allowFilters ? { ...this._allowFilters } : null
36
- copy._copyScopes(this)
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
- if (!this._ignoreScopes[SYMBOL_ALL]) {
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._executeFirst?.()
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._clearScopes(false)
73
+ this.#clearScopes(false)
107
74
  } else {
108
75
  // Inherit the graph scopes from the parent query.
109
- this._copyScopes(query, true)
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._allowScopes && !this._allowScopes[scope]) {
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._scopes[scope] ||= graph
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._clearScopes(true)
108
+ return this.#clearScopes(true)
140
109
  }
141
110
 
142
111
  ignoreScope(...scopes) {
143
- if (!this._ignoreScopes[SYMBOL_ALL]) {
144
- this._ignoreScopes = scopes.length > 0
145
- ? {
146
- ...this._ignoreScopes,
147
- ...createLookup(scopes)
148
- }
149
- // Empty arguments = ignore all scopes
150
- : {
151
- [SYMBOL_ALL]: true
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._scopes`,
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._applyScope(scope, graph)
135
+ this.#applyScope(scope, graph)
166
136
  }
167
137
  }
168
138
  return this
169
139
  }
170
140
 
171
141
  allowScope(...scopes) {
172
- this._allowScopes ||= {
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._allowScopes[scope] = true
148
+ this.#allowScopes[scope] = true
179
149
  }
180
150
  }
181
151
  }
182
152
 
183
153
  clearAllowScope() {
184
- this._allowScopes = null
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(`QueryBuilder#mergeScope() is deprecated. Use #withScope() instead.`)
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(`QueryBuilder#clearScope() is deprecated. Use #clearWithScope() or #ignoreScope() instead.`)
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
- _clearScopes(addDefault) {
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._scopes = addDefault
209
- ? { default: true } // eager-apply the default scope
182
+ this.#scopes = addDefault
183
+ ? { default: true } // Eager-apply the default scope
210
184
  : {}
211
185
  return this
212
186
  }
213
187
 
214
- _copyScopes(query, isChildQuery = false) {
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._allowScopes = query._allowScopes ? { ...query._allowScopes } : null
219
- this._ignoreScopes = { ...query._ignoreScopes }
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 already
223
- // applied scopes into account and copy those too.
224
- ? { ...query._appliedScopes, ...query._scopes }
225
- : { ...query._scopes }
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 && isChildQuery && query.has(/GraphAndFetch$/)
231
- this._scopes = copyAllScopes
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
- _applyScope(scope, graph) {
237
- if (!this._ignoreScopes[SYMBOL_ALL] && !this._ignoreScopes[scope]) {
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._appliedScopes)) {
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._appliedScopes[scope] = graph
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 => query.withScope(name)
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._allowFilters && !this._allowFilters[name]) {
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._allowFilters ||= {}
313
+ this.#allowFilters ||= {}
285
314
  for (const filter of filters) {
286
- this._allowFilters[filter] = true
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._graphAlgorithm } = options
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._ignoreGraph) {
306
- this._graphAlgorithm = algorithm
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
- .runBefore((result, builder) => {
417
- if (!builder.context().isMainQuery) {
418
- // At this point the builder should only contain a bunch of `where*`
419
- // operations. Store this query for later use in runAfter(). Also mark
420
- // the query with `isMainQuery: true` so we can skip all this when
421
- // this function is called for the `mainQuery`.
422
- mainQuery = builder.clone().context({ isMainQuery: true })
423
- // Call update() on the original query, turning it into an update.
424
- builder[options.update ? 'update' : 'patch'](data)
425
- }
426
- return result
427
- })
428
- .runAfter((result, builder) => {
429
- if (!builder.context().isMainQuery) {
430
- return result === 0
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
- : mainQuery.first()
437
- }
438
- return result
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.allowed()
446
- // If it's already a lookup object just use it, otherwise convert it:
447
- : isPlainObject(allowParam) ? allowParam : createLookup(allowParam)
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._upsertAndFetch(data)
534
+ ? this.#upsertAndFetch(data)
504
535
  : super.patchAndFetch(data)
505
536
  }
506
537
 
507
538
  updateAndFetch(data) {
508
539
  return isArray(data)
509
- ? this._upsertAndFetch(data, { update: true })
540
+ ? this.#upsertAndFetch(data, { update: true })
510
541
  : super.updateAndFetch(data)
511
542
  }
512
543
 
513
544
  upsertAndFetch(data) {
514
- return this._upsertAndFetch(data, {
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
- _upsertAndFetch(data, options) {
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._handleDitoGraph('insertGraph',
535
- data, options, insertDitoGraphOptions)
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._handleDitoGraph('insertGraphAndFetch',
540
- data, options, insertDitoGraphOptions)
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._handleDitoGraph('upsertGraph',
545
- data, options, upsertDitoGraphOptions)
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._handleDitoGraph('upsertGraphAndFetch',
550
- data, options, upsertDitoGraphOptions)
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._handleDitoGraph('upsertGraph',
555
- data, options, patchDitoGraphOptions)
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._handleDitoGraph('upsertGraphAndFetch',
560
- data, options, patchDitoGraphOptions)
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._handleDitoGraph('upsertGraph',
565
- data, options, updateDitoGraphOptions)
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._handleDitoGraph('upsertGraphAndFetch',
570
- data, options, updateDitoGraphOptions)
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
- ...data,
577
- ...this.modelClass().getReference(id)
578
- }, options)
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
- ...data,
585
- ...this.modelClass().getReference(id)
586
- }, options)
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
- ...data,
593
- ...this.modelClass().getReference(id)
594
- }, options)
660
+ return this.updateDitoGraphAndFetch(
661
+ {
662
+ ...data,
663
+ ...this.modelClass().getReference(id)
664
+ },
665
+ options
666
+ )
595
667
  }
596
668
 
597
- _handleDitoGraph(method, data, options, defaultOptions) {
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._executeFirst = async () => {
619
- this._executeFirst = null
690
+ this.#executeFirst = async () => {
691
+ this.#executeFirst = null
620
692
  handleGraph(
621
- await this.clone()._upsertCyclicDitoGraphAndFetch(data, options)
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 _upsertCyclicDitoGraphAndFetch(data, options) {
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(Object.entries(scopes).filter(
805
- ([scope, graph]) => callback(scope, graph)
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?.includes(scope) && (
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',