@cap-js-community/common 0.1.7 → 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
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.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "CAP Node.js Community Common",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"engines": {
|
|
@@ -44,26 +44,26 @@
|
|
|
44
44
|
"audit": "npm audit --only=prod"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@cap-js/sqlite": "^
|
|
48
|
-
"commander": "^
|
|
47
|
+
"@cap-js/sqlite": "^2.0.1",
|
|
48
|
+
"commander": "^14.0.0",
|
|
49
49
|
"redis": "^4.7.1",
|
|
50
50
|
"verror": "^1.10.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@cap-js-community/common": "./",
|
|
54
|
-
"@cap-js/cds-test": "^0.
|
|
55
|
-
"@sap/cds": "^
|
|
56
|
-
"@sap/cds-common-content": "^
|
|
57
|
-
"@sap/cds-dk": "^
|
|
58
|
-
"eslint": "9.
|
|
59
|
-
"eslint-config-prettier": "10.1.
|
|
60
|
-
"eslint-plugin-jest": "
|
|
61
|
-
"eslint-plugin-n": "^17.
|
|
62
|
-
"jest": "
|
|
54
|
+
"@cap-js/cds-test": "^0.4.0",
|
|
55
|
+
"@sap/cds": "^9.0.4",
|
|
56
|
+
"@sap/cds-common-content": "^3.0.1",
|
|
57
|
+
"@sap/cds-dk": "^9.0.5",
|
|
58
|
+
"eslint": "9.29.0",
|
|
59
|
+
"eslint-config-prettier": "10.1.5",
|
|
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.
|
|
66
|
-
"shelljs": "^0.
|
|
65
|
+
"prettier": "3.6.1",
|
|
66
|
+
"shelljs": "^0.10.0"
|
|
67
67
|
},
|
|
68
68
|
"cds": {
|
|
69
69
|
"requires": {
|
|
@@ -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 () => {
|
|
@@ -490,7 +503,7 @@ class ReplicationCache {
|
|
|
490
503
|
search(query) {
|
|
491
504
|
let search = true;
|
|
492
505
|
if (query.SELECT.search?.length > 0) {
|
|
493
|
-
const ref = query.
|
|
506
|
+
const ref = query._target.name;
|
|
494
507
|
this.stats.search[ref] ??= 0;
|
|
495
508
|
this.stats.search[ref]++;
|
|
496
509
|
this.log.debug("Replication cache skipped for search", {
|
|
@@ -504,7 +517,7 @@ class ReplicationCache {
|
|
|
504
517
|
localized(query, refs) {
|
|
505
518
|
let localized = true;
|
|
506
519
|
if (query.SELECT.localized) {
|
|
507
|
-
const ref = query.
|
|
520
|
+
const ref = query._target.name;
|
|
508
521
|
this.stats.localized[ref] ??= 0;
|
|
509
522
|
this.stats.localized[ref]++;
|
|
510
523
|
this.log.debug("Replication cache not enabled for 'localized' without deploy feature", {
|
|
@@ -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,19 +872,25 @@ function fromRefs(model, query) {
|
|
|
852
872
|
return refs;
|
|
853
873
|
}, []);
|
|
854
874
|
}
|
|
855
|
-
|
|
875
|
+
return refs;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function selectRefs(model, query) {
|
|
879
|
+
let refs = selectFromRefs(model, query);
|
|
880
|
+
if (query._target) {
|
|
881
|
+
const target = model.definitions[query._target.name];
|
|
856
882
|
if (query.SELECT.orderBy) {
|
|
857
|
-
refs = refs.concat(expressionRefs(model, query.
|
|
883
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.orderBy, query.SELECT.mixin));
|
|
858
884
|
}
|
|
859
885
|
if (query.SELECT.columns) {
|
|
860
|
-
refs = refs.concat(expressionRefs(model, query.
|
|
861
|
-
refs = refs.concat(expandRefs(model, query.
|
|
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));
|
|
862
888
|
}
|
|
863
889
|
if (query.SELECT.where) {
|
|
864
|
-
refs = refs.concat(expressionRefs(model, query.
|
|
890
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.where, query.SELECT.mixin));
|
|
865
891
|
}
|
|
866
892
|
if (query.SELECT.having) {
|
|
867
|
-
refs = refs.concat(expressionRefs(model, query.
|
|
893
|
+
refs = refs.concat(expressionRefs(model, target, query.SELECT.having, query.SELECT.mixin));
|
|
868
894
|
}
|
|
869
895
|
}
|
|
870
896
|
return refs;
|
|
@@ -891,14 +917,17 @@ function resolveRefs(model, refs) {
|
|
|
891
917
|
return resolvedRefs;
|
|
892
918
|
}
|
|
893
919
|
|
|
894
|
-
function identifierRefs(model, definition,
|
|
920
|
+
function identifierRefs(model, definition, expressions, mixin) {
|
|
895
921
|
let refs = [];
|
|
896
|
-
for (const
|
|
897
|
-
if (Array.isArray(
|
|
922
|
+
for (const expression of expressions) {
|
|
923
|
+
if (Array.isArray(expression.ref)) {
|
|
898
924
|
let current = definition;
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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 = {};
|
|
902
931
|
refs.push(current.name);
|
|
903
932
|
}
|
|
904
933
|
}
|
|
@@ -907,27 +936,30 @@ function identifierRefs(model, definition, array) {
|
|
|
907
936
|
return refs;
|
|
908
937
|
}
|
|
909
938
|
|
|
910
|
-
function expressionRefs(model, definition,
|
|
911
|
-
let refs = identifierRefs(model, definition,
|
|
912
|
-
for (const
|
|
913
|
-
if (
|
|
914
|
-
refs = refs.concat(expressionRefs(model, definition,
|
|
915
|
-
} else if (
|
|
916
|
-
refs = refs.concat(expressionRefs(model, definition,
|
|
917
|
-
} else if (
|
|
918
|
-
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));
|
|
919
948
|
}
|
|
920
949
|
}
|
|
921
950
|
return refs;
|
|
922
951
|
}
|
|
923
952
|
|
|
924
|
-
function expandRefs(model, definition, columns) {
|
|
953
|
+
function expandRefs(model, definition, columns, mixin) {
|
|
925
954
|
let refs = [];
|
|
926
955
|
for (const column of columns) {
|
|
927
956
|
if (Array.isArray(column.ref) && column.expand) {
|
|
928
957
|
let current = definition;
|
|
958
|
+
let currentMixin = mixin;
|
|
929
959
|
for (const ref of column.ref) {
|
|
930
|
-
|
|
960
|
+
const element = current.elements[ref] || currentMixin?.[ref];
|
|
961
|
+
current = model.definitions[element.target];
|
|
962
|
+
currentMixin = {};
|
|
931
963
|
refs.push(current.name);
|
|
932
964
|
}
|
|
933
965
|
refs = refs.concat(expandRefs(model, current, column.expand));
|
|
@@ -936,6 +968,15 @@ function expandRefs(model, definition, columns) {
|
|
|
936
968
|
return refs;
|
|
937
969
|
}
|
|
938
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
|
+
|
|
939
980
|
function cached(cache, field, init) {
|
|
940
981
|
try {
|
|
941
982
|
if (init && !cache.get(field)) {
|