@cap-js-community/common 0.2.0 → 0.2.1
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 +71 -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.1",
|
|
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,20 +872,25 @@ function fromRefs(model, query) {
|
|
|
852
872
|
return refs;
|
|
853
873
|
}, []);
|
|
854
874
|
}
|
|
875
|
+
return refs;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function selectRefs(model, query) {
|
|
879
|
+
let refs = selectFromRefs(model, query);
|
|
855
880
|
if (query._target) {
|
|
856
881
|
const target = model.definitions[query._target.name];
|
|
857
882
|
if (query.SELECT.orderBy) {
|
|
858
|
-
refs = refs.concat(expressionRefs(model, target, query.SELECT.orderBy));
|
|
883
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.orderBy, query.SELECT.mixin));
|
|
859
884
|
}
|
|
860
885
|
if (query.SELECT.columns) {
|
|
861
|
-
refs = refs.concat(expressionRefs(model, target, query.SELECT.columns));
|
|
862
|
-
refs = refs.concat(expandRefs(model, target, query.SELECT.columns));
|
|
886
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.columns, query.SELECT.mixin));
|
|
887
|
+
refs = refs.concat(expandRefs(model, target, query.SELECT.columns, query.SELECT.mixin));
|
|
863
888
|
}
|
|
864
889
|
if (query.SELECT.where) {
|
|
865
|
-
refs = refs.concat(expressionRefs(model, target, query.SELECT.where));
|
|
890
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.where, query.SELECT.mixin));
|
|
866
891
|
}
|
|
867
892
|
if (query.SELECT.having) {
|
|
868
|
-
refs = refs.concat(expressionRefs(model, target, query.SELECT.having));
|
|
893
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.having, query.SELECT.mixin));
|
|
869
894
|
}
|
|
870
895
|
}
|
|
871
896
|
return refs;
|
|
@@ -892,14 +917,17 @@ function resolveRefs(model, refs) {
|
|
|
892
917
|
return resolvedRefs;
|
|
893
918
|
}
|
|
894
919
|
|
|
895
|
-
function identifierRefs(model, definition,
|
|
920
|
+
function identifierRefs(model, definition, expressions, mixin) {
|
|
896
921
|
let refs = [];
|
|
897
|
-
for (const
|
|
898
|
-
if (Array.isArray(
|
|
922
|
+
for (const expression of expressions) {
|
|
923
|
+
if (Array.isArray(expression.ref)) {
|
|
899
924
|
let current = definition;
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
925
|
+
let currentMixin = mixin;
|
|
926
|
+
for (const ref of expression.ref) {
|
|
927
|
+
const element = current.elements[ref] || currentMixin?.[ref];
|
|
928
|
+
if (element.type === "cds.Association" || element.type === "cds.Composition") {
|
|
929
|
+
current = model.definitions[element.target];
|
|
930
|
+
currentMixin = {};
|
|
903
931
|
refs.push(current.name);
|
|
904
932
|
}
|
|
905
933
|
}
|
|
@@ -908,27 +936,30 @@ function identifierRefs(model, definition, array) {
|
|
|
908
936
|
return refs;
|
|
909
937
|
}
|
|
910
938
|
|
|
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(
|
|
939
|
+
function expressionRefs(model, definition, expressions, mixin) {
|
|
940
|
+
let refs = identifierRefs(model, definition, expressions, mixin);
|
|
941
|
+
for (const expression of expressions) {
|
|
942
|
+
if (expression.xpr) {
|
|
943
|
+
refs = refs.concat(expressionRefs(model, definition, expression.xpr, mixin));
|
|
944
|
+
} else if (expression.args) {
|
|
945
|
+
refs = refs.concat(expressionRefs(model, definition, expression.args, mixin));
|
|
946
|
+
} else if (expression.SELECT) {
|
|
947
|
+
refs = refs.concat(selectRefs(model, expression));
|
|
920
948
|
}
|
|
921
949
|
}
|
|
922
950
|
return refs;
|
|
923
951
|
}
|
|
924
952
|
|
|
925
|
-
function expandRefs(model, definition, columns) {
|
|
953
|
+
function expandRefs(model, definition, columns, mixin) {
|
|
926
954
|
let refs = [];
|
|
927
955
|
for (const column of columns) {
|
|
928
956
|
if (Array.isArray(column.ref) && column.expand) {
|
|
929
957
|
let current = definition;
|
|
958
|
+
let currentMixin = mixin;
|
|
930
959
|
for (const ref of column.ref) {
|
|
931
|
-
|
|
960
|
+
const element = current.elements[ref] || currentMixin?.[ref];
|
|
961
|
+
current = model.definitions[element.target];
|
|
962
|
+
currentMixin = {};
|
|
932
963
|
refs.push(current.name);
|
|
933
964
|
}
|
|
934
965
|
refs = refs.concat(expandRefs(model, current, column.expand));
|
|
@@ -937,6 +968,15 @@ function expandRefs(model, definition, columns) {
|
|
|
937
968
|
return refs;
|
|
938
969
|
}
|
|
939
970
|
|
|
971
|
+
function staticRefs(model, refs) {
|
|
972
|
+
for (const ref of refs) {
|
|
973
|
+
if (!model.definitions[ref][Annotations.ReplicateStatic]) {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
|
|
940
980
|
function cached(cache, field, init) {
|
|
941
981
|
try {
|
|
942
982
|
if (init && !cache.get(field)) {
|