objection 3.1.3 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -219,27 +219,27 @@ class Model {
219
219
  return this.constructor.traverseAsync(filterConstructor, this, callback);
220
220
  }
221
221
 
222
- $omitFromJson(props) {
222
+ $omitFromJson(...props) {
223
223
  if (arguments.length === 0) {
224
224
  return this.$$omitFromJson;
225
225
  } else {
226
226
  if (!this.hasOwnProperty('$$omitFromJson')) {
227
- defineNonEnumerableProperty(this, '$$omitFromJson', props);
228
- } else {
229
- this.$$omitFromJson = this.$$omitFromJson.concat(props);
227
+ defineNonEnumerableProperty(this, '$$omitFromJson', []);
230
228
  }
229
+ this.$$omitFromJson = this.$$omitFromJson.concat(asPropsArray(props));
230
+ return this;
231
231
  }
232
232
  }
233
233
 
234
- $omitFromDatabaseJson(props) {
234
+ $omitFromDatabaseJson(...props) {
235
235
  if (arguments.length === 0) {
236
236
  return this.$$omitFromDatabaseJson;
237
237
  } else {
238
238
  if (!this.hasOwnProperty('$$omitFromDatabaseJson')) {
239
- defineNonEnumerableProperty(this, '$$omitFromDatabaseJson', props);
240
- } else {
241
- this.$$omitFromDatabaseJson = this.$$omitFromDatabaseJson.concat(props);
239
+ defineNonEnumerableProperty(this, '$$omitFromDatabaseJson', []);
242
240
  }
241
+ this.$$omitFromDatabaseJson = this.$$omitFromDatabaseJson.concat(asPropsArray(props));
242
+ return this;
243
243
  }
244
244
  }
245
245
 
@@ -907,6 +907,20 @@ function getTraverseArgs(filterConstructor, models, traverser) {
907
907
  };
908
908
  }
909
909
 
910
+ function asPropsArray(props) {
911
+ if (props.length === 1) {
912
+ const arg = props[0];
913
+ if (Array.isArray(arg)) {
914
+ return arg;
915
+ } else if (arg && typeof arg === 'object') {
916
+ return Object.entries(arg)
917
+ .filter(([, value]) => value)
918
+ .map(([key]) => key);
919
+ }
920
+ }
921
+ return props;
922
+ }
923
+
910
924
  module.exports = {
911
925
  Model,
912
926
  };
@@ -4,6 +4,7 @@ class InternalOptions {
4
4
  constructor() {
5
5
  this.skipUndefined = false;
6
6
  this.keepImplicitJoinProps = false;
7
+ this.returnImmediatelyValue = undefined;
7
8
  this.isInternalQuery = false;
8
9
  this.debug = false;
9
10
  this.schema = undefined;
@@ -14,6 +15,7 @@ class InternalOptions {
14
15
 
15
16
  copy.skipUndefined = this.skipUndefined;
16
17
  copy.keepImplicitJoinProps = this.keepImplicitJoinProps;
18
+ copy.returnImmediatelyValue = this.returnImmediatelyValue;
17
19
  copy.isInternalQuery = this.isInternalQuery;
18
20
  copy.debug = this.debug;
19
21
  copy.schema = this.schema;
@@ -1119,7 +1119,24 @@ function afterExecute(builder, result) {
1119
1119
  return promise;
1120
1120
  }
1121
1121
 
1122
+ class ReturnImmediatelyException {
1123
+ constructor(value) {
1124
+ this.value = value;
1125
+ }
1126
+ }
1127
+
1128
+ function handleReturnImmediatelyValue(builder) {
1129
+ const { returnImmediatelyValue } = builder.internalOptions();
1130
+ if (returnImmediatelyValue !== undefined) {
1131
+ throw new ReturnImmediatelyException(returnImmediatelyValue);
1132
+ }
1133
+ }
1134
+
1122
1135
  function handleExecuteError(builder, err) {
1136
+ if (err instanceof ReturnImmediatelyException) {
1137
+ return Promise.resolve(err.value);
1138
+ }
1139
+
1123
1140
  let promise = Promise.reject(wrapError(err));
1124
1141
 
1125
1142
  builder.forEachOperation(true, (op) => {
@@ -1139,9 +1156,11 @@ function chainOperationHooks(promise, builder, hookName) {
1139
1156
 
1140
1157
  builder.forEachOperation(true, (op) => {
1141
1158
  if (op.hasHook(hookName)) {
1142
- promise = promise.then((result) =>
1143
- builder.callAsyncOperationMethod(op, hookName, [builder, result]),
1144
- );
1159
+ promise = promise.then((result) => {
1160
+ const res = builder.callAsyncOperationMethod(op, hookName, [builder, result]);
1161
+ handleReturnImmediatelyValue(builder);
1162
+ return res;
1163
+ });
1145
1164
  }
1146
1165
  });
1147
1166
 
@@ -1261,7 +1280,11 @@ async function chainHooks(promise, builder, func) {
1261
1280
  let promise = Promise.resolve(result);
1262
1281
 
1263
1282
  if (isFunction(func)) {
1264
- promise = promise.then((result) => func.call(builder, result, builder));
1283
+ promise = promise.then((result) => {
1284
+ const res = func.call(builder, result, builder);
1285
+ handleReturnImmediatelyValue(builder);
1286
+ return res;
1287
+ });
1265
1288
  } else if (Array.isArray(func)) {
1266
1289
  func.forEach((func) => {
1267
1290
  promise = chainHooks(promise, builder, func);
@@ -3,7 +3,7 @@
3
3
  const { getModel } = require('../../../model/getModel');
4
4
  const { GraphAction } = require('../GraphAction');
5
5
  const { isInternalProp } = require('../../../utils/internalPropUtils');
6
- const { union, difference, isObject, jsonEquals } = require('../../../utils/objectUtils');
6
+ const { difference, isObject, jsonEquals } = require('../../../utils/objectUtils');
7
7
  const promiseUtils = require('../../../utils/promiseUtils');
8
8
 
9
9
  class GraphPatchAction extends GraphAction {
@@ -33,7 +33,7 @@ class GraphPatchAction extends GraphAction {
33
33
 
34
34
  const handleUpdate = () => {
35
35
  const { changedProps, unchangedProps } = this._findChanges(node);
36
- const allProps = union(changedProps, unchangedProps);
36
+ const allProps = [...changedProps, ...unchangedProps];
37
37
 
38
38
  const propsToUpdate = difference(
39
39
  shouldPatch || shouldUpdate
@@ -59,12 +59,16 @@ class GraphPatchAction extends GraphAction {
59
59
  // See if the model defines a beforeUpdate or $beforeUpdate hook. If it does
60
60
  // not, we can check for updated properties now and drop out immediately if
61
61
  // there is nothing to update. Otherwise, we need to wait for the hook to be
62
- // called before calling handleUpdate. See issue #2233.
62
+ // called before calling handleUpdate, but only if the node contains changes
63
+ // that aren't id properties (relates). See issues #2233, #2605.
63
64
  const hasBeforeUpdate =
64
65
  node.obj.constructor.beforeUpdate !== Model.beforeUpdate ||
65
66
  node.obj.$beforeUpdate !== Model.prototype.$beforeUpdate;
66
67
 
67
- if (!hasBeforeUpdate && !handleUpdate()) {
68
+ if (
69
+ (hasBeforeUpdate && !this._hasNonIdPropertyChanges(node)) ||
70
+ (!hasBeforeUpdate && !handleUpdate())
71
+ ) {
68
72
  return null;
69
73
  }
70
74
 
@@ -82,24 +86,15 @@ class GraphPatchAction extends GraphAction {
82
86
  .copyFrom(builder, GraphAction.ReturningAllSelector);
83
87
 
84
88
  if (hasBeforeUpdate) {
85
- updateBuilder
86
- .context({
87
- runBefore: () => {
88
- // Call handleUpdate in the runBefore hook which runs after the
89
- // $beforeUpdate hook, allowing it to modify the object before the
90
- // updated properties are determined. See issue #2233.
91
- if (hasBeforeUpdate && !handleUpdate()) {
92
- throw new ReturnNullException();
93
- }
94
- },
95
- })
96
- .onError((error) => {
97
- if (error instanceof ReturnNullException) {
98
- return null;
99
- } else {
100
- return Promise.reject(error);
101
- }
102
- });
89
+ updateBuilder.internalContext().runBefore.push((result, builder) => {
90
+ // Call handleUpdate in the runBefore hook which runs after the
91
+ // $beforeUpdate hook, allowing it to modify the object before the
92
+ // updated properties are determined. See issue #2233.
93
+ if (hasBeforeUpdate && !handleUpdate()) {
94
+ builder.internalOptions().returnImmediatelyValue = null;
95
+ }
96
+ return result;
97
+ });
103
98
  }
104
99
 
105
100
  if (shouldPatch) {
@@ -216,6 +211,11 @@ class GraphPatchAction extends GraphAction {
216
211
  };
217
212
  }
218
213
 
214
+ _hasNonIdPropertyChanges(node) {
215
+ const idProps = node.modelClass.getIdPropertyArray();
216
+ return this._findChanges(node).changedProps.some((prop) => !idProps.includes(prop));
217
+ }
218
+
219
219
  _createBuilder(node) {
220
220
  if (node.parentEdge && !node.parentEdge.relation.isObjectionHasManyRelation) {
221
221
  return this._createRelatedBuilder(node);
@@ -235,8 +235,6 @@ class GraphPatchAction extends GraphAction {
235
235
  }
236
236
  }
237
237
 
238
- class ReturnNullException {}
239
-
240
238
  function childQueryOptions() {
241
239
  return {
242
240
  fork: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "objection",
3
- "version": "3.1.3",
3
+ "version": "3.1.4",
4
4
  "description": "An SQL-friendly ORM for Node.js",
5
5
  "main": "lib/objection.js",
6
6
  "license": "MIT",
@@ -72,7 +72,7 @@
72
72
  "mocha": "^10.2.0",
73
73
  "mysql": "^2.18.1",
74
74
  "pg": "^8.11.3",
75
- "prettier": "3.1.0",
75
+ "prettier": "3.2.5",
76
76
  "sqlite3": "^5.1.6",
77
77
  "typescript": ">=5.3.2",
78
78
  "vuepress": "1.9.10"
@@ -229,13 +229,14 @@ declare namespace Objection {
229
229
  }
230
230
  : never;
231
231
 
232
- type PartialModelGraphField<F> = Defined<F> extends Model
233
- ? PartialModelGraph<Defined<F>>
234
- : Defined<F> extends Array<infer I>
235
- ? I extends Model
236
- ? PartialModelGraph<I>[]
237
- : Expression<F>
238
- : Expression<F>;
232
+ type PartialModelGraphField<F> =
233
+ Defined<F> extends Model
234
+ ? PartialModelGraph<Defined<F>>
235
+ : Defined<F> extends Array<infer I>
236
+ ? I extends Model
237
+ ? PartialModelGraph<I>[]
238
+ : Expression<F>
239
+ : Expression<F>;
239
240
 
240
241
  /**
241
242
  * Extracts the property names (excluding relations) of a model class.
@@ -1699,13 +1700,13 @@ declare namespace Objection {
1699
1700
  ): this;
1700
1701
 
1701
1702
  $set(obj: Pojo): this;
1702
- $omit(keys: string | string[] | { [key: string]: boolean }): this;
1703
- $pick(keys: string | string[] | { [key: string]: boolean }): this;
1704
1703
  $clone(opt?: CloneOptions): this;
1705
1704
  $traverse(filterConstructor: typeof Model, traverser: TraverserFunction): this;
1706
1705
  $traverse(traverser: TraverserFunction): this;
1707
1706
  $traverseAsync(filterConstructor: typeof Model, traverser: TraverserFunction): Promise<this>;
1708
1707
  $traverseAsync(traverser: TraverserFunction): Promise<this>;
1708
+ $omitFromJson(keys: string | string[] | { [key: string]: boolean }): this;
1709
+ $omitFromDatabaseJson(keys: string | string[] | { [key: string]: boolean }): this;
1709
1710
 
1710
1711
  $knex(): Knex;
1711
1712
  $transaction(): Knex;