remult 0.24.2-exp.0 → 0.25.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 (46) hide show
  1. package/README.md +4 -4
  2. package/esm/index.js +2 -2
  3. package/esm/remult-mongo.js +1 -1
  4. package/esm/remult-sql-js.js +113 -0
  5. package/esm/src/data-providers/array-entity-data-provider.js +1 -1
  6. package/esm/src/data-providers/json-data-provider.js +1 -1
  7. package/esm/src/data-providers/{ipfs-data-provider.js → json-entity-opfs-storage.js} +1 -1
  8. package/esm/src/data-providers/sql-database.js +10 -6
  9. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +4 -5
  10. package/esm/src/remult-proxy.js +4 -0
  11. package/esm/src/remult3/Fields.js +22 -0
  12. package/esm/src/remult3/RepositoryImplementation.js +109 -60
  13. package/esm/src/remult3/entity.js +2 -2
  14. package/esm/src/remult3/getEntityRef.js +5 -5
  15. package/esm/src/remult3/relationInfoMember.js +1 -1
  16. package/esm/src/remult3/remult3.js +6 -7
  17. package/esm/src/server-action-info.js +1 -1
  18. package/esm/src/server-action.js +1 -1
  19. package/esm/src/validators.js +120 -28
  20. package/index.d.ts +3 -3
  21. package/index.js +8 -3
  22. package/package.json +6 -1
  23. package/remult-mongo.js +1 -1
  24. package/remult-sql-js.d.ts +18 -0
  25. package/remult-sql-js.js +211 -0
  26. package/src/column-interfaces.d.ts +12 -6
  27. package/src/data-providers/array-entity-data-provider.js +9 -10
  28. package/src/data-providers/json-data-provider.js +1 -1
  29. package/src/data-providers/{ipfs-data-provider.d.ts → json-entity-opfs-storage.d.ts} +1 -1
  30. package/src/data-providers/{ipfs-data-provider.js → json-entity-opfs-storage.js} +7 -7
  31. package/src/data-providers/sql-database.js +14 -6
  32. package/src/filter/filter-consumer-bridge-to-sql-request.js +4 -5
  33. package/src/remult-proxy.js +11 -0
  34. package/src/remult3/Fields.js +22 -0
  35. package/src/remult3/RepositoryImplementation.d.ts +6 -6
  36. package/src/remult3/RepositoryImplementation.js +136 -78
  37. package/src/remult3/entity.js +2 -2
  38. package/src/remult3/getEntityRef.js +5 -5
  39. package/src/remult3/relationInfoMember.js +1 -1
  40. package/src/remult3/remult3.d.ts +33 -0
  41. package/src/remult3/remult3.js +6 -7
  42. package/src/server-action-info.js +1 -1
  43. package/src/server-action.js +1 -1
  44. package/src/sql-command.d.ts +1 -0
  45. package/src/validators.d.ts +41 -9
  46. package/src/validators.js +183 -54
@@ -619,9 +619,11 @@ export class RepositoryImplementation {
619
619
  for (const col of this.fieldsOf(json)) {
620
620
  let ei = getEntitySettings(col.valueType, false);
621
621
  if (ei) {
622
- result[col.key] = this.remult
623
- .repo(col.valueType)
624
- .fromJson(json[col.key]);
622
+ let val = json[col.key];
623
+ if (typeof val === 'string' || typeof val === 'number')
624
+ result[col.key] = val;
625
+ else
626
+ result[col.key] = this.remult.repo(col.valueType).fromJson(val);
625
627
  }
626
628
  else {
627
629
  if (json[col.key] !== undefined) {
@@ -650,6 +652,8 @@ export class RepositoryImplementation {
650
652
  async findOne(options, skipOrderByAndLimit = false) {
651
653
  let r;
652
654
  let cacheInfo;
655
+ if (!options)
656
+ options = {};
653
657
  if (options.useCache) {
654
658
  let f = findOptionsToJson(options, this.metadata);
655
659
  let key = JSON.stringify(f);
@@ -816,7 +820,7 @@ export function createOldEntity(entity, remult) {
816
820
  return new EntityFullInfo(prepareColumnInfo(r, remult), info, remult, entity, key);
817
821
  }
818
822
  class rowHelperBase {
819
- columnsInfo;
823
+ fieldsMetadata;
820
824
  instance;
821
825
  remult;
822
826
  isNewRow;
@@ -829,8 +833,8 @@ class rowHelperBase {
829
833
  this._error = val;
830
834
  this._subscribers?.reportChanged();
831
835
  }
832
- constructor(columnsInfo, instance, remult, isNewRow) {
833
- this.columnsInfo = columnsInfo;
836
+ constructor(fieldsMetadata, instance, remult, isNewRow) {
837
+ this.fieldsMetadata = fieldsMetadata;
834
838
  this.instance = instance;
835
839
  this.remult = remult;
836
840
  this.isNewRow = isNewRow;
@@ -840,10 +844,10 @@ class rowHelperBase {
840
844
  remult = fac.remultFactory();
841
845
  }
842
846
  }
843
- for (const col of columnsInfo) {
847
+ for (const col of fieldsMetadata) {
844
848
  let ei = getEntitySettings(col.valueType, false);
845
849
  if (ei && remult) {
846
- let lookup = new LookupColumn(remult.repo(col.valueType), Boolean(col?.[relationInfoMember]), col.allowNull);
850
+ let lookup = new LookupColumn(remult.repo(col.valueType), Boolean(getRelationInfo(col.options)), col.allowNull);
847
851
  this.lookups.set(col.key, lookup);
848
852
  let val = instance[col.key];
849
853
  let refImpl;
@@ -876,7 +880,7 @@ class rowHelperBase {
876
880
  });
877
881
  lookup.set(val);
878
882
  }
879
- else if (getRelationInfo(col)?.type === 'toOne') {
883
+ else if (getRelationInfo(col.options)?.type === 'toOne') {
880
884
  let hasVal = instance.hasOwnProperty(col.key);
881
885
  let val = instance[col.key];
882
886
  if (isNewRow && !val)
@@ -889,10 +893,10 @@ class rowHelperBase {
889
893
  val = newVal;
890
894
  if (newVal === undefined)
891
895
  return;
892
- const op = col;
896
+ const op = col.options;
893
897
  if (op.field) {
894
898
  this.instance[op.field] = this.remult
895
- .repo(getRelationInfo(col).toType())
899
+ .repo(getRelationInfo(col.options).toType())
896
900
  .metadata.idMetadata.getId(newVal);
897
901
  }
898
902
  if (op.fields) {
@@ -920,7 +924,7 @@ class rowHelperBase {
920
924
  initSubscribers() {
921
925
  if (!this._subscribers) {
922
926
  this._subscribers = new SubscribableImp();
923
- for (const col of this.columnsInfo) {
927
+ for (const col of this.fieldsMetadata) {
924
928
  let ei = getEntitySettings(col.valueType, false);
925
929
  let refImpl = this.fields.find(col.key);
926
930
  refImpl._subscribers = new SubscribableImp();
@@ -968,7 +972,7 @@ class rowHelperBase {
968
972
  message: this.error,
969
973
  };
970
974
  if (!error.message) {
971
- for (const col of this.columnsInfo) {
975
+ for (const col of this.fieldsMetadata) {
972
976
  if (this.errors[col.key]) {
973
977
  error.message =
974
978
  this.fields[col.key].metadata.caption + ': ' + this.errors[col.key];
@@ -1021,10 +1025,10 @@ class rowHelperBase {
1021
1025
  }
1022
1026
  copyDataToObject(isNew = false) {
1023
1027
  let d = {};
1024
- for (const col of this.columnsInfo) {
1028
+ for (const col of this.fieldsMetadata) {
1025
1029
  let lu = this.lookups.get(col.key);
1026
1030
  let val = undefined;
1027
- const rel = getRelationInfo(col);
1031
+ const rel = getRelationInfo(col.options);
1028
1032
  if (lu)
1029
1033
  val = lu.id;
1030
1034
  else
@@ -1077,11 +1081,8 @@ class rowHelperBase {
1077
1081
  async __performColumnAndEntityValidations() { }
1078
1082
  toApiJson(includeRelatedEntities = false, notJustApi = false) {
1079
1083
  let result = {};
1080
- for (const col of this.columnsInfo) {
1081
- if (notJustApi ||
1082
- !this.remult ||
1083
- col.includeInApi === undefined ||
1084
- this.remult.isAllowedForInstance(this.instance, col.includeInApi)) {
1084
+ for (const col of this.fieldsMetadata) {
1085
+ if (notJustApi || !this.remult || col.includedInApi(this.instance)) {
1085
1086
  let val;
1086
1087
  let lu = this.lookups.get(col.key);
1087
1088
  let disable = false;
@@ -1094,7 +1095,7 @@ class rowHelperBase {
1094
1095
  else
1095
1096
  val = lu.id;
1096
1097
  else {
1097
- if (getRelationInfo(col) && !includeRelatedEntities) {
1098
+ if (getRelationInfo(col.options) && !includeRelatedEntities) {
1098
1099
  disable = true;
1099
1100
  }
1100
1101
  else {
@@ -1117,14 +1118,12 @@ class rowHelperBase {
1117
1118
  }
1118
1119
  async _updateEntityBasedOnApi(body, ignoreApiAllowed = false) {
1119
1120
  let keys = Object.keys(body);
1120
- for (const col of this.columnsInfo) {
1121
+ for (const col of this.fieldsMetadata) {
1121
1122
  if (keys.includes(col.key))
1122
- if (col.includeInApi === undefined ||
1123
- this.remult.isAllowedForInstance(this.instance, col.includeInApi)) {
1123
+ if (col.includedInApi(this.instance)) {
1124
1124
  if (!this.remult ||
1125
1125
  ignoreApiAllowed ||
1126
- col.allowApiUpdate === undefined ||
1127
- this.remult.isAllowedForInstance(this.instance, col.allowApiUpdate)) {
1126
+ col.apiUpdateAllowed(this.instance)) {
1128
1127
  let lu = this.lookups.get(col.key);
1129
1128
  if (lu)
1130
1129
  lu.id = body[col.key];
@@ -1142,20 +1141,20 @@ export class rowHelperImplementation extends rowHelperBase {
1142
1141
  edp;
1143
1142
  _isNew;
1144
1143
  constructor(info, instance, repository, edp, remult, _isNew) {
1145
- super(info.columnsInfo, instance, remult, _isNew);
1144
+ super(info.fieldsMetadata, instance, remult, _isNew);
1146
1145
  this.info = info;
1147
1146
  this.repository = repository;
1148
1147
  this.edp = edp;
1149
1148
  this._isNew = _isNew;
1150
1149
  this.metadata = info;
1151
1150
  if (_isNew) {
1152
- for (const col of info.columnsInfo) {
1153
- if (col.defaultValue && instance[col.key] === undefined) {
1154
- if (typeof col.defaultValue === 'function') {
1155
- instance[col.key] = col.defaultValue(instance);
1151
+ for (const col of info.fieldsMetadata) {
1152
+ if (col.options.defaultValue && instance[col.key] === undefined) {
1153
+ if (typeof col.options.defaultValue === 'function') {
1154
+ instance[col.key] = col.options.defaultValue(instance);
1156
1155
  }
1157
1156
  else if (!instance[col.key])
1158
- instance[col.key] = col.defaultValue;
1157
+ instance[col.key] = col.options.defaultValue;
1159
1158
  }
1160
1159
  }
1161
1160
  }
@@ -1221,8 +1220,8 @@ export class rowHelperImplementation extends rowHelperBase {
1221
1220
  [Symbol.iterator]: () => _items[Symbol.iterator](),
1222
1221
  toArray: () => _items,
1223
1222
  };
1224
- for (const c of this.info.columnsInfo) {
1225
- _items.push((r[c.key] = new FieldRefImplementation(c, this.info.fields[c.key], this.instance, this, this)));
1223
+ for (const c of this.info.fieldsMetadata) {
1224
+ _items.push((r[c.key] = new FieldRefImplementation(c.options, c, this.instance, this, this)));
1226
1225
  }
1227
1226
  this._columns = r;
1228
1227
  }
@@ -1389,9 +1388,9 @@ export class rowHelperImplementation extends rowHelperBase {
1389
1388
  async calcServerExpression() {
1390
1389
  if (isBackend())
1391
1390
  //y2 should be changed to be based on data provider - consider naming
1392
- for (const col of this.info.columnsInfo) {
1393
- if (col.serverExpression) {
1394
- this.instance[col.key] = await col.serverExpression(this.instance);
1391
+ for (const col of this.info.fieldsMetadata) {
1392
+ if (col.options.serverExpression) {
1393
+ this.instance[col.key] = await col.options.serverExpression(this.instance);
1395
1394
  }
1396
1395
  }
1397
1396
  }
@@ -1410,9 +1409,9 @@ export class rowHelperImplementation extends rowHelperBase {
1410
1409
  return false;
1411
1410
  }
1412
1411
  async __performColumnAndEntityValidations() {
1413
- for (const c of this.columnsInfo) {
1414
- if (c.validate) {
1415
- let col = new FieldRefImplementation(c, this.info.fields[c.key], this.instance, this, this);
1412
+ for (const c of this.fieldsMetadata) {
1413
+ if (c.options.validate) {
1414
+ let col = new FieldRefImplementation(c.options, c, this.instance, this, this);
1416
1415
  await col.__performValidation();
1417
1416
  }
1418
1417
  }
@@ -1426,7 +1425,7 @@ export class rowHelperImplementation extends rowHelperBase {
1426
1425
  }
1427
1426
  }
1428
1427
  }
1429
- const controllerColumns = Symbol('controllerColumns');
1428
+ const controllerColumns = Symbol.for('controllerColumns');
1430
1429
  function prepareColumnInfo(r, remult) {
1431
1430
  return r.map((x) => decorateColumnSettings(x.settings(remult), remult));
1432
1431
  }
@@ -1450,7 +1449,7 @@ export function getControllerRef(container, remultArg) {
1450
1449
  }
1451
1450
  base = Object.getPrototypeOf(base);
1452
1451
  }
1453
- container[controllerColumns] = result = new controllerRefImpl(prepareColumnInfo(columnSettings, remultVar), container, remultVar);
1452
+ container[controllerColumns] = result = new controllerRefImpl(prepareColumnInfo(columnSettings, remultVar).map((x) => new columnDefsImpl(x, undefined, remultVar)), container, remultVar);
1454
1453
  }
1455
1454
  return result;
1456
1455
  }
@@ -1464,7 +1463,7 @@ export class controllerRefImpl extends rowHelperBase {
1464
1463
  toArray: () => _items,
1465
1464
  };
1466
1465
  for (const col of columnsInfo) {
1467
- _items.push((r[col.key] = new FieldRefImplementation(col, new columnDefsImpl(col, undefined, remult), instance, undefined, this)));
1466
+ _items.push((r[col.key] = new FieldRefImplementation(col.options, col, instance, undefined, this)));
1468
1467
  }
1469
1468
  this.fields = r;
1470
1469
  }
@@ -1525,6 +1524,8 @@ export class FieldRefImplementation {
1525
1524
  .relations(this.container)[this.metadata.key].findOne();
1526
1525
  if (val)
1527
1526
  this.container[this.metadata.key] = val;
1527
+ else
1528
+ return null;
1528
1529
  }
1529
1530
  }
1530
1531
  else if (lu) {
@@ -1620,14 +1621,42 @@ export class FieldRefImplementation {
1620
1621
  entityRef;
1621
1622
  async __performValidation() {
1622
1623
  try {
1623
- let x = typeof this.settings.validate;
1624
- if (Array.isArray(this.settings.validate)) {
1625
- for (const v of this.settings.validate) {
1626
- await v(this.container, this);
1624
+ const processValidation = (result) => {
1625
+ if (result !== true && result !== undefined && !this.error) {
1626
+ if (typeof result === 'string' && result.length > 0)
1627
+ this.error = result;
1628
+ else
1629
+ this.error = 'invalid value';
1627
1630
  }
1631
+ };
1632
+ if (this.settings.validate) {
1633
+ let self = this;
1634
+ let event = {
1635
+ entityRef: this.entityRef,
1636
+ get error() {
1637
+ return self.error;
1638
+ },
1639
+ set error(value) {
1640
+ self.error = value;
1641
+ },
1642
+ isNew: self.rowBase.isNewRow,
1643
+ load: () => self.load(),
1644
+ metadata: self.metadata,
1645
+ originalValue: self.originalValue,
1646
+ value: self.value,
1647
+ valueChanged: () => self.valueChanged(),
1648
+ originalValueIsNull: () => self.originalValueIsNull(),
1649
+ valueIsNull: () => self.valueIsNull(),
1650
+ isBackend: () => !self.rowBase?.remult?.dataProvider?.isProxy,
1651
+ };
1652
+ if (Array.isArray(this.settings.validate)) {
1653
+ for (const v of this.settings.validate) {
1654
+ processValidation(await v(this.container, event));
1655
+ }
1656
+ }
1657
+ else if (typeof this.settings.validate === 'function')
1658
+ processValidation(await this.settings.validate(this.container, event));
1628
1659
  }
1629
- else if (typeof this.settings.validate === 'function')
1630
- await this.settings.validate(this.container, this);
1631
1660
  }
1632
1661
  catch (error) {
1633
1662
  if (typeof error === 'string')
@@ -1698,7 +1727,28 @@ export class columnDefsImpl {
1698
1727
  this.remult = remult;
1699
1728
  this.options = this.settings;
1700
1729
  this.target = this.settings.target;
1701
- this.valueConverter = this.settings.valueConverter;
1730
+ this.valueConverter = new Proxy(this.settings.valueConverter, {
1731
+ get: (target, prop) => {
1732
+ let result = target[prop];
1733
+ if (typeof result === 'function') {
1734
+ return (...args) => {
1735
+ try {
1736
+ return target[prop](...args);
1737
+ }
1738
+ catch (err) {
1739
+ const error = `${String(prop)} failed for value ${args?.[0]}. Error: ${typeof err === 'string' ? err : err.message}`;
1740
+ throw {
1741
+ message: this.caption + ': ' + error,
1742
+ modelState: {
1743
+ [this.key]: error,
1744
+ },
1745
+ };
1746
+ }
1747
+ };
1748
+ }
1749
+ return result;
1750
+ },
1751
+ });
1702
1752
  this.allowNull = !!this.settings.allowNull;
1703
1753
  this.valueType = this.settings.valueType;
1704
1754
  this.key = this.settings.key;
@@ -1754,14 +1804,13 @@ export class columnDefsImpl {
1754
1804
  valueType;
1755
1805
  }
1756
1806
  class EntityFullInfo {
1757
- columnsInfo;
1758
1807
  entityInfo;
1759
1808
  remult;
1760
1809
  entityType;
1761
1810
  key;
1762
1811
  options;
1812
+ fieldsMetadata = [];
1763
1813
  constructor(columnsInfo, entityInfo, remult, entityType, key) {
1764
- this.columnsInfo = columnsInfo;
1765
1814
  this.entityInfo = entityInfo;
1766
1815
  this.remult = remult;
1767
1816
  this.entityType = entityType;
@@ -1788,14 +1837,14 @@ class EntityFullInfo {
1788
1837
  this.key = entityType.name;
1789
1838
  if (!entityInfo.dbName)
1790
1839
  entityInfo.dbName = this.key;
1791
- let _items = [];
1840
+ this.dbName = entityInfo.dbName;
1792
1841
  let r = {
1793
1842
  find: (c) => r[typeof c === 'string' ? c : c.key],
1794
- [Symbol.iterator]: () => _items[Symbol.iterator](),
1795
- toArray: () => _items,
1843
+ [Symbol.iterator]: () => this.fieldsMetadata[Symbol.iterator](),
1844
+ toArray: () => this.fieldsMetadata,
1796
1845
  };
1797
1846
  for (const x of columnsInfo) {
1798
- _items.push((r[x.key] = new columnDefsImpl(x, this, remult)));
1847
+ this.fieldsMetadata.push((r[x.key] = new columnDefsImpl(x, this, remult)));
1799
1848
  }
1800
1849
  this.fields = r;
1801
1850
  this.caption = buildCaption(entityInfo.caption, this.key, remult, this);
@@ -1903,7 +1952,7 @@ export function FieldType(...options) {
1903
1952
  options = [];
1904
1953
  }
1905
1954
  options.splice(0, 0, { valueType: target });
1906
- Reflect.defineMetadata(storableMember, options, target);
1955
+ target[storableMember] = options;
1907
1956
  return target;
1908
1957
  };
1909
1958
  }
@@ -1950,7 +1999,7 @@ export class ValueListInfo {
1950
1999
  if (this.isNumeric) {
1951
2000
  this.fieldTypeInDb = 'integer';
1952
2001
  }
1953
- var options = Reflect.getMetadata(storableMember, this.valueListType);
2002
+ var options = this.valueListType[storableMember];
1954
2003
  if (options) {
1955
2004
  for (const op of options) {
1956
2005
  if (op?.getValues) {
@@ -2020,7 +2069,7 @@ export function getValueList(type) {
2020
2069
  }
2021
2070
  return ValueListInfo.get(type).getValues();
2022
2071
  }
2023
- export const storableMember = Symbol('storableMember');
2072
+ export const storableMember = Symbol.for('storableMember');
2024
2073
  export function buildOptions(options, remult) {
2025
2074
  let r = {};
2026
2075
  for (const o of options) {
@@ -2035,7 +2084,7 @@ export function buildOptions(options, remult) {
2035
2084
  }
2036
2085
  export function decorateColumnSettings(settings, remult) {
2037
2086
  if (settings.valueType) {
2038
- let settingsOnTypeLevel = Reflect.getMetadata(storableMember, settings.valueType);
2087
+ let settingsOnTypeLevel = settings.valueType[storableMember];
2039
2088
  if (settingsOnTypeLevel) {
2040
2089
  settings = {
2041
2090
  ...buildOptions(settingsOnTypeLevel, remult),
@@ -62,8 +62,8 @@ export function Entity(key, ...options) {
62
62
  };
63
63
  allEntities.push(target);
64
64
  setControllerSettings(target, { key });
65
- Reflect.defineMetadata(entityInfo, factory, target);
66
- Reflect.defineMetadata(entityInfo_key, key, target);
65
+ target[entityInfo] = factory;
66
+ target[entityInfo_key] = key;
67
67
  return target;
68
68
  };
69
69
  }
@@ -6,9 +6,9 @@ export function getEntityRef(entity, throwException = true) {
6
6
  ' was not initialized using a context');
7
7
  return x;
8
8
  }
9
- export const entityMember = Symbol('entityMember');
10
- export const entityInfo = Symbol('entityInfo');
11
- export const entityInfo_key = Symbol('entityInfo_key');
9
+ export const entityMember = Symbol.for('entityMember');
10
+ export const entityInfo = Symbol.for('entityInfo');
11
+ export const entityInfo_key = Symbol.for('entityInfo_key');
12
12
  export function getEntitySettings(entity, throwError = true) {
13
13
  if (entity === undefined)
14
14
  if (throwError) {
@@ -16,12 +16,12 @@ export function getEntitySettings(entity, throwError = true) {
16
16
  }
17
17
  else
18
18
  return undefined;
19
- let info = Reflect.getMetadata(entityInfo, entity);
19
+ let info = entity[entityInfo];
20
20
  if (!info && throwError)
21
21
  throw new Error(entity.prototype.constructor.name +
22
22
  " is not a known entity, did you forget to set @Entity() or did you forget to add the '@' before the call to Entity?");
23
23
  return info;
24
24
  }
25
25
  export function getEntityKey(entity) {
26
- return Reflect.getMetadata(entityInfo_key, entity);
26
+ return entity[entityInfo_key];
27
27
  }
@@ -1,4 +1,4 @@
1
- export const relationInfoMember = Symbol('relationInfo');
1
+ export const relationInfoMember = Symbol.for('relationInfo');
2
2
  export function getRelationInfo(options) {
3
3
  return options?.[relationInfoMember];
4
4
  }
@@ -1,12 +1,7 @@
1
1
  export {};
2
- /*y1 - investigated sqlite in web, the chrome recommendation seemed hardly used -
3
- - https://developer.chrome.com/blog/sqlite-wasm-in-the-browser-backed-by-the-origin-private-file-system/
4
- - https://www.npmjs.com/package/@sqlite.org/sqlite-wasm
5
- All I could find is correspondance around evolu :) the guy I met in prague last year
6
- Indexed db was also annoying with it's upgrade process etc...
7
- I'm OPFS & json for now.
2
+ //y1 - discuss limit and include queries
3
+ //y1 - tried to upgrade vitest, nuxt tests are failing with loading uuid - sounds familiar?
8
4
  //y2 - from the crm-demo(https://crm-demo.up.railway.app/deals), after editing a deal: - _updateEntityBasedOnApi
9
-
10
5
  //y2 - Backend methods are transactions, it's not intuitive and maybe should be optional / opt in
11
6
  //y2 - how to run a transaction as a user
12
7
  //p1 - enum column
@@ -48,6 +43,8 @@ remult.apiClient.url='localhost:3007/api
48
43
 
49
44
 
50
45
  */
46
+ //p2 - document validators
47
+ //p2 - and validators to reference
51
48
  //y2 - discuss a default date formatter
52
49
  //y2 - add some api testing framework for user unit tests (will help with codesandbox based discussions)
53
50
  //y2 - live query refresh of view on table update
@@ -71,3 +68,5 @@ remult.apiClient.url='localhost:3007/api
71
68
  //[ ] V2 - what to do about for relations count?
72
69
  //[ ] V2 - condition? not to fetch if null etc....
73
70
  //[ ] V3 - all these fields will also appear in the where etc... in the typescript api - but we will not enforce them
71
+ //y2 - consider if field types should include validation in them by default (string,number that it's not NaN etc...) and if so, what message?
72
+ //y2 - should enforce integer - currently we probably round / truncate it
@@ -9,4 +9,4 @@ export const actionInfo = {
9
9
  close: () => { },
10
10
  }),
11
11
  };
12
- export const serverActionField = Symbol('serverActionField');
12
+ export const serverActionField = Symbol.for('serverActionField');
@@ -395,4 +395,4 @@ export async function prepareReceivedArgs(types, args, remult, ds, res) {
395
395
  }
396
396
  return args;
397
397
  }
398
- export const classBackendMethodsArray = Symbol('classBackendMethodsArray');
398
+ export const classBackendMethodsArray = Symbol.for('classBackendMethodsArray');
@@ -1,36 +1,128 @@
1
- import { isBackend } from './context.js';
2
1
  export class Validators {
3
- static required = Object.assign((entity, col, message = undefined) => {
4
- if (!col.value ||
5
- (typeof col.value === 'string' && col.value.trim().length == 0))
6
- col.error = message || Validators.required.defaultMessage;
7
- }, {
8
- withMessage: (message) => {
9
- return (entity, col) => Validators.required(entity, col, message);
10
- },
11
- defaultMessage: 'Should not be empty',
12
- });
13
- static unique = Object.assign(async (entity, col, message = undefined) => {
14
- if (!col.entityRef)
2
+ static required = createValidator(async (_, e) => e.value != null && e.value != undefined && e.value !== '' && e.value != 0, 'Should not be empty');
3
+ static unique = createValidator(async (_, e) => {
4
+ if (!e.entityRef)
15
5
  throw 'unique validation may only work on columns that are attached to an entity';
16
- if (col.entityRef.isNew() || col.valueChanged()) {
17
- if (await col.entityRef.repository.count({
18
- [col.metadata.key]: col.value,
19
- }))
20
- col.error = message || Validators.unique.defaultMessage;
6
+ if (e.isBackend() && (e.isNew || e.valueChanged())) {
7
+ return ((await e.entityRef.repository.count({
8
+ [e.metadata.key]: e.value,
9
+ })) == 0);
21
10
  }
22
- }, {
23
- withMessage: (message) => {
24
- return (entity, col) => Validators.unique(entity, col, message);
11
+ else
12
+ return true;
13
+ }, 'already exists');
14
+ /**
15
+ * @deprecated is `unique` instead - it also runs only on the backend
16
+ */
17
+ static uniqueOnBackend = createValidator(async (_, e) => {
18
+ if (e.isBackend() && (e.isNew || e.valueChanged())) {
19
+ return ((await e.entityRef.repository.count({
20
+ [e.metadata.key]: e.value,
21
+ })) == 0);
22
+ }
23
+ else
24
+ return true;
25
+ }, Validators.unique.defaultMessage);
26
+ static regex = createValueValidatorWithArgs((val, regex) => regex.test(val));
27
+ static email = createValueValidator((val) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), 'Invalid Email');
28
+ static url = createValueValidator((val) => !!new URL(val), 'Invalid Url');
29
+ static in = createValueValidatorWithArgs((val, values) => values.includes(val), (values) => `Value must be one of ${values.join(', ')}`);
30
+ static notNull = createValueValidator((val) => val != null, 'Should not be null');
31
+ static enum = createValueValidatorWithArgs((value, enumObj) => Object.values(enumObj).includes(value), (enumObj) => `Value must be one of ${Object.values(enumObj)
32
+ .filter((x) => typeof enumObj[x] !== 'number')
33
+ .join(', ')}`);
34
+ static relationExists = createValidator(async (_, e) => {
35
+ if (e.valueIsNull())
36
+ return true;
37
+ if (!e.isBackend())
38
+ return true;
39
+ return Boolean(await e.load());
40
+ }, 'Relation value does not exist');
41
+ static maxLength = createValueValidatorWithArgs((val, maxLength) => val.length <= maxLength, (maxLength) => `Value must be at most ${maxLength} characters`);
42
+ static defaultMessage = 'Invalid value';
43
+ }
44
+ export function createValidator(validate, defaultMessage) {
45
+ const validation = async (entity, e, message) => {
46
+ const valid = await validate(entity, e);
47
+ if (typeof valid === 'string' && valid.length > 0)
48
+ e.error = valid;
49
+ else if (!valid)
50
+ e.error =
51
+ (typeof message === 'function' && message(entity, e, undefined)) ||
52
+ message ||
53
+ (typeof defaultMessage === 'function' &&
54
+ defaultMessage(entity, e, undefined)) ||
55
+ defaultMessage ||
56
+ Validators.defaultMessage;
57
+ };
58
+ const result = (entityOrMessage, e, message) => {
59
+ if (typeof entityOrMessage === 'string' ||
60
+ entityOrMessage === 'function' ||
61
+ (entityOrMessage === undefined && e === undefined)) {
62
+ return async (entity, e, message) => await validation(entity, e, entityOrMessage || message);
63
+ }
64
+ return validation(entityOrMessage, e, message);
65
+ };
66
+ //@ts-ignore
67
+ return Object.assign(result, {
68
+ withMessage: (message) => async (entity, e) => result(entity, e, message),
69
+ get defaultMessage() {
70
+ return defaultMessage;
71
+ },
72
+ set defaultMessage(val) {
73
+ defaultMessage = val;
74
+ },
75
+ isValid: validate,
76
+ });
77
+ }
78
+ export function valueValidator(validate, defaultMessage) {
79
+ return (entity, e) => validate(e.value) || defaultMessage || false;
80
+ }
81
+ export function createValueValidator(validate, defaultMessage) {
82
+ return createValidator((_, e) => {
83
+ if (e.value === undefined || e.value === null)
84
+ return true;
85
+ return validate(e.value);
86
+ }, defaultMessage);
87
+ }
88
+ export function createValueValidatorWithArgs(validate, defaultMessage) {
89
+ const result = createValidatorWithArgs((_, e, args) => {
90
+ if (e.value === undefined || e.value === null)
91
+ return true;
92
+ return validate(e.value, args);
93
+ }, (_, e, args) => (typeof defaultMessage === 'function' && defaultMessage(args)) ||
94
+ defaultMessage);
95
+ return Object.assign((entity, e) => result(entity, e), {
96
+ get defaultMessage() {
97
+ return defaultMessage;
98
+ },
99
+ set defaultMessage(val) {
100
+ defaultMessage = val;
25
101
  },
26
- defaultMessage: 'already exists',
27
102
  });
28
- static uniqueOnBackend = Object.assign(async (entity, col, message = undefined) => {
29
- if (isBackend())
30
- return Validators.unique(entity, col, message);
31
- }, {
32
- withMessage: (message) => {
33
- return (entity, col) => Validators.uniqueOnBackend(entity, col, message);
103
+ }
104
+ export function createValidatorWithArgs(validate, defaultMessage) {
105
+ const result = (args, message) => async (entity, e) => {
106
+ const valid = await validate(entity, e, args);
107
+ if (typeof valid === 'string')
108
+ e.error = valid;
109
+ else if (!valid)
110
+ e.error =
111
+ message || defaultMessage
112
+ ? typeof defaultMessage === 'function'
113
+ ? defaultMessage(entity, e, args)
114
+ : defaultMessage
115
+ : Validators.defaultMessage;
116
+ };
117
+ return Object.assign(result, {
118
+ get defaultMessage() {
119
+ return defaultMessage;
120
+ },
121
+ set defaultMessage(val) {
122
+ defaultMessage = val;
34
123
  },
35
124
  });
36
125
  }
126
+ //y1 - talk about the 3rd parameter as message
127
+ //y1 - talk about the changed signature breaking existing validations and preventing the usage of unique outside the normal flow
128
+ //y1 - talk about the defaultMessage value now being a function that get's too many arguments, that makes it's reuse that much harder