mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-14 01:45:12 +00:00
fix(login): replace custom SWR cache with lru-cache (#11945)
# Which Problems Are Solved
The in-memory SWR promiseCache in zitadel.ts used keys like
getBrandingSettings-${org} that had no instance identifier. In
multi-tenant mode, where a single Next.js process serves multiple
instances, an instance A's cached settings without org context
(branding, languages, settings, etc.) could be served to Instance B
request without org context. The cache now additionally is bound to 100
entries by default (configurable via maxSize in `API_CACHE_CONFIG`).
When capacity is exceeded, expired entries are swept first
It additionally replaces the hand-rolled PromiseCache (Map-based, FIFO
eviction) with lru-cache, leveraging its built-in fetchMethod for
stale-while-revalidate, request deduplication, and true LRU eviction.
# How the Problems Are Solved
Added an instanceCacheKey() helpe that prefixes every cache key with
serviceConfig.instanceHost in addition to the org context if available.
---------
Co-authored-by: Ramon <[email protected]>
This commit is contained in:
Vendored
+4
-3
@@ -85,12 +85,13 @@ declare namespace NodeJS {
|
|||||||
API_CACHE_ENABLED?: string;
|
API_CACHE_ENABLED?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional: JSON string to configure the cache TTLs (in minutes) for specific backend API routes or global fallbacks.
|
* Optional: JSON string to configure the cache TTLs (in minutes) and size limits for specific backend API routes or global fallbacks.
|
||||||
* Example: '{"defaultMinutes": 15, "longMinutes": 60, "getBrandingSettings": 120}'
|
* Example: '{"defaultMinutes": 15, "longMinutes": 60, "maxSize": 200, "getBrandingSettings": 120}'
|
||||||
*
|
*
|
||||||
* Properties:
|
* Properties:
|
||||||
* - \`defaultMinutes\`: The globally utilized default TTL in minutes (falls back to 15 if not set).
|
* - \`defaultMinutes\`: The globally utilized default TTL in minutes (falls back to 15 if not set).
|
||||||
* - \`longMinutes\`: The TTL utilized string for long-cached routes, like branding/translation (falls back to 60 if not set).
|
* - \`longMinutes\`: The TTL utilized string for long-cached routes, like branding/translation (falls back to 60 if not set).
|
||||||
|
* - \`maxSize\`: Maximum number of entries the in-memory cache may hold. Oldest entries are evicted when capacity is reached (defaults to 100).
|
||||||
* - \`[route_name]\`: Explicit overrides per specific API method (e.g., \`getHostedLoginTranslation\`).
|
* - \`[route_name]\`: Explicit overrides per specific API method (e.g., \`getHostedLoginTranslation\`).
|
||||||
*/
|
*/
|
||||||
API_CACHE_CONFIG?: string;
|
API_CACHE_CONFIG?: string;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
|
"lru-cache": "^11.3.2",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "16.2.2",
|
"next": "16.2.2",
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||||
|
import { PromiseCache } from "./cache";
|
||||||
|
|
||||||
|
// Suppress logger output during tests
|
||||||
|
vi.mock("./logger", () => ({
|
||||||
|
createLogger: () => ({
|
||||||
|
warn: vi.fn(),
|
||||||
|
info: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
debug: vi.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock clock for lru-cache's `perf` option.
|
||||||
|
* lru-cache uses `performance.now()` internally for TTL tracking, and caches
|
||||||
|
* the reference at import time. The `perf` option lets us inject a custom clock.
|
||||||
|
*/
|
||||||
|
function mockClock(initial = 1000) {
|
||||||
|
let now = initial;
|
||||||
|
return {
|
||||||
|
perf: { now: () => now },
|
||||||
|
advance: (ms: number) => {
|
||||||
|
now += ms;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("PromiseCache", () => {
|
||||||
|
let cache: PromiseCache;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cache?.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getOrFetch", () => {
|
||||||
|
test("should return the fetcher result on cache miss", async () => {
|
||||||
|
cache = new PromiseCache(10);
|
||||||
|
const result = await cache.getOrFetch("key1", () => Promise.resolve("value1"), 60_000);
|
||||||
|
expect(result).toBe("value1");
|
||||||
|
expect(cache.size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return cached value on cache hit", async () => {
|
||||||
|
cache = new PromiseCache(10);
|
||||||
|
let callCount = 0;
|
||||||
|
const fetcher = () => {
|
||||||
|
callCount++;
|
||||||
|
return Promise.resolve(`value-${callCount}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const first = await cache.getOrFetch("key1", fetcher, 60_000);
|
||||||
|
const second = await cache.getOrFetch("key1", fetcher, 60_000);
|
||||||
|
|
||||||
|
expect(first).toBe("value-1");
|
||||||
|
expect(second).toBe("value-1");
|
||||||
|
expect(callCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return stale value immediately after TTL expires (SWR)", async () => {
|
||||||
|
const clock = mockClock();
|
||||||
|
cache = new PromiseCache(10, clock.perf);
|
||||||
|
|
||||||
|
let callCount = 0;
|
||||||
|
let resolveRevalidation: ((v: string) => void) | undefined;
|
||||||
|
|
||||||
|
const fetcher = () => {
|
||||||
|
callCount++;
|
||||||
|
if (callCount === 1) {
|
||||||
|
return Promise.resolve("value-1");
|
||||||
|
}
|
||||||
|
// Second call: return a promise we control
|
||||||
|
return new Promise<string>((resolve) => {
|
||||||
|
resolveRevalidation = resolve;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// First call — blocks on fetch
|
||||||
|
const first = await cache.getOrFetch("key1", fetcher, 100);
|
||||||
|
expect(first).toBe("value-1");
|
||||||
|
expect(callCount).toBe(1);
|
||||||
|
|
||||||
|
// Expire the entry
|
||||||
|
clock.advance(101);
|
||||||
|
|
||||||
|
// Second call after expiry — should get stale value immediately
|
||||||
|
const second = await cache.getOrFetch("key1", fetcher, 100);
|
||||||
|
expect(second).toBe("value-1"); // stale value, not blocking
|
||||||
|
expect(callCount).toBe(2); // revalidation was triggered
|
||||||
|
|
||||||
|
// Third call while revalidation is in-flight — also gets stale value
|
||||||
|
const third = await cache.getOrFetch("key1", fetcher, 100);
|
||||||
|
expect(third).toBe("value-1"); // stale value
|
||||||
|
expect(callCount).toBe(2); // no additional fetch (already revalidating)
|
||||||
|
|
||||||
|
// Resolve the background revalidation and flush the .then() chain
|
||||||
|
resolveRevalidation!("value-2");
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
|
||||||
|
// Now we should get the fresh value
|
||||||
|
const fourth = await cache.getOrFetch("key1", fetcher, 100);
|
||||||
|
expect(fourth).toBe("value-2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should keep stale value when revalidation fails", async () => {
|
||||||
|
const clock = mockClock();
|
||||||
|
cache = new PromiseCache(10, clock.perf);
|
||||||
|
|
||||||
|
let callCount = 0;
|
||||||
|
|
||||||
|
const fetcher = () => {
|
||||||
|
callCount++;
|
||||||
|
if (callCount === 1) {
|
||||||
|
return Promise.resolve("value-1");
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error("network error"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const first = await cache.getOrFetch("key1", fetcher, 100);
|
||||||
|
expect(first).toBe("value-1");
|
||||||
|
|
||||||
|
clock.advance(101);
|
||||||
|
|
||||||
|
// After TTL: returns stale, triggers failing revalidation
|
||||||
|
const second = await cache.getOrFetch("key1", fetcher, 100);
|
||||||
|
expect(second).toBe("value-1");
|
||||||
|
expect(callCount).toBe(2);
|
||||||
|
|
||||||
|
// Flush the rejected promise's handlers
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
|
||||||
|
// Next call can retry
|
||||||
|
clock.advance(1);
|
||||||
|
const third = await cache.getOrFetch("key1", fetcher, 100);
|
||||||
|
expect(third).toBe("value-1"); // still stale
|
||||||
|
expect(callCount).toBe(3); // retried
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should re-fetch after entry is evicted", async () => {
|
||||||
|
cache = new PromiseCache(10);
|
||||||
|
let callCount = 0;
|
||||||
|
const fetcher = () => {
|
||||||
|
callCount++;
|
||||||
|
return Promise.resolve(`value-${callCount}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const first = await cache.getOrFetch("key1", fetcher, 60_000);
|
||||||
|
expect(first).toBe("value-1");
|
||||||
|
|
||||||
|
// Evict the entry manually
|
||||||
|
cache.clear();
|
||||||
|
|
||||||
|
const second = await cache.getOrFetch("key1", fetcher, 60_000);
|
||||||
|
expect(second).toBe("value-2");
|
||||||
|
expect(callCount).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should reject on first-fetch failure", async () => {
|
||||||
|
cache = new PromiseCache(10);
|
||||||
|
const failingFetcher = () => Promise.reject(new Error("fail"));
|
||||||
|
|
||||||
|
await expect(cache.getOrFetch("key1", failingFetcher, 60_000)).rejects.toThrow();
|
||||||
|
|
||||||
|
// Wait a tick for internal cleanup
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
expect(cache.size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should deduplicate concurrent requests for the same key", async () => {
|
||||||
|
cache = new PromiseCache(10);
|
||||||
|
let callCount = 0;
|
||||||
|
const fetcher = () => {
|
||||||
|
callCount++;
|
||||||
|
return new Promise<string>((resolve) => setTimeout(() => resolve(`value-${callCount}`), 10));
|
||||||
|
};
|
||||||
|
|
||||||
|
const [a, b] = await Promise.all([
|
||||||
|
cache.getOrFetch("key1", fetcher, 60_000),
|
||||||
|
cache.getOrFetch("key1", fetcher, 60_000),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(a).toBe("value-1");
|
||||||
|
expect(b).toBe("value-1");
|
||||||
|
expect(callCount).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("maxSize eviction", () => {
|
||||||
|
test("should evict entries when maxSize is exceeded", async () => {
|
||||||
|
cache = new PromiseCache(3);
|
||||||
|
|
||||||
|
await cache.getOrFetch("a", () => Promise.resolve(1), 60_000);
|
||||||
|
await cache.getOrFetch("b", () => Promise.resolve(2), 60_000);
|
||||||
|
await cache.getOrFetch("c", () => Promise.resolve(3), 60_000);
|
||||||
|
expect(cache.size).toBe(3);
|
||||||
|
|
||||||
|
// Adding a 4th entry should trigger eviction of the LRU entry ("a")
|
||||||
|
await cache.getOrFetch("d", () => Promise.resolve(4), 60_000);
|
||||||
|
expect(cache.size).toBe(3);
|
||||||
|
|
||||||
|
// "a" should have been evicted — re-fetching should call a new fetcher
|
||||||
|
let refetched = false;
|
||||||
|
await cache.getOrFetch(
|
||||||
|
"a",
|
||||||
|
() => {
|
||||||
|
refetched = true;
|
||||||
|
return Promise.resolve(10);
|
||||||
|
},
|
||||||
|
60_000,
|
||||||
|
);
|
||||||
|
expect(refetched).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should respect maxSize of 1", async () => {
|
||||||
|
cache = new PromiseCache(1);
|
||||||
|
|
||||||
|
await cache.getOrFetch("a", () => Promise.resolve(1), 60_000);
|
||||||
|
expect(cache.size).toBe(1);
|
||||||
|
|
||||||
|
await cache.getOrFetch("b", () => Promise.resolve(2), 60_000);
|
||||||
|
expect(cache.size).toBe(1);
|
||||||
|
|
||||||
|
// Only "b" should remain
|
||||||
|
let aRefetched = false;
|
||||||
|
await cache.getOrFetch(
|
||||||
|
"a",
|
||||||
|
() => {
|
||||||
|
aRefetched = true;
|
||||||
|
return Promise.resolve(10);
|
||||||
|
},
|
||||||
|
60_000,
|
||||||
|
);
|
||||||
|
expect(aRefetched).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("clear", () => {
|
||||||
|
test("should remove all entries", async () => {
|
||||||
|
cache = new PromiseCache(10);
|
||||||
|
|
||||||
|
await cache.getOrFetch("a", () => Promise.resolve(1), 60_000);
|
||||||
|
await cache.getOrFetch("b", () => Promise.resolve(2), 60_000);
|
||||||
|
expect(cache.size).toBe(2);
|
||||||
|
|
||||||
|
cache.clear();
|
||||||
|
expect(cache.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { LRUCache } from "lru-cache";
|
||||||
|
|
||||||
|
interface FetchContext {
|
||||||
|
fetcher: () => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bounded, stale-while-revalidate in-memory promise cache backed by lru-cache.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - True LRU eviction
|
||||||
|
* - Deduplicates concurrent requests (built-in to lru-cache's fetchMethod)
|
||||||
|
* - Serves stale data immediately while revalidating in the background
|
||||||
|
* - Keeps stale value on fetch rejection
|
||||||
|
* - Bounded to `maxSize` entries to prevent unbounded memory growth
|
||||||
|
*/
|
||||||
|
export class PromiseCache {
|
||||||
|
private readonly cache: LRUCache<string, any, FetchContext>;
|
||||||
|
|
||||||
|
constructor(maxSize = 100_000, perf?: { now: () => number }) {
|
||||||
|
this.cache = new LRUCache<string, any, FetchContext>({
|
||||||
|
max: Math.max(1, maxSize),
|
||||||
|
// A global TTL is required to initialize lru-cache's TTL tracking internals.
|
||||||
|
// Per-entry TTLs passed to fetch() will override this default.
|
||||||
|
ttl: 1,
|
||||||
|
allowStale: true,
|
||||||
|
noDeleteOnStaleGet: true,
|
||||||
|
noDeleteOnFetchRejection: true,
|
||||||
|
allowStaleOnFetchRejection: true,
|
||||||
|
fetchMethod: async (_key, _staleValue, { context }) => {
|
||||||
|
return context.fetcher();
|
||||||
|
},
|
||||||
|
...(perf ? { perf, ttlResolution: 0 } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a cached value or execute the fetcher and cache its result.
|
||||||
|
*
|
||||||
|
* After the first successful fetch, expired entries return the stale
|
||||||
|
* value immediately and trigger a background revalidation (SWR).
|
||||||
|
* Only the very first call for a key (or after eviction) blocks
|
||||||
|
* on the fetch.
|
||||||
|
*/
|
||||||
|
getOrFetch<T>(key: string, fetcher: () => Promise<T>, ttlMs: number): Promise<T> {
|
||||||
|
return this.cache.forceFetch(key, {
|
||||||
|
ttl: ttlMs,
|
||||||
|
context: { fetcher },
|
||||||
|
}) as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Current number of entries (including stale). */
|
||||||
|
get size(): number {
|
||||||
|
return this.cache.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear all entries. */
|
||||||
|
clear(): void {
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import { createLogger } from "./logger";
|
|||||||
import { errorClassificationInterceptor } from "@/lib/grpc/interceptors/error-classification";
|
import { errorClassificationInterceptor } from "@/lib/grpc/interceptors/error-classification";
|
||||||
import { otelGrpcInterceptor } from "@/lib/grpc/interceptors/otel";
|
import { otelGrpcInterceptor } from "@/lib/grpc/interceptors/otel";
|
||||||
import { Code, ConnectError, Interceptor } from "@connectrpc/connect";
|
import { Code, ConnectError, Interceptor } from "@connectrpc/connect";
|
||||||
|
import { PromiseCache } from "./cache";
|
||||||
import { createServiceForHost } from "./service";
|
import { createServiceForHost } from "./service";
|
||||||
|
|
||||||
const logger = createLogger("zitadel");
|
const logger = createLogger("zitadel");
|
||||||
@@ -67,29 +68,30 @@ function getTTLForKey(keyPrefix: string, fallbackTtl: number) {
|
|||||||
return fallbackTtl;
|
return fallbackTtl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promiseCache = new Map<string, { promise: Promise<any>; expiresAt: number }>();
|
/**
|
||||||
|
* Build a cache key scoped to the current instance.
|
||||||
|
* In multi-tenant mode serviceConfig.instanceHost distinguishes tenants;
|
||||||
|
* without it we fall back to "default" (single-tenant / self-hosted).
|
||||||
|
*/
|
||||||
|
function instanceCacheKey(serviceConfig: ServiceConfig, key: string): string {
|
||||||
|
return `${serviceConfig.instanceHost || "default"}:${key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promiseCache = new PromiseCache(Number(cacheConfig.maxSize) || 100);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stale-while-revalidate in-memory cache to keep data fresh and deduplicate concurrent requests.
|
* A stale-while-revalidate in-memory cache to keep data fresh and deduplicate concurrent requests.
|
||||||
* We cache the Promise, so concurrent requests share the exact same execution.
|
* We cache the Promise, so concurrent requests share the exact same execution.
|
||||||
|
*
|
||||||
|
* The cache is bounded and periodically swept for expired entries
|
||||||
|
* to prevent unbounded memory growth.
|
||||||
*/
|
*/
|
||||||
function freshCache<T>(key: string, fetcher: () => Promise<T>, ttlMs: number): Promise<T> {
|
function freshCache<T>(key: string, fetcher: () => Promise<T>, ttlMs: number): Promise<T> {
|
||||||
if (!useCache) {
|
if (!useCache) {
|
||||||
return fetcher();
|
return fetcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
return promiseCache.getOrFetch(key, fetcher, ttlMs);
|
||||||
const cached = promiseCache.get(key);
|
|
||||||
if (cached && now < cached.expiresAt) {
|
|
||||||
return cached.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
const promise = fetcher();
|
|
||||||
promiseCache.set(key, { promise, expiresAt: now + ttlMs });
|
|
||||||
|
|
||||||
promise.catch(() => promiseCache.delete(key));
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHostedLoginTranslation({
|
export async function getHostedLoginTranslation({
|
||||||
@@ -125,7 +127,7 @@ export async function getHostedLoginTranslation({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(
|
return freshCache(
|
||||||
`getHostedLoginTranslation-${organization || "instance"}-${locale || "default"}`,
|
instanceCacheKey(serviceConfig, `getHostedLoginTranslation-${organization || "instance"}-${locale || "default"}`),
|
||||||
fetcher,
|
fetcher,
|
||||||
getTTLForKey("getHostedLoginTranslation", longCacheTTL),
|
getTTLForKey("getHostedLoginTranslation", longCacheTTL),
|
||||||
);
|
);
|
||||||
@@ -146,7 +148,7 @@ export async function getBrandingSettings({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(
|
return freshCache(
|
||||||
`getBrandingSettings-${organization || "instance"}`,
|
instanceCacheKey(serviceConfig, `getBrandingSettings-${organization || "instance"}`),
|
||||||
fetcher,
|
fetcher,
|
||||||
getTTLForKey("getBrandingSettings", longCacheTTL),
|
getTTLForKey("getBrandingSettings", longCacheTTL),
|
||||||
);
|
);
|
||||||
@@ -167,7 +169,7 @@ export async function getLoginSettings({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(
|
return freshCache(
|
||||||
`getLoginSettings-${organization || "instance"}`,
|
instanceCacheKey(serviceConfig, `getLoginSettings-${organization || "instance"}`),
|
||||||
fetcher,
|
fetcher,
|
||||||
getTTLForKey("getLoginSettings", defaultCacheTTL),
|
getTTLForKey("getLoginSettings", defaultCacheTTL),
|
||||||
);
|
);
|
||||||
@@ -180,7 +182,11 @@ export async function getSecuritySettings({ serviceConfig }: WithServiceConfig)
|
|||||||
return settingsService.getSecuritySettings({}).then((resp) => (resp.settings ? resp.settings : undefined));
|
return settingsService.getSecuritySettings({}).then((resp) => (resp.settings ? resp.settings : undefined));
|
||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(`getSecuritySettings-instance`, fetcher, getTTLForKey("getSecuritySettings", defaultCacheTTL));
|
return freshCache(
|
||||||
|
instanceCacheKey(serviceConfig, `getSecuritySettings-instance`),
|
||||||
|
fetcher,
|
||||||
|
getTTLForKey("getSecuritySettings", defaultCacheTTL),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLockoutSettings({ serviceConfig, orgId }: WithServiceConfig<{ orgId?: string }>) {
|
export async function getLockoutSettings({ serviceConfig, orgId }: WithServiceConfig<{ orgId?: string }>) {
|
||||||
@@ -193,7 +199,7 @@ export async function getLockoutSettings({ serviceConfig, orgId }: WithServiceCo
|
|||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(
|
return freshCache(
|
||||||
`getLockoutSettings-${orgId || "instance"}`,
|
instanceCacheKey(serviceConfig, `getLockoutSettings-${orgId || "instance"}`),
|
||||||
fetcher,
|
fetcher,
|
||||||
getTTLForKey("getLockoutSettings", defaultCacheTTL),
|
getTTLForKey("getLockoutSettings", defaultCacheTTL),
|
||||||
);
|
);
|
||||||
@@ -209,7 +215,7 @@ export async function getPasswordExpirySettings({ serviceConfig, orgId }: WithSe
|
|||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(
|
return freshCache(
|
||||||
`getPasswordExpirySettings-${orgId || "instance"}`,
|
instanceCacheKey(serviceConfig, `getPasswordExpirySettings-${orgId || "instance"}`),
|
||||||
fetcher,
|
fetcher,
|
||||||
getTTLForKey("getPasswordExpirySettings", defaultCacheTTL),
|
getTTLForKey("getPasswordExpirySettings", defaultCacheTTL),
|
||||||
);
|
);
|
||||||
@@ -251,7 +257,11 @@ export async function getAllowedLanguages({ serviceConfig }: WithServiceConfig)
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(`getGeneralSettings-instance`, fetcher, getTTLForKey("getGeneralSettings", longCacheTTL));
|
return freshCache(
|
||||||
|
instanceCacheKey(serviceConfig, `getGeneralSettings-instance`),
|
||||||
|
fetcher,
|
||||||
|
getTTLForKey("getGeneralSettings", longCacheTTL),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLegalAndSupportSettings({
|
export async function getLegalAndSupportSettings({
|
||||||
@@ -269,7 +279,7 @@ export async function getLegalAndSupportSettings({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(
|
return freshCache(
|
||||||
`getLegalAndSupportSettings-${organization || "instance"}`,
|
instanceCacheKey(serviceConfig, `getLegalAndSupportSettings-${organization || "instance"}`),
|
||||||
fetcher,
|
fetcher,
|
||||||
getTTLForKey("getLegalAndSupportSettings", longCacheTTL),
|
getTTLForKey("getLegalAndSupportSettings", longCacheTTL),
|
||||||
);
|
);
|
||||||
@@ -290,7 +300,7 @@ export async function getPasswordComplexitySettings({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return freshCache(
|
return freshCache(
|
||||||
`getPasswordComplexitySettings-${organization || "instance"}`,
|
instanceCacheKey(serviceConfig, `getPasswordComplexitySettings-${organization || "instance"}`),
|
||||||
fetcher,
|
fetcher,
|
||||||
getTTLForKey("getPasswordComplexitySettings", defaultCacheTTL),
|
getTTLForKey("getPasswordComplexitySettings", defaultCacheTTL),
|
||||||
);
|
);
|
||||||
@@ -850,7 +860,11 @@ export async function getDefaultOrg({ serviceConfig }: WithServiceConfig): Promi
|
|||||||
};
|
};
|
||||||
|
|
||||||
return useCache
|
return useCache
|
||||||
? freshCache(`getDefaultOrg-${"instance"}`, fetcher, getTTLForKey("getDefaultOrg", defaultCacheTTL))
|
? freshCache(
|
||||||
|
instanceCacheKey(serviceConfig, "getDefaultOrg-instance"),
|
||||||
|
fetcher,
|
||||||
|
getTTLForKey("getDefaultOrg", defaultCacheTTL),
|
||||||
|
)
|
||||||
: fetcher();
|
: fetcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+9
@@ -256,6 +256,9 @@ importers:
|
|||||||
escape-html:
|
escape-html:
|
||||||
specifier: ^1.0.3
|
specifier: ^1.0.3
|
||||||
version: 1.0.3
|
version: 1.0.3
|
||||||
|
lru-cache:
|
||||||
|
specifier: ^11.3.2
|
||||||
|
version: 11.3.2
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.577.0
|
specifier: ^0.577.0
|
||||||
version: 0.577.0([email protected])
|
version: 0.577.0([email protected])
|
||||||
@@ -9286,6 +9289,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
|
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
|
||||||
engines: {node: 20 || >=22}
|
engines: {node: 20 || >=22}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ==}
|
||||||
|
engines: {node: 20 || >=22}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
@@ -21867,6 +21874,8 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|||||||
Reference in New Issue
Block a user