feat: 기능 추가 과정중
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import type { CacheScopeSnapshot } from "../../contracts/server-state-scope.ts";
|
||||
|
||||
export type ConditionalValidatorBinding = Readonly<{
|
||||
definitionId: string;
|
||||
identityToken: string;
|
||||
representationVersion: number;
|
||||
scope: CacheScopeSnapshot;
|
||||
}>;
|
||||
|
||||
export type ConditionalValidatorStore = Readonly<{
|
||||
install(
|
||||
binding: ConditionalValidatorBinding,
|
||||
validator: string,
|
||||
cacheRevision: number,
|
||||
): boolean;
|
||||
prepare(
|
||||
binding: ConditionalValidatorBinding,
|
||||
cacheRevision: number,
|
||||
): string | null;
|
||||
acceptNotModified(
|
||||
binding: ConditionalValidatorBinding,
|
||||
cacheRevision: number,
|
||||
hasMappedValue: boolean,
|
||||
): boolean;
|
||||
remove(binding: ConditionalValidatorBinding): void;
|
||||
clear(): void;
|
||||
}>;
|
||||
|
||||
type ValidatorRow = {
|
||||
validator: string;
|
||||
cacheRevision: number;
|
||||
generation: number;
|
||||
};
|
||||
|
||||
export function createConditionalValidatorStore(
|
||||
maxEntries = 1_024,
|
||||
): ConditionalValidatorStore {
|
||||
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
||||
throw new TypeError("Invalid conditional validator capacity.");
|
||||
}
|
||||
const rows = new Map<string, ValidatorRow>();
|
||||
|
||||
function key(binding: ConditionalValidatorBinding): string | null {
|
||||
if (
|
||||
!binding.scope.isCurrent() ||
|
||||
!binding.definitionId ||
|
||||
!/^[A-Za-z0-9._:-]{16,128}$/.test(binding.identityToken) ||
|
||||
!Number.isSafeInteger(binding.representationVersion) ||
|
||||
binding.representationVersion < 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
binding.scope.fingerprint,
|
||||
binding.definitionId,
|
||||
binding.identityToken,
|
||||
binding.representationVersion,
|
||||
].join(":");
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
install(binding, validator, cacheRevision) {
|
||||
const selectedKey = key(binding);
|
||||
if (
|
||||
!selectedKey ||
|
||||
!isSafeEntityTag(validator) ||
|
||||
!Number.isSafeInteger(cacheRevision) ||
|
||||
cacheRevision < 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (!rows.has(selectedKey) && rows.size >= maxEntries) return false;
|
||||
rows.set(selectedKey, {
|
||||
validator,
|
||||
cacheRevision,
|
||||
generation: binding.scope.generation,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
prepare(binding, cacheRevision) {
|
||||
const selectedKey = key(binding);
|
||||
if (!selectedKey) return null;
|
||||
const row = rows.get(selectedKey);
|
||||
return row &&
|
||||
row.generation === binding.scope.generation &&
|
||||
row.cacheRevision === cacheRevision
|
||||
? row.validator
|
||||
: null;
|
||||
},
|
||||
acceptNotModified(binding, cacheRevision, hasMappedValue) {
|
||||
const selectedKey = key(binding);
|
||||
if (!selectedKey || !hasMappedValue) return false;
|
||||
const row = rows.get(selectedKey);
|
||||
return Boolean(
|
||||
row &&
|
||||
row.generation === binding.scope.generation &&
|
||||
row.cacheRevision === cacheRevision,
|
||||
);
|
||||
},
|
||||
remove(binding) {
|
||||
const selectedKey = key(binding);
|
||||
if (selectedKey) rows.delete(selectedKey);
|
||||
},
|
||||
clear() {
|
||||
rows.clear();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function isSafeEntityTag(value: string): boolean {
|
||||
if (value.length < 3 || value.length > 256) return false;
|
||||
const opaque = value.startsWith('W/"')
|
||||
? value.slice(3, -1)
|
||||
: value.startsWith('"')
|
||||
? value.slice(1, -1)
|
||||
: null;
|
||||
if (opaque === null || !value.endsWith('"')) return false;
|
||||
return [...opaque].every((character) => {
|
||||
const code = character.codePointAt(0) ?? 0;
|
||||
return code === 0x21 || (code >= 0x23 && code <= 0x7e) ||
|
||||
(code >= 0x80 && code <= 0xff);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user