objection 4.0.0-rc.2 → 4.0.0-rc.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.
@@ -17,6 +17,12 @@ export class JsonDialect {
17
17
  return sql;
18
18
  }
19
19
 
20
+ // The value that the path updates of a column start from, an empty one where
21
+ // the column is NULL, as `set()` leaves NULL as it is.
22
+ setBase(sql, access) {
23
+ return `COALESCE(${sql}, '${access[0].type === 'array' ? '[]' : '{}'}')`;
24
+ }
25
+
20
26
  // Reference the bare column if there is no path, so that the database can use
21
27
  // its indexes.
22
28
  field(sql, access) {
@@ -70,8 +70,11 @@ export class UpdateOperation extends QueryBuilderOperation {
70
70
  if (this.modelOptions.patch) {
71
71
  return knexBuilder.update(convertFieldExpressionsToRaw(builder, json));
72
72
  } else {
73
- checkFieldExpressions(builder.modelClass(), this.model, 'update');
74
- return knexBuilder.update(json);
73
+ const { fieldExpressionKeys } = builder.internalOptions();
74
+ checkFieldExpressions(builder.modelClass(), this.model, 'update', fieldExpressionKeys);
75
+ return knexBuilder.update(
76
+ fieldExpressionKeys.length > 0 ? convertFieldExpressionsToRaw(builder, json) : json,
77
+ );
75
78
  }
76
79
  }
77
80
 
@@ -7,13 +7,14 @@ import { ValidationErrorType } from '../../model/ValidationError.js';
7
7
  import { getQueryPropKeys } from '../../model/modelQueryProps.js';
8
8
 
9
9
  // Inserts and updates write whole values, so they can't write into json
10
- // columns with field expressions like `'meta:color'`. Keys with a ':' that don't
11
- // parse as field expressions are columns, as in the query properties.
12
- export function checkFieldExpressions(modelClass, model, operation) {
10
+ // columns with field expressions like `'meta:color'`, except for the
11
+ // `allowedKeys` of internal writes. Keys with a ':' that don't parse as field
12
+ // expressions are columns, as in the query properties.
13
+ export function checkFieldExpressions(modelClass, model, operation, allowedKeys = []) {
13
14
  const keys = getFieldExpressionKeys(modelClass, [
14
15
  ...Object.keys(model),
15
16
  ...getQueryPropKeys(model),
16
- ]);
17
+ ]).filter((key) => !allowedKeys.includes(key));
17
18
 
18
19
  if (keys.length > 0) {
19
20
  throw modelClass.createValidationError({
@@ -48,7 +49,7 @@ export function convertFieldExpressionsToRaw(builder, json) {
48
49
 
49
50
  if (getFieldExpressionColumn(key, declaredColumns) !== null) {
50
51
  // 'col:attr' : ref('other:lol') is transformed to
51
- // "col" : raw(`jsonb_set("table"."col", '{attr}', to_jsonb("other"#>'{lol}'), true)`)
52
+ // "col" : raw(`jsonb_set(COALESCE("table"."col", '{}'), '{attr}', to_jsonb("other"#>'{lol}'), true)`)
52
53
  // on Postgres, with the column qualified, as it is ambiguous with
53
54
  // `excluded` in merge().
54
55
  dialect.checkSupport();
@@ -82,9 +83,11 @@ export function convertFieldExpressionsToRaw(builder, json) {
82
83
  val = JSON.stringify(val);
83
84
  }
84
85
 
86
+ const { access } = parsed.parsedExpr;
87
+ const previous = convertedJson[parsed.column];
85
88
  convertedJson[parsed.column] = knex.raw(
86
- dialect.set('??', parsed.parsedExpr.access, valuePlaceholder),
87
- [convertedJson[parsed.column] || `${builder.tableRef()}.${parsed.column}`, val],
89
+ dialect.set(previous ? '??' : dialect.setBase('??', access), access, valuePlaceholder),
90
+ [previous || `${builder.tableRef()}.${parsed.column}`, val],
88
91
  );
89
92
  } else {
90
93
  convertedJson[key] = val;
@@ -126,6 +126,12 @@ export class RelationProperty {
126
126
  return this._propSetters[index](obj, value);
127
127
  }
128
128
 
129
+ // Returns true if the index:th column is a key at the top level of a json
130
+ // column.
131
+ isJsonKey(index) {
132
+ return this._refs[index].parsedExpr.access.length === 1;
133
+ }
134
+
129
135
  // Returns an instance of ReferenceBuilder that points to the index:th
130
136
  // value of a row.
131
137
  ref(builder, index) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "objection",
3
- "version": "4.0.0-rc.2",
3
+ "version": "4.0.0-rc.4",
4
4
  "description": "An SQL-friendly ORM for Node.js",
5
5
  "type": "module",
6
6
  "main": "lib/objection.js",
@@ -17,7 +17,7 @@
17
17
  "test": "pnpm eslint && vitest run && pnpm test:typings",
18
18
  "test:fast": "vitest run --bail 1",
19
19
  "test:watch": "vitest",
20
- "test:typings": "tsc && tsc -p tests/ts/strict && tsc -p tests/bench/typings && prettier --check \"typings/**/*.ts\" \"tests/ts/**/*.ts\" \"tests/bench/typings/app/*.ts\"",
20
+ "test:typings": "tsc && tsc -p tests/ts/strict && tsc -p tests/bench/typings && node tests/ts/completions.js && prettier --check \"typings/**/*.ts\" \"tests/ts/**/*.ts\" \"tests/bench/typings/*.ts\" \"tests/bench/typings/app/*.ts\"",
21
21
  "bench:typings": "node tests/bench/typings/bench.js",
22
22
  "db:up": "docker compose up -d --wait",
23
23
  "db:down": "docker compose --profile mssql down -v",
@@ -91,6 +91,7 @@
91
91
  "sqlite3": "^6.0.1",
92
92
  "tedious": "^20.3.7",
93
93
  "typescript": "^7.0.0",
94
+ "typescript-5": "npm:typescript@5.8.3",
94
95
  "vitepress": "1.6.4",
95
96
  "vitest": "^4.1.11"
96
97
  }