perf: cache allowlist and account-id normalization
This commit is contained in:
parent
3beb1b9da9
commit
9bde7f4fde
@ -16,6 +16,17 @@ export type AllowlistMatch<TSource extends string = AllowlistMatchSource> = {
|
|||||||
matchSource?: TSource;
|
matchSource?: TSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CachedAllowListSet = {
|
||||||
|
size: number;
|
||||||
|
set: Set<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALLOWLIST_SET_CACHE = new WeakMap<string[], CachedAllowListSet>();
|
||||||
|
const SIMPLE_ALLOWLIST_CACHE = new WeakMap<
|
||||||
|
Array<string | number>,
|
||||||
|
{ normalized: string[]; size: number; wildcard: boolean; set: Set<string> }
|
||||||
|
>();
|
||||||
|
|
||||||
export function formatAllowlistMatchMeta(
|
export function formatAllowlistMatchMeta(
|
||||||
match?: { matchKey?: string; matchSource?: string } | null,
|
match?: { matchKey?: string; matchSource?: string } | null,
|
||||||
): string {
|
): string {
|
||||||
@ -26,11 +37,12 @@ export function resolveAllowlistMatchByCandidates<TSource extends string>(params
|
|||||||
allowList: string[];
|
allowList: string[];
|
||||||
candidates: Array<{ value?: string; source: TSource }>;
|
candidates: Array<{ value?: string; source: TSource }>;
|
||||||
}): AllowlistMatch<TSource> {
|
}): AllowlistMatch<TSource> {
|
||||||
|
const allowSet = resolveAllowListSet(params.allowList);
|
||||||
for (const candidate of params.candidates) {
|
for (const candidate of params.candidates) {
|
||||||
if (!candidate.value) {
|
if (!candidate.value) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.allowList.includes(candidate.value)) {
|
if (allowSet.has(candidate.value)) {
|
||||||
return {
|
return {
|
||||||
allowed: true,
|
allowed: true,
|
||||||
matchKey: candidate.value,
|
matchKey: candidate.value,
|
||||||
@ -47,26 +59,57 @@ export function resolveAllowlistMatchSimple(params: {
|
|||||||
senderName?: string | null;
|
senderName?: string | null;
|
||||||
allowNameMatching?: boolean;
|
allowNameMatching?: boolean;
|
||||||
}): AllowlistMatch<"wildcard" | "id" | "name"> {
|
}): AllowlistMatch<"wildcard" | "id" | "name"> {
|
||||||
const allowFrom = params.allowFrom
|
const allowFrom = resolveSimpleAllowFrom(params.allowFrom);
|
||||||
.map((entry) => String(entry).trim().toLowerCase())
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
if (allowFrom.length === 0) {
|
if (allowFrom.size === 0) {
|
||||||
return { allowed: false };
|
return { allowed: false };
|
||||||
}
|
}
|
||||||
if (allowFrom.includes("*")) {
|
if (allowFrom.wildcard) {
|
||||||
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderId = params.senderId.toLowerCase();
|
const senderId = params.senderId.toLowerCase();
|
||||||
if (allowFrom.includes(senderId)) {
|
if (allowFrom.set.has(senderId)) {
|
||||||
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderName = params.senderName?.toLowerCase();
|
const senderName = params.senderName?.toLowerCase();
|
||||||
if (params.allowNameMatching === true && senderName && allowFrom.includes(senderName)) {
|
if (params.allowNameMatching === true && senderName && allowFrom.set.has(senderName)) {
|
||||||
return { allowed: true, matchKey: senderName, matchSource: "name" };
|
return { allowed: true, matchKey: senderName, matchSource: "name" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { allowed: false };
|
return { allowed: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveAllowListSet(allowList: string[]): Set<string> {
|
||||||
|
const cached = ALLOWLIST_SET_CACHE.get(allowList);
|
||||||
|
if (cached && cached.size === allowList.length) {
|
||||||
|
return cached.set;
|
||||||
|
}
|
||||||
|
const set = new Set(allowList);
|
||||||
|
ALLOWLIST_SET_CACHE.set(allowList, { size: allowList.length, set });
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSimpleAllowFrom(allowFrom: Array<string | number>): {
|
||||||
|
normalized: string[];
|
||||||
|
size: number;
|
||||||
|
wildcard: boolean;
|
||||||
|
set: Set<string>;
|
||||||
|
} {
|
||||||
|
const cached = SIMPLE_ALLOWLIST_CACHE.get(allowFrom);
|
||||||
|
if (cached && cached.size === allowFrom.length) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = allowFrom.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean);
|
||||||
|
const set = new Set(normalized);
|
||||||
|
const built = {
|
||||||
|
normalized,
|
||||||
|
size: allowFrom.length,
|
||||||
|
wildcard: set.has("*"),
|
||||||
|
set,
|
||||||
|
};
|
||||||
|
SIMPLE_ALLOWLIST_CACHE.set(allowFrom, built);
|
||||||
|
return built;
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,10 @@ const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
|||||||
const INVALID_CHARS_RE = /[^a-z0-9_-]+/g;
|
const INVALID_CHARS_RE = /[^a-z0-9_-]+/g;
|
||||||
const LEADING_DASH_RE = /^-+/;
|
const LEADING_DASH_RE = /^-+/;
|
||||||
const TRAILING_DASH_RE = /-+$/;
|
const TRAILING_DASH_RE = /-+$/;
|
||||||
|
const ACCOUNT_ID_CACHE_MAX = 512;
|
||||||
|
|
||||||
|
const normalizeAccountIdCache = new Map<string, string>();
|
||||||
|
const normalizeOptionalAccountIdCache = new Map<string, string | undefined>();
|
||||||
|
|
||||||
function canonicalizeAccountId(value: string): string {
|
function canonicalizeAccountId(value: string): string {
|
||||||
if (VALID_ID_RE.test(value)) {
|
if (VALID_ID_RE.test(value)) {
|
||||||
@ -32,7 +36,13 @@ export function normalizeAccountId(value: string | undefined | null): string {
|
|||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return DEFAULT_ACCOUNT_ID;
|
return DEFAULT_ACCOUNT_ID;
|
||||||
}
|
}
|
||||||
return normalizeCanonicalAccountId(trimmed) || DEFAULT_ACCOUNT_ID;
|
const cached = normalizeAccountIdCache.get(trimmed);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
const normalized = normalizeCanonicalAccountId(trimmed) || DEFAULT_ACCOUNT_ID;
|
||||||
|
setNormalizeCache(normalizeAccountIdCache, trimmed, normalized);
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeOptionalAccountId(value: string | undefined | null): string | undefined {
|
export function normalizeOptionalAccountId(value: string | undefined | null): string | undefined {
|
||||||
@ -40,5 +50,21 @@ export function normalizeOptionalAccountId(value: string | undefined | null): st
|
|||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return normalizeCanonicalAccountId(trimmed) || undefined;
|
if (normalizeOptionalAccountIdCache.has(trimmed)) {
|
||||||
|
return normalizeOptionalAccountIdCache.get(trimmed);
|
||||||
|
}
|
||||||
|
const normalized = normalizeCanonicalAccountId(trimmed) || undefined;
|
||||||
|
setNormalizeCache(normalizeOptionalAccountIdCache, trimmed, normalized);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNormalizeCache<T>(cache: Map<string, T>, key: string, value: T): void {
|
||||||
|
cache.set(key, value);
|
||||||
|
if (cache.size <= ACCOUNT_ID_CACHE_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const oldest = cache.keys().next();
|
||||||
|
if (!oldest.done) {
|
||||||
|
cache.delete(oldest.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,24 @@ import {
|
|||||||
normalizeStringEntriesLower,
|
normalizeStringEntriesLower,
|
||||||
} from "../../shared/string-normalization.js";
|
} from "../../shared/string-normalization.js";
|
||||||
|
|
||||||
|
const SLACK_SLUG_CACHE_MAX = 512;
|
||||||
|
const slackSlugCache = new Map<string, string>();
|
||||||
|
|
||||||
export function normalizeSlackSlug(raw?: string) {
|
export function normalizeSlackSlug(raw?: string) {
|
||||||
return normalizeHyphenSlug(raw);
|
const key = raw ?? "";
|
||||||
|
const cached = slackSlugCache.get(key);
|
||||||
|
if (cached !== undefined) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
const normalized = normalizeHyphenSlug(raw);
|
||||||
|
slackSlugCache.set(key, normalized);
|
||||||
|
if (slackSlugCache.size > SLACK_SLUG_CACHE_MAX) {
|
||||||
|
const oldest = slackSlugCache.keys().next();
|
||||||
|
if (!oldest.done) {
|
||||||
|
slackSlugCache.delete(oldest.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeAllowList(list?: Array<string | number>) {
|
export function normalizeAllowList(list?: Array<string | number>) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user