fix(data): 清理旧数据并保护服务端写入
This commit is contained in:
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user