@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":"AAQA,OAAO,EAGH,KAAK,kBAAkB,EAgD1B,MAAM,2BAA2B,CAAC;
|
|
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
|
|
1120
|
-
// then
|
|
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
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
//
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
?
|
|
1133
|
-
: undefined;
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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
|
|
1145
|
-
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
Promise.
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
//
|
|
1174
|
-
|
|
1175
|
-
|
|
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) => {
|
package/esm/tsdoc-metadata.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-backend-base",
|
|
3
|
-
"version": "11.50.0-alpha.
|
|
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.
|
|
33
|
-
"@gooddata/sdk-model": "11.50.0-alpha.
|
|
34
|
-
"@gooddata/util": "11.50.0-alpha.
|
|
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.
|
|
60
|
-
"@gooddata/oxlint-config": "11.50.0-alpha.
|
|
61
|
-
"@gooddata/reference-workspace": "11.50.0-alpha.
|
|
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",
|