fix(data): 清理旧数据并保护服务端写入

This commit is contained in:
Script Generator
2026-07-02 09:30:50 +08:00
parent 28e0c6de19
commit 916bd17a48
38 changed files with 1346 additions and 194 deletions

View File

@@ -1,4 +1,4 @@
import { api } from './api';
import { api, ApiRequestError } from './api';
export type ServerDataKey =
| 'products-overview'
@@ -19,13 +19,72 @@ export type ServerDataKey =
interface ServerDataResponse<T> {
key: ServerDataKey;
value: T | null;
version: string | null;
}
type ServerDataConflictBody<T> = {
code: 'APP_DATA_CONFLICT';
key: ServerDataKey;
currentValue: T | null;
currentVersion: string | null;
};
const serverDataVersions = new Map<ServerDataKey, string | null>();
export class ServerDataConflictError<T = unknown> extends Error {
constructor(
public readonly key: ServerDataKey,
public readonly currentValue: T | null,
public readonly currentVersion: string | null,
) {
super('Server data was changed by another user. Reload before saving again.');
this.name = 'ServerDataConflictError';
}
}
export async function loadServerData<T>(key: ServerDataKey): Promise<T | null> {
const res = await api.get<ServerDataResponse<T>>(`/data/${key}`);
serverDataVersions.set(key, res.version ?? null);
return res.value;
}
export async function saveServerData<T>(key: ServerDataKey, value: T): Promise<void> {
await api.put(`/data/${key}`, { value });
const payload: { value: T; version?: string | null } = { value };
if (serverDataVersions.has(key)) {
payload.version = serverDataVersions.get(key) ?? null;
}
try {
const res = await api.put<ServerDataResponse<T>>(`/data/${key}`, payload);
serverDataVersions.set(key, res.version ?? null);
} catch (error) {
if (
error instanceof ApiRequestError &&
error.status === 409 &&
isServerDataConflictBody<T>(error.body, key)
) {
throw new ServerDataConflictError(
error.body.key,
error.body.currentValue,
error.body.currentVersion,
);
}
throw error;
}
}
function isServerDataConflictBody<T>(
body: unknown,
key: ServerDataKey,
): body is ServerDataConflictBody<T> {
return (
typeof body === 'object' &&
body !== null &&
(body as { code?: unknown }).code === 'APP_DATA_CONFLICT' &&
(body as { key?: unknown }).key === key &&
('currentVersion' in body
? typeof (body as { currentVersion?: unknown }).currentVersion === 'string' ||
(body as { currentVersion?: unknown }).currentVersion === null
: true)
);
}