@gooddata/sdk-backend-base 11.50.0-alpha.0 → 11.50.0-alpha.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cachingBackend/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cachingBackend/index.ts"],"names":[],"mappings":"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"}
@@ -717,6 +717,9 @@ function getOrCreateAttributeCache(ctx, workspace) {
717
717
  connectedAttributes: new LRUCache({
718
718
  max: (ctx.config.maxConnectedAttributesPerWorkspace ?? ctx.config.maxAttributesPerWorkspace),
719
719
  }),
720
+ attributesWithReferences: new LRUCache({
721
+ max: ctx.config.maxAttributesPerWorkspace,
722
+ }),
720
723
  attributeElementResults: cachingEnabled(ctx.config.maxAttributeElementResultsPerWorkspace)
721
724
  ? new LRUCache({
722
725
  max: ctx.config.maxAttributeElementResultsPerWorkspace,
@@ -1114,65 +1117,110 @@ class WithAttributesCaching extends DecoratedWorkspaceAttributesService {
1114
1117
  return this.decorated.updateAttributeMeta(updatedAttribute);
1115
1118
  }
1116
1119
  async getAttributesWithReferences(refs) {
1120
+ const attributesCache = getOrCreateAttributeCache(this.ctx, this.workspace).attributesWithReferences;
1117
1121
  const attributeByDfCache = getOrCreateAttributeCache(this.ctx, this.workspace).attributesByDisplayForms;
1118
1122
  const dataSetByAttributeCache = getOrCreateAttributeCache(this.ctx, this.workspace).dataSetsMeta;
1119
- // grab a reference to the cache results as soon as possible in case they would get evicted while loading the ones with missing data in cache
1120
- // then would might not be able to call cache.get again and be guaranteed to get the data
1123
+ // grab a reference to the cache results as soon as possible in case they would get evicted while
1124
+ // loading the ones with missing data in cache; then we might not be able to call cache.get again
1125
+ // and be guaranteed to get the data
1121
1126
  const refsWithCacheResults = [];
1122
1127
  for (const ref of refs) {
1123
- // First, get attribute cached by display form if available
1124
- const attrCacheKey = isIdentifierRef(ref) ? ref.identifier : undefined;
1125
- const attrCacheHit = attrCacheKey ? attributeByDfCache.get(attrCacheKey) : undefined;
1126
- const attributeFromCache = attrCacheHit ? await attrCacheHit : undefined;
1127
- // If attribute was cached, try to get dataSet by attribute from cache
1128
- const dataSetCacheKey = attributeFromCache && isIdentifierRef(attributeFromCache.ref)
1129
- ? attributeFromCache.ref.identifier
1130
- : undefined;
1131
- const dataSetCacheHit = dataSetCacheKey
1132
- ? dataSetByAttributeCache.get(dataSetCacheKey)
1133
- : undefined;
1134
- const dataSetFromCache = dataSetCacheHit ? await dataSetCacheHit : undefined;
1135
- // If both attribute and dataSet were cached, consider it as a cacheHit of attribute with references
1136
- const cacheHit = attributeFromCache && dataSetFromCache
1137
- ? Promise.resolve({
1138
- attribute: attributeFromCache,
1139
- dataSet: dataSetFromCache,
1140
- })
1141
- : undefined;
1142
- refsWithCacheResults.push({ ref, cacheHit });
1128
+ const cacheKey = isIdentifierRef(ref) ? ref.identifier : undefined;
1129
+ // First, reuse an in-flight/settled result registered for this exact display form so that N
1130
+ // concurrent callers for the same ref share a single underlying bulk request (in-flight dedupe).
1131
+ let cacheHit = cacheKey ? attributesCache.get(cacheKey) : undefined;
1132
+ // Otherwise fall back to the display-form-keyed secondary caches (as on master): once an
1133
+ // attribute has been loaded for ANY of its display forms, attributesByDisplayForms holds every
1134
+ // display form of that attribute, so a request for a sibling display form is still a cache hit
1135
+ // and no request is fired. This keeps the outgoing request set identical to master.
1136
+ if (!cacheHit) {
1137
+ const attrCacheHit = cacheKey ? attributeByDfCache.get(cacheKey) : undefined;
1138
+ const attributeFromCache = attrCacheHit ? await attrCacheHit : undefined;
1139
+ const dataSetCacheKey = attributeFromCache && isIdentifierRef(attributeFromCache.ref)
1140
+ ? attributeFromCache.ref.identifier
1141
+ : undefined;
1142
+ const dataSetCacheHit = dataSetCacheKey
1143
+ ? dataSetByAttributeCache.get(dataSetCacheKey)
1144
+ : undefined;
1145
+ const dataSetFromCache = dataSetCacheHit ? await dataSetCacheHit : undefined;
1146
+ cacheHit =
1147
+ attributeFromCache && dataSetFromCache
1148
+ ? Promise.resolve({
1149
+ attribute: attributeFromCache,
1150
+ dataSet: dataSetFromCache,
1151
+ })
1152
+ : undefined;
1153
+ }
1154
+ refsWithCacheResults.push({ ref, cacheKey, cacheHit });
1143
1155
  }
1144
- const [withCacheHits, withoutCacheHits] = partition(refsWithCacheResults, ({ cacheHit }) => !!cacheHit);
1145
- const refsToLoad = withoutCacheHits.map((item) => item.ref);
1146
- const [alreadyInCache, loadedFromServer] = await Promise.all([
1147
- // await the stuff from cache, we need the data available (we cannot just return the promises)
1148
- Promise.all(withCacheHits.map((item) => item.cacheHit)),
1149
- // load items not in cache using the bulk operation
1150
- refsToLoad.length > 0
1151
- ? this.decorated.getAttributesWithReferences(refsToLoad)
1152
- : Promise.resolve([]),
1153
- ]);
1154
- // save newly loaded to cache for future reference
1156
+ const refsToLoad = refsWithCacheResults.filter((item) => !item.cacheHit).map((item) => item.ref);
1157
+ // fire a SINGLE bulk request for the misses
1158
+ const loadPromise = refsToLoad.length > 0
1159
+ ? this.decorated.getAttributesWithReferences(refsToLoad)
1160
+ : Promise.resolve([]);
1161
+ // Synchronously (before awaiting) derive a per-ref promise from the single bulk request and register
1162
+ // it in the cache, so concurrent callers for the same display form share this in-flight request
1163
+ // instead of firing their own. Keep input ordering.
1164
+ const entries = refsWithCacheResults.map((item) => {
1165
+ if (item.cacheHit) {
1166
+ return { ref: item.ref, cacheKey: item.cacheKey, promise: item.cacheHit };
1167
+ }
1168
+ const { cacheKey } = item;
1169
+ const promise = loadPromise
1170
+ .then((loaded) => {
1171
+ const match = loaded.find((awr) => awr.attribute.displayForms.some((df) => areObjRefsEqual(item.ref, df.ref)));
1172
+ if (!match && cacheKey) {
1173
+ // The server omitted this ref (inconsistent relations); evict so a later call can
1174
+ // retry it and so a concurrent caller does not observe a cached resolved-undefined.
1175
+ attributesCache.delete(cacheKey);
1176
+ }
1177
+ return match;
1178
+ })
1179
+ .catch((e) => {
1180
+ if (cacheKey) {
1181
+ attributesCache.delete(cacheKey);
1182
+ }
1183
+ throw e;
1184
+ });
1185
+ if (cacheKey) {
1186
+ attributesCache.set(cacheKey, promise);
1187
+ }
1188
+ return { ref: item.ref, cacheKey, promise };
1189
+ });
1190
+ // await every promise (also marks in-flight rejections as handled -> no unhandled rejections);
1191
+ // resolved is aligned with the input refs ordering, entries may be undefined for omitted refs
1192
+ const resolved = await Promise.all(entries.map((entry) => entry.promise));
1193
+ // keep populating the secondary caches for the loaded items: attribute-by-display-form for EVERY
1194
+ // display form and dataset-by-attribute, so subsequent reads for the same attribute - including a
1195
+ // sibling display form or a scalar read - are served from cache, exactly as on master
1196
+ const loadedFromServer = await loadPromise;
1155
1197
  loadedFromServer.forEach((loaded) => {
1156
- // Cache attribute by display form refs
1157
1198
  loaded.attribute.displayForms.forEach((displayForm) => {
1158
1199
  void this.cacheAttributeByDisplayForm(displayForm.ref, loaded.attribute);
1159
1200
  });
1160
- // Cache dataSet by attribute ref
1161
1201
  if (loaded.dataSet) {
1162
1202
  void this.cacheAttributeDataSetMeta(loaded.attribute.ref, loaded.dataSet);
1163
1203
  }
1164
1204
  });
1205
+ // candidates are the values that actually resolved (per-ref, aligned with the input refs)
1206
+ const candidates = resolved.filter((item) => item !== undefined);
1207
+ // mirror the previous capability branch: with inconsistent relations we drop server-omitted refs
1208
+ // (skipMissingReferences), otherwise every requested ref must resolve and a match miss throws below
1165
1209
  const loadedRefs = loadedFromServer.flatMap((item) => item.attribute.displayForms.map((df) => df.ref));
1166
1210
  const outputRefs = this.ctx.capabilities.allowsInconsistentRelations
1167
1211
  ? skipMissingReferences(refs, refsToLoad, loadedRefs)
1168
1212
  : refs;
1169
- // reconstruct the original ordering
1170
- const candidates = [...loadedFromServer, ...alreadyInCache];
1171
- return outputRefs.map((ref) => {
1172
- const match = candidates.find((item) => item.attribute.displayForms.some((df) => areObjRefsEqual(ref, df.ref)));
1173
- // if this bombs, some data got lost in the process
1174
- invariant(match);
1175
- return match;
1213
+ // reconstruct the output preserving input ordering
1214
+ return outputRefs
1215
+ .map((ref) => candidates.find((item) => item.attribute.displayForms.some((df) => areObjRefsEqual(ref, df.ref))))
1216
+ .filter((match) => {
1217
+ // When the backend guarantees consistent relations, every requested ref must resolve;
1218
+ // surface (rather than silently drop) any per-ref match miss.
1219
+ if (!match) {
1220
+ invariant(this.ctx.capabilities.allowsInconsistentRelations);
1221
+ return false;
1222
+ }
1223
+ return true;
1176
1224
  });
1177
1225
  }
1178
1226
  cacheAttributeByDisplayForm = (displayFormRef, attribute) => {
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.58.9"
8
+ "packageVersion": "7.58.12"
9
9
  }
10
10
  ]
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-backend-base",
3
- "version": "11.50.0-alpha.0",
3
+ "version": "11.50.0-alpha.2",
4
4
  "description": "GoodData.UI SDK - Base for backend implementations",
5
5
  "license": "MIT",
6
6
  "author": "GoodData",
@@ -29,9 +29,9 @@
29
29
  "ts-invariant": "0.10.3",
30
30
  "tslib": "2.8.1",
31
31
  "uuid": "11.1.1",
32
- "@gooddata/sdk-backend-spi": "11.50.0-alpha.0",
33
- "@gooddata/sdk-model": "11.50.0-alpha.0",
34
- "@gooddata/util": "11.50.0-alpha.0"
32
+ "@gooddata/sdk-backend-spi": "11.50.0-alpha.2",
33
+ "@gooddata/sdk-model": "11.50.0-alpha.2",
34
+ "@gooddata/util": "11.50.0-alpha.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@microsoft/api-documenter": "^7.17.0",
@@ -56,9 +56,9 @@
56
56
  "oxlint-tsgolint": "0.15.0",
57
57
  "typescript": "5.9.3",
58
58
  "vitest": "4.1.8",
59
- "@gooddata/eslint-config": "11.50.0-alpha.0",
60
- "@gooddata/oxlint-config": "11.50.0-alpha.0",
61
- "@gooddata/reference-workspace": "11.50.0-alpha.0"
59
+ "@gooddata/eslint-config": "11.50.0-alpha.2",
60
+ "@gooddata/oxlint-config": "11.50.0-alpha.2",
61
+ "@gooddata/reference-workspace": "11.50.0-alpha.2"
62
62
  },
63
63
  "scripts": {
64
64
  "_phase:build": "npm run build",