remult 3.4.0-next.7 → 3.4.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.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CustomSqlFilterBuilder = exports.FilterConsumerBridgeToSqlRequest = void 0;
3
+ exports.CustomSqlFilterBuilder = exports.FilterConsumerBridgeToSqlRequest = exports.LIKE_ESCAPE_SQL = void 0;
4
+ exports.escapeLike = escapeLike;
4
5
  exports.isDbReadonly = isDbReadonly;
5
6
  exports.shouldNotCreateField = shouldNotCreateField;
6
7
  exports.shouldCreateEntity = shouldCreateEntity;
@@ -11,12 +12,24 @@ var tslib_1 = require("tslib");
11
12
  var sql_database_js_1 = require("../data-providers/sql-database.js");
12
13
  var remult_proxy_js_1 = require("../remult-proxy.js");
13
14
  var RepositoryImplementation_js_1 = require("../remult3/RepositoryImplementation.js");
15
+ var valueConverters_js_1 = require("../valueConverters.js");
14
16
  var entityDbName_js_1 = require("./entityDbName.js");
15
17
  var fieldDbName_js_1 = require("./fieldDbName.js");
18
+ // `[` starts a character class in sql server's like. `]` on its own is a literal
19
+ // everywhere, so escaping the opening bracket is enough. The other engines treat
20
+ // an escaped non-wildcard as that literal, so one escape set works for all of them.
21
+ var REGEX_LIKE_SPECIAL = /[!%_\[]/g;
22
+ // `!` not `\`: a backslash in a string literal is an escape in mysql and in pg without standard_conforming_strings
23
+ exports.LIKE_ESCAPE_SQL = " escape '!'";
24
+ function escapeLike(val) {
25
+ return val.replace(REGEX_LIKE_SPECIAL, '!$&');
26
+ }
16
27
  var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
17
- function FilterConsumerBridgeToSqlRequest(r, nameProvider) {
28
+ function FilterConsumerBridgeToSqlRequest(r, nameProvider, jsonColumnStorage) {
29
+ if (jsonColumnStorage === void 0) { jsonColumnStorage = 'native'; }
18
30
  this.r = r;
19
31
  this.nameProvider = nameProvider;
32
+ this.jsonColumnStorage = jsonColumnStorage;
20
33
  this.where = '';
21
34
  this._addWhere = true;
22
35
  this.promises = [];
@@ -81,7 +94,7 @@ var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
81
94
  case 1:
82
95
  if (!!orElements_1_1.done) return [3 /*break*/, 4];
83
96
  element = orElements_1_1.value;
84
- f = new FilterConsumerBridgeToSqlRequest(this.r, this.nameProvider);
97
+ f = new FilterConsumerBridgeToSqlRequest(this.r, this.nameProvider, this.jsonColumnStorage);
85
98
  f._addWhere = false;
86
99
  element.__applyToConsumer(f);
87
100
  return [4 /*yield*/, f.resolveWhere()];
@@ -128,7 +141,7 @@ var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
128
141
  return tslib_1.__generator(this, function (_a) {
129
142
  switch (_a.label) {
130
143
  case 0:
131
- f = new FilterConsumerBridgeToSqlRequest(this.r, this.nameProvider);
144
+ f = new FilterConsumerBridgeToSqlRequest(this.r, this.nameProvider, this.jsonColumnStorage);
132
145
  f._addWhere = false;
133
146
  element.__applyToConsumer(f);
134
147
  return [4 /*yield*/, f.resolveWhere()];
@@ -163,7 +176,9 @@ var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
163
176
  this.addToWhere(this.nameProvider.$dbNameOf(col) +
164
177
  ' in (' +
165
178
  val
166
- .map(function (x) { return toDbSql(_this.r, col, col.valueConverter.toDb(x)); })
179
+ .map(function (x) {
180
+ return toDbSql(_this.r, col, (0, valueConverters_js_1.toDbColumn)(col, x, _this.jsonColumnStorage));
181
+ })
167
182
  .join(',') +
168
183
  ')');
169
184
  else
@@ -191,53 +206,29 @@ var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
191
206
  this.add(col, val, '<');
192
207
  };
193
208
  FilterConsumerBridgeToSqlRequest.prototype.containsCaseInsensitive = function (col, val) {
194
- var _this = this;
195
- this.promises.push((function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
196
- return tslib_1.__generator(this, function (_a) {
197
- this.addToWhere('lower (' +
198
- this.nameProvider.$dbNameOf(col) +
199
- ") like lower ('%" +
200
- val.replace(/'/g, "''") +
201
- "%')");
202
- return [2 /*return*/];
203
- });
204
- }); })());
209
+ this.like(col, '%' + escapeLike(val) + '%');
205
210
  };
206
211
  FilterConsumerBridgeToSqlRequest.prototype.notContainsCaseInsensitive = function (col, val) {
207
- var _this = this;
208
- this.promises.push((function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
209
- return tslib_1.__generator(this, function (_a) {
210
- this.addToWhere('not lower (' +
211
- this.nameProvider.$dbNameOf(col) +
212
- ") like lower ('%" +
213
- val.replace(/'/g, "''") +
214
- "%')");
215
- return [2 /*return*/];
216
- });
217
- }); })());
212
+ this.like(col, '%' + escapeLike(val) + '%', 'not ');
218
213
  };
219
214
  FilterConsumerBridgeToSqlRequest.prototype.startsWithCaseInsensitive = function (col, val) {
220
- var _this = this;
221
- this.promises.push((function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
222
- return tslib_1.__generator(this, function (_a) {
223
- this.addToWhere('lower (' +
224
- this.nameProvider.$dbNameOf(col) +
225
- ") like lower ('" +
226
- val.replace(/'/g, "''") +
227
- "%')");
228
- return [2 /*return*/];
229
- });
230
- }); })());
215
+ this.like(col, escapeLike(val) + '%');
231
216
  };
232
217
  FilterConsumerBridgeToSqlRequest.prototype.endsWithCaseInsensitive = function (col, val) {
218
+ this.like(col, '%' + escapeLike(val));
219
+ };
220
+ FilterConsumerBridgeToSqlRequest.prototype.like = function (col, pattern, not) {
233
221
  var _this = this;
222
+ if (not === void 0) { not = ''; }
234
223
  this.promises.push((function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
235
224
  return tslib_1.__generator(this, function (_a) {
236
- this.addToWhere('lower (' +
225
+ this.addToWhere(not +
226
+ 'lower (' +
237
227
  this.nameProvider.$dbNameOf(col) +
238
- ") like lower ('%" +
239
- val.replace(/'/g, "''") +
240
- "')");
228
+ ') like lower (' +
229
+ this.r.param(pattern) +
230
+ ')' +
231
+ exports.LIKE_ESCAPE_SQL);
241
232
  return [2 /*return*/];
242
233
  });
243
234
  }); })());
@@ -251,7 +242,7 @@ var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
251
242
  ' ' +
252
243
  operator +
253
244
  ' ' +
254
- toDbSql(this.r, col, col.valueConverter.toDb(val));
245
+ toDbSql(this.r, col, (0, valueConverters_js_1.toDbColumn)(col, val, this.jsonColumnStorage));
255
246
  this.addToWhere(x);
256
247
  return [2 /*return*/];
257
248
  });
@@ -274,7 +265,7 @@ var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
274
265
  switch (_a.label) {
275
266
  case 0:
276
267
  if (!(databaseCustom === null || databaseCustom === void 0 ? void 0 : databaseCustom.buildSql)) return [3 /*break*/, 2];
277
- item = new CustomSqlFilterBuilder(this.r, this.nameProvider.wrapIdentifier);
268
+ item = new CustomSqlFilterBuilder(this.r, this.nameProvider.wrapIdentifier, this.jsonColumnStorage);
278
269
  return [4 /*yield*/, databaseCustom.buildSql(item)];
279
270
  case 1:
280
271
  sql = _a.sent();
@@ -296,10 +287,12 @@ exports.FilterConsumerBridgeToSqlRequest = FilterConsumerBridgeToSqlRequest;
296
287
  * Represents a custom SQL filter builder.
297
288
  */
298
289
  var CustomSqlFilterBuilder = /** @class */ (function () {
299
- function CustomSqlFilterBuilder(r, wrapIdentifier) {
290
+ function CustomSqlFilterBuilder(r, wrapIdentifier, jsonColumnStorage) {
291
+ if (jsonColumnStorage === void 0) { jsonColumnStorage = 'native'; }
300
292
  var _this = this;
301
293
  this.r = r;
302
294
  this.wrapIdentifier = wrapIdentifier;
295
+ this.jsonColumnStorage = jsonColumnStorage;
303
296
  this.sql = '';
304
297
  /**
305
298
  * Adds a parameter value.
@@ -308,8 +301,8 @@ var CustomSqlFilterBuilder = /** @class */ (function () {
308
301
  * @returns {string} - The SQL token.
309
302
  */
310
303
  this.param = function (val, field) {
311
- if (typeof field === 'object' && field.valueConverter.toDb) {
312
- val = field.valueConverter.toDb(val);
304
+ if (field) {
305
+ val = (0, valueConverters_js_1.toDbColumn)(field, val, _this.jsonColumnStorage);
313
306
  }
314
307
  return _this.r.param(val);
315
308
  };
@@ -0,0 +1,3 @@
1
+ /** Return this from {@link walkFilter}'s visitor to drop that field key. */
2
+ export declare const omitFilterField: unique symbol;
3
+ export declare function walkFilter(filter: unknown, visit: (key: string, value: unknown) => unknown): unknown;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.omitFilterField = void 0;
4
+ exports.walkFilter = walkFilter;
5
+ var tslib_1 = require("tslib");
6
+ /** Return this from {@link walkFilter}'s visitor to drop that field key. */
7
+ exports.omitFilterField = Symbol('omitFilterField');
8
+ function isCompoundFilterKey(key) {
9
+ return key === '$and' || key === '$or' || key === '$not';
10
+ }
11
+ // `$and`, `$or`, and `$not` are entered. Every other key is a field, passed to `visit`.
12
+ // `visit` returns the value to keep, or `omitFilterField` to drop the key.
13
+ function walkFilter(filter, visit) {
14
+ var e_1, _a, e_2, _b;
15
+ if (Array.isArray(filter)) {
16
+ var result_1 = [];
17
+ try {
18
+ for (var filter_1 = tslib_1.__values(filter), filter_1_1 = filter_1.next(); !filter_1_1.done; filter_1_1 = filter_1.next()) {
19
+ var item = filter_1_1.value;
20
+ result_1.push(walkFilter(item, visit));
21
+ }
22
+ }
23
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
24
+ finally {
25
+ try {
26
+ if (filter_1_1 && !filter_1_1.done && (_a = filter_1.return)) _a.call(filter_1);
27
+ }
28
+ finally { if (e_1) throw e_1.error; }
29
+ }
30
+ return result_1;
31
+ }
32
+ if (!filter || typeof filter !== 'object')
33
+ return filter;
34
+ var result = {};
35
+ try {
36
+ for (var _c = tslib_1.__values(Object.keys(filter)), _d = _c.next(); !_d.done; _d = _c.next()) {
37
+ var key = _d.value;
38
+ var value = filter[key];
39
+ if (isCompoundFilterKey(key)) {
40
+ result[key] = walkFilter(value, visit);
41
+ continue;
42
+ }
43
+ var next = visit(key, value);
44
+ if (next !== exports.omitFilterField)
45
+ result[key] = next;
46
+ }
47
+ }
48
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
49
+ finally {
50
+ try {
51
+ if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
52
+ }
53
+ finally { if (e_2) throw e_2.error; }
54
+ }
55
+ return result;
56
+ }
@@ -112,16 +112,11 @@ var RemultProxy = /** @class */ (function () {
112
112
  .repo.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(args), false)))
113
113
  .updateMany.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(args2), false));
114
114
  },
115
- _a.find = function () {
116
- var _a, _b;
117
- var args2 = [];
118
- for (var _i = 0; _i < arguments.length; _i++) {
119
- args2[_i] = arguments[_i];
120
- }
121
- return (_a = (_b = self
115
+ _a.find = function (options) {
116
+ var _a;
117
+ return (_a = self
122
118
  .remultFactory())
123
- .repo.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(args), false)))
124
- .find.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(args2), false));
119
+ .repo.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(args), false)).find(options);
125
120
  },
126
121
  _a.groupBy = function () {
127
122
  var _a, _b;
@@ -145,27 +140,17 @@ var RemultProxy = /** @class */ (function () {
145
140
  .repo.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(args), false)))
146
141
  .aggregate.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(args2), false));
147
142
  },
148
- _a.findFirst = function () {
149
- var _a, _b;
150
- var args2 = [];
151
- for (var _i = 0; _i < arguments.length; _i++) {
152
- args2[_i] = arguments[_i];
153
- }
154
- return (_a = (_b = self
143
+ _a.findFirst = function (where, options) {
144
+ var _a;
145
+ return (_a = self
155
146
  .remultFactory())
156
- .repo.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(args), false)))
157
- .findFirst.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(args2), false));
147
+ .repo.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(args), false)).findFirst(where, options);
158
148
  },
159
- _a.findOne = function () {
160
- var _a, _b;
161
- var args2 = [];
162
- for (var _i = 0; _i < arguments.length; _i++) {
163
- args2[_i] = arguments[_i];
164
- }
165
- return (_a = (_b = self
149
+ _a.findOne = function (options) {
150
+ var _a;
151
+ return (_a = self
166
152
  .remultFactory())
167
- .repo.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(args), false)))
168
- .findOne.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(args2), false));
153
+ .repo.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(args), false)).findOne(options);
169
154
  },
170
155
  _a.findId = function (a, b) {
171
156
  var _a;
@@ -106,6 +106,7 @@ declare abstract class rowHelperBase<T> {
106
106
  * must not be serialized by {@link toApiJson}. */
107
107
  _fieldsExcludedBySelect?: Set<string>;
108
108
  constructor(fieldsMetadata: FieldMetadata[], instance: T, remult: Remult, isNewRow: boolean, excludeRelationMembers?: Set<string>);
109
+ _skipRelationFieldSync: boolean;
109
110
  _subscribers?: SubscribableImp;
110
111
  subscribe(listener: RefSubscriber): Unsubscribe;
111
112
  _isLoading: boolean;
@@ -302,7 +303,7 @@ export declare function isAutoIncrement(f: FieldMetadata): boolean;
302
303
  export declare function ValueListFieldType<valueType extends ValueListItem = ValueListItem>(...options: (ValueListFieldOptions<any, valueType> | ((options: FieldOptions<any, valueType>, remult: Remult) => void))[]): (type: ClassType<valueType>, context?: any) => void;
303
304
  export interface ValueListFieldOptions<entityType, valueType> extends FieldOptions<entityType, valueType> {
304
305
  getValues?: () => valueType[];
305
- /** Value used when a stored id is not in the list. A value, or `(id) => value`. @default undefined */
306
+ /** Value used when a stored id is not in the list. A value, or `(id) => value`. Only `null` and `undefined` stay `undefined`; a blank `''` uses this, which is the usual legacy column. A declared id of `''` is unaffected. @default undefined */
306
307
  unknownIdValue?: valueType | ((id: any) => valueType | undefined);
307
308
  }
308
309
  export declare class ValueListInfo<T extends ValueListItem> implements ValueConverter<T> {
@@ -314,7 +315,8 @@ export declare class ValueListInfo<T extends ValueListItem> implements ValueConv
314
315
  private unknownIdValue?;
315
316
  private constructor();
316
317
  getValues(): T[];
317
- byId(key: any): T | undefined;
318
+ /** Declared id only. Pass `useUnknownIdValue` to also apply `unknownIdValue` (`null` and `undefined` still return `undefined`). */
319
+ byId(key: any, useUnknownIdValue?: boolean): T | undefined;
318
320
  fromJson(val: any): T;
319
321
  toJson(val: T): any;
320
322
  fromDb(val: any): T;
@@ -57,6 +57,18 @@ var classValidatorValidate = undefined;
57
57
  // })
58
58
  // .catch(() => {
59
59
  // });
60
+ // Assigning a `toOne` also writes its `field`/`fields`, which would wipe the FK of a row whose
61
+ // target is missing.
62
+ function setMissingRelationToNull(row, key) {
63
+ var ref = (0, getEntityRef_js_1.getEntityRef)(row);
64
+ ref._skipRelationFieldSync = true;
65
+ try {
66
+ row[key] = null;
67
+ }
68
+ finally {
69
+ ref._skipRelationFieldSync = false;
70
+ }
71
+ }
60
72
  var RepositoryImplementation = /** @class */ (function () {
61
73
  function RepositoryImplementation(_entity, _remult, _dataProvider, _info, _defaultFindOptions) {
62
74
  this._entity = _entity;
@@ -954,7 +966,7 @@ var RepositoryImplementation = /** @class */ (function () {
954
966
  };
955
967
  RepositoryImplementation.prototype._rawFind = function (options_1) {
956
968
  return tslib_1.__awaiter(this, arguments, void 0, function (options, skipOrderByAndLimit, loader, actualFind) {
957
- var opt, rawRows, result;
969
+ var findOptions, opt, rawRows, result;
958
970
  if (skipOrderByAndLimit === void 0) { skipOrderByAndLimit = false; }
959
971
  return tslib_1.__generator(this, function (_a) {
960
972
  switch (_a.label) {
@@ -964,7 +976,13 @@ var RepositoryImplementation = /** @class */ (function () {
964
976
  if (this._defaultFindOptions) {
965
977
  options = tslib_1.__assign(tslib_1.__assign({}, this._defaultFindOptions), options);
966
978
  }
967
- return [4 /*yield*/, this._buildEntityDataProviderFindOptions(options)];
979
+ findOptions = options;
980
+ if ((!findOptions.orderBy || Object.keys(findOptions.orderBy).length === 0) &&
981
+ !this._dataProvider.isProxy &&
982
+ this._info.entityInfo.defaultOrderBy) {
983
+ findOptions = tslib_1.__assign(tslib_1.__assign({}, findOptions), { orderBy: this._info.entityInfo.defaultOrderBy });
984
+ }
985
+ return [4 /*yield*/, this._buildEntityDataProviderFindOptions(findOptions)];
968
986
  case 1:
969
987
  opt = _a.sent();
970
988
  if (skipOrderByAndLimit) {
@@ -1029,10 +1047,6 @@ var RepositoryImplementation = /** @class */ (function () {
1029
1047
  return f.key;
1030
1048
  });
1031
1049
  }
1032
- if (!options.orderBy || Object.keys(options.orderBy).length === 0) {
1033
- if (!this._dataProvider.isProxy)
1034
- options.orderBy = this._info.entityInfo.defaultOrderBy;
1035
- }
1036
1050
  _a = opt;
1037
1051
  return [4 /*yield*/, this._translateWhereToFilter(options.where)];
1038
1052
  case 1:
@@ -1225,10 +1239,12 @@ var RepositoryImplementation = /** @class */ (function () {
1225
1239
  var _loop_2 = function (row) {
1226
1240
  var _e = this_1._findOptionsBasedOnRelation(rel, col, incl, row, otherRepo), findOptions = _e.findOptions, returnNull = _e.returnNull, returnUndefined = _e.returnUndefined;
1227
1241
  var colKey = col.key;
1228
- if (returnNull)
1229
- row[colKey] = null;
1230
- else if (returnUndefined)
1231
- row[colKey] = undefined;
1242
+ if (returnNull || returnUndefined) {
1243
+ if (rel.type === 'toMany')
1244
+ row[colKey] = (returnNull ? null : undefined);
1245
+ else
1246
+ setMissingRelationToNull(row, colKey);
1247
+ }
1232
1248
  else {
1233
1249
  var entityType = rel.toEntity;
1234
1250
  var toRepo_1 = otherRepo;
@@ -1243,14 +1259,12 @@ var RepositoryImplementation = /** @class */ (function () {
1243
1259
  metadata: toRepo_1.metadata,
1244
1260
  }, findOptions)
1245
1261
  .then(function (result) {
1246
- if (result.length == 0 && rel.type == 'toOne')
1247
- return;
1248
- row[colKey] =
1249
- rel.type !== 'toMany'
1250
- ? result.length == 0
1251
- ? null
1252
- : result[0]
1253
- : result;
1262
+ if (rel.type === 'toMany')
1263
+ row[colKey] = result;
1264
+ else if (result.length == 0)
1265
+ setMissingRelationToNull(row, colKey);
1266
+ else
1267
+ row[colKey] = result[0];
1254
1268
  });
1255
1269
  }
1256
1270
  };
@@ -1894,6 +1908,7 @@ var rowHelperBase = /** @class */ (function () {
1894
1908
  this.instance = instance;
1895
1909
  this.remult = remult;
1896
1910
  this.isNewRow = isNewRow;
1911
+ this._skipRelationFieldSync = false;
1897
1912
  this._isLoading = false;
1898
1913
  this.lookups = new Map();
1899
1914
  this.originalValues = {};
@@ -1953,7 +1968,7 @@ var rowHelperBase = /** @class */ (function () {
1953
1968
  },
1954
1969
  set: function (newVal) {
1955
1970
  val_1 = newVal;
1956
- if (newVal === undefined)
1971
+ if (newVal === undefined || _this._skipRelationFieldSync)
1957
1972
  return;
1958
1973
  var op = col.options;
1959
1974
  if (op.field) {
@@ -2804,7 +2819,9 @@ var rowHelperImplementation = /** @class */ (function (_super) {
2804
2819
  finally { if (e_41) throw e_41.error; }
2805
2820
  return [7 /*endfinally*/];
2806
2821
  case 12:
2807
- this.repo._remult.liveQueryPublisher.itemChanged(this.repo.metadata.key, [{ id: this.getId(), oldId: this.getOriginalId(), deleted: false }]);
2822
+ this.repo._remult.liveQueryPublisher.itemChanged(this.repo.metadata.key, [
2823
+ { id: this.getId(), oldId: this.getOriginalId(), deleted: false },
2824
+ ]);
2808
2825
  this.saveOriginalData();
2809
2826
  this._isNew = false;
2810
2827
  return [2 /*return*/, this.instance];
@@ -4045,21 +4062,22 @@ var ValueListInfo = /** @class */ (function () {
4045
4062
  ValueListInfo.prototype.getValues = function () {
4046
4063
  return this.values;
4047
4064
  };
4048
- ValueListInfo.prototype.byId = function (key) {
4049
- if (this.isNumeric)
4050
- key = +key;
4051
- return this.byIdMap.get(key);
4052
- };
4053
- ValueListInfo.prototype.fromJson = function (val) {
4054
- var known = this.byId(val);
4055
- if (known !== undefined)
4065
+ /** Declared id only. Pass `useUnknownIdValue` to also apply `unknownIdValue` (`null` and `undefined` still return `undefined`). */
4066
+ ValueListInfo.prototype.byId = function (key, useUnknownIdValue) {
4067
+ if (useUnknownIdValue === void 0) { useUnknownIdValue = false; }
4068
+ var lookupKey = this.isNumeric ? +key : key;
4069
+ var known = this.byIdMap.get(lookupKey);
4070
+ if (known !== undefined || !useUnknownIdValue)
4056
4071
  return known;
4057
- if (val == null)
4072
+ if (key == null)
4058
4073
  return undefined;
4059
4074
  var fallback = this.unknownIdValue;
4060
4075
  if (typeof fallback !== 'function')
4061
4076
  return fallback;
4062
- return fallback(this.isNumeric ? +val : val);
4077
+ return fallback(lookupKey);
4078
+ };
4079
+ ValueListInfo.prototype.fromJson = function (val) {
4080
+ return this.byId(val, true);
4063
4081
  };
4064
4082
  ValueListInfo.prototype.toJson = function (val) {
4065
4083
  if (!val)
@@ -395,6 +395,10 @@ export type GroupByResult<entityType, groupByFields extends (keyof entityType)[]
395
395
  };
396
396
  /** used to perform CRUD operations on an `entityType` */
397
397
  export interface Repository<entityType> {
398
+ /** returns an array based on the provided options. Included relations drop their `?`, see {@link Loaded} */
399
+ find<const include extends MembersToInclude<entityType>>(options: FindOptions<entityType> & {
400
+ include: include;
401
+ }): Promise<Loaded<entityType, include>[]>;
398
402
  /** returns an array based on the provided options */
399
403
  find(options?: FindOptions<entityType>): Promise<entityType[]>;
400
404
  /** Using the same options as the {@link find} method, but subscribing to entity changes.
@@ -410,6 +414,9 @@ export interface Repository<entityType> {
410
414
  * @example
411
415
  * await taskRepo.findFirst({ completed:false },{ createIfNotFound: true })
412
416
  * */
417
+ findFirst<const include extends MembersToInclude<entityType>>(where: EntityFilter<entityType> | undefined, options: FindFirstOptions<entityType> & {
418
+ include: include;
419
+ }): Promise<Loaded<entityType, include> | undefined>;
413
420
  findFirst(where?: EntityFilter<entityType>, options?: FindFirstOptions<entityType>): Promise<entityType | undefined>;
414
421
  /** returns the first item that matchers the `where` condition
415
422
  * @example
@@ -417,8 +424,14 @@ export interface Repository<entityType> {
417
424
  * @example
418
425
  * await taskRepo.findFirst({ where:{ completed:false }, createIfNotFound: true })
419
426
  * */
427
+ findOne<const include extends MembersToInclude<entityType>>(options: FindFirstOptions<entityType> & {
428
+ include: include;
429
+ }): Promise<Loaded<entityType, include> | undefined>;
420
430
  findOne(options?: FindFirstOptions<entityType>): Promise<entityType | undefined>;
421
431
  /** returns the items that matches the id. If id is undefined | null, returns null */
432
+ findId<const include extends MembersToInclude<entityType>>(id: idType<entityType>, options: FindFirstOptionsBase<entityType> & {
433
+ include: include;
434
+ }): Promise<Loaded<entityType, include> | undefined | null>;
422
435
  findId(id: idType<entityType>, options?: FindFirstOptionsBase<entityType>): Promise<entityType | undefined | null>;
423
436
  /**
424
437
  * Performs an aggregation on the repository's entity type based on the specified options.
@@ -1183,6 +1196,29 @@ export type ObjectMembersOnly<T> = MembersOnly<{
1183
1196
  export type MembersToInclude<T> = {
1184
1197
  [K in keyof ObjectMembersOnly<T>]?: boolean | (NonNullable<T[K]> extends Array<any> ? FindOptions<NonNullable<T[K]>[number]> : FindFirstOptions<NonNullable<T[K]>>);
1185
1198
  };
1199
+ type LoadedTarget<target, include> = include extends {
1200
+ include: infer nested;
1201
+ } ? Loaded<target, nested> : target;
1202
+ type LoadedRelation<value, include> = NonNullable<value> extends (infer target)[] ? LoadedTarget<target, include>[] : null extends value ? LoadedTarget<NonNullable<value>, include> | null : LoadedTarget<NonNullable<value>, include>;
1203
+ type LoadedKeys<entityType, include> = {
1204
+ [K in keyof include & keyof entityType]-?: [include[K]] extends [
1205
+ true | object
1206
+ ] ? K : never;
1207
+ }[keyof include & keyof entityType];
1208
+ /**
1209
+ * The entity type `find` returns for a given `include`.
1210
+ * Included relations drop their `?`, and nested `include`s work the same way.
1211
+ * A relation declared `| null` stays nullable.
1212
+ * A relation included with `false` or a widened `boolean` keeps its declared type.
1213
+ * An included relation whose target row is missing loads as `null`.
1214
+ * Declare it `| null` when that can happen, e.g. an optional FK or a target a `backendPrefilter` can hide.
1215
+ * @example
1216
+ * const tasks = await repo(Task).find({ include: { category: true } })
1217
+ * tasks[0].category.name // `category` is loaded, no `?.` needed
1218
+ */
1219
+ export type Loaded<entityType, include> = 0 extends 1 & (entityType | include) ? entityType : [LoadedKeys<entityType, include>] extends [never] ? entityType : Omit<entityType, LoadedKeys<entityType, include>> & {
1220
+ [K in LoadedKeys<entityType, include>]-?: LoadedRelation<Exclude<entityType[K], undefined>, include[K]>;
1221
+ };
1186
1222
  export type RepositoryRelations<entityType> = {
1187
1223
  [K in keyof ObjectMembersOnly<entityType>]-?: NonNullable<entityType[K]> extends Array<infer R> ? Repository<R> : entityType[K] extends infer R ? {
1188
1224
  findOne: (options?: FindOptionsBase<R>) => Promise<R>;
@@ -1229,3 +1265,4 @@ export declare const flags: {
1229
1265
  /** Version poll while SSE is stale */
1230
1266
  liveQueryPollWhenStaleMs: number;
1231
1267
  };
1268
+ export {};
package/src/sort.js CHANGED
@@ -159,9 +159,6 @@ var Sort = /** @class */ (function () {
159
159
  */
160
160
  Sort.createUniqueSort = function (entityMetadata, orderBy) {
161
161
  var e_3, _a;
162
- if ((!orderBy || Object.keys(orderBy).length === 0) &&
163
- entityMetadata.options.defaultOrderBy)
164
- orderBy = Sort.translateOrderByToSort(entityMetadata, entityMetadata.options.defaultOrderBy);
165
162
  if (!orderBy)
166
163
  orderBy = new Sort();
167
164
  var _loop_2 = function (field) {
@@ -18,6 +18,11 @@ export interface SqlImplementation extends HasWrapIdentifier {
18
18
  export interface HasWrapIdentifier {
19
19
  wrapIdentifier?(name: string): string;
20
20
  }
21
+ /** How a provider stores `Fields.json`: a real json column, a text column, or text only on write (mysql2 parses on read). */
22
+ export type JsonColumnStorage = 'native' | 'text' | 'textOnWrite';
23
+ export interface HasJsonColumnStorage {
24
+ jsonColumnStorage: JsonColumnStorage;
25
+ }
21
26
  export interface SqlCommandFactory extends HasWrapIdentifier {
22
27
  createCommand(): SqlCommand;
23
28
  execute(sql: string): Promise<SqlResult>;
@@ -1,4 +1,5 @@
1
- import type { ValueConverter } from './column-interfaces.js';
1
+ import type { FieldMetadata, ValueConverter } from './column-interfaces.js';
2
+ import type { JsonColumnStorage } from './sql-command.js';
2
3
  export declare class ValueConverters {
3
4
  static readonly Date: ValueConverter<Date>;
4
5
  static readonly DateOnly: ValueConverter<Date>;
@@ -13,3 +14,5 @@ export declare class ValueConverters {
13
14
  static readonly JsonString: ValueConverter<any>;
14
15
  static readonly JsonValue: ValueConverter<any>;
15
16
  }
17
+ export declare function fromDbColumn(field: FieldMetadata, value: any, storage: JsonColumnStorage): any;
18
+ export declare function toDbColumn(field: FieldMetadata, value: any, storage: JsonColumnStorage): any;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ValueConverters = void 0;
4
+ exports.fromDbColumn = fromDbColumn;
5
+ exports.toDbColumn = toDbColumn;
4
6
  var tslib_1 = require("tslib");
5
7
  var inputTypes_js_1 = require("../inputTypes.js");
6
8
  var zeroDate = new Date('0000-01-01');
@@ -213,6 +215,17 @@ var ValueConverters = /** @class */ (function () {
213
215
  return ValueConverters;
214
216
  }());
215
217
  exports.ValueConverters = ValueConverters;
218
+ function fromDbColumn(field, value, storage) {
219
+ if (storage === 'text' && field.valueConverter.fieldTypeInDb === 'json')
220
+ return ValueConverters.JsonString.fromDb(value);
221
+ return field.valueConverter.fromDb(value);
222
+ }
223
+ function toDbColumn(field, value, storage) {
224
+ if ((storage === 'text' || storage === 'textOnWrite') &&
225
+ field.valueConverter.fieldTypeInDb === 'json')
226
+ return ValueConverters.JsonString.toDb(value);
227
+ return field.valueConverter.toDb(value);
228
+ }
216
229
  function enforceString(value) {
217
230
  if (value === null || value === undefined)
218
231
  return value;