cds-caching 1.3.0 → 1.3.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.
@@ -1655,7 +1655,7 @@ sap.ui.predefine("sap/tnt/library", ["./IllustratedMessageType","sap/ui/base/Dat
1655
1655
  * (c) Copyright 2025 SAP SE or an SAP affiliate company.
1656
1656
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
1657
1657
  */
1658
- sap.ui.predefine("sap/ui/Global", ["sap/ui/VersionInfo","sap/base/Log","sap/base/assert","sap/base/util/ObjectPath"],function(e,a,r,t){"use strict";if(globalThis.OpenAjax&&globalThis.OpenAjax.hub){OpenAjax.hub.registerLibrary("sap","http://www.sap.com/","0.1",{})}var n;let s={version:"1.136.0",buildinfo:{lastchange:"",buildtime:"20260413-2231"}};s=Object.assign(sap.ui,s);var o=sap.ui.loader._.getSyncCallBehavior();sap.ui.getVersionInfo=function(r){if(r&&r.async){a.info("Do not use deprecated function 'sap.ui.getVersionInfo'. Use"+" 'sap/ui/VersionInfo' module's asynchronous .load function instead")}else{a.warning("Do not use deprecated function 'sap.ui.getVersionInfo' synchronously! Use"+" 'sap/ui/VersionInfo' module's asynchronous .load function instead","Deprecation",null,function(){return{type:"sap.ui.getVersionInfo",name:"Global"}})}return e._load(r)};sap.ui.namespace=function(e){r(false,"sap.ui.namespace is long time deprecated and shouldn't be used");return t.create(e)};sap.ui.lazyRequire=function(e,s,i){r(typeof e==="string"&&e,"lazyRequire: sClassName must be a non-empty string");r(!s||typeof s==="string","lazyRequire: sMethods must be empty or a string");if(o===2){a.error("[nosync] lazy stub creation ignored for '"+e+"'");return}var u=e.replace(/\//gi,"."),l=u.lastIndexOf("."),c=u.substr(0,l),p=u.substr(l+1),f=t.create(c),y=f[p],d=(s||"new").split(" "),b=d.indexOf("new");i=i||u;if(!y){if(b>=0){y=function(){if(o){if(o===1){a.error("[nosync] lazy stub for constructor '"+u+"' called")}}else{a.debug("lazy stub for constructor '"+u+"' called.")}sap.ui.requireSync(i.replace(/\./g,"/"));var t=f[p];r(typeof t==="function","lazyRequire: oRealClass must be a function after loading");if(t._sapUiLazyLoader){throw new Error("lazyRequire: stub '"+u+"'has not been replaced by module '"+i+"'")}var s=Object.create(t.prototype);if(!(this instanceof y)){n=n||sap.ui.require("sap/ui/base/Object");if(n&&s instanceof n){a.error("Constructor "+e+' has been called without "new" operator!',null,null,function(){try{throw new Error}catch(e){return e}})}}var l=t.apply(s,arguments);if(l&&(typeof l==="function"||typeof l==="object")){s=l}return s};y._sapUiLazyLoader=true;d.splice(b,1)}else{y={}}f[p]=y}d.forEach(function(e){if(!y[e]){y[e]=function(){if(o){if(o===1){a.error("[no-sync] lazy stub for method '"+u+"."+e+"' called")}}else{a.debug("lazy stub for method '"+u+"."+e+"' called.")}sap.ui.requireSync(i.replace(/\./g,"/"));var t=f[p];r(typeof t==="function"||typeof t==="object","lazyRequire: oRealClass must be a function or object after loading");r(typeof t[e]==="function","lazyRequire: method must be a function");if(t[e]._sapUiLazyLoader){throw new Error("lazyRequire: stub '"+u+"."+e+"' has not been replaced by loaded module '"+i+"'")}return t[e].apply(t,arguments)};y[e]._sapUiLazyLoader=true}})};sap.ui.lazyRequire._isStub=function(e){r(typeof e==="string"&&e,"lazyRequire._isStub: sClassName must be a non-empty string");var a=e.lastIndexOf("."),n=e.slice(0,a),s=e.slice(a+1),o=t.get(n||"");return!!(o&&typeof o[s]==="function"&&o[s]._sapUiLazyLoader)};sap.ui.resource=function(e,a){r(typeof e==="string","sLibraryName must be a string");r(typeof a==="string","sResourcePath must be a string");return sap.ui.require.toUrl((String(e).replace(/\./g,"/")+"/"+a).replace(/^\/*/,""))};sap.ui.localResources=function(e){r(e,"sNamespace must not be empty");var a={};a[e.replace(/\./g,"/")]="./"+e.replace(/\./g,"/");sap.ui.loader.config({paths:a})};return s});
1658
+ sap.ui.predefine("sap/ui/Global", ["sap/ui/VersionInfo","sap/base/Log","sap/base/assert","sap/base/util/ObjectPath"],function(e,a,r,t){"use strict";if(globalThis.OpenAjax&&globalThis.OpenAjax.hub){OpenAjax.hub.registerLibrary("sap","http://www.sap.com/","0.1",{})}var n;let s={version:"1.136.0",buildinfo:{lastchange:"",buildtime:"20260415-1158"}};s=Object.assign(sap.ui,s);var o=sap.ui.loader._.getSyncCallBehavior();sap.ui.getVersionInfo=function(r){if(r&&r.async){a.info("Do not use deprecated function 'sap.ui.getVersionInfo'. Use"+" 'sap/ui/VersionInfo' module's asynchronous .load function instead")}else{a.warning("Do not use deprecated function 'sap.ui.getVersionInfo' synchronously! Use"+" 'sap/ui/VersionInfo' module's asynchronous .load function instead","Deprecation",null,function(){return{type:"sap.ui.getVersionInfo",name:"Global"}})}return e._load(r)};sap.ui.namespace=function(e){r(false,"sap.ui.namespace is long time deprecated and shouldn't be used");return t.create(e)};sap.ui.lazyRequire=function(e,s,i){r(typeof e==="string"&&e,"lazyRequire: sClassName must be a non-empty string");r(!s||typeof s==="string","lazyRequire: sMethods must be empty or a string");if(o===2){a.error("[nosync] lazy stub creation ignored for '"+e+"'");return}var u=e.replace(/\//gi,"."),l=u.lastIndexOf("."),c=u.substr(0,l),p=u.substr(l+1),f=t.create(c),y=f[p],d=(s||"new").split(" "),b=d.indexOf("new");i=i||u;if(!y){if(b>=0){y=function(){if(o){if(o===1){a.error("[nosync] lazy stub for constructor '"+u+"' called")}}else{a.debug("lazy stub for constructor '"+u+"' called.")}sap.ui.requireSync(i.replace(/\./g,"/"));var t=f[p];r(typeof t==="function","lazyRequire: oRealClass must be a function after loading");if(t._sapUiLazyLoader){throw new Error("lazyRequire: stub '"+u+"'has not been replaced by module '"+i+"'")}var s=Object.create(t.prototype);if(!(this instanceof y)){n=n||sap.ui.require("sap/ui/base/Object");if(n&&s instanceof n){a.error("Constructor "+e+' has been called without "new" operator!',null,null,function(){try{throw new Error}catch(e){return e}})}}var l=t.apply(s,arguments);if(l&&(typeof l==="function"||typeof l==="object")){s=l}return s};y._sapUiLazyLoader=true;d.splice(b,1)}else{y={}}f[p]=y}d.forEach(function(e){if(!y[e]){y[e]=function(){if(o){if(o===1){a.error("[no-sync] lazy stub for method '"+u+"."+e+"' called")}}else{a.debug("lazy stub for method '"+u+"."+e+"' called.")}sap.ui.requireSync(i.replace(/\./g,"/"));var t=f[p];r(typeof t==="function"||typeof t==="object","lazyRequire: oRealClass must be a function or object after loading");r(typeof t[e]==="function","lazyRequire: method must be a function");if(t[e]._sapUiLazyLoader){throw new Error("lazyRequire: stub '"+u+"."+e+"' has not been replaced by loaded module '"+i+"'")}return t[e].apply(t,arguments)};y[e]._sapUiLazyLoader=true}})};sap.ui.lazyRequire._isStub=function(e){r(typeof e==="string"&&e,"lazyRequire._isStub: sClassName must be a non-empty string");var a=e.lastIndexOf("."),n=e.slice(0,a),s=e.slice(a+1),o=t.get(n||"");return!!(o&&typeof o[s]==="function"&&o[s]._sapUiLazyLoader)};sap.ui.resource=function(e,a){r(typeof e==="string","sLibraryName must be a string");r(typeof a==="string","sResourcePath must be a string");return sap.ui.require.toUrl((String(e).replace(/\./g,"/")+"/"+a).replace(/^\/*/,""))};sap.ui.localResources=function(e){r(e,"sNamespace must not be empty");var a={};a[e.replace(/\./g,"/")]="./"+e.replace(/\./g,"/");sap.ui.loader.config({paths:a})};return s});
1659
1659
  /*!
1660
1660
  * OpenUI5
1661
1661
  * (c) Copyright 2025 SAP SE or an SAP affiliate company.
@@ -87,15 +87,16 @@ class CachingService extends cds.Service {
87
87
  this.log.debug(`CLEAR`);
88
88
  await this.cache.clear();
89
89
 
90
- // Also clear statistics
91
90
  if (this.statistics) {
92
91
  await this.statistics.resetCurrentStats();
93
92
 
94
- try {
95
- await this.statistics.deleteMetrics();
96
- await this.statistics.deleteKeyMetrics();
97
- } catch (error) {
98
- this.log.warn(`Failed to clear statistics for cache ${this.name}:`, error);
93
+ if (event.data.clearStatistics) {
94
+ try {
95
+ await this.statistics.deleteMetrics();
96
+ await this.statistics.deleteKeyMetrics();
97
+ } catch (error) {
98
+ this.log.warn(`Failed to clear statistics for cache ${this.name}:`, error);
99
+ }
99
100
  }
100
101
  }
101
102
  }
@@ -173,10 +174,10 @@ class CachingService extends cds.Service {
173
174
  return this.basicOperations.delete(key);
174
175
  }
175
176
 
176
- async clear(tx = null) {
177
- if (tx) return this.basicOperations.clear(tx);
178
- if (this.options.transactionalOperations) return this.basicOperations.clearInTx();
179
- return this.basicOperations.clear();
177
+ async clear(options = {}, tx = null) {
178
+ if (tx) return this.basicOperations.clear(options, tx);
179
+ if (this.options.transactionalOperations) return this.basicOperations.clearInTx(options);
180
+ return this.basicOperations.clear(options);
180
181
  }
181
182
 
182
183
  async deleteByTag(tag, tx = null) {
@@ -156,12 +156,15 @@ class BasicOperations {
156
156
 
157
157
  /**
158
158
  * Clear all cache entries
159
+ * @param {object} options - clear options
160
+ * @param {boolean} [options.clearStatistics=false] - also delete persisted metrics and key metrics
159
161
  * @returns {Promise<void>}
160
162
  */
161
- async clear(tx = null) {
163
+ async clear(options = {}, tx = null) {
162
164
  const srv = tx || this.cache;
163
165
  await srv.send('CLEAR', {
164
- deleteAll: true
166
+ deleteAll: true,
167
+ clearStatistics: options.clearStatistics || false
165
168
  });
166
169
 
167
170
  // Record the native clear operation
@@ -318,10 +321,10 @@ class BasicOperations {
318
321
  }
319
322
  }
320
323
 
321
- async clearInTx() {
324
+ async clearInTx(options = {}) {
322
325
  const tx = await this.cache.tx();
323
326
  try {
324
- await this.clear(tx);
327
+ await this.clear(options, tx);
325
328
  await tx.commit();
326
329
  return;
327
330
  } catch (error) {
@@ -78,13 +78,34 @@ class KeyManager {
78
78
  case "Request":
79
79
  case "NoaRequest":
80
80
  case "ODataRequest":
81
- // Only hash the request information, not user/tenant/locale
81
+ // Hash request information including the HTTP URL which
82
+ // contains $filter/$select/$orderby query parameters.
83
+ //
84
+ // Why req.query (CQN) is unreliable for hashing:
85
+ // NoaRequest ("New OData Adapter Request", defined in
86
+ // @sap/cds/libx/odata/ODataAdapter.js) carries cds.ql
87
+ // query objects where SELECT.where lives on the prototype
88
+ // chain, not as an own property. JSON.stringify only
89
+ // serializes own enumerable properties, so the where clause
90
+ // from $filter is silently dropped — two requests that
91
+ // differ only by $filter produce the same hash.
92
+ // This does NOT affect programmatic CQL (e.g.
93
+ // SELECT.from(Entity).where(...)) where `where` is an own
94
+ // property. It only affects CQN built from OData URL
95
+ // parsing in the adapter pipeline.
96
+ //
97
+ // The HTTP URL (req._.req.url / req.http.req.url) always
98
+ // contains the full query string as a plain string and is
99
+ // the reliable source for all OData query options. When a
100
+ // URL is available it supersedes req.query.
101
+ const url = this.normalizeUrl(keyOrObject._?.req?.url || keyOrObject.http?.req?.url);
82
102
  return this.createHash({
83
103
  method: keyOrObject.method,
84
104
  path: keyOrObject.path || keyOrObject.http?.req?.path,
105
+ url,
85
106
  data: keyOrObject.data,
86
107
  params: keyOrObject.params,
87
- query: keyOrObject.query,
108
+ query: url ? undefined : keyOrObject.query,
88
109
  event: keyOrObject.event,
89
110
  target: keyOrObject.target?.name
90
111
  });
@@ -105,6 +126,23 @@ class KeyManager {
105
126
  return this.createHash({ content: keyOrObject });
106
127
  }
107
128
 
129
+ /**
130
+ * Normalize a URL by sorting its query parameters alphabetically by key.
131
+ * Ensures the same parameters in different order produce the same cache key.
132
+ * @param {string} url - URL string to normalize
133
+ * @returns {string|undefined} - Normalized URL or undefined if input is falsy
134
+ */
135
+ normalizeUrl(url) {
136
+ if (!url) return undefined;
137
+ try {
138
+ const urlObj = new URL(url, 'http://localhost');
139
+ urlObj.searchParams.sort();
140
+ return urlObj.pathname + (urlObj.searchParams.size ? '?' + urlObj.searchParams.toString() : '');
141
+ } catch {
142
+ return url;
143
+ }
144
+ }
145
+
108
146
  /**
109
147
  * Serialize function arguments for cache key generation
110
148
  * @param {any} arg - argument to serialize
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cds-caching",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "A caching plugin for SAP CAP applications",
5
5
  "repository": {
6
6
  "type": "git",