@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.7",
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": "^1.11.0",
48
- "commander": "^13.1.0",
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.3.0",
55
- "@sap/cds": "^8.9.3",
56
- "@sap/cds-common-content": "^2.1.0",
57
- "@sap/cds-dk": "^8.9.3",
58
- "eslint": "9.26.0",
59
- "eslint-config-prettier": "10.1.3",
60
- "eslint-plugin-jest": "28.11.0",
61
- "eslint-plugin-n": "^17.17.0",
62
- "jest": "29.7.0",
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.5.3",
66
- "shelljs": "^0.9.2"
65
+ "prettier": "3.6.1",
66
+ "shelljs": "^0.10.0"
67
67
  },
68
68
  "cds": {
69
69
  "requires": {
@@ -342,7 +342,6 @@ function releasedEntityCheck(csnBuild, csnProd, whitelist, options) {
342
342
  definitionProd.name,
343
343
  elementProdName,
344
344
  );
345
- return;
346
345
  }
347
346
  }
348
347
  }
@@ -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
- req.tenant,
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(req.tenant).db;
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.target.name;
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.target.name;
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(fromRefs(model, query));
860
+ return unique(selectRefs(model, query));
841
861
  }
842
862
 
843
- function fromRefs(model, query) {
863
+ function selectFromRefs(model, query) {
844
864
  let refs = [];
845
865
  if (query.SELECT.from.SELECT) {
846
- refs = fromRefs(model, query.SELECT.from);
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
- if (query.target) {
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.target, query.SELECT.orderBy));
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.target, query.SELECT.columns));
861
- refs = refs.concat(expandRefs(model, query.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));
862
888
  }
863
889
  if (query.SELECT.where) {
864
- refs = refs.concat(expressionRefs(model, query.target, query.SELECT.where));
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.target, query.SELECT.having));
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, array) {
920
+ function identifierRefs(model, definition, expressions, mixin) {
895
921
  let refs = [];
896
- for (const entry of array) {
897
- if (Array.isArray(entry.ref)) {
922
+ for (const expression of expressions) {
923
+ if (Array.isArray(expression.ref)) {
898
924
  let current = definition;
899
- for (const ref of entry.ref) {
900
- if (current.elements[ref].type === "cds.Association" || current.elements[ref].type === "cds.Composition") {
901
- current = current.elements[ref]._target;
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, array) {
911
- let refs = identifierRefs(model, definition, array);
912
- for (const entry of array) {
913
- if (entry.xpr) {
914
- refs = refs.concat(expressionRefs(model, definition, entry.xpr));
915
- } else if (entry.args) {
916
- refs = refs.concat(expressionRefs(model, definition, entry.args));
917
- } else if (entry.SELECT) {
918
- refs = refs.concat(fromRefs(model, entry));
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
- current = current.elements[ref]._target;
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)) {