objection 3.1.3 → 3.1.5

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.
@@ -36,8 +36,10 @@ class AjvValidator extends Validator {
36
36
  self.cache = new Map();
37
37
 
38
38
  const setupAjv = (ajv) => {
39
- conf.onCreateAjv(ajv);
40
- // Only add AJV formats if they weren't added in user-space already
39
+ if (conf.onCreateAjv) {
40
+ conf.onCreateAjv(ajv);
41
+ }
42
+ // Only add Ajv formats if they weren't added in user-space already
41
43
  if (!ajv.formats['date-time']) {
42
44
  addFormats(ajv);
43
45
  }
@@ -77,7 +79,7 @@ class AjvValidator extends Validator {
77
79
  }
78
80
 
79
81
  validator.call(model, json);
80
- const error = parseValidationError(validator.errors, modelClass, options);
82
+ const error = parseValidationError(validator.errors, modelClass, options, this.ajvOptions);
81
83
 
82
84
  if (error) {
83
85
  throw error;
@@ -87,7 +89,7 @@ class AjvValidator extends Validator {
87
89
  }
88
90
 
89
91
  getValidator(modelClass, jsonSchema, isPatchObject) {
90
- // Use the AJV custom serializer if provided.
92
+ // Use the Ajv custom serializer if provided.
91
93
  const createCacheKey = this.ajvOptions.serialize || JSON.stringify;
92
94
 
93
95
  // Optimization for the common case where jsonSchema is never modified.
@@ -103,7 +105,7 @@ class AjvValidator extends Validator {
103
105
  if (!validators) {
104
106
  validators = {
105
107
  // Validator created for the schema object without `required` properties
106
- // using the AJV instance that doesn't set default values.
108
+ // using the Ajv instance that doesn't set default values.
107
109
  patchValidator: null,
108
110
 
109
111
  // Validator created for the unmodified schema.
@@ -143,7 +145,7 @@ class AjvValidator extends Validator {
143
145
  }
144
146
  }
145
147
 
146
- function parseValidationError(errors, modelClass, options) {
148
+ function parseValidationError(errors, modelClass, options, ajvOptions) {
147
149
  if (!errors) {
148
150
  return null;
149
151
  }
@@ -178,13 +180,21 @@ function parseValidationError(errors, modelClass, options) {
178
180
  // More than one error can occur for the same key in Ajv, merge them in the array:
179
181
  const array = errorHash[key] || (errorHash[key] = []);
180
182
 
181
- // Use unshift instead of push so that the last error ends up at [0],
182
- // preserving previous behavior where only the last error was stored.
183
- array.unshift({
183
+ // Prepare error object
184
+ const errorObj = {
184
185
  message: error.message,
185
186
  keyword: error.keyword,
186
187
  params: error.params,
187
- });
188
+ };
189
+
190
+ // Add data if verbose enabled
191
+ if (ajvOptions.verbose) {
192
+ errorObj.data = error.data;
193
+ }
194
+
195
+ // Use unshift instead of push so that the last error ends up at [0],
196
+ // preserving previous behavior where only the last error was stored.
197
+ array.unshift(errorObj);
188
198
 
189
199
  ++numErrors;
190
200
  }
@@ -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
 
@@ -317,9 +317,6 @@ class Model {
317
317
 
318
318
  static createValidator() {
319
319
  return new AjvValidator({
320
- onCreateAjv: (ajv) => {
321
- /* Do Nothing by default */
322
- },
323
320
  options: {
324
321
  allErrors: true,
325
322
  validateSchema: false,
@@ -907,6 +904,20 @@ function getTraverseArgs(filterConstructor, models, traverser) {
907
904
  };
908
905
  }
909
906
 
907
+ function asPropsArray(props) {
908
+ if (props.length === 1) {
909
+ const arg = props[0];
910
+ if (Array.isArray(arg)) {
911
+ return arg;
912
+ } else if (arg && typeof arg === 'object') {
913
+ return Object.entries(arg)
914
+ .filter(([, value]) => value)
915
+ .map(([key]) => key);
916
+ }
917
+ }
918
+ return props;
919
+ }
920
+
910
921
  module.exports = {
911
922
  Model,
912
923
  };
@@ -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);
@@ -12,7 +12,7 @@ const WhereSelector = /^(where|orWhere|andWhere|find\w+)/;
12
12
  const OnSelector = /^(on|orOn|andOn)/;
13
13
  const OrderBySelector = /orderBy/;
14
14
  const JoinSelector = /(join|joinRaw|joinRelated)$/i;
15
- const FromSelector = /^(from|into|table)$/;
15
+ const FromSelector = /^(from|fromRaw|into|table)$/;
16
16
 
17
17
  class QueryBuilderOperationSupport {
18
18
  constructor(...args) {
@@ -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.5",
4
4
  "description": "An SQL-friendly ORM for Node.js",
5
5
  "main": "lib/objection.js",
6
6
  "license": "MIT",
@@ -53,7 +53,7 @@
53
53
  ],
54
54
  "types": "./typings/objection/index.d.ts",
55
55
  "dependencies": {
56
- "ajv": "^8.12.0",
56
+ "ajv": "^8.17.1",
57
57
  "ajv-formats": "^2.1.1",
58
58
  "db-errors": "^0.2.3"
59
59
  },
@@ -61,20 +61,20 @@
61
61
  "knex": ">=1.0.1"
62
62
  },
63
63
  "devDependencies": {
64
- "@types/node": "^20.10.0",
64
+ "@types/node": "^22.7.0",
65
65
  "chai": "^4.3.10",
66
66
  "chai-subset": "^1.6.0",
67
67
  "eslint": "^8.54.0",
68
68
  "eslint-formatter-codeframe": "^7.32.1",
69
- "eslint-plugin-prettier": "^5.0.1",
69
+ "eslint-plugin-prettier": "^5.2.1",
70
70
  "expect.js": "^0.3.1",
71
- "knex": "^3.0.1",
72
- "mocha": "^10.2.0",
71
+ "knex": "^3.1.0",
72
+ "mocha": "^10.7.3",
73
73
  "mysql": "^2.18.1",
74
- "pg": "^8.11.3",
75
- "prettier": "3.1.0",
76
- "sqlite3": "^5.1.6",
77
- "typescript": ">=5.3.2",
74
+ "pg": "^8.13.0",
75
+ "prettier": "3.3.3",
76
+ "sqlite3": "^5.1.7",
77
+ "typescript": ">=5.6.2",
78
78
  "vuepress": "1.9.10"
79
79
  }
80
80
  }
@@ -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.
@@ -1069,7 +1070,7 @@ declare namespace Objection {
1069
1070
  insert(insert: PartialModelObject<M>[]): ArrayQueryBuilder<this>;
1070
1071
  insert(): SingleQueryBuilder<this>;
1071
1072
 
1072
- onConflict(column?: string | string[] | true): this;
1073
+ onConflict(column?: ColumnRef | ColumnRef[] | true): this;
1073
1074
  ignore(): this;
1074
1075
  merge(merge?: PartialModelObject<M> | string[]): this;
1075
1076
 
@@ -1422,6 +1423,19 @@ declare namespace Objection {
1422
1423
  new (): T;
1423
1424
  }
1424
1425
 
1426
+ interface PrototypeType<T> extends Function {
1427
+ prototype: T;
1428
+ }
1429
+
1430
+ interface ConstructorFunctionType<T = any> extends PrototypeType<T> {
1431
+ new (...args: any[]): T;
1432
+ }
1433
+
1434
+ // for internal use on generic static this deduction, copied from https://github.com/microsoft/TypeScript/issues/5863#issuecomment-1483978415
1435
+ type ConstructorType<T = unknown, Static extends Record<string, any> = PrototypeType<T>> = (ConstructorFunctionType<T> | PrototypeType<T>) & {
1436
+ [Key in keyof Static]: Static[Key];
1437
+ };
1438
+
1425
1439
  export interface ModelConstructor<M extends Model> extends Constructor<M> {}
1426
1440
 
1427
1441
  export interface ModelClass<M extends Model> extends ModelConstructor<M> {
@@ -1472,6 +1486,9 @@ declare namespace Objection {
1472
1486
  fromJson(json: object, opt?: ModelOptions): M;
1473
1487
  fromDatabaseJson(json: object): M;
1474
1488
 
1489
+ columnNameToPropertyName(columnName: string): string;
1490
+ propertyNameToColumnName(propertyName: string): string;
1491
+
1475
1492
  createValidator(): Validator;
1476
1493
  createValidationError(args: CreateValidationErrorArgs): Error;
1477
1494
  createNotFoundError(queryContext: QueryContext, args: CreateNotFoundErrorArgs): Error;
@@ -1569,7 +1586,7 @@ declare namespace Objection {
1569
1586
  ): QueryBuilderType<M>;
1570
1587
 
1571
1588
  static relatedQuery<M extends Model, K extends keyof M>(
1572
- this: Constructor<M>,
1589
+ this: ConstructorType<M>,
1573
1590
  relationName: K,
1574
1591
  trxOrKnex?: TransactionOrKnex,
1575
1592
  ): ArrayRelatedQueryBuilder<M[K]>;
@@ -1582,6 +1599,9 @@ declare namespace Objection {
1582
1599
  static fromJson<M extends Model>(this: Constructor<M>, json: object, opt?: ModelOptions): M;
1583
1600
  static fromDatabaseJson<M extends Model>(this: Constructor<M>, json: object): M;
1584
1601
 
1602
+ static columnNameToPropertyName(columnName: string): string;
1603
+ static propertyNameToColumnName(propertyName: string): string;
1604
+
1585
1605
  static createValidator(): Validator;
1586
1606
  static createValidationError(args: CreateValidationErrorArgs): Error;
1587
1607
  static createNotFoundError(queryContext: QueryContext, args: CreateNotFoundErrorArgs): Error;
@@ -1595,7 +1615,7 @@ declare namespace Objection {
1595
1615
 
1596
1616
  static transaction<T>(callback: (trx: Transaction) => Promise<T>): Promise<T>;
1597
1617
  static transaction<T>(
1598
- trxOrKnex: TransactionOrKnex,
1618
+ trxOrKnex: TransactionOrKnex | undefined,
1599
1619
  callback: (trx: Transaction) => Promise<T>,
1600
1620
  ): Promise<T>;
1601
1621
 
@@ -1699,13 +1719,13 @@ declare namespace Objection {
1699
1719
  ): this;
1700
1720
 
1701
1721
  $set(obj: Pojo): this;
1702
- $omit(keys: string | string[] | { [key: string]: boolean }): this;
1703
- $pick(keys: string | string[] | { [key: string]: boolean }): this;
1704
1722
  $clone(opt?: CloneOptions): this;
1705
1723
  $traverse(filterConstructor: typeof Model, traverser: TraverserFunction): this;
1706
1724
  $traverse(traverser: TraverserFunction): this;
1707
1725
  $traverseAsync(filterConstructor: typeof Model, traverser: TraverserFunction): Promise<this>;
1708
1726
  $traverseAsync(traverser: TraverserFunction): Promise<this>;
1727
+ $omitFromJson(keys: string | string[] | { [key: string]: boolean }): this;
1728
+ $omitFromDatabaseJson(keys: string | string[] | { [key: string]: boolean }): this;
1709
1729
 
1710
1730
  $knex(): Knex;
1711
1731
  $transaction(): Knex;