mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(core): add cache class and invalidate cache after onboarding
This commit is contained in:
parent
dd91ebddfa
commit
ae389c0a7a
9 changed files with 62 additions and 37 deletions
|
@ -2,29 +2,49 @@ import Keyv from 'keyv';
|
|||
import type { AnyAsyncFunction } from 'p-memoize';
|
||||
import pMemoize from 'p-memoize';
|
||||
|
||||
const cacheKeys = Object.freeze(['sie', 'sie-full', 'phrases', 'lng-tags'] as const);
|
||||
const cacheKeys = Object.freeze(['sie', 'sie-full', 'phrases', 'phrases-lng-tags'] as const);
|
||||
|
||||
/** Well-known data type key for cache. */
|
||||
export type WellKnownCacheKey = (typeof cacheKeys)[number];
|
||||
|
||||
// Not sure if we need guard value for `.has()` and `.get()`,
|
||||
// trust cache value for now.
|
||||
const wellKnownCache = new Keyv({ ttl: 300_000 /* 5 minutes */ });
|
||||
const buildKey = (tenantId: string, key: WellKnownCacheKey) => `${tenantId}:${key}` as const;
|
||||
|
||||
class WellKnownCache {
|
||||
// Not sure if we need guard value for `.has()` and `.get()`,
|
||||
// trust cache value for now.
|
||||
#keyv = new Keyv({ ttl: 180_000 /* 3 minutes */ });
|
||||
|
||||
/**
|
||||
* Use for centralized well-known data caching.
|
||||
*
|
||||
* WARN: You should store only well-known (public) data since it's a central cache.
|
||||
*/
|
||||
use<FunctionToMemoize extends AnyAsyncFunction>(
|
||||
tenantId: string,
|
||||
key: WellKnownCacheKey,
|
||||
run: FunctionToMemoize
|
||||
) {
|
||||
return pMemoize(run, {
|
||||
cacheKey: () => buildKey(tenantId, key),
|
||||
cache: this.#keyv,
|
||||
});
|
||||
}
|
||||
|
||||
async invalidate(tenantId: string, keys = [...cacheKeys]) {
|
||||
return this.#keyv.delete(keys.map((key) => buildKey(tenantId, key)));
|
||||
}
|
||||
|
||||
async set(tenantId: string, key: WellKnownCacheKey, value: unknown) {
|
||||
return this.#keyv.set(buildKey(tenantId, key), value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use for centralized well-known data caching.
|
||||
* The central TTL cache for well-known data. The default TTL is 3 minutes.
|
||||
*
|
||||
* This cache is intended for public APIs that are tolerant for data freshness.
|
||||
* For Management APIs, you should use uncached functions instead.
|
||||
*
|
||||
* WARN: You should store only well-known (public) data since it's a central cache.
|
||||
*/
|
||||
export const useWellKnownCache = <FunctionToMemoize extends AnyAsyncFunction>(
|
||||
tenantId: string,
|
||||
key: WellKnownCacheKey,
|
||||
run: FunctionToMemoize
|
||||
) =>
|
||||
pMemoize(run, {
|
||||
cacheKey: () => `${tenantId}:${key}`,
|
||||
cache: wellKnownCache,
|
||||
});
|
||||
|
||||
export const invalidateWellKnownCache = async (tenantId: string) =>
|
||||
wellKnownCache.delete(cacheKeys.map((key) => `${tenantId}:${key}` as const));
|
||||
export const wellKnownCache = new WellKnownCache();
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
zhCnTag,
|
||||
zhHkTag,
|
||||
} from '#src/__mocks__/custom-phrase.js';
|
||||
import { invalidateWellKnownCache } from '#src/caches/well-known.js';
|
||||
import { wellKnownCache } from '#src/caches/well-known.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { MockQueries } from '#src/test-utils/tenant.js';
|
||||
|
||||
|
@ -50,7 +50,7 @@ const { getPhrases } = createPhraseLibrary(
|
|||
);
|
||||
|
||||
afterEach(async () => {
|
||||
await invalidateWellKnownCache(tenantId);
|
||||
await wellKnownCache.invalidate(tenantId);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { CustomPhrase } from '@logto/schemas';
|
|||
import cleanDeep from 'clean-deep';
|
||||
import deepmerge from 'deepmerge';
|
||||
|
||||
import { useWellKnownCache } from '#src/caches/well-known.js';
|
||||
import { wellKnownCache } from '#src/caches/well-known.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
|
||||
export const createPhraseLibrary = (queries: Queries, tenantId: string) => {
|
||||
|
@ -31,11 +31,11 @@ export const createPhraseLibrary = (queries: Queries, tenantId: string) => {
|
|||
);
|
||||
};
|
||||
|
||||
const getPhrases = useWellKnownCache(tenantId, 'phrases', _getPhrases);
|
||||
const getPhrases = wellKnownCache.use(tenantId, 'phrases', _getPhrases);
|
||||
|
||||
const getAllCustomLanguageTags = useWellKnownCache(
|
||||
const getAllCustomLanguageTags = wellKnownCache.use(
|
||||
tenantId,
|
||||
'lng-tags',
|
||||
'phrases-lng-tags',
|
||||
findAllCustomLanguageTags
|
||||
);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { SignInExperiences, ConnectorType } from '@logto/schemas';
|
|||
import { deduplicate } from '@silverhand/essentials';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useWellKnownCache } from '#src/caches/well-known.js';
|
||||
import { wellKnownCache } from '#src/caches/well-known.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type { ConnectorLibrary } from '#src/libraries/connector.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
|
@ -55,7 +55,7 @@ export const createSignInExperienceLibrary = (
|
|||
});
|
||||
};
|
||||
|
||||
const getSignInExperience = useWellKnownCache(tenantId, 'sie', findDefaultSignInExperience);
|
||||
const getSignInExperience = wellKnownCache.use(tenantId, 'sie', findDefaultSignInExperience);
|
||||
|
||||
const _getFullSignInExperience = async (): Promise<FullSignInExperience> => {
|
||||
const [signInExperience, logtoConnectors] = await Promise.all([
|
||||
|
@ -88,7 +88,11 @@ export const createSignInExperienceLibrary = (
|
|||
};
|
||||
};
|
||||
|
||||
const getFullSignInExperience = useWellKnownCache(tenantId, 'sie-full', _getFullSignInExperience);
|
||||
const getFullSignInExperience = wellKnownCache.use(
|
||||
tenantId,
|
||||
'sie-full',
|
||||
_getFullSignInExperience
|
||||
);
|
||||
|
||||
return {
|
||||
validateLanguageInfo,
|
||||
|
|
|
@ -18,10 +18,6 @@ export const createSignInExperienceQueries = (pool: CommonQueryMethods) => {
|
|||
|
||||
return {
|
||||
updateDefaultSignInExperience,
|
||||
/**
|
||||
* NOTE: Use `getSignInExperience()` from sign-in experience library
|
||||
* if possible since that function leverages cache.
|
||||
*/
|
||||
findDefaultSignInExperience,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from '@logto/schemas';
|
||||
import { conditional, conditionalArray } from '@silverhand/essentials';
|
||||
|
||||
import { wellKnownCache } from '#src/caches/well-known.js';
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import type { ConnectorLibrary } from '#src/libraries/connector.js';
|
||||
import { assignInteractionResults } from '#src/libraries/session.js';
|
||||
|
@ -149,7 +150,7 @@ const parseUserProfile = async (
|
|||
export default async function submitInteraction(
|
||||
interaction: VerifiedInteractionResult,
|
||||
ctx: WithInteractionDetailsContext,
|
||||
{ provider, libraries, connectors, queries }: TenantContext,
|
||||
{ provider, libraries, connectors, queries, id: tenantId }: TenantContext,
|
||||
log?: LogEntry
|
||||
) {
|
||||
const { hasActiveUsers, findUserById, updateUserById } = queries.users;
|
||||
|
@ -191,6 +192,10 @@ export default async function submitInteraction(
|
|||
await updateDefaultSignInExperience({
|
||||
signInMode: isCloud ? SignInMode.SignInAndRegister : SignInMode.SignIn,
|
||||
});
|
||||
|
||||
// Normally we don't need to manually invalidate TTL cache.
|
||||
// This is for better OSS onboarding experience.
|
||||
await wellKnownCache.invalidate(tenantId, ['sie', 'sie-full']);
|
||||
}
|
||||
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||
|
|
|
@ -4,7 +4,7 @@ import { pickDefault } from '@logto/shared/esm';
|
|||
|
||||
import { trTrTag, zhCnTag, zhHkTag } from '#src/__mocks__/custom-phrase.js';
|
||||
import { mockSignInExperience } from '#src/__mocks__/index.js';
|
||||
import { invalidateWellKnownCache } from '#src/caches/well-known.js';
|
||||
import { wellKnownCache } from '#src/caches/well-known.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
|
@ -42,7 +42,7 @@ const phraseRequest = createRequester({
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await invalidateWellKnownCache(tenantContext.id);
|
||||
await wellKnownCache.invalidate(tenantContext.id);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
|||
|
||||
import { zhCnTag } from '#src/__mocks__/custom-phrase.js';
|
||||
import { mockSignInExperience } from '#src/__mocks__/index.js';
|
||||
import { invalidateWellKnownCache } from '#src/caches/well-known.js';
|
||||
import { wellKnownCache } from '#src/caches/well-known.js';
|
||||
import Queries from '#src/tenants/Queries.js';
|
||||
import { createMockProvider } from '#src/test-utils/oidc-provider.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
|
@ -60,7 +60,7 @@ const phraseRequest = createRequester({
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await invalidateWellKnownCache(tenantContext.id);
|
||||
await wellKnownCache.invalidate(tenantContext.id);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
mockWechatConnector,
|
||||
mockWechatNativeConnector,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import { invalidateWellKnownCache } from '#src/caches/well-known.js';
|
||||
import { wellKnownCache } from '#src/caches/well-known.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm } = createMockUtils(jest);
|
||||
|
@ -56,7 +56,7 @@ const tenantContext = new MockTenant(
|
|||
|
||||
describe('GET /.well-known/sign-in-exp', () => {
|
||||
afterEach(async () => {
|
||||
await invalidateWellKnownCache(tenantContext.id);
|
||||
await wellKnownCache.invalidate(tenantContext.id);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue