remult 0.25.8 → 0.26.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 (102) hide show
  1. package/README.md +194 -129
  2. package/esm/graphql.js +131 -59
  3. package/esm/index.js +1 -1
  4. package/esm/migrations/compare-migration-snapshots.js +61 -0
  5. package/esm/migrations/generate-migrations.js +68 -0
  6. package/esm/migrations/index.js +2 -0
  7. package/esm/migrations/migrate.js +90 -0
  8. package/esm/migrations/migration-types.js +36 -0
  9. package/esm/migrations/update-migrations-ts-file.js +115 -0
  10. package/esm/postgres/postgres-data-provider.js +24 -3
  11. package/esm/postgres/schema-builder.js +35 -26
  12. package/esm/remult-better-sqlite3.js +47 -0
  13. package/esm/remult-bun-sqlite.js +41 -0
  14. package/esm/remult-knex/index.js +180 -51
  15. package/esm/remult-mongo.js +2 -2
  16. package/esm/remult-sql-js.js +3 -78
  17. package/esm/remult-sqlite-core.js +104 -0
  18. package/esm/server/expressBridge.js +130 -29
  19. package/esm/server/initAsyncHooks.js +75 -1
  20. package/esm/src/CompoundIdField.js +8 -2
  21. package/esm/src/context.js +20 -24
  22. package/esm/src/data-api.js +71 -30
  23. package/esm/src/data-providers/rest-data-provider.js +17 -15
  24. package/esm/src/data-providers/sql-database.js +32 -7
  25. package/esm/src/data-providers/web-sql-data-provider.js +1 -9
  26. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +37 -11
  27. package/esm/src/filter/filter-interfaces.js +229 -17
  28. package/esm/src/isOfType.js +9 -0
  29. package/esm/src/remult3/Fields.js +70 -18
  30. package/esm/src/remult3/RepositoryImplementation.js +47 -21
  31. package/esm/src/remult3/addValidator.js +15 -0
  32. package/esm/src/remult3/remult3.js +28 -57
  33. package/esm/src/server-action.js +4 -4
  34. package/esm/src/validators.js +6 -3
  35. package/esm/src/valueConverters.js +1 -1
  36. package/graphql.js +156 -71
  37. package/index.d.ts +4 -4
  38. package/index.js +2 -1
  39. package/migrations/compare-migration-snapshots.d.ts +27 -0
  40. package/migrations/compare-migration-snapshots.js +165 -0
  41. package/migrations/generate-migrations.d.ts +21 -0
  42. package/migrations/generate-migrations.js +104 -0
  43. package/migrations/index.d.ts +3 -0
  44. package/migrations/index.js +7 -0
  45. package/migrations/migrate.d.ts +27 -0
  46. package/migrations/migrate.js +175 -0
  47. package/migrations/migration-types.d.ts +48 -0
  48. package/migrations/migration-types.js +88 -0
  49. package/migrations/update-migrations-ts-file.d.ts +1 -0
  50. package/migrations/update-migrations-ts-file.js +141 -0
  51. package/package.json +21 -1
  52. package/postgres/postgres-data-provider.d.ts +8 -3
  53. package/postgres/postgres-data-provider.js +46 -3
  54. package/postgres/schema-builder.js +81 -48
  55. package/remult-better-sqlite3.d.ts +12 -0
  56. package/remult-better-sqlite3.js +64 -0
  57. package/remult-bun-sqlite.d.ts +11 -0
  58. package/remult-bun-sqlite.js +56 -0
  59. package/remult-knex/index.d.ts +12 -5
  60. package/remult-knex/index.js +257 -90
  61. package/remult-mongo.d.ts +2 -2
  62. package/remult-mongo.js +2 -2
  63. package/remult-sql-js.d.ts +2 -15
  64. package/remult-sql-js.js +11 -152
  65. package/remult-sqlite-core.d.ts +21 -0
  66. package/remult-sqlite-core.js +217 -0
  67. package/server/expressBridge.d.ts +30 -1
  68. package/server/expressBridge.js +140 -32
  69. package/server/initAsyncHooks.d.ts +16 -0
  70. package/server/initAsyncHooks.js +128 -2
  71. package/src/CompoundIdField.js +8 -2
  72. package/src/column-interfaces.d.ts +9 -3
  73. package/src/context.d.ts +8 -6
  74. package/src/context.js +27 -36
  75. package/src/data-api.d.ts +13 -9
  76. package/src/data-api.js +184 -135
  77. package/src/data-providers/rest-data-provider.js +26 -15
  78. package/src/data-providers/sql-database.d.ts +17 -5
  79. package/src/data-providers/sql-database.js +31 -7
  80. package/src/data-providers/web-sql-data-provider.d.ts +1 -2
  81. package/src/data-providers/web-sql-data-provider.js +4 -8
  82. package/src/entity.d.ts +64 -6
  83. package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +31 -7
  84. package/src/filter/filter-consumer-bridge-to-sql-request.js +46 -21
  85. package/src/filter/filter-interfaces.d.ts +106 -39
  86. package/src/filter/filter-interfaces.js +312 -32
  87. package/src/isOfType.d.ts +2 -0
  88. package/src/isOfType.js +14 -0
  89. package/src/remult3/Fields.d.ts +36 -0
  90. package/src/remult3/Fields.js +81 -19
  91. package/src/remult3/RepositoryImplementation.d.ts +7 -3
  92. package/src/remult3/RepositoryImplementation.js +106 -71
  93. package/src/remult3/addValidator.d.ts +2 -0
  94. package/src/remult3/addValidator.js +20 -0
  95. package/src/remult3/remult3.d.ts +6 -2
  96. package/src/remult3/remult3.js +28 -57
  97. package/src/server-action.d.ts +1 -1
  98. package/src/server-action.js +5 -4
  99. package/src/sql-command.d.ts +6 -1
  100. package/src/validators.d.ts +1 -0
  101. package/src/validators.js +10 -4
  102. package/src/valueConverters.js +1 -1
@@ -6,8 +6,87 @@ import { getRelationFieldInfo } from '../remult3/relationInfoMember.js';
6
6
  */
7
7
  export class Filter {
8
8
  apply;
9
- static createCustom(rawFilterTranslator, key = '') {
10
- let rawFilterInfo = { key: key, rawFilterTranslator };
9
+ //@internal
10
+ static throwErrorIfFilterIsEmpty(where, methodName) {
11
+ if (Filter.isFilterEmpty(where)) {
12
+ throw {
13
+ message: `${methodName}: requires a filter to protect against accidental delete/update of all rows`,
14
+ httpStatusCode: 400,
15
+ };
16
+ }
17
+ }
18
+ //@internal
19
+ static isFilterEmpty(where) {
20
+ if (where.$and) {
21
+ for (const a of where.$and) {
22
+ if (!Filter.isFilterEmpty(a)) {
23
+ return false;
24
+ }
25
+ }
26
+ }
27
+ if (where.$or) {
28
+ for (const a of where.$or) {
29
+ if (Filter.isFilterEmpty(a)) {
30
+ return true;
31
+ }
32
+ }
33
+ return false;
34
+ }
35
+ if (Object.keys(where).filter((x) => !['$or', '$and'].includes(x)).length == 0) {
36
+ return true;
37
+ }
38
+ return false;
39
+ }
40
+ /**
41
+ * Retrieves precise values for each property in a filter for an entity.
42
+ * @template entityType The type of the entity being filtered.
43
+ * @param metadata The metadata of the entity being filtered.
44
+ * @param filter The filter to analyze.
45
+ * @returns A promise that resolves to a FilterPreciseValues object containing the precise values for each property.
46
+ * @example
47
+ * const preciseValues = await Filter.getPreciseValues(meta, {
48
+ * status: { $ne: 'active' },
49
+ * $or: [
50
+ * { customerId: ["1", "2"] },
51
+ * { customerId: "3" }
52
+ * ]
53
+ * });
54
+ * console.log(preciseValues);
55
+ * // Output:
56
+ * // {
57
+ * // "customerId": ["1", "2", "3"], // Precise values inferred from the filter
58
+ * // "status": undefined, // Cannot infer precise values for 'status'
59
+ * // }
60
+
61
+ */
62
+ static async getPreciseValues(metadata, filter) {
63
+ const result = new preciseValuesCollector();
64
+ await Filter.fromEntityFilter(metadata, filter).__applyToConsumer(result);
65
+ return result.preciseValues;
66
+ }
67
+ /**
68
+ * Retrieves precise values for each property in a filter for an entity.
69
+ * @template entityType The type of the entity being filtered.
70
+ * @param metadata The metadata of the entity being filtered.
71
+ * @param filter The filter to analyze.
72
+ * @returns A promise that resolves to a FilterPreciseValues object containing the precise values for each property.
73
+ * @example
74
+ * const preciseValues = await where.getPreciseValues();
75
+ * console.log(preciseValues);
76
+ * // Output:
77
+ * // {
78
+ * // "customerId": ["1", "2", "3"], // Precise values inferred from the filter
79
+ * // "status": undefined, // Cannot infer precise values for 'status'
80
+ * // }
81
+
82
+ */
83
+ async getPreciseValues() {
84
+ const result = new preciseValuesCollector();
85
+ await this.__applyToConsumer(result);
86
+ return result.preciseValues;
87
+ }
88
+ static createCustom(translator, key = '') {
89
+ let rawFilterInfo = { key: key, rawFilterTranslator: translator };
11
90
  return Object.assign((x) => {
12
91
  let z = {};
13
92
  if (x == undefined)
@@ -190,7 +269,6 @@ export class Filter {
190
269
  return await filter();
191
270
  return filter;
192
271
  }
193
- //@internal
194
272
  toJson() {
195
273
  let r = new FilterSerializer();
196
274
  this.__applyToConsumer(r);
@@ -474,7 +552,21 @@ export function entityFilterToJson(entityDefs, where) {
474
552
  return Filter.fromEntityFilter(entityDefs, where).toJson();
475
553
  }
476
554
  export function buildFilterFromRequestParameters(entity, filterInfo) {
477
- let where = [];
555
+ let where = {};
556
+ function addAnd(what) {
557
+ if (!where.$and) {
558
+ where.$and = [];
559
+ }
560
+ where.$and.push(what);
561
+ }
562
+ function addToFilterObject(key, val) {
563
+ if (where[key] === undefined)
564
+ where[key] = val;
565
+ else {
566
+ addAnd({ [key]: val });
567
+ }
568
+ }
569
+ ;
478
570
  [...entity.fields].forEach((col) => {
479
571
  function addFilter(operation, theFilter, jsonArray = false, asString = false) {
480
572
  let val = filterInfo.get(col.key + operation);
@@ -494,7 +586,7 @@ export function buildFilterFromRequestParameters(entity, filterInfo) {
494
586
  }
495
587
  let f = theFilter(theVal);
496
588
  if (f !== undefined) {
497
- where.push({ [col.key]: f });
589
+ addToFilterObject(col.key, f);
498
590
  }
499
591
  };
500
592
  if (!jsonArray && val instanceof Array) {
@@ -528,10 +620,10 @@ export function buildFilterFromRequestParameters(entity, filterInfo) {
528
620
  case 'y':
529
621
  case 'true':
530
622
  case 'yes':
531
- where.push({ [col.key]: null });
623
+ addToFilterObject(col.key, null);
532
624
  break;
533
625
  default:
534
- where.push({ [col.key]: { $ne: null } });
626
+ addToFilterObject(col.key, { $ne: null });
535
627
  break;
536
628
  }
537
629
  }
@@ -546,12 +638,19 @@ export function buildFilterFromRequestParameters(entity, filterInfo) {
546
638
  get: (key) => x[key],
547
639
  })),
548
640
  }));
549
- if (or.length == 1)
550
- where.push(or[0]);
551
- else
552
- where.push({
641
+ if (or.length == 1) {
642
+ if (!where.$or) {
643
+ where.$or = or[0].$or;
644
+ }
645
+ else {
646
+ where.$or.push(or[0].$or);
647
+ }
648
+ }
649
+ else {
650
+ addAnd({
553
651
  $and: or,
554
652
  });
653
+ }
555
654
  }
556
655
  for (const key in entity.entityType) {
557
656
  const element = entity.entityType[key];
@@ -563,7 +662,7 @@ export function buildFilterFromRequestParameters(entity, filterInfo) {
563
662
  const addItem = (item) => {
564
663
  if (item[customArrayToken] != undefined)
565
664
  item = item[customArrayToken];
566
- where.push({ [customUrlToken + key]: item });
665
+ addToFilterObject(customUrlToken + key, item);
567
666
  };
568
667
  if (Array.isArray(custom)) {
569
668
  custom.forEach((item) => addItem(item));
@@ -573,9 +672,7 @@ export function buildFilterFromRequestParameters(entity, filterInfo) {
573
672
  }
574
673
  }
575
674
  }
576
- if (where.length == 1)
577
- return where[0];
578
- return { $and: where };
675
+ return where;
579
676
  function separateArrayFromInnerArray(val) {
580
677
  if (!Array.isArray(val))
581
678
  return [val];
@@ -691,5 +788,120 @@ export function __updateEntityBasedOnWhere(entityDefs, where, r) {
691
788
  });
692
789
  }
693
790
  }
694
- // toRaw of default remult threw and exception
695
- // toRaw didn't respect
791
+ class preciseValuesCollector {
792
+ rawValues = {};
793
+ preciseValues = new Proxy(this.rawValues, {
794
+ get: (target, prop) => {
795
+ if (prop in target) {
796
+ let result = target[prop];
797
+ if (result.bad)
798
+ return undefined;
799
+ if (result.values.length > 0) {
800
+ const relInfo = getRelationFieldInfo(result.field);
801
+ if (relInfo) {
802
+ if (relInfo.type === 'reference') {
803
+ return result.values.map((x) => {
804
+ return relInfo.toRepo.metadata.idMetadata.getIdFilter(x);
805
+ });
806
+ }
807
+ else
808
+ throw new Error('Only relations toOne without field are supported.');
809
+ }
810
+ return result.values;
811
+ }
812
+ }
813
+ return undefined;
814
+ },
815
+ });
816
+ ok(col, ...val) {
817
+ let x = this.rawValues[col.key];
818
+ if (!x) {
819
+ this.rawValues[col.key] = {
820
+ field: col,
821
+ bad: false,
822
+ values: [...val],
823
+ };
824
+ }
825
+ else {
826
+ x.values.push(...val.filter((y) => !x.values.includes(y)));
827
+ }
828
+ }
829
+ notOk(col) {
830
+ let x = this.rawValues[col.key];
831
+ if (!x) {
832
+ this.rawValues[col.key] = {
833
+ field: col,
834
+ bad: true,
835
+ values: [],
836
+ };
837
+ }
838
+ else {
839
+ x.bad = true;
840
+ }
841
+ }
842
+ or(orElements) {
843
+ const result = orElements.map((or) => {
844
+ let x = new preciseValuesCollector();
845
+ or.__applyToConsumer(x);
846
+ return x;
847
+ });
848
+ for (const or of result) {
849
+ for (const key in or.rawValues) {
850
+ if (Object.prototype.hasOwnProperty.call(or.rawValues, key)) {
851
+ const element = or.rawValues[key];
852
+ if (element) {
853
+ if (element.bad)
854
+ this.notOk(element.field);
855
+ else {
856
+ this.ok(element.field, ...element.values);
857
+ }
858
+ }
859
+ }
860
+ }
861
+ }
862
+ for (const key in this.rawValues) {
863
+ if (Object.prototype.hasOwnProperty.call(this.rawValues, key)) {
864
+ for (const r of result) {
865
+ const element = r.rawValues[key];
866
+ if (!element)
867
+ this.notOk(this.rawValues[key].field);
868
+ }
869
+ }
870
+ }
871
+ }
872
+ isEqualTo(col, val) {
873
+ this.ok(col, val);
874
+ }
875
+ isDifferentFrom(col, val) {
876
+ this.notOk(col);
877
+ }
878
+ isNull(col) {
879
+ this.ok(col, null);
880
+ }
881
+ isNotNull(col) {
882
+ this.notOk(col);
883
+ }
884
+ isGreaterOrEqualTo(col, val) {
885
+ this.notOk(col);
886
+ }
887
+ isGreaterThan(col, val) {
888
+ this.notOk(col);
889
+ }
890
+ isLessOrEqualTo(col, val) {
891
+ this.notOk(col);
892
+ }
893
+ isLessThan(col, val) {
894
+ this.notOk(col);
895
+ }
896
+ containsCaseInsensitive(col, val) {
897
+ this.notOk(col);
898
+ }
899
+ notContainsCaseInsensitive(col, val) {
900
+ this.notOk(col);
901
+ }
902
+ isIn(col, val) {
903
+ this.ok(col, ...val);
904
+ }
905
+ custom(key, customItem) { }
906
+ databaseCustom(databaseCustom) { }
907
+ }
@@ -0,0 +1,9 @@
1
+ export function isOfType(obj, checkMethod) {
2
+ return typeof obj[checkMethod] !== 'undefined';
3
+ }
4
+ export function cast(obj, checkMethod) {
5
+ if (isOfType(obj, checkMethod)) {
6
+ return obj;
7
+ }
8
+ throw new Error(`Object is not of type ${checkMethod.toString()}`);
9
+ }
@@ -2,9 +2,13 @@ import { createId } from '@paralleldrive/cuid2';
2
2
  import { v4 as uuid } from 'uuid';
3
3
  import { ValueConverters } from '../valueConverters.js';
4
4
  import { buildOptions } from './RepositoryImplementation.js';
5
- import { Validators } from '../validators.js';
5
+ import { Validators, createValueValidator, getEnumValues } from '../validators.js';
6
6
  import { relationInfoMemberInOptions } from './relationInfoMember.js';
7
7
  import { remultStatic } from '../remult-static.js';
8
+ import { addValidator } from './addValidator.js';
9
+ const validateNumber = createValueValidator((x) => {
10
+ return !isNaN(x) && isFinite(x);
11
+ });
8
12
  export class Fields {
9
13
  /**
10
14
  * Stored as a JSON.stringify - to store as json use Fields.json
@@ -34,6 +38,7 @@ export class Fields {
34
38
  static integer(...options) {
35
39
  return Field(() => Number, {
36
40
  valueConverter: ValueConverters.Integer,
41
+ validate: validateNumber,
37
42
  }, ...options);
38
43
  }
39
44
  static autoIncrement(...options) {
@@ -47,7 +52,9 @@ export class Fields {
47
52
  }, ...options);
48
53
  }
49
54
  static number(...options) {
50
- return Field(() => Number, ...options);
55
+ return Field(() => Number, {
56
+ validate: validateNumber,
57
+ }, ...options);
51
58
  }
52
59
  static createdAt(...options) {
53
60
  return Field(() => Date, {
@@ -76,6 +83,11 @@ export class Fields {
76
83
  },
77
84
  }, ...options);
78
85
  }
86
+ /**
87
+ * A CUID (Collision Resistant Unique Identifier) field.
88
+ * This id value is determined on the backend on insert, and can't be updated through the API.
89
+ * The CUID is generated using the `@paralleldrive/cuid2` npm package.
90
+ */
79
91
  static cuid(...options) {
80
92
  return Field(() => String, {
81
93
  allowApiUpdate: false,
@@ -86,6 +98,61 @@ export class Fields {
86
98
  },
87
99
  }, ...options);
88
100
  }
101
+ /**
102
+ * Defines a field that can hold a value from a specified set of string literals.
103
+ * @param {() => readonly valueType[]} optionalValues - A function that returns an array of allowed string literals.
104
+ * @returns {ClassFieldDecorator<entityType, valueType | undefined>} - A class field decorator.
105
+ *
106
+ * @example
107
+
108
+ * class MyEntity {
109
+ * .@Fields.literal(() => ['open', 'closed', 'frozen', 'in progress'] as const)
110
+ * status: 'open' | 'closed' | 'frozen' | 'in progress' = 'open';
111
+ * }
112
+
113
+ *
114
+ * // This defines a field `status` in `MyEntity` that can only hold the values 'open', 'closed', 'frozen', or 'in progress'.
115
+ *
116
+ * @example
117
+ * // For better reusability and maintainability:
118
+
119
+ * const statuses = ['open', 'closed', 'frozen', 'in progress'] as const;
120
+ * type StatusType = typeof statuses[number];
121
+ *
122
+ * class MyEntity {
123
+ * .@Fields.literal(() => statuses)
124
+ * status: StatusType = 'open';
125
+ * }
126
+
127
+ *
128
+ * // This approach allows easy management and updates of the allowed values for the `status` field.
129
+ */
130
+ static literal(optionalValues, ...options) {
131
+ return Fields.string({
132
+ validate: (entity, event) => Validators.in(optionalValues())(entity, event)
133
+ }, ...options);
134
+ }
135
+ static enum(enumType, ...options) {
136
+ let valueConverter;
137
+ return Field(() =>
138
+ //@ts-ignore
139
+ enumType(), {
140
+ validate: (entity, event) => Validators.enum(enumType())(entity, event)
141
+ }, ...options, (options) => {
142
+ if (valueConverter === undefined) {
143
+ let enumObj = enumType();
144
+ let enumValues = getEnumValues(enumObj);
145
+ valueConverter = enumValues.find(x => typeof x === "string") ? ValueConverters.String : ValueConverters.Integer;
146
+ }
147
+ if (!options.valueConverter) {
148
+ options.valueConverter = valueConverter;
149
+ }
150
+ else if (!options.valueConverter.fieldTypeInDb) {
151
+ //@ts-ignore
152
+ options.valueConverter.fieldTypeInDb = valueConverter.fieldTypeInDb;
153
+ }
154
+ });
155
+ }
89
156
  static string(...options) {
90
157
  return Field(() => String, ...options);
91
158
  }
@@ -186,7 +253,7 @@ export function Field(valueType, ...options) {
186
253
  let factory = (remult) => {
187
254
  let r = buildOptions(options, remult);
188
255
  if (r.required) {
189
- r.validate = addValidator(r.validate, Validators.required);
256
+ r.validate = addValidator(r.validate, Validators.required, true);
190
257
  }
191
258
  if (r.maxLength) {
192
259
  r.validate = addValidator(r.validate, Validators.maxLength(r.maxLength));
@@ -240,18 +307,3 @@ export function checkTarget(target) {
240
307
  if (!target)
241
308
  throw new Error("Set the 'experimentalDecorators:true' option in your 'tsconfig' or 'jsconfig' (target undefined)");
242
309
  }
243
- function addValidator(validators, newValidator, atStart = false) {
244
- if (!newValidator)
245
- return;
246
- const newValidators = Array.isArray(newValidator)
247
- ? newValidator
248
- : [newValidator];
249
- const validatorsArray = Array.isArray(validators)
250
- ? validators
251
- : validators
252
- ? [validators]
253
- : [];
254
- return atStart
255
- ? [...newValidators, ...validatorsArray]
256
- : [...validatorsArray, ...newValidators];
257
- }
@@ -14,6 +14,8 @@ import { RelationLoader } from './relation-loader.js';
14
14
  import { getInternalKey, } from './repository-internals.js';
15
15
  import { entityDbName, fieldDbName, } from '../filter/filter-consumer-bridge-to-sql-request.js';
16
16
  import { remultStatic } from '../remult-static.js';
17
+ import { Validators } from '../validators.js';
18
+ import { addValidator } from './addValidator.js';
17
19
  //import { remult } from "../remult-proxy";
18
20
  let classValidatorValidate = undefined;
19
21
  // import ("class-validator".toString())
@@ -269,7 +271,8 @@ export class RepositoryImplementation {
269
271
  }
270
272
  }
271
273
  }
272
- async updateMany(where, set) {
274
+ async updateMany({ where }, set) {
275
+ Filter.throwErrorIfFilterIsEmpty(where, 'updateMany');
273
276
  if (this._dataProvider.isProxy) {
274
277
  return this._edp.updateMany(await this._translateWhereToFilter(where), set);
275
278
  }
@@ -635,7 +638,8 @@ export class RepositoryImplementation {
635
638
  async count(where) {
636
639
  return this._edp.count(await this._translateWhereToFilter(where));
637
640
  }
638
- async deleteMany(where) {
641
+ async deleteMany({ where, }) {
642
+ Filter.throwErrorIfFilterIsEmpty(where, 'deleteMany');
639
643
  if (this._dataProvider.isProxy) {
640
644
  return this._edp.deleteMany(await this._translateWhereToFilter(where));
641
645
  }
@@ -767,11 +771,22 @@ export class RepositoryImplementation {
767
771
  $and: [z, this._defaultFindOptions?.where],
768
772
  };
769
773
  }
770
- if (this.metadata.options.backendPrefilter && !this._dataProvider.isProxy) {
771
- let z = where;
772
- where = {
773
- $and: [z, await Filter.resolve(this.metadata.options.backendPrefilter)],
774
- };
774
+ if (!this._dataProvider.isProxy) {
775
+ if (this.metadata.options.backendPreprocessFilter) {
776
+ where = await this.metadata.options.backendPreprocessFilter(where, {
777
+ metadata: this.metadata,
778
+ getFilterPreciseValues: (filter) => Filter.getPreciseValues(this.metadata, filter || where),
779
+ });
780
+ }
781
+ if (this.metadata.options.backendPrefilter) {
782
+ let z = where;
783
+ where = {
784
+ $and: [
785
+ z,
786
+ await Filter.resolve(this.metadata.options.backendPrefilter),
787
+ ],
788
+ };
789
+ }
775
790
  }
776
791
  let r = await Filter.fromEntityFilter(this.metadata, where);
777
792
  if (r && !this._dataProvider.isProxy) {
@@ -1184,7 +1199,7 @@ export class rowHelperImplementation extends rowHelperBase {
1184
1199
  return this.instance[y.key];
1185
1200
  };
1186
1201
  if (this.metadata.idMetadata.field instanceof CompoundIdField)
1187
- return this.metadata.idMetadata.field.fields.map(getVal).join(',');
1202
+ return this.metadata.idMetadata.field.getId(getVal);
1188
1203
  return getVal(this.metadata.idMetadata.field);
1189
1204
  }
1190
1205
  saveMoreOriginalData() {
@@ -1242,7 +1257,7 @@ export class rowHelperImplementation extends rowHelperBase {
1242
1257
  await this.__validateEntity();
1243
1258
  let doNotSave = false;
1244
1259
  let e = this.buildLifeCycleEvent(() => (doNotSave = true));
1245
- if (!this.remult.dataProvider.isProxy) {
1260
+ if (!this.repository._dataProvider.isProxy) {
1246
1261
  for (const col of this.fields) {
1247
1262
  if (col.metadata.options.saving)
1248
1263
  await col.metadata.options.saving(this.instance, col, e);
@@ -1296,7 +1311,7 @@ export class rowHelperImplementation extends rowHelperBase {
1296
1311
  }
1297
1312
  }
1298
1313
  await this.loadDataFrom(updatedRow);
1299
- if (!this.remult.dataProvider.isProxy) {
1314
+ if (!this.repository._dataProvider.isProxy) {
1300
1315
  if (this.info.entityInfo.saved)
1301
1316
  await this.info.entityInfo.saved(this.instance, e);
1302
1317
  if (this.repository.listeners)
@@ -1359,14 +1374,14 @@ export class rowHelperImplementation extends rowHelperBase {
1359
1374
  async delete() {
1360
1375
  this.__clearErrorsAndReportChanged();
1361
1376
  let e = this.buildLifeCycleEvent();
1362
- if (!this.remult.dataProvider.isProxy) {
1377
+ if (!this.repository._dataProvider.isProxy) {
1363
1378
  if (this.info.entityInfo.deleting)
1364
1379
  await this.info.entityInfo.deleting(this.instance, e);
1365
1380
  }
1366
1381
  this.__assertValidity();
1367
1382
  try {
1368
1383
  await this.edp.delete(this.id);
1369
- if (!this.remult.dataProvider.isProxy) {
1384
+ if (!this.repository._dataProvider.isProxy) {
1370
1385
  if (this.info.entityInfo.deleted)
1371
1386
  await this.info.entityInfo.deleted(this.instance, e);
1372
1387
  }
@@ -1922,9 +1937,13 @@ class EntityFullInfo {
1922
1937
  getIdFilter: (...ids) => {
1923
1938
  if (this.idMetadata.field instanceof CompoundIdField) {
1924
1939
  let field = this.idMetadata.field;
1925
- return {
1926
- $or: ids.map((x) => field.isEqualTo(x)),
1927
- };
1940
+ if (ids.length == 1) {
1941
+ return field.isEqualTo(ids[0]);
1942
+ }
1943
+ else
1944
+ return {
1945
+ $or: ids.map((x) => field.isEqualTo(x)),
1946
+ };
1928
1947
  }
1929
1948
  if (ids.length == 1)
1930
1949
  return {
@@ -1959,6 +1978,13 @@ export function ValueListFieldType(...options) {
1959
1978
  ;
1960
1979
  (o.valueConverter = ValueListInfo.get(type)),
1961
1980
  (o.displayValue = (item, val) => val?.caption);
1981
+ o.validate = (entity, ref) => {
1982
+ const values = ValueListInfo.get(type).getValues();
1983
+ if (ref.value && !values.find((v) => v === ref.value)) {
1984
+ ref.value = values.find((v) => v.id === ref.value.id) || ref.value;
1985
+ }
1986
+ return Validators.in(values)(entity, ref);
1987
+ };
1962
1988
  }, ...options)(type, context);
1963
1989
  };
1964
1990
  }
@@ -2070,8 +2096,11 @@ export function buildOptions(options, remult) {
2070
2096
  if (o) {
2071
2097
  if (typeof o === 'function')
2072
2098
  o(r, remult);
2073
- else
2074
- Object.assign(r, o);
2099
+ else {
2100
+ const { validate, ...otherOptions } = o;
2101
+ r.validate = addValidator(r.validate, validate);
2102
+ Object.assign(r, otherOptions);
2103
+ }
2075
2104
  }
2076
2105
  }
2077
2106
  return r;
@@ -2080,10 +2109,7 @@ export function decorateColumnSettings(settings, remult) {
2080
2109
  if (settings.valueType) {
2081
2110
  let settingsOnTypeLevel = settings.valueType[storableMember];
2082
2111
  if (settingsOnTypeLevel) {
2083
- settings = {
2084
- ...buildOptions(settingsOnTypeLevel, remult),
2085
- ...settings,
2086
- };
2112
+ settings = buildOptions([...settingsOnTypeLevel, settings], remult);
2087
2113
  }
2088
2114
  }
2089
2115
  if (settings.valueType == String) {
@@ -0,0 +1,15 @@
1
+ export function addValidator(validators, newValidator, atStart = false) {
2
+ if (!newValidator)
3
+ return validators;
4
+ const newValidators = Array.isArray(newValidator)
5
+ ? newValidator
6
+ : [newValidator];
7
+ const validatorsArray = Array.isArray(validators)
8
+ ? validators
9
+ : validators
10
+ ? [validators]
11
+ : [];
12
+ return atStart
13
+ ? [...newValidators, ...validatorsArray]
14
+ : [...validatorsArray, ...newValidators];
15
+ }