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.
- package/README.md +194 -129
- package/esm/graphql.js +131 -59
- package/esm/index.js +1 -1
- package/esm/migrations/compare-migration-snapshots.js +61 -0
- package/esm/migrations/generate-migrations.js +68 -0
- package/esm/migrations/index.js +2 -0
- package/esm/migrations/migrate.js +90 -0
- package/esm/migrations/migration-types.js +36 -0
- package/esm/migrations/update-migrations-ts-file.js +115 -0
- package/esm/postgres/postgres-data-provider.js +24 -3
- package/esm/postgres/schema-builder.js +35 -26
- package/esm/remult-better-sqlite3.js +47 -0
- package/esm/remult-bun-sqlite.js +41 -0
- package/esm/remult-knex/index.js +180 -51
- package/esm/remult-mongo.js +2 -2
- package/esm/remult-sql-js.js +3 -78
- package/esm/remult-sqlite-core.js +104 -0
- package/esm/server/expressBridge.js +130 -29
- package/esm/server/initAsyncHooks.js +75 -1
- package/esm/src/CompoundIdField.js +8 -2
- package/esm/src/context.js +20 -24
- package/esm/src/data-api.js +71 -30
- package/esm/src/data-providers/rest-data-provider.js +17 -15
- package/esm/src/data-providers/sql-database.js +32 -7
- package/esm/src/data-providers/web-sql-data-provider.js +1 -9
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +37 -11
- package/esm/src/filter/filter-interfaces.js +229 -17
- package/esm/src/isOfType.js +9 -0
- package/esm/src/remult3/Fields.js +70 -18
- package/esm/src/remult3/RepositoryImplementation.js +47 -21
- package/esm/src/remult3/addValidator.js +15 -0
- package/esm/src/remult3/remult3.js +28 -57
- package/esm/src/server-action.js +4 -4
- package/esm/src/validators.js +6 -3
- package/esm/src/valueConverters.js +1 -1
- package/graphql.js +156 -71
- package/index.d.ts +4 -4
- package/index.js +2 -1
- package/migrations/compare-migration-snapshots.d.ts +27 -0
- package/migrations/compare-migration-snapshots.js +165 -0
- package/migrations/generate-migrations.d.ts +21 -0
- package/migrations/generate-migrations.js +104 -0
- package/migrations/index.d.ts +3 -0
- package/migrations/index.js +7 -0
- package/migrations/migrate.d.ts +27 -0
- package/migrations/migrate.js +175 -0
- package/migrations/migration-types.d.ts +48 -0
- package/migrations/migration-types.js +88 -0
- package/migrations/update-migrations-ts-file.d.ts +1 -0
- package/migrations/update-migrations-ts-file.js +141 -0
- package/package.json +21 -1
- package/postgres/postgres-data-provider.d.ts +8 -3
- package/postgres/postgres-data-provider.js +46 -3
- package/postgres/schema-builder.js +81 -48
- package/remult-better-sqlite3.d.ts +12 -0
- package/remult-better-sqlite3.js +64 -0
- package/remult-bun-sqlite.d.ts +11 -0
- package/remult-bun-sqlite.js +56 -0
- package/remult-knex/index.d.ts +12 -5
- package/remult-knex/index.js +257 -90
- package/remult-mongo.d.ts +2 -2
- package/remult-mongo.js +2 -2
- package/remult-sql-js.d.ts +2 -15
- package/remult-sql-js.js +11 -152
- package/remult-sqlite-core.d.ts +21 -0
- package/remult-sqlite-core.js +217 -0
- package/server/expressBridge.d.ts +30 -1
- package/server/expressBridge.js +140 -32
- package/server/initAsyncHooks.d.ts +16 -0
- package/server/initAsyncHooks.js +128 -2
- package/src/CompoundIdField.js +8 -2
- package/src/column-interfaces.d.ts +9 -3
- package/src/context.d.ts +8 -6
- package/src/context.js +27 -36
- package/src/data-api.d.ts +13 -9
- package/src/data-api.js +184 -135
- package/src/data-providers/rest-data-provider.js +26 -15
- package/src/data-providers/sql-database.d.ts +17 -5
- package/src/data-providers/sql-database.js +31 -7
- package/src/data-providers/web-sql-data-provider.d.ts +1 -2
- package/src/data-providers/web-sql-data-provider.js +4 -8
- package/src/entity.d.ts +64 -6
- package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +31 -7
- package/src/filter/filter-consumer-bridge-to-sql-request.js +46 -21
- package/src/filter/filter-interfaces.d.ts +106 -39
- package/src/filter/filter-interfaces.js +312 -32
- package/src/isOfType.d.ts +2 -0
- package/src/isOfType.js +14 -0
- package/src/remult3/Fields.d.ts +36 -0
- package/src/remult3/Fields.js +81 -19
- package/src/remult3/RepositoryImplementation.d.ts +7 -3
- package/src/remult3/RepositoryImplementation.js +106 -71
- package/src/remult3/addValidator.d.ts +2 -0
- package/src/remult3/addValidator.js +20 -0
- package/src/remult3/remult3.d.ts +6 -2
- package/src/remult3/remult3.js +28 -57
- package/src/server-action.d.ts +1 -1
- package/src/server-action.js +5 -4
- package/src/sql-command.d.ts +6 -1
- package/src/validators.d.ts +1 -0
- package/src/validators.js +10 -4
- 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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
623
|
+
addToFilterObject(col.key, null);
|
|
532
624
|
break;
|
|
533
625
|
default:
|
|
534
|
-
|
|
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
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
695
|
-
|
|
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,
|
|
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 (
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1926
|
-
|
|
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
|
-
|
|
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
|
+
}
|