chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
const APP_NAMESPACE = "ca-frontend";
|
||||
|
||||
export type StorageBackend =
|
||||
| "memory"
|
||||
| "sessionStorage"
|
||||
| "localStorage"
|
||||
| "disabled"
|
||||
| "forbidden";
|
||||
|
||||
export type StorageValueCodec =
|
||||
| "color-scheme-v1"
|
||||
| "opaque-string-v1"
|
||||
| "none";
|
||||
|
||||
export type StorageKeyInput = Readonly<{
|
||||
logicalName: string;
|
||||
scope: string;
|
||||
name: string;
|
||||
backend: StorageBackend;
|
||||
classification:
|
||||
| "public-preference"
|
||||
| "opaque-cache"
|
||||
| "sensitive-forbidden";
|
||||
schemaVersion: number;
|
||||
valueCodec: StorageValueCodec;
|
||||
ttl: number | "session" | null;
|
||||
migration: "discard";
|
||||
quotaFallback: "memory" | "no-persist" | "feature-disable";
|
||||
}>;
|
||||
|
||||
export type StorageDefinition = Readonly<
|
||||
StorageKeyInput & { physicalKey: string }
|
||||
>;
|
||||
|
||||
export const STORAGE_REGISTRY = Object.freeze({
|
||||
COLOR_SCHEME: defineStorageKey({
|
||||
logicalName: "COLOR_SCHEME",
|
||||
scope: "preference",
|
||||
name: "color-scheme",
|
||||
backend: "localStorage",
|
||||
classification: "public-preference",
|
||||
schemaVersion: 1,
|
||||
valueCodec: "color-scheme-v1",
|
||||
ttl: null,
|
||||
migration: "discard",
|
||||
quotaFallback: "memory",
|
||||
}),
|
||||
CHUNK_RELOAD_GUARD: defineStorageKey({
|
||||
logicalName: "CHUNK_RELOAD_GUARD",
|
||||
scope: "release",
|
||||
name: "chunk-reload-guard",
|
||||
backend: "sessionStorage",
|
||||
classification: "opaque-cache",
|
||||
schemaVersion: 1,
|
||||
valueCodec: "opaque-string-v1",
|
||||
ttl: "session",
|
||||
migration: "discard",
|
||||
quotaFallback: "no-persist",
|
||||
}),
|
||||
QUERY_PERSISTENCE: defineStorageKey({
|
||||
logicalName: "QUERY_PERSISTENCE",
|
||||
scope: "cache",
|
||||
name: "query-persistence",
|
||||
backend: "disabled",
|
||||
classification: "sensitive-forbidden",
|
||||
schemaVersion: 1,
|
||||
valueCodec: "none",
|
||||
ttl: null,
|
||||
migration: "discard",
|
||||
quotaFallback: "feature-disable",
|
||||
}),
|
||||
AUTH_TOKEN: defineStorageKey({
|
||||
logicalName: "AUTH_TOKEN",
|
||||
scope: "auth",
|
||||
name: "auth-token",
|
||||
backend: "forbidden",
|
||||
classification: "sensitive-forbidden",
|
||||
schemaVersion: 1,
|
||||
valueCodec: "none",
|
||||
ttl: null,
|
||||
migration: "discard",
|
||||
quotaFallback: "feature-disable",
|
||||
}),
|
||||
});
|
||||
|
||||
export function defineStorageKey<Definition extends StorageKeyInput>(
|
||||
definition: Definition,
|
||||
): Readonly<Definition & { physicalKey: string }> {
|
||||
if (
|
||||
!["color-scheme-v1", "opaque-string-v1", "none"].includes(
|
||||
definition.valueCodec,
|
||||
)
|
||||
) {
|
||||
throw new Error("Unknown client storage value codec");
|
||||
}
|
||||
if (definition.migration !== "discard") {
|
||||
throw new Error("Unsupported client storage migration policy");
|
||||
}
|
||||
if (definition.classification === "sensitive-forbidden") {
|
||||
if (!["disabled", "forbidden"].includes(definition.backend)) {
|
||||
throw new Error("Sensitive client storage registration is forbidden");
|
||||
}
|
||||
if (definition.valueCodec !== "none") {
|
||||
throw new Error("Sensitive client storage codec is forbidden");
|
||||
}
|
||||
} else if (definition.valueCodec === "none") {
|
||||
throw new Error("Persisted storage keys require a value codec");
|
||||
}
|
||||
if (!Number.isInteger(definition.schemaVersion) || definition.schemaVersion < 1) {
|
||||
throw new Error("Storage schemaVersion must be a positive integer");
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
...definition,
|
||||
physicalKey: buildPhysicalKey(
|
||||
definition.scope,
|
||||
definition.schemaVersion,
|
||||
definition.name,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
export function buildPhysicalKey(
|
||||
scope: string,
|
||||
schemaVersion: number,
|
||||
name: string,
|
||||
): string {
|
||||
return `${APP_NAMESPACE}:${scope}:v${schemaVersion}:${name}`;
|
||||
}
|
||||
|
||||
export function getStorageDefinition(logicalName: string): StorageDefinition {
|
||||
const registry: Readonly<Record<string, StorageDefinition>> = STORAGE_REGISTRY;
|
||||
const definition = registry[logicalName];
|
||||
if (!definition) throw new Error(`Unregistered storage key: ${logicalName}`);
|
||||
if (definition.classification === "sensitive-forbidden") {
|
||||
throw new Error(`Forbidden storage key: ${logicalName}`);
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
|
||||
export function isStorageValueAllowed(
|
||||
definition: StorageDefinition,
|
||||
value: unknown,
|
||||
): boolean {
|
||||
switch (definition.valueCodec) {
|
||||
case "color-scheme-v1":
|
||||
return value === "light" || value === "dark" || value === "system";
|
||||
case "opaque-string-v1":
|
||||
return (
|
||||
typeof value === "string" &&
|
||||
value.length >= 1 &&
|
||||
value.length <= 2_048
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user