@cap-js-community/common 0.2.1 → 0.2.3
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/CHANGELOG.md +79 -0
- package/README.md +3 -0
- package/package.json +5 -4
- package/src/replication-cache/ReplicationCache.js +168 -38
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## Version 0.2.3 - 2025-07-01
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Improve reference detection
|
|
13
|
+
|
|
14
|
+
## Version 0.2.2 - 2025-06-26
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Improve reference detection for aliases
|
|
19
|
+
|
|
20
|
+
## Version 0.2.1 - 2025-06-26
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Improve reference detection
|
|
25
|
+
- Static non-tenant aware replication via `cds.replicate.static`
|
|
26
|
+
|
|
27
|
+
## Version 0.2.0 - 2025-06-03
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- CDS 9 compatibility
|
|
32
|
+
|
|
33
|
+
## Version 0.1.7 - 2025-05-08
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Enabling journal mode and changing entity in same cycle is not allowed
|
|
38
|
+
|
|
39
|
+
## Version 0.1.6 - 2025-05-07
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Redis 4
|
|
44
|
+
|
|
45
|
+
## Version 0.1.5 - 2025-05-05
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Dependencies
|
|
50
|
+
|
|
51
|
+
## Version 0.1.4 - 2025-04-10
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- Improvements
|
|
56
|
+
|
|
57
|
+
## Version 0.1.3 - 2025-04-10
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- Redis client improvements
|
|
62
|
+
|
|
63
|
+
## Version 0.1.2 - 2025-04-09
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- Improvements
|
|
68
|
+
|
|
69
|
+
## Version 0.1.1 - 2025-04-09
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- Improvements
|
|
74
|
+
|
|
75
|
+
## Version 0.1.0 - 2025-04-09
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- Internal release
|
package/README.md
CHANGED
|
@@ -121,6 +121,7 @@ Options can be passed to migration check via CDS environment via `cds.migrationC
|
|
|
121
121
|
|
|
122
122
|
- Maintain the whitelist extension file `migration-extension-whitelist.json` for compatible changes:
|
|
123
123
|
- **Whitelist Entity**:
|
|
124
|
+
|
|
124
125
|
```json
|
|
125
126
|
{
|
|
126
127
|
"definitions": {
|
|
@@ -128,7 +129,9 @@ Options can be passed to migration check via CDS environment via `cds.migrationC
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
```
|
|
132
|
+
|
|
131
133
|
- **Whitelist Entity Element**:
|
|
134
|
+
|
|
132
135
|
```json
|
|
133
136
|
{
|
|
134
137
|
"definitions": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/common",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "CAP Node.js Community Common",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"engines": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"bin",
|
|
21
21
|
"src",
|
|
22
22
|
"LICENSE",
|
|
23
|
-
"cds-plugin.js"
|
|
23
|
+
"cds-plugin.js",
|
|
24
|
+
"CHANGELOG.md"
|
|
24
25
|
],
|
|
25
26
|
"main": "index.js",
|
|
26
27
|
"types": "index.d.ts",
|
|
@@ -55,14 +56,14 @@
|
|
|
55
56
|
"@sap/cds": "^9.0.4",
|
|
56
57
|
"@sap/cds-common-content": "^3.0.1",
|
|
57
58
|
"@sap/cds-dk": "^9.0.5",
|
|
58
|
-
"eslint": "9.
|
|
59
|
+
"eslint": "9.30.0",
|
|
59
60
|
"eslint-config-prettier": "10.1.5",
|
|
60
61
|
"eslint-plugin-jest": "29.0.1",
|
|
61
62
|
"eslint-plugin-n": "^17.20.0",
|
|
62
63
|
"jest": "30.0.3",
|
|
63
64
|
"jest-html-reporters": "3.1.7",
|
|
64
65
|
"jest-junit": "16.0.0",
|
|
65
|
-
"prettier": "3.6.
|
|
66
|
+
"prettier": "3.6.2",
|
|
66
67
|
"shelljs": "^0.10.0"
|
|
67
68
|
},
|
|
68
69
|
"cds": {
|
|
@@ -178,7 +178,7 @@ class ReplicationCache {
|
|
|
178
178
|
if (fromRefs.length === 0 || !this.relevant(fromRefs)) {
|
|
179
179
|
return await next();
|
|
180
180
|
}
|
|
181
|
-
let refs = queryRefs(model, req.query);
|
|
181
|
+
let refs = queryRefs(model, req.query, req.target);
|
|
182
182
|
if (!this.options.deploy) {
|
|
183
183
|
if (!this.localized(req.query, refs)) {
|
|
184
184
|
return await next();
|
|
@@ -818,13 +818,18 @@ function baseRefs(model, refs) {
|
|
|
818
818
|
const baseRefs = [];
|
|
819
819
|
let currentRefs = refs;
|
|
820
820
|
let nextRefs = [];
|
|
821
|
+
const visited = new Set();
|
|
821
822
|
while (currentRefs.length > 0) {
|
|
822
823
|
for (const ref of currentRefs) {
|
|
824
|
+
if (visited.has(ref)) {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
visited.add(ref);
|
|
823
828
|
const definition = model.definitions[ref];
|
|
824
829
|
if (!definition.query) {
|
|
825
830
|
baseRefs.push(ref);
|
|
826
831
|
} else {
|
|
827
|
-
nextRefs = nextRefs.concat(queryRefs(model, definition.query));
|
|
832
|
+
nextRefs = nextRefs.concat(queryRefs(model, definition.query, definition));
|
|
828
833
|
}
|
|
829
834
|
}
|
|
830
835
|
currentRefs = nextRefs;
|
|
@@ -853,49 +858,148 @@ function queryFromRefs(model, query) {
|
|
|
853
858
|
return unique(selectFromRefs(model, query));
|
|
854
859
|
}
|
|
855
860
|
|
|
856
|
-
function queryRefs(model, query) {
|
|
861
|
+
function queryRefs(model, query, definition) {
|
|
857
862
|
if (!query.SELECT) {
|
|
858
863
|
return [];
|
|
859
864
|
}
|
|
860
|
-
return unique(selectRefs(model, query));
|
|
865
|
+
return unique(selectRefs(model, definition, query));
|
|
861
866
|
}
|
|
862
867
|
|
|
863
868
|
function selectFromRefs(model, query) {
|
|
864
869
|
let refs = [];
|
|
865
|
-
if (query.SELECT.from.
|
|
866
|
-
refs = selectFromRefs(model, query.SELECT.from);
|
|
867
|
-
} else if (query.SELECT.from.ref) {
|
|
870
|
+
if (query.SELECT.from.ref) {
|
|
868
871
|
refs = resolveRefs(model, query.SELECT.from.ref);
|
|
869
|
-
} else if (
|
|
872
|
+
} else if (query.SELECT.from.args) {
|
|
870
873
|
refs = query.SELECT.from.args.reduce((refs, arg) => {
|
|
871
|
-
|
|
874
|
+
if (arg.ref) {
|
|
875
|
+
refs = refs.concat(resolveRefs(model, arg.ref));
|
|
876
|
+
} else if (arg.args) {
|
|
877
|
+
refs = refs.concat(selectFromRefs(model, { SELECT: { from: { args: arg.args } } }));
|
|
878
|
+
}
|
|
872
879
|
return refs;
|
|
873
880
|
}, []);
|
|
881
|
+
} else if (query.SELECT.from.SELECT) {
|
|
882
|
+
refs = selectFromRefs(model, query.SELECT.from);
|
|
874
883
|
}
|
|
875
884
|
return refs;
|
|
876
885
|
}
|
|
877
886
|
|
|
878
|
-
function
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
887
|
+
function selectFromPrimaryRef(model, query) {
|
|
888
|
+
if (query.SELECT.from.ref) {
|
|
889
|
+
return resolveRef(model, query.SELECT.from.ref);
|
|
890
|
+
} else if (query.SELECT.from.args) {
|
|
891
|
+
for (const arg of query.SELECT.from.args) {
|
|
892
|
+
if (arg.ref) {
|
|
893
|
+
return resolveRef(model, arg.ref);
|
|
894
|
+
} else if (arg.args) {
|
|
895
|
+
return selectFromPrimaryRef(model, { SELECT: { from: { args: arg.args } } });
|
|
896
|
+
}
|
|
884
897
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
898
|
+
} else if (query.SELECT.from.SELECT) {
|
|
899
|
+
return selectFromPrimaryRef(model, query.SELECT.from);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function selectFromAliases(model, definition, query) {
|
|
904
|
+
let aliases = {};
|
|
905
|
+
if (definition?.name) {
|
|
906
|
+
aliases["$self"] = definition.name;
|
|
907
|
+
}
|
|
908
|
+
if (query.SELECT.from.SELECT) {
|
|
909
|
+
// Sub-select aliases are not (yet) supported
|
|
910
|
+
} else if (query.SELECT.from.ref) {
|
|
911
|
+
const ref = resolveRef(model, query.SELECT.from.ref);
|
|
912
|
+
if (query.SELECT.from.as) {
|
|
913
|
+
aliases[query.SELECT.from.as] = ref;
|
|
914
|
+
} else {
|
|
915
|
+
const as = ref.split(".").pop();
|
|
916
|
+
aliases[as] = ref;
|
|
888
917
|
}
|
|
889
|
-
|
|
890
|
-
|
|
918
|
+
} else if (query.SELECT.from.args) {
|
|
919
|
+
for (const arg of query.SELECT.from.args) {
|
|
920
|
+
if (arg.ref) {
|
|
921
|
+
const ref = resolveRef(model, arg.ref);
|
|
922
|
+
if (arg.as) {
|
|
923
|
+
aliases[arg.as] = ref;
|
|
924
|
+
} else {
|
|
925
|
+
const as = ref.split(".").pop();
|
|
926
|
+
aliases[as] = ref;
|
|
927
|
+
}
|
|
928
|
+
} else if (arg.args) {
|
|
929
|
+
for (const subArg of arg.args) {
|
|
930
|
+
aliases = {
|
|
931
|
+
...aliases,
|
|
932
|
+
...selectFromAliases(model, definition, { SELECT: { from: { args: subArg.args } } }),
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
}
|
|
891
936
|
}
|
|
892
|
-
|
|
893
|
-
|
|
937
|
+
}
|
|
938
|
+
return aliases;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function selectOuterAliases(model, target, columns, mixins, aliases) {
|
|
942
|
+
const outerAliases = {};
|
|
943
|
+
if (columns) {
|
|
944
|
+
for (const column of columns) {
|
|
945
|
+
if (column.ref) {
|
|
946
|
+
const as = column.as || column.ref[column.ref.length - 1];
|
|
947
|
+
let current = target;
|
|
948
|
+
for (const ref of column.ref) {
|
|
949
|
+
const currentTarget = targetEntity(model, current, mixins, aliases, ref);
|
|
950
|
+
if (currentTarget != null) {
|
|
951
|
+
current = currentTarget;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
outerAliases[as] = (current || target).name;
|
|
955
|
+
}
|
|
894
956
|
}
|
|
895
957
|
}
|
|
958
|
+
return outerAliases;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function selectRefs(model, definition, query, aliases) {
|
|
962
|
+
let targetName = selectFromPrimaryRef(model, query);
|
|
963
|
+
const target = targetName ? model.definitions[targetName] : undefined;
|
|
964
|
+
let refs = selectFromRefs(model, query);
|
|
965
|
+
aliases = {
|
|
966
|
+
...aliases,
|
|
967
|
+
...selectFromAliases(model, definition, query),
|
|
968
|
+
};
|
|
969
|
+
if (query.SELECT.columns) {
|
|
970
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.columns, query.SELECT.mixin, aliases));
|
|
971
|
+
refs = refs.concat(expandRefs(model, target, query.SELECT.columns, query.SELECT.mixin, aliases));
|
|
972
|
+
}
|
|
973
|
+
if (query.SELECT.where) {
|
|
974
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.where, query.SELECT.mixin, aliases));
|
|
975
|
+
}
|
|
976
|
+
const outerAliases = {
|
|
977
|
+
...aliases,
|
|
978
|
+
...selectOuterAliases(model, target, query.SELECT.columns, query.SELECT.mixin, aliases),
|
|
979
|
+
};
|
|
980
|
+
if (query.SELECT.having) {
|
|
981
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.having, query.SELECT.mixin, outerAliases));
|
|
982
|
+
}
|
|
983
|
+
if (query.SELECT.orderBy) {
|
|
984
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.orderBy, query.SELECT.mixin, outerAliases));
|
|
985
|
+
}
|
|
896
986
|
return refs;
|
|
897
987
|
}
|
|
898
988
|
|
|
989
|
+
function resolveRef(model, refs) {
|
|
990
|
+
let ref = refs[0];
|
|
991
|
+
if (ref.id) {
|
|
992
|
+
ref = ref.id;
|
|
993
|
+
}
|
|
994
|
+
let current = model.definitions[ref];
|
|
995
|
+
for (const ref of refs.slice(1)) {
|
|
996
|
+
if (current.elements[ref].type === "cds.Association" || current.elements[ref].type === "cds.Composition") {
|
|
997
|
+
current = current.elements[ref]._target;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return current.name;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
899
1003
|
function resolveRefs(model, refs) {
|
|
900
1004
|
let resolvedRefs = [];
|
|
901
1005
|
let ref = refs[0];
|
|
@@ -917,49 +1021,55 @@ function resolveRefs(model, refs) {
|
|
|
917
1021
|
return resolvedRefs;
|
|
918
1022
|
}
|
|
919
1023
|
|
|
920
|
-
function identifierRefs(model, definition, expressions,
|
|
1024
|
+
function identifierRefs(model, definition, expressions, mixins, aliases) {
|
|
921
1025
|
let refs = [];
|
|
922
1026
|
for (const expression of expressions) {
|
|
923
1027
|
if (Array.isArray(expression.ref)) {
|
|
924
1028
|
let current = definition;
|
|
925
|
-
let
|
|
1029
|
+
let currentMixins = mixins;
|
|
926
1030
|
for (const ref of expression.ref) {
|
|
927
|
-
|
|
928
|
-
if (element.type === "cds.Association" || element.type === "cds.Composition") {
|
|
929
|
-
current = model.definitions[element.target];
|
|
930
|
-
currentMixin = {};
|
|
1031
|
+
if (current?.name !== definition?.name) {
|
|
931
1032
|
refs.push(current.name);
|
|
932
1033
|
}
|
|
1034
|
+
if (ref.startsWith("$")) {
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
current = targetEntity(model, current, currentMixins, aliases, ref);
|
|
1038
|
+
currentMixins = {};
|
|
933
1039
|
}
|
|
934
1040
|
}
|
|
935
1041
|
}
|
|
936
1042
|
return refs;
|
|
937
1043
|
}
|
|
938
1044
|
|
|
939
|
-
function expressionRefs(model, definition, expressions,
|
|
940
|
-
let refs = identifierRefs(model, definition, expressions,
|
|
1045
|
+
function expressionRefs(model, definition, expressions, mixins, aliases) {
|
|
1046
|
+
let refs = identifierRefs(model, definition, expressions, mixins, aliases);
|
|
941
1047
|
for (const expression of expressions) {
|
|
942
1048
|
if (expression.xpr) {
|
|
943
|
-
refs = refs.concat(expressionRefs(model, definition, expression.xpr,
|
|
1049
|
+
refs = refs.concat(expressionRefs(model, definition, expression.xpr, mixins, aliases));
|
|
944
1050
|
} else if (expression.args) {
|
|
945
|
-
refs = refs.concat(expressionRefs(model, definition, expression.args,
|
|
1051
|
+
refs = refs.concat(expressionRefs(model, definition, expression.args, mixins, aliases));
|
|
946
1052
|
} else if (expression.SELECT) {
|
|
947
|
-
refs = refs.concat(
|
|
1053
|
+
refs = refs.concat(
|
|
1054
|
+
selectRefs(model, undefined, expression, {
|
|
1055
|
+
...aliases,
|
|
1056
|
+
["$self"]: undefined,
|
|
1057
|
+
}),
|
|
1058
|
+
);
|
|
948
1059
|
}
|
|
949
1060
|
}
|
|
950
1061
|
return refs;
|
|
951
1062
|
}
|
|
952
1063
|
|
|
953
|
-
function expandRefs(model, definition, columns,
|
|
1064
|
+
function expandRefs(model, definition, columns, mixins, aliases) {
|
|
954
1065
|
let refs = [];
|
|
955
1066
|
for (const column of columns) {
|
|
956
1067
|
if (Array.isArray(column.ref) && column.expand) {
|
|
957
1068
|
let current = definition;
|
|
958
|
-
let
|
|
1069
|
+
let currentMixins = mixins;
|
|
959
1070
|
for (const ref of column.ref) {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
currentMixin = {};
|
|
1071
|
+
current = targetEntity(model, current, currentMixins, aliases, ref);
|
|
1072
|
+
currentMixins = {};
|
|
963
1073
|
refs.push(current.name);
|
|
964
1074
|
}
|
|
965
1075
|
refs = refs.concat(expandRefs(model, current, column.expand));
|
|
@@ -968,6 +1078,26 @@ function expandRefs(model, definition, columns, mixin) {
|
|
|
968
1078
|
return refs;
|
|
969
1079
|
}
|
|
970
1080
|
|
|
1081
|
+
function targetEntity(model, entity, mixins, aliases, ref) {
|
|
1082
|
+
if (aliases?.[ref]) {
|
|
1083
|
+
return typeof aliases[ref] === "string" ? model.definitions[aliases[ref]] : aliases[ref];
|
|
1084
|
+
}
|
|
1085
|
+
if (entity?.name.endsWith(`.${ref}`)) {
|
|
1086
|
+
return entity;
|
|
1087
|
+
}
|
|
1088
|
+
const element = entity?.elements[ref] || mixins?.[ref];
|
|
1089
|
+
if (!element) {
|
|
1090
|
+
cds.log(Component).warn("Reference not found in entity", {
|
|
1091
|
+
entity: entity?.name,
|
|
1092
|
+
ref,
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
if (element.type === "cds.Association" || element.type === "cds.Composition") {
|
|
1096
|
+
return model.definitions[element.target];
|
|
1097
|
+
}
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
971
1101
|
function staticRefs(model, refs) {
|
|
972
1102
|
for (const ref of refs) {
|
|
973
1103
|
if (!model.definitions[ref][Annotations.ReplicateStatic]) {
|