objection 3.2.0 → 3.3.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 (52) hide show
  1. package/README.md +13 -13
  2. package/lib/model/AjvValidator.js +133 -3
  3. package/lib/queryBuilder/QueryBuilder.js +186 -5
  4. package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
  5. package/lib/queryBuilder/ReferenceBuilder.js +5 -3
  6. package/lib/queryBuilder/RelationExpression.js +13 -1
  7. package/lib/queryBuilder/StaticHookArguments.js +12 -1
  8. package/lib/queryBuilder/graph/GraphUpsert.js +22 -0
  9. package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
  10. package/lib/queryBuilder/graph/insert/GraphInsert.js +107 -8
  11. package/lib/queryBuilder/graph/insert/GraphInsertAction.js +8 -1
  12. package/lib/queryBuilder/join/JoinResultParser.js +22 -0
  13. package/lib/queryBuilder/join/RelationJoiner.js +71 -22
  14. package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
  15. package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
  16. package/lib/queryBuilder/operations/InsertOperation.js +121 -12
  17. package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
  18. package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
  19. package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
  20. package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
  21. package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
  22. package/lib/queryBuilder/operations/eager/EagerOperation.js +4 -0
  23. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +2 -6
  24. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +10 -33
  25. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
  26. package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
  27. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +41 -34
  28. package/lib/queryBuilder/operations/select/Selection.js +95 -4
  29. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
  30. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
  31. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
  32. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
  33. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
  34. package/lib/queryBuilder/whereGroups.js +125 -0
  35. package/lib/relations/Relation.js +5 -2
  36. package/lib/relations/hasMany/HasManyRelateOperation.js +5 -2
  37. package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
  38. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
  39. package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
  40. package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
  41. package/lib/relations/manyToMany/ManyToManyRelation.js +70 -48
  42. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
  43. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
  44. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
  45. package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
  46. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +8 -2
  47. package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
  48. package/lib/utils/deprecate.js +6 -0
  49. package/lib/utils/identifierMapping.js +15 -1
  50. package/lib/utils/parseFieldExpression.js +28 -0
  51. package/package.json +10 -6
  52. package/typings/objection/index.d.ts +280 -28
@@ -4,12 +4,13 @@ const { getModel } = require('../../model/getModel');
4
4
  const { Relation } = require('../Relation');
5
5
  const { RelationProperty } = require('../RelationProperty');
6
6
 
7
- const { isSqlite } = require('../../utils/knexUtils');
7
+ const { isSqlite, isMySql, isPostgres } = require('../../utils/knexUtils');
8
8
  const { inheritModel } = require('../../model/inheritModel');
9
9
  const { resolveModel } = require('../../utils/resolveModel');
10
10
  const { mapAfterAllReturn } = require('../../utils/promiseUtils');
11
11
  const { isFunction, isObject, isString } = require('../../utils/objectUtils');
12
12
  const { createModifier } = require('../../utils/createModifier');
13
+ const { addWhereGroup } = require('../../queryBuilder/whereGroups');
13
14
 
14
15
  const { ManyToManyFindOperation } = require('./find/ManyToManyFindOperation');
15
16
  const { ManyToManyInsertOperation } = require('./insert/ManyToManyInsertOperation');
@@ -19,9 +20,15 @@ const { ManyToManyUnrelateOperation } = require('./unrelate/ManyToManyUnrelateOp
19
20
  const {
20
21
  ManyToManyUnrelateSqliteOperation,
21
22
  } = require('./unrelate/ManyToManyUnrelateSqliteOperation');
23
+ const { ManyToManyUnrelateMySqlOperation } = require('./unrelate/ManyToManyUnrelateMySqlOperation');
24
+ const {
25
+ ManyToManyUnrelatePostgresOperation,
26
+ } = require('./unrelate/ManyToManyUnrelatePostgresOperation');
22
27
 
23
28
  const { ManyToManyUpdateOperation } = require('./update/ManyToManyUpdateOperation');
24
29
  const { ManyToManyUpdateSqliteOperation } = require('./update/ManyToManyUpdateSqliteOperation');
30
+ const { ManyToManyUpdateMySqlOperation } = require('./update/ManyToManyUpdateMySqlOperation');
31
+ const { ManyToManyUpdatePostgresOperation } = require('./update/ManyToManyUpdatePostgresOperation');
25
32
 
26
33
  const { ManyToManyDeleteOperation } = require('./delete/ManyToManyDeleteOperation');
27
34
  const { ManyToManyDeleteSqliteOperation } = require('./delete/ManyToManyDeleteSqliteOperation');
@@ -91,7 +98,7 @@ class ManyToManyRelation extends Relation {
91
98
  });
92
99
  });
93
100
 
94
- owner.buildFindQuery(builder, this, joinTableOwnerRefs);
101
+ addWhereGroup(builder, () => owner.buildFindQuery(builder, this, joinTableOwnerRefs));
95
102
  return this.applyModify(builder);
96
103
  }
97
104
 
@@ -160,47 +167,25 @@ class ManyToManyRelation extends Relation {
160
167
  }
161
168
 
162
169
  update(builder, owner) {
163
- if (isSqlite(builder.knex())) {
164
- return new ManyToManyUpdateSqliteOperation('update', {
165
- relation: this,
166
- owner,
167
- });
168
- } else {
169
- return new ManyToManyUpdateOperation('update', {
170
- relation: this,
171
- owner,
172
- });
173
- }
170
+ return new (getUpdateOperationClass(builder))('update', {
171
+ relation: this,
172
+ owner,
173
+ });
174
174
  }
175
175
 
176
176
  patch(builder, owner) {
177
- if (isSqlite(builder.knex())) {
178
- return new ManyToManyUpdateSqliteOperation('patch', {
179
- modelOptions: { patch: true },
180
- relation: this,
181
- owner,
182
- });
183
- } else {
184
- return new ManyToManyUpdateOperation('patch', {
185
- modelOptions: { patch: true },
186
- relation: this,
187
- owner,
188
- });
189
- }
177
+ return new (getUpdateOperationClass(builder))('patch', {
178
+ modelOptions: { patch: true },
179
+ relation: this,
180
+ owner,
181
+ });
190
182
  }
191
183
 
192
184
  delete(builder, owner) {
193
- if (isSqlite(builder.knex())) {
194
- return new ManyToManyDeleteSqliteOperation('delete', {
195
- relation: this,
196
- owner,
197
- });
198
- } else {
199
- return new ManyToManyDeleteOperation('delete', {
200
- relation: this,
201
- owner,
202
- });
203
- }
185
+ return new (getDeleteOperationClass(builder))('delete', {
186
+ relation: this,
187
+ owner,
188
+ });
204
189
  }
205
190
 
206
191
  relate(builder, owner) {
@@ -211,17 +196,10 @@ class ManyToManyRelation extends Relation {
211
196
  }
212
197
 
213
198
  unrelate(builder, owner) {
214
- if (isSqlite(builder.knex())) {
215
- return new ManyToManyUnrelateSqliteOperation('unrelate', {
216
- relation: this,
217
- owner,
218
- });
219
- } else {
220
- return new ManyToManyUnrelateOperation('unrelate', {
221
- relation: this,
222
- owner,
223
- });
224
- }
199
+ return new (getUnrelateOperationClass(builder))('unrelate', {
200
+ relation: this,
201
+ owner,
202
+ });
225
203
  }
226
204
 
227
205
  createJoinModels(ownerId, related) {
@@ -498,6 +476,50 @@ function finalizeJoinModelClass(ctx) {
498
476
  return ctx;
499
477
  }
500
478
 
479
+ // The modify operations need dialect specific code to only modify the join
480
+ // rows that match the filters, see the `ManyToMany*ModifyMixin` classes.
481
+ function getOperationClass(builder, { sqlite, mysql, postgres, other }) {
482
+ const knex = builder.knex();
483
+
484
+ if (isSqlite(knex)) {
485
+ return sqlite;
486
+ } else if (isMySql(knex)) {
487
+ return mysql;
488
+ } else if (isPostgres(knex)) {
489
+ return postgres;
490
+ } else {
491
+ return other;
492
+ }
493
+ }
494
+
495
+ function getUpdateOperationClass(builder) {
496
+ return getOperationClass(builder, {
497
+ sqlite: ManyToManyUpdateSqliteOperation,
498
+ mysql: ManyToManyUpdateMySqlOperation,
499
+ postgres: ManyToManyUpdatePostgresOperation,
500
+ other: ManyToManyUpdateOperation,
501
+ });
502
+ }
503
+
504
+ function getUnrelateOperationClass(builder) {
505
+ return getOperationClass(builder, {
506
+ sqlite: ManyToManyUnrelateSqliteOperation,
507
+ mysql: ManyToManyUnrelateMySqlOperation,
508
+ postgres: ManyToManyUnrelatePostgresOperation,
509
+ other: ManyToManyUnrelateOperation,
510
+ });
511
+ }
512
+
513
+ function getDeleteOperationClass(builder) {
514
+ // Deletes don't modify the join table, so only sqlite needs its own operation.
515
+ return getOperationClass(builder, {
516
+ sqlite: ManyToManyDeleteSqliteOperation,
517
+ mysql: ManyToManyDeleteOperation,
518
+ postgres: ManyToManyDeleteOperation,
519
+ other: ManyToManyDeleteOperation,
520
+ });
521
+ }
522
+
501
523
  module.exports = {
502
524
  ManyToManyRelation,
503
525
  };
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const { ManyToManyUnrelateOperationBase } = require('./ManyToManyUnrelateOperationBase');
4
+ const { ManyToManyMySqlModifyMixin } = require('../ManyToManyMySqlModifyMixin');
5
+
6
+ class ManyToManyUnrelateMySqlOperation extends ManyToManyMySqlModifyMixin(
7
+ ManyToManyUnrelateOperationBase,
8
+ ) {
9
+ get modifyMainQuery() {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ module.exports = {
15
+ ManyToManyUnrelateMySqlOperation,
16
+ };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { UnrelateOperation } = require('../../../queryBuilder/operations/UnrelateOperation');
4
+ const { addWhereGroup } = require('../../../queryBuilder/whereGroups');
4
5
 
5
6
  class ManyToManyUnrelateOperationBase extends UnrelateOperation {
6
7
  queryExecutor(builder) {
@@ -10,7 +11,10 @@ class ManyToManyUnrelateOperationBase extends UnrelateOperation {
10
11
  .childQueryOf(builder)
11
12
  .delete();
12
13
 
13
- return this.applyModifyFilterForJoinTable(unrelateQuery).modify(this.relation.joinTableModify);
14
+ addWhereGroup(unrelateQuery, () => this.applyModifyFilterForJoinTable(unrelateQuery));
15
+ addWhereGroup(unrelateQuery, () => unrelateQuery.modify(this.relation.joinTableModify));
16
+
17
+ return unrelateQuery;
14
18
  }
15
19
 
16
20
  /* istanbul ignore next */
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const { ManyToManyUnrelateOperationBase } = require('./ManyToManyUnrelateOperationBase');
4
+ const { ManyToManyPostgresModifyMixin } = require('../ManyToManyPostgresModifyMixin');
5
+
6
+ class ManyToManyUnrelatePostgresOperation extends ManyToManyPostgresModifyMixin(
7
+ ManyToManyUnrelateOperationBase,
8
+ ) {
9
+ get modifyMainQuery() {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ module.exports = {
15
+ ManyToManyUnrelatePostgresOperation,
16
+ };
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const { ManyToManyUpdateOperationBase } = require('./ManyToManyUpdateOperationBase');
4
+ const { ManyToManyMySqlModifyMixin } = require('../ManyToManyMySqlModifyMixin');
5
+
6
+ class ManyToManyUpdateMySqlOperation extends ManyToManyMySqlModifyMixin(
7
+ ManyToManyUpdateOperationBase,
8
+ ) {}
9
+
10
+ module.exports = {
11
+ ManyToManyUpdateMySqlOperation,
12
+ };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { UpdateOperation } = require('../../../queryBuilder/operations/UpdateOperation');
4
+ const { addWhereGroup } = require('../../../queryBuilder/whereGroups');
4
5
 
5
6
  class ManyToManyUpdateOperationBase extends UpdateOperation {
6
7
  constructor(name, opt) {
@@ -44,9 +45,14 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
44
45
  .childQueryOf(builder)
45
46
  .patch(this.joinTablePatch);
46
47
 
47
- await this.applyModifyFilterForJoinTable(joinTableUpdateQuery).modify(
48
- this.relation.joinTableModify,
48
+ addWhereGroup(joinTableUpdateQuery, () =>
49
+ this.applyModifyFilterForJoinTable(joinTableUpdateQuery),
49
50
  );
51
+ addWhereGroup(joinTableUpdateQuery, () =>
52
+ joinTableUpdateQuery.modify(this.relation.joinTableModify),
53
+ );
54
+
55
+ await joinTableUpdateQuery;
50
56
  return result;
51
57
  } else {
52
58
  return result;
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const { ManyToManyUpdateOperationBase } = require('./ManyToManyUpdateOperationBase');
4
+ const { ManyToManyPostgresModifyMixin } = require('../ManyToManyPostgresModifyMixin');
5
+
6
+ class ManyToManyUpdatePostgresOperation extends ManyToManyPostgresModifyMixin(
7
+ ManyToManyUpdateOperationBase,
8
+ ) {}
9
+
10
+ module.exports = {
11
+ ManyToManyUpdatePostgresOperation,
12
+ };
@@ -10,6 +10,12 @@ function deprecate(message) {
10
10
  }
11
11
  }
12
12
 
13
+ // Only meant for tests that need to see a deprecation message again.
14
+ function resetDeprecations() {
15
+ LOGGED_DEPRECATIONS.clear();
16
+ }
17
+
13
18
  module.exports = {
14
19
  deprecate,
20
+ resetDeprecations,
15
21
  };
@@ -168,10 +168,24 @@ function keyMapper(mapper) {
168
168
  };
169
169
  }
170
170
 
171
+ // Returns a function that only calls `mapper` for the part of the input string
172
+ // before the first `separator`, and leaves the rest untouched. For field
173
+ // expressions like `jsonColumn:some.jsonPath` this maps the column but keeps
174
+ // the json path as is. Column names can't contain `:`, so the first one marks
175
+ // the boundary.
176
+ function mapFirstPart(mapper, separator) {
177
+ return (str) => {
178
+ const idx = str.indexOf(separator);
179
+ return idx === -1 ? mapper(str) : mapper(str.slice(0, idx)) + str.slice(idx);
180
+ };
181
+ }
182
+
171
183
  function snakeCaseMappers(opt = {}) {
184
+ const format = (str) => snakeCase(str, opt);
185
+
172
186
  return {
173
187
  parse: keyMapper(memoize((str) => camelCase(str, opt))),
174
- format: keyMapper(memoize((str) => snakeCase(str, opt))),
188
+ format: keyMapper(memoize(opt.preserveJsonKeys ? mapFirstPart(format, ':') : format)),
175
189
  };
176
190
  }
177
191
 
@@ -46,6 +46,34 @@ function freezeParsedExpr(parsedExpr) {
46
46
  return parsedExpr;
47
47
  }
48
48
 
49
+ // Formats the json path of a parsed field expression as a Postgres text array
50
+ // literal, e.g. `{a,"",b}`. Keys that would otherwise be misread (empty keys,
51
+ // keys with whitespace, delimiters, quotes or backslashes and `NULL`) are
52
+ // double-quoted, with quotes and backslashes escaped.
53
+ function toPostgresJsonPath(access) {
54
+ const keys = access.map(({ ref }) => {
55
+ const key = String(ref);
56
+ return key === '' || /[{}",\\\s]/.test(key) || key.toUpperCase() === 'NULL'
57
+ ? `"${key.replace(/["\\]/g, '\\$&')}"`
58
+ : key;
59
+ });
60
+ return `{${keys.join(',')}}`;
61
+ }
62
+
63
+ // Formats the json path of a parsed field expression as a quoted string literal
64
+ // to be used in raw knex SQL, e.g. `'{a,b}'`. The path can't be passed as a
65
+ // binding, as knex doesn't keep the order of bindings in raw columns (e.g. in
66
+ // `whereIn(ref(...), values)`), so the keys need to be escaped instead: single
67
+ // quotes are doubled, and `?` is escaped so that knex doesn't read it as a
68
+ // binding placeholder. Backslashes are always doubled by `toPostgresJsonPath()`,
69
+ // so they can't escape the closing quote either.
70
+ function toPostgresJsonPathLiteral(access) {
71
+ const path = toPostgresJsonPath(access).replace(/'/g, "''").replace(/\?/g, '\\?');
72
+ return `'${path}'`;
73
+ }
74
+
49
75
  module.exports = {
50
76
  parseFieldExpression,
77
+ toPostgresJsonPath,
78
+ toPostgresJsonPathLiteral,
51
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "objection",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "An SQL-friendly ORM for Node.js",
5
5
  "main": "lib/objection.js",
6
6
  "license": "MIT",
@@ -10,8 +10,9 @@
10
10
  "test:typings": "tsc && prettier --check \"typings/**/*.ts\" \"tests/ts/**/*.ts\"",
11
11
  "prettier": "prettier --write \"{examples,lib,tests,testUtils,typings,doc}/**/*.{js,ts}\"",
12
12
  "eslint": "eslint --format codeframe \"examples/**/*.js\" \"lib/**/*.js\" \"tests/**/*.js\"",
13
- "docs:dev": "vuepress dev doc",
14
- "docs:build": "vuepress build doc"
13
+ "docs:dev": "vitepress dev doc",
14
+ "docs:build": "vitepress build doc",
15
+ "docs:preview": "vitepress preview doc"
15
16
  },
16
17
  "publishConfig": {
17
18
  "tag": "latest"
@@ -35,7 +36,7 @@
35
36
  "bugs": {
36
37
  "url": "https://github.com/ditojs/objection/issues"
37
38
  },
38
- "homepage": "https://github.com/ditojs/objection",
39
+ "homepage": "https://ditojs.github.io/objection/",
39
40
  "engines": {
40
41
  "node": ">=14.0.0"
41
42
  },
@@ -66,19 +67,22 @@
66
67
  },
67
68
  "devDependencies": {
68
69
  "@types/node": "^22.15.3",
70
+ "bluebird": "^3.7.2",
69
71
  "chai": "^4.3.10",
70
72
  "chai-subset": "^1.6.0",
71
73
  "eslint": "^8.54.0",
72
74
  "eslint-formatter-codeframe": "^7.32.1",
73
75
  "eslint-plugin-prettier": "^5.3.1",
74
76
  "expect.js": "^0.3.1",
75
- "knex": "^3.1.0",
77
+ "knex": "^3.3.0",
78
+ "lodash": "^4.18.1",
76
79
  "mocha": "^11.2.2",
77
80
  "mysql": "^2.18.1",
78
81
  "pg": "^8.15.6",
79
82
  "prettier": "3.9.9",
80
83
  "sqlite3": "^5.1.7",
81
84
  "typescript": ">=5.8.3",
82
- "vuepress": "1.9.10"
85
+ "uuid": "^9.0.1",
86
+ "vitepress": "1.6.4"
83
87
  }
84
88
  }