function createFormRequestCache({ getHours, getUserId }) { const entries = new Map(); const pending = new Map(); let generation = 0; let owner = ""; const queries = new Set([ "requestWorkloads", "requestWeekDetails", "requestTimeEntries", "requestActiveServiceRequests", "requestServiceRequestExpand", "requestLookupTaskType", "requestActionTeamMembers", "requestActionFindLookup", "requestSrLookupPrimaryPillar", "requestSrLookupLanguage", "requestSrLookupTechPlatform", "requestSrLookupOpportunityBusiness", "requestLookupTypePlatform", "requestInternalSrServices" ]); const writes = new Set(["requestTimeEntriesDelete", "requestTimeEntriesSave", "requestMgmtAction", "requestPostInternalServiceRequest", "requestPostServiceRequest", "requestWorkloadForecastUpdate"]); function invalidate() { generation++; entries.clear(); pending.clear(); } function maxAge() { const hours = Number(getHours()); return (Number.isFinite(hours) && hours > 0 ? Math.min(hours, 168) : 24) * 3600000; } function copy(value) { return JSON.parse(JSON.stringify(value)); } function stable(value) { if (Array.isArray(value)) return value.map(stable); if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map(key => [key, stable(value[key])])); return value; } async function send(message, transport) { const userId = String(getUserId() || "").trim().toLowerCase(); if (userId !== owner) { invalidate(); owner = userId; } const operation = String(message.type || "").split(".").pop(); const duration = maxAge(); const request = queries.has(operation) || writes.has(operation) || operation === "requestResourceUser" ? { ...message, cacheMaxAgeMs: duration } : message; if (operation === "invalidateProductsCache") invalidate(); // Only explicitly known read operations are reusable. Writes and token generation always execute. if (!queries.has(operation) || !userId) { const result = await transport(request); if (result?.ok && writes.has(operation) && !(operation === "requestPostServiceRequest" && message.phase === "prepare")) invalidate(); return result; } // Workloads are scoped to the user/account, not the rotating WWB token or the selected Reference. const key = JSON.stringify(stable(operation === "requestWorkloads" ? { type: message.type, customerId: String(message.customerId), principal: String(message.principal || userId).trim().toLowerCase() } : message)); const hit = entries.get(key); if (hit && Date.now() >= hit.fetchedAt && Date.now() - hit.fetchedAt < duration) { return { ...copy(hit.result), debugRequests: [], fromCache: true, cachedAt: hit.fetchedAt }; } entries.delete(key); if (pending.has(key)) { const result = await pending.get(key); return { ...copy(result), debugRequests: [] }; } const version = generation; const promise = transport(request).then(result => { const workload = operation === "requestWorkloads"; const fetchedAt = workload && Number.isFinite(result?.cachedAt) ? result.cachedAt : Date.now(); const cacheable = !workload || (Array.isArray(result?.workloads) && (!result.fromCache || Number.isFinite(result.cachedAt))); if (result?.ok && cacheable && version === generation) { // Keep this per-page cache bounded and avoid persisting credentials or customer data to disk. if (entries.size >= 500) entries.delete(entries.keys().next().value); // Reusing a persistent workload entry must not restart its original TTL. entries.set(key, { result: copy({ ...result, debugRequests: [] }), fetchedAt }); } return result; }); pending.set(key, promise); try { return await promise; } finally { if (pending.get(key) === promise) pending.delete(key); } } return { send, invalidate }; }