50c59ca9d4
Co-Authored-By: Claude Code <noreply@anthropic.com>
79 lines
2.4 KiB
TypeScript
79 lines
2.4 KiB
TypeScript
/**
|
||
* 资产预签名上传状态机(纯函数 reducer)
|
||
*
|
||
* 三段链路 init(POST /assets 预签名)→ PUT(staging 直传)→ complete
|
||
* (核验生效并返回签名 URL)。任何一步失败进入 error(携带阶段与原因,
|
||
* 可重新开始);complete 幂等,成功态保留签名 URL 与过期时间用于预览。
|
||
* 抽成纯 reducer 以便单测与 UI 解耦。
|
||
*/
|
||
|
||
export type AssetUploadStage =
|
||
'idle' | 'initializing' | 'uploading' | 'completing' | 'done' | 'error'
|
||
|
||
export interface AssetUploadState {
|
||
stage: AssetUploadStage
|
||
/** 失败发生的阶段(error 态定位重试入口) */
|
||
failedStage?: 'init' | 'put' | 'complete'
|
||
/** 错误消息(已本地化/服务端文案) */
|
||
error?: string
|
||
/** complete 返回的签名预览 URL */
|
||
previewUrl?: string
|
||
/** 签名 URL 过期时间(ISO) */
|
||
previewExpiresAt?: string
|
||
}
|
||
|
||
export type AssetUploadEvent =
|
||
| { type: 'START' }
|
||
| { type: 'INIT_OK' }
|
||
| { type: 'PUT_OK' }
|
||
| { type: 'COMPLETE_OK'; url: string; expiresAt: string }
|
||
| { type: 'FAIL'; stage: 'init' | 'put' | 'complete'; message?: string }
|
||
| { type: 'RESET' }
|
||
|
||
export const INITIAL_ASSET_UPLOAD_STATE: AssetUploadState = { stage: 'idle' }
|
||
|
||
/**
|
||
* 状态转移:非法转移(如 idle 收到 PUT_OK)保持原状——UI 侧的迟到响应
|
||
* 不得污染已重置的新会话。
|
||
*/
|
||
export function nextAssetUploadState(
|
||
state: AssetUploadState,
|
||
event: AssetUploadEvent
|
||
): AssetUploadState {
|
||
switch (event.type) {
|
||
case 'START':
|
||
if (state.stage === 'idle' || state.stage === 'done' || state.stage === 'error') {
|
||
return { stage: 'initializing' }
|
||
}
|
||
return state
|
||
case 'INIT_OK':
|
||
if (state.stage === 'initializing') return { stage: 'uploading' }
|
||
return state
|
||
case 'PUT_OK':
|
||
if (state.stage === 'uploading') return { stage: 'completing' }
|
||
return state
|
||
case 'COMPLETE_OK':
|
||
if (state.stage === 'completing') {
|
||
return {
|
||
stage: 'done',
|
||
previewUrl: event.url,
|
||
previewExpiresAt: event.expiresAt
|
||
}
|
||
}
|
||
return state
|
||
case 'FAIL':
|
||
if (
|
||
state.stage === 'initializing' ||
|
||
state.stage === 'uploading' ||
|
||
state.stage === 'completing'
|
||
) {
|
||
return { stage: 'error', failedStage: event.stage, error: event.message }
|
||
}
|
||
return state
|
||
case 'RESET':
|
||
return INITIAL_ASSET_UPLOAD_STATE
|
||
default:
|
||
return state
|
||
}
|
||
}
|