Files
cozsweet-frontend-nextjs/src/utils/storage.ts
T

233 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
/**
* SpAsyncUtil — 键值对持久化工具类(基于 unstorage)
*
* 用法:
* ```ts
* import { SpAsyncUtil } from "@/utils/storage";
*
* const r = await SpAsyncUtil.getString("auth_token");
* if (r.success && r.data) { ... }
* ```
*/
import { createStorage, type Storage } from "unstorage";
import localStorageDriver from "unstorage/drivers/localstorage";
import memoryDriver from "unstorage/drivers/memory";
import type { ZodType } from "zod";
import { Result, type Result as ResultT } from "@/utils/result";
export class SpAsyncUtil {
// ===== 私有静态状态 =====
// 环境判断 + 安全初始化:SSR 用 memory driver,浏览器用 localStorage driver。
// unstorage 1.10+ 要求显式传入 localStorage,故这里同步注入 window.localStorage。
private static _storage: Storage = createDefaultStorage();
// 私有构造函数:禁止实例化(对齐 Dart `SpAsyncUtil._()`
private constructor() {}
// ===== 底层 storage 访问 =====
/**
* 替换底层 storage 实例(仅测试/Storybook 用)
* @internal
*/
static setStorage(instance: Storage): void {
SpAsyncUtil._storage = instance;
}
/** 获取底层 storage 实例(高级用法) */
static getRawStorage(): Storage {
return SpAsyncUtil._storage;
}
// ===== string =====
static async getString(key: string): Promise<ResultT<string | null>> {
try {
const value = await SpAsyncUtil._storage.getItem<string>(key);
if (value === null || value === undefined) return Result.ok(null);
if (typeof value !== "string") return Result.ok(null);
if (value.length === 0) return Result.ok(null);
return Result.ok(value);
} catch (e) {
return Result.err(e);
}
}
static async setString(
key: string,
value: string,
): Promise<ResultT<void>> {
try {
await SpAsyncUtil._storage.setItem(key, value);
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
// ===== int =====
static async getInt(key: string): Promise<ResultT<number | null>> {
try {
const value = await SpAsyncUtil._storage.getItem<number>(key);
if (value === null || value === undefined) return Result.ok(null);
if (typeof value !== "number" || !Number.isFinite(value)) return Result.ok(null);
return Result.ok(Math.trunc(value));
} catch (e) {
return Result.err(e);
}
}
static async setInt(key: string, value: number): Promise<ResultT<void>> {
try {
await SpAsyncUtil._storage.setItem(key, value);
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
// ===== double =====
static async getDouble(key: string): Promise<ResultT<number | null>> {
try {
const value = await SpAsyncUtil._storage.getItem<number>(key);
if (value === null || value === undefined) return Result.ok(null);
if (typeof value !== "number" || !Number.isFinite(value)) return Result.ok(null);
return Result.ok(value);
} catch (e) {
return Result.err(e);
}
}
static async setDouble(key: string, value: number): Promise<ResultT<void>> {
try {
await SpAsyncUtil._storage.setItem(key, value);
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
// ===== bool =====
static async getBool(key: string): Promise<ResultT<boolean | null>> {
try {
const value = await SpAsyncUtil._storage.getItem<boolean>(key);
if (value === null || value === undefined) return Result.ok(null);
if (typeof value !== "boolean") return Result.ok(null);
return Result.ok(value);
} catch (e) {
return Result.err(e);
}
}
static async setBool(key: string, value: boolean): Promise<ResultT<void>> {
try {
await SpAsyncUtil._storage.setItem(key, value);
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
// ===== string list =====
static async getStringList(key: string): Promise<ResultT<string[] | null>> {
try {
const value = await SpAsyncUtil._storage.getItem<string[]>(key);
if (value === null || value === undefined) return Result.ok(null);
if (!Array.isArray(value)) return Result.ok(null);
if (!value.every((v) => typeof v === "string")) return Result.ok(null);
return Result.ok(value);
} catch (e) {
return Result.err(e);
}
}
static async setStringList(
key: string,
value: string[],
): Promise<ResultT<void>> {
try {
await SpAsyncUtil._storage.setItem(key, value);
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
// ===== JSON (with optional Zod validation) =====
static async getJson<T>(
key: string,
schema: ZodType<T>,
): Promise<ResultT<T | null>> {
try {
const value = await SpAsyncUtil._storage.getItem<unknown>(key);
if (value === null || value === undefined) return Result.ok(null);
const json = typeof value === "string" ? JSON.parse(value) : value;
const parsed = schema.parse(json);
return Result.ok(parsed);
} catch (e) {
return Result.err(e);
}
}
static async setJson<T>(
key: string,
value: T,
schema?: ZodType<T>,
): Promise<ResultT<void>> {
try {
const validated = schema ? schema.parse(value) : value;
await SpAsyncUtil._storage.setItem(key, JSON.stringify(validated));
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
// ===== generic operations =====
static async remove(key: string): Promise<ResultT<void>> {
try {
await SpAsyncUtil._storage.removeItem(key);
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
static async clear(): Promise<ResultT<void>> {
try {
await SpAsyncUtil._storage.clear();
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
static async has(key: string): Promise<ResultT<boolean>> {
try {
const exists = await SpAsyncUtil._storage.hasItem(key);
return Result.ok(exists);
} catch (e) {
return Result.err(e);
}
}
}
function createDefaultStorage(): Storage {
if (typeof window === "undefined") {
return createStorage({ driver: memoryDriver() });
}
try {
return createStorage({
driver: localStorageDriver({
base: "cozsweet:",
localStorage: window.localStorage,
window,
}),
});
} catch {
return createStorage({ driver: memoryDriver() });
}
}