@cap-js-community/common 0.2.2 → 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 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.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.29.0",
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.1",
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,30 +858,53 @@ 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.SELECT) {
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 ((query.SELECT.from.join || query.SELECT.from.SET) && query.SELECT.from.args) {
872
+ } else if (query.SELECT.from.args) {
870
873
  refs = query.SELECT.from.args.reduce((refs, arg) => {
871
- refs = refs.concat(resolveRefs(model, arg.ref || arg));
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 selectFromAliases(model, query) {
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
+ }
897
+ }
898
+ } else if (query.SELECT.from.SELECT) {
899
+ return selectFromPrimaryRef(model, query.SELECT.from);
900
+ }
901
+ }
902
+
903
+ function selectFromAliases(model, definition, query) {
879
904
  let aliases = {};
905
+ if (definition?.name) {
906
+ aliases["$self"] = definition.name;
907
+ }
880
908
  if (query.SELECT.from.SELECT) {
881
909
  // Sub-select aliases are not (yet) supported
882
910
  } else if (query.SELECT.from.ref) {
@@ -887,39 +915,74 @@ function selectFromAliases(model, query) {
887
915
  const as = ref.split(".").pop();
888
916
  aliases[as] = ref;
889
917
  }
890
- } else if ((query.SELECT.from.join || query.SELECT.from.SET) && query.SELECT.from.args) {
918
+ } else if (query.SELECT.from.args) {
891
919
  for (const arg of query.SELECT.from.args) {
892
- const ref = resolveRef(model, arg.ref);
893
- if (arg.as) {
894
- aliases[arg.as] = ref;
895
- } else {
896
- const as = ref.split(".").pop();
897
- aliases[as] = ref;
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
+ }
898
935
  }
899
936
  }
900
937
  }
901
938
  return aliases;
902
939
  }
903
940
 
904
- function selectRefs(model, query) {
905
- let refs = selectFromRefs(model, query);
906
- const aliases = selectFromAliases(model, query);
907
- if (query._target) {
908
- const target = model.definitions[query._target.name];
909
- if (query.SELECT.orderBy) {
910
- refs = refs.concat(expressionRefs(model, target, query.SELECT.orderBy, query.SELECT.mixin, aliases));
911
- }
912
- if (query.SELECT.columns) {
913
- refs = refs.concat(expressionRefs(model, target, query.SELECT.columns, query.SELECT.mixin, aliases));
914
- refs = refs.concat(expandRefs(model, target, query.SELECT.columns, query.SELECT.mixin, aliases));
915
- }
916
- if (query.SELECT.where) {
917
- refs = refs.concat(expressionRefs(model, target, query.SELECT.where, query.SELECT.mixin, aliases));
918
- }
919
- if (query.SELECT.having) {
920
- refs = refs.concat(expressionRefs(model, target, query.SELECT.having, query.SELECT.mixin, aliases));
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
+ }
921
956
  }
922
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
+ }
923
986
  return refs;
924
987
  }
925
988
 
@@ -965,11 +1028,14 @@ function identifierRefs(model, definition, expressions, mixins, aliases) {
965
1028
  let current = definition;
966
1029
  let currentMixins = mixins;
967
1030
  for (const ref of expression.ref) {
968
- current = target(model, current, currentMixins, aliases, ref);
969
- if (current !== null) {
970
- currentMixins = {};
1031
+ if (current?.name !== definition?.name) {
971
1032
  refs.push(current.name);
972
1033
  }
1034
+ if (ref.startsWith("$")) {
1035
+ break;
1036
+ }
1037
+ current = targetEntity(model, current, currentMixins, aliases, ref);
1038
+ currentMixins = {};
973
1039
  }
974
1040
  }
975
1041
  }
@@ -984,7 +1050,12 @@ function expressionRefs(model, definition, expressions, mixins, aliases) {
984
1050
  } else if (expression.args) {
985
1051
  refs = refs.concat(expressionRefs(model, definition, expression.args, mixins, aliases));
986
1052
  } else if (expression.SELECT) {
987
- refs = refs.concat(selectRefs(model, expression));
1053
+ refs = refs.concat(
1054
+ selectRefs(model, undefined, expression, {
1055
+ ...aliases,
1056
+ ["$self"]: undefined,
1057
+ }),
1058
+ );
988
1059
  }
989
1060
  }
990
1061
  return refs;
@@ -997,7 +1068,7 @@ function expandRefs(model, definition, columns, mixins, aliases) {
997
1068
  let current = definition;
998
1069
  let currentMixins = mixins;
999
1070
  for (const ref of column.ref) {
1000
- current = target(model, current, currentMixins, aliases, ref);
1071
+ current = targetEntity(model, current, currentMixins, aliases, ref);
1001
1072
  currentMixins = {};
1002
1073
  refs.push(current.name);
1003
1074
  }
@@ -1007,17 +1078,17 @@ function expandRefs(model, definition, columns, mixins, aliases) {
1007
1078
  return refs;
1008
1079
  }
1009
1080
 
1010
- function target(model, entity, mixins, aliases, ref) {
1081
+ function targetEntity(model, entity, mixins, aliases, ref) {
1011
1082
  if (aliases?.[ref]) {
1012
1083
  return typeof aliases[ref] === "string" ? model.definitions[aliases[ref]] : aliases[ref];
1013
1084
  }
1014
- if (entity.name.endsWith(`.${ref}`)) {
1085
+ if (entity?.name.endsWith(`.${ref}`)) {
1015
1086
  return entity;
1016
1087
  }
1017
- const element = entity.elements[ref] || mixins?.[ref];
1088
+ const element = entity?.elements[ref] || mixins?.[ref];
1018
1089
  if (!element) {
1019
1090
  cds.log(Component).warn("Reference not found in entity", {
1020
- entity: entity.name,
1091
+ entity: entity?.name,
1021
1092
  ref,
1022
1093
  });
1023
1094
  }