You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
4.3 KiB
157 lines
4.3 KiB
import {
|
|
CACHE_DURATION_UNIT_DAY,
|
|
CACHE_DURATION_UNIT_HOUR,
|
|
CACHE_DURATION_UNIT_MINUTE,
|
|
CACHE_DURATION_UNIT_MS,
|
|
CACHE_DURATION_UNIT_SECOND,
|
|
CACHE_ERROR_MESSAGES,
|
|
CACHE_MS_DAY,
|
|
CACHE_MS_HOUR,
|
|
CACHE_MS_MINUTE,
|
|
CACHE_MS_SECOND,
|
|
CACHE_POLICY_CATALOG,
|
|
CACHE_POLICY_IMMUTABLE,
|
|
CACHE_POLICY_INTERACTIVE,
|
|
CACHE_POLICY_PRIVATE_SESSION,
|
|
CACHE_POLICY_REALTIME,
|
|
CACHE_READ_MODE_CACHE_FIRST,
|
|
CACHE_READ_MODE_MUST_REVALIDATE,
|
|
CACHE_READ_MODE_STALE_WHILE_REVALIDATE,
|
|
DEFAULT_CACHE_POLICY
|
|
} from './consts.ts';
|
|
import { CachePolicyError } from './errors.ts';
|
|
import type { CachePolicy, CacheReadMode, DurationInput, ResolvedCachePolicy } from './types.ts';
|
|
|
|
export function defaultCachePolicies(): Record<string, CachePolicy> {
|
|
return {
|
|
[CACHE_POLICY_REALTIME]: {
|
|
freshFor: 0,
|
|
staleFor: 0,
|
|
staleIfErrorFor: 0,
|
|
gcAfter: CACHE_MS_MINUTE,
|
|
mode: CACHE_READ_MODE_MUST_REVALIDATE,
|
|
persist: false
|
|
},
|
|
[CACHE_POLICY_INTERACTIVE]: {
|
|
freshFor: 30 * CACHE_MS_SECOND,
|
|
staleFor: 5 * CACHE_MS_MINUTE,
|
|
staleIfErrorFor: 30 * CACHE_MS_MINUTE,
|
|
gcAfter: 30 * CACHE_MS_MINUTE,
|
|
mode: CACHE_READ_MODE_STALE_WHILE_REVALIDATE,
|
|
persist: true
|
|
},
|
|
[CACHE_POLICY_CATALOG]: {
|
|
freshFor: 10 * CACHE_MS_MINUTE,
|
|
staleFor: CACHE_MS_DAY,
|
|
staleIfErrorFor: 7 * CACHE_MS_DAY,
|
|
gcAfter: 7 * CACHE_MS_DAY,
|
|
mode: CACHE_READ_MODE_STALE_WHILE_REVALIDATE,
|
|
persist: true
|
|
},
|
|
[CACHE_POLICY_PRIVATE_SESSION]: {
|
|
freshFor: 10 * CACHE_MS_SECOND,
|
|
staleFor: CACHE_MS_MINUTE,
|
|
staleIfErrorFor: 0,
|
|
gcAfter: 15 * CACHE_MS_MINUTE,
|
|
mode: CACHE_READ_MODE_MUST_REVALIDATE,
|
|
persist: false
|
|
},
|
|
[CACHE_POLICY_IMMUTABLE]: {
|
|
freshFor: Number.MAX_SAFE_INTEGER,
|
|
staleFor: Number.MAX_SAFE_INTEGER,
|
|
staleIfErrorFor: Number.MAX_SAFE_INTEGER,
|
|
gcAfter: Number.MAX_SAFE_INTEGER,
|
|
mode: CACHE_READ_MODE_CACHE_FIRST,
|
|
persist: true
|
|
}
|
|
};
|
|
}
|
|
|
|
export function resolvePolicy(input: {
|
|
policy?: string | CachePolicy;
|
|
policies?: Record<string, CachePolicy>;
|
|
defaultPolicy?: string | CachePolicy;
|
|
mode?: CacheReadMode;
|
|
persist?: boolean;
|
|
}): ResolvedCachePolicy {
|
|
const namedPolicies = {
|
|
...defaultCachePolicies(),
|
|
...(input.policies ?? {})
|
|
};
|
|
const selected = input.policy ?? input.defaultPolicy ?? DEFAULT_CACHE_POLICY;
|
|
|
|
let raw: CachePolicy;
|
|
let name: string | undefined;
|
|
|
|
if (typeof selected === 'string') {
|
|
name = selected;
|
|
const named = namedPolicies[selected];
|
|
if (!named) throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_UNKNOWN(selected));
|
|
raw = named;
|
|
} else {
|
|
raw = selected;
|
|
}
|
|
|
|
const merged: CachePolicy = {
|
|
...DEFAULT_CACHE_POLICY,
|
|
...raw,
|
|
mode: input.mode ?? raw.mode ?? DEFAULT_CACHE_POLICY.mode,
|
|
persist: input.persist ?? raw.persist ?? DEFAULT_CACHE_POLICY.persist
|
|
};
|
|
|
|
const freshForMs = durationToMs(merged.freshFor, 'freshFor');
|
|
const staleForMs = durationToMs(merged.staleFor ?? 0, 'staleFor');
|
|
const staleIfErrorForMs = durationToMs(merged.staleIfErrorFor ?? 0, 'staleIfErrorFor');
|
|
|
|
const gcAfterMs = durationToMs(
|
|
merged.gcAfter ?? Math.max(freshForMs + staleForMs, freshForMs + staleIfErrorForMs),
|
|
'gcAfter'
|
|
);
|
|
|
|
if (freshForMs < 0 || staleForMs < 0 || staleIfErrorForMs < 0 || gcAfterMs < 0) {
|
|
throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_DURATION_NEGATIVE);
|
|
}
|
|
|
|
return {
|
|
...(name ? { name } : {}),
|
|
freshForMs,
|
|
staleForMs,
|
|
staleIfErrorForMs,
|
|
gcAfterMs,
|
|
mode: merged.mode ?? CACHE_READ_MODE_STALE_WHILE_REVALIDATE,
|
|
persist: merged.persist ?? true
|
|
};
|
|
}
|
|
|
|
export function durationToMs(value: DurationInput, fieldName = 'duration'): number {
|
|
if (typeof value === 'number') {
|
|
if (!Number.isFinite(value)) {
|
|
throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_DURATION_NOT_FINITE(fieldName));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/.exec(value);
|
|
if (!match) {
|
|
throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_DURATION_INVALID(fieldName));
|
|
}
|
|
|
|
const amount = Number(match[1]);
|
|
const unit = match[2];
|
|
|
|
switch (unit) {
|
|
case CACHE_DURATION_UNIT_MS:
|
|
return amount;
|
|
case CACHE_DURATION_UNIT_SECOND:
|
|
return amount * CACHE_MS_SECOND;
|
|
case CACHE_DURATION_UNIT_MINUTE:
|
|
return amount * CACHE_MS_MINUTE;
|
|
case CACHE_DURATION_UNIT_HOUR:
|
|
return amount * CACHE_MS_HOUR;
|
|
case CACHE_DURATION_UNIT_DAY:
|
|
return amount * CACHE_MS_DAY;
|
|
default:
|
|
throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_DURATION_UNIT_INVALID(fieldName));
|
|
}
|
|
}
|