@cap-js-community/common 0.2.0 → 0.2.2
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 +2 -1
- package/package.json +9 -9
- package/src/replication-cache/ReplicationCache.js +130 -31
package/README.md
CHANGED
|
@@ -41,13 +41,14 @@ entity Books {
|
|
|
41
41
|
|
|
42
42
|
### Annotations
|
|
43
43
|
|
|
44
|
-
Annotations can be used to enable replication cache for a service:
|
|
44
|
+
Annotations can be used to enable replication cache for a service on entity level:
|
|
45
45
|
|
|
46
46
|
- `@cds.replicate: Boolean | Object`: Enable replication cache for entity
|
|
47
47
|
- `@cds.replicate.ttl: Number`: Time-To-Live (TTL) of cache entry in milliseconds
|
|
48
48
|
- `@cds.replicate.auto: Boolean`: Replication is managed automatically
|
|
49
49
|
- `@cds.replicate.preload: Boolean`: Preload replication for entity
|
|
50
50
|
- `@cds.replicate.group: String`: Replication group name
|
|
51
|
+
- `@cds.replicate.static: Boolean`: Statically replicate non-tenant aware
|
|
51
52
|
|
|
52
53
|
Defaults are taken from CDS environment.
|
|
53
54
|
|
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.2",
|
|
4
4
|
"description": "CAP Node.js Community Common",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"engines": {
|
|
@@ -51,18 +51,18 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@cap-js-community/common": "./",
|
|
54
|
-
"@cap-js/cds-test": "^0.
|
|
55
|
-
"@sap/cds": "^9.0.
|
|
54
|
+
"@cap-js/cds-test": "^0.4.0",
|
|
55
|
+
"@sap/cds": "^9.0.4",
|
|
56
56
|
"@sap/cds-common-content": "^3.0.1",
|
|
57
|
-
"@sap/cds-dk": "^9.0.
|
|
58
|
-
"eslint": "9.
|
|
57
|
+
"@sap/cds-dk": "^9.0.5",
|
|
58
|
+
"eslint": "9.29.0",
|
|
59
59
|
"eslint-config-prettier": "10.1.5",
|
|
60
|
-
"eslint-plugin-jest": "
|
|
61
|
-
"eslint-plugin-n": "^17.
|
|
62
|
-
"jest": "
|
|
60
|
+
"eslint-plugin-jest": "29.0.1",
|
|
61
|
+
"eslint-plugin-n": "^17.20.0",
|
|
62
|
+
"jest": "30.0.3",
|
|
63
63
|
"jest-html-reporters": "3.1.7",
|
|
64
64
|
"jest-junit": "16.0.0",
|
|
65
|
-
"prettier": "3.
|
|
65
|
+
"prettier": "3.6.1",
|
|
66
66
|
"shelljs": "^0.10.0"
|
|
67
67
|
},
|
|
68
68
|
"cds": {
|
|
@@ -28,6 +28,7 @@ const Status = {
|
|
|
28
28
|
|
|
29
29
|
const Annotations = {
|
|
30
30
|
Replicate: "@cds.replicate",
|
|
31
|
+
ReplicateStatic: "@cds.replicate.static",
|
|
31
32
|
ReplicateGroup: "@cds.replicate.group",
|
|
32
33
|
ReplicateAuto: "@cds.replicate.auto",
|
|
33
34
|
ReplicateTTL: "@cds.replicate.ttl",
|
|
@@ -61,7 +62,7 @@ class ReplicationCache {
|
|
|
61
62
|
});
|
|
62
63
|
cds.on("connect", (service) => {
|
|
63
64
|
if (service.name === this.name) {
|
|
64
|
-
const refs = ReplicationCache.replicationRefs(this.model, service);
|
|
65
|
+
const refs = ReplicationCache.replicationRefs(this.model, service, this.options.deploy);
|
|
65
66
|
if (refs.length > 0) {
|
|
66
67
|
this.setup(service, refs);
|
|
67
68
|
this.log.info("using replication cache", {
|
|
@@ -82,12 +83,12 @@ class ReplicationCache {
|
|
|
82
83
|
});
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
static replicationRefs(model, service) {
|
|
86
|
+
static replicationRefs(model, service, deploy) {
|
|
86
87
|
const refs = Object.keys(model.definitions).filter((name) => {
|
|
87
88
|
const definition = model.definitions[name];
|
|
88
89
|
return (
|
|
89
90
|
definition.kind === "entity" &&
|
|
90
|
-
!definition.projection &&
|
|
91
|
+
(!(definition.query || definition.projection) || !deploy) &&
|
|
91
92
|
(service.name === "db" || name.startsWith(`${service.name}.`)) &&
|
|
92
93
|
Object.values(Annotations).find((annotation) => {
|
|
93
94
|
return definition[annotation] !== undefined;
|
|
@@ -169,6 +170,14 @@ class ReplicationCache {
|
|
|
169
170
|
if (!this.options.search && !this.search(req.query)) {
|
|
170
171
|
return await next();
|
|
171
172
|
}
|
|
173
|
+
let fromRefs = queryFromRefs(model, req.query);
|
|
174
|
+
if (this.options.deploy) {
|
|
175
|
+
fromRefs = baseRefs(model, fromRefs);
|
|
176
|
+
fromRefs = localizedRefs(model, req.query, fromRefs);
|
|
177
|
+
}
|
|
178
|
+
if (fromRefs.length === 0 || !this.relevant(fromRefs)) {
|
|
179
|
+
return await next();
|
|
180
|
+
}
|
|
172
181
|
let refs = queryRefs(model, req.query);
|
|
173
182
|
if (!this.options.deploy) {
|
|
174
183
|
if (!this.localized(req.query, refs)) {
|
|
@@ -189,8 +198,12 @@ class ReplicationCache {
|
|
|
189
198
|
this.stats.counts[ref] ??= 0;
|
|
190
199
|
this.stats.counts[ref]++;
|
|
191
200
|
}
|
|
201
|
+
let tenant = req.tenant;
|
|
202
|
+
if (staticRefs(model, refs)) {
|
|
203
|
+
tenant = undefined; // non-tenant cache
|
|
204
|
+
}
|
|
192
205
|
const status = await this.load(
|
|
193
|
-
|
|
206
|
+
tenant,
|
|
194
207
|
refs,
|
|
195
208
|
{
|
|
196
209
|
auto: this.options.auto,
|
|
@@ -203,7 +216,7 @@ class ReplicationCache {
|
|
|
203
216
|
this.stats.used++;
|
|
204
217
|
this.stats.ratio = Math.round(this.stats.used / this.stats.hits);
|
|
205
218
|
this.log.debug("Replication cache was used");
|
|
206
|
-
const db = this.cache.get(
|
|
219
|
+
const db = this.cache.get(tenant).db;
|
|
207
220
|
if (this.options.measure) {
|
|
208
221
|
return this.measure(
|
|
209
222
|
async () => {
|
|
@@ -530,7 +543,7 @@ class ReplicationCache {
|
|
|
530
543
|
let projections = true;
|
|
531
544
|
for (const ref of refs) {
|
|
532
545
|
const definition = model.definitions[ref];
|
|
533
|
-
if (definition.query) {
|
|
546
|
+
if (definition.query || definition.projection) {
|
|
534
547
|
this.stats.projections[ref] ??= 0;
|
|
535
548
|
this.stats.projections[ref]++;
|
|
536
549
|
this.log.debug("Replication cache not enabled for 'projections' without deploy feature", {
|
|
@@ -833,17 +846,24 @@ function localizedRefs(model, query, refs) {
|
|
|
833
846
|
return unique(refs.concat(localizedRefs));
|
|
834
847
|
}
|
|
835
848
|
|
|
849
|
+
function queryFromRefs(model, query) {
|
|
850
|
+
if (!query.SELECT) {
|
|
851
|
+
return [];
|
|
852
|
+
}
|
|
853
|
+
return unique(selectFromRefs(model, query));
|
|
854
|
+
}
|
|
855
|
+
|
|
836
856
|
function queryRefs(model, query) {
|
|
837
857
|
if (!query.SELECT) {
|
|
838
858
|
return [];
|
|
839
859
|
}
|
|
840
|
-
return unique(
|
|
860
|
+
return unique(selectRefs(model, query));
|
|
841
861
|
}
|
|
842
862
|
|
|
843
|
-
function
|
|
863
|
+
function selectFromRefs(model, query) {
|
|
844
864
|
let refs = [];
|
|
845
865
|
if (query.SELECT.from.SELECT) {
|
|
846
|
-
refs =
|
|
866
|
+
refs = selectFromRefs(model, query.SELECT.from);
|
|
847
867
|
} else if (query.SELECT.from.ref) {
|
|
848
868
|
refs = resolveRefs(model, query.SELECT.from.ref);
|
|
849
869
|
} else if ((query.SELECT.from.join || query.SELECT.from.SET) && query.SELECT.from.args) {
|
|
@@ -852,25 +872,71 @@ function fromRefs(model, query) {
|
|
|
852
872
|
return refs;
|
|
853
873
|
}, []);
|
|
854
874
|
}
|
|
875
|
+
return refs;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function selectFromAliases(model, query) {
|
|
879
|
+
let aliases = {};
|
|
880
|
+
if (query.SELECT.from.SELECT) {
|
|
881
|
+
// Sub-select aliases are not (yet) supported
|
|
882
|
+
} else if (query.SELECT.from.ref) {
|
|
883
|
+
const ref = resolveRef(model, query.SELECT.from.ref);
|
|
884
|
+
if (query.SELECT.from.as) {
|
|
885
|
+
aliases[query.SELECT.from.as] = ref;
|
|
886
|
+
} else {
|
|
887
|
+
const as = ref.split(".").pop();
|
|
888
|
+
aliases[as] = ref;
|
|
889
|
+
}
|
|
890
|
+
} else if ((query.SELECT.from.join || query.SELECT.from.SET) && query.SELECT.from.args) {
|
|
891
|
+
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;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return aliases;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function selectRefs(model, query) {
|
|
905
|
+
let refs = selectFromRefs(model, query);
|
|
906
|
+
const aliases = selectFromAliases(model, query);
|
|
855
907
|
if (query._target) {
|
|
856
908
|
const target = model.definitions[query._target.name];
|
|
857
909
|
if (query.SELECT.orderBy) {
|
|
858
|
-
refs = refs.concat(expressionRefs(model, target, query.SELECT.orderBy));
|
|
910
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.orderBy, query.SELECT.mixin, aliases));
|
|
859
911
|
}
|
|
860
912
|
if (query.SELECT.columns) {
|
|
861
|
-
refs = refs.concat(expressionRefs(model, target, query.SELECT.columns));
|
|
862
|
-
refs = refs.concat(expandRefs(model, target, 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));
|
|
863
915
|
}
|
|
864
916
|
if (query.SELECT.where) {
|
|
865
|
-
refs = refs.concat(expressionRefs(model, target, query.SELECT.where));
|
|
917
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.where, query.SELECT.mixin, aliases));
|
|
866
918
|
}
|
|
867
919
|
if (query.SELECT.having) {
|
|
868
|
-
refs = refs.concat(expressionRefs(model, target, query.SELECT.having));
|
|
920
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.having, query.SELECT.mixin, aliases));
|
|
869
921
|
}
|
|
870
922
|
}
|
|
871
923
|
return refs;
|
|
872
924
|
}
|
|
873
925
|
|
|
926
|
+
function resolveRef(model, refs) {
|
|
927
|
+
let ref = refs[0];
|
|
928
|
+
if (ref.id) {
|
|
929
|
+
ref = ref.id;
|
|
930
|
+
}
|
|
931
|
+
let current = model.definitions[ref];
|
|
932
|
+
for (const ref of refs.slice(1)) {
|
|
933
|
+
if (current.elements[ref].type === "cds.Association" || current.elements[ref].type === "cds.Composition") {
|
|
934
|
+
current = current.elements[ref]._target;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return current.name;
|
|
938
|
+
}
|
|
939
|
+
|
|
874
940
|
function resolveRefs(model, refs) {
|
|
875
941
|
let resolvedRefs = [];
|
|
876
942
|
let ref = refs[0];
|
|
@@ -892,14 +958,16 @@ function resolveRefs(model, refs) {
|
|
|
892
958
|
return resolvedRefs;
|
|
893
959
|
}
|
|
894
960
|
|
|
895
|
-
function identifierRefs(model, definition,
|
|
961
|
+
function identifierRefs(model, definition, expressions, mixins, aliases) {
|
|
896
962
|
let refs = [];
|
|
897
|
-
for (const
|
|
898
|
-
if (Array.isArray(
|
|
963
|
+
for (const expression of expressions) {
|
|
964
|
+
if (Array.isArray(expression.ref)) {
|
|
899
965
|
let current = definition;
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
966
|
+
let currentMixins = mixins;
|
|
967
|
+
for (const ref of expression.ref) {
|
|
968
|
+
current = target(model, current, currentMixins, aliases, ref);
|
|
969
|
+
if (current !== null) {
|
|
970
|
+
currentMixins = {};
|
|
903
971
|
refs.push(current.name);
|
|
904
972
|
}
|
|
905
973
|
}
|
|
@@ -908,27 +976,29 @@ function identifierRefs(model, definition, array) {
|
|
|
908
976
|
return refs;
|
|
909
977
|
}
|
|
910
978
|
|
|
911
|
-
function expressionRefs(model, definition,
|
|
912
|
-
let refs = identifierRefs(model, definition,
|
|
913
|
-
for (const
|
|
914
|
-
if (
|
|
915
|
-
refs = refs.concat(expressionRefs(model, definition,
|
|
916
|
-
} else if (
|
|
917
|
-
refs = refs.concat(expressionRefs(model, definition,
|
|
918
|
-
} else if (
|
|
919
|
-
refs = refs.concat(
|
|
979
|
+
function expressionRefs(model, definition, expressions, mixins, aliases) {
|
|
980
|
+
let refs = identifierRefs(model, definition, expressions, mixins, aliases);
|
|
981
|
+
for (const expression of expressions) {
|
|
982
|
+
if (expression.xpr) {
|
|
983
|
+
refs = refs.concat(expressionRefs(model, definition, expression.xpr, mixins, aliases));
|
|
984
|
+
} else if (expression.args) {
|
|
985
|
+
refs = refs.concat(expressionRefs(model, definition, expression.args, mixins, aliases));
|
|
986
|
+
} else if (expression.SELECT) {
|
|
987
|
+
refs = refs.concat(selectRefs(model, expression));
|
|
920
988
|
}
|
|
921
989
|
}
|
|
922
990
|
return refs;
|
|
923
991
|
}
|
|
924
992
|
|
|
925
|
-
function expandRefs(model, definition, columns) {
|
|
993
|
+
function expandRefs(model, definition, columns, mixins, aliases) {
|
|
926
994
|
let refs = [];
|
|
927
995
|
for (const column of columns) {
|
|
928
996
|
if (Array.isArray(column.ref) && column.expand) {
|
|
929
997
|
let current = definition;
|
|
998
|
+
let currentMixins = mixins;
|
|
930
999
|
for (const ref of column.ref) {
|
|
931
|
-
current = current
|
|
1000
|
+
current = target(model, current, currentMixins, aliases, ref);
|
|
1001
|
+
currentMixins = {};
|
|
932
1002
|
refs.push(current.name);
|
|
933
1003
|
}
|
|
934
1004
|
refs = refs.concat(expandRefs(model, current, column.expand));
|
|
@@ -937,6 +1007,35 @@ function expandRefs(model, definition, columns) {
|
|
|
937
1007
|
return refs;
|
|
938
1008
|
}
|
|
939
1009
|
|
|
1010
|
+
function target(model, entity, mixins, aliases, ref) {
|
|
1011
|
+
if (aliases?.[ref]) {
|
|
1012
|
+
return typeof aliases[ref] === "string" ? model.definitions[aliases[ref]] : aliases[ref];
|
|
1013
|
+
}
|
|
1014
|
+
if (entity.name.endsWith(`.${ref}`)) {
|
|
1015
|
+
return entity;
|
|
1016
|
+
}
|
|
1017
|
+
const element = entity.elements[ref] || mixins?.[ref];
|
|
1018
|
+
if (!element) {
|
|
1019
|
+
cds.log(Component).warn("Reference not found in entity", {
|
|
1020
|
+
entity: entity.name,
|
|
1021
|
+
ref,
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
if (element.type === "cds.Association" || element.type === "cds.Composition") {
|
|
1025
|
+
return model.definitions[element.target];
|
|
1026
|
+
}
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function staticRefs(model, refs) {
|
|
1031
|
+
for (const ref of refs) {
|
|
1032
|
+
if (!model.definitions[ref][Annotations.ReplicateStatic]) {
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
940
1039
|
function cached(cache, field, init) {
|
|
941
1040
|
try {
|
|
942
1041
|
if (init && !cache.get(field)) {
|