Files
UHD_Media_Tool/packages/mapping/src/download.ts
T
rootandClaude Opus 5.5 42ecc93fa5 feat(mapping): centralize domain <-> contracts mappings (B5)
New @umt/mapping (depends on domain + contracts only): download status + pause reason,
evidence level / media kind, identity resolution status, slot status / spec columns /
evaluation, ParsedRelease <-> source_release.parsed, SiteRelease <-> source_release,
PathBinding, OrganizePlan -> organization_record / file results / organize.plan output.
Tables are Record<union, ...> so adding a value on either side fails typecheck or tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 19:20:30 +08:00

88 lines
3.4 KiB
TypeScript

import type { DownloadStatus, PauseReason } from "@umt/contracts";
import type { DownloadTaskStatus as DomainDownloadStatus } from "@umt/domain";
// 下载状态:domain 只描述"下载器里看到的状态"(mapQbState 的结果),
// contracts / download_task.status 还叠加业务阶段(submitting / stalled / importing / imported)和 pauseReason。
export type { DomainDownloadStatus };
export interface ContractDownloadState {
status: DownloadStatus;
pauseReason: PauseReason | null;
}
const TO_CONTRACT: Record<DomainDownloadStatus, ContractDownloadState> = {
queued: { status: "queued", pauseReason: null },
downloading: { status: "downloading", pauseReason: null },
paused_by_threshold: { status: "paused", pauseReason: "threshold" },
paused_by_user: { status: "paused", pauseReason: "user" },
completed: { status: "completed", pauseReason: null },
error: { status: "failed", pauseReason: null },
missing_files: { status: "missing", pauseReason: null },
removed: { status: "removed", pauseReason: null },
};
/** contracts 状态 → 下载器层面的 domain 状态(业务叠加态折回其底层下载状态) */
const TO_DOMAIN: Record<DownloadStatus, DomainDownloadStatus | "by_pause_reason"> = {
queued: "queued",
// 提交中:下载器里还没有/刚出现,按排队处理
submitting: "queued",
downloading: "downloading",
paused: "by_pause_reason",
// 无进展是业务判定(detectNoProgress),下载器里仍是 downloading
stalled: "downloading",
completed: "completed",
// 入库阶段:下载本身已完成
importing: "completed",
imported: "completed",
failed: "error",
removed: "removed",
missing: "missing_files",
};
/** 暂停原因 → domain 状态:只有 threshold 会被阈值恢复自动恢复,其余都按用户暂停对待(REQ §8.4.1) */
const PAUSE_TO_DOMAIN: Record<PauseReason, DomainDownloadStatus> = {
threshold: "paused_by_threshold",
user: "paused_by_user",
policy: "paused_by_user",
agent: "paused_by_user",
};
export const DOMAIN_DOWNLOAD_STATUSES = Object.keys(TO_CONTRACT) as DomainDownloadStatus[];
export function toContractDownloadState(s: DomainDownloadStatus): ContractDownloadState {
return { ...TO_CONTRACT[s] };
}
export function toDomainDownloadStatus(status: DownloadStatus, pauseReason: PauseReason | null): DomainDownloadStatus {
const d = TO_DOMAIN[status];
if (d !== "by_pause_reason") return d;
return PAUSE_TO_DOMAIN[pauseReason ?? "user"];
}
/**
* 同步轮询时合并"下载器观察"与"当前业务状态":
* - 业务叠加态在其底层下载状态不变时保留(importing/imported 遇 completed、stalled 遇 downloading、submitting 遇 queued);
* - 暂停原因为 policy/agent 且下载器仍是暂停时保留原因,不被改写成 user;
* - 其余以观察为准。
*/
export function mergeObservedDownloadState(
current: ContractDownloadState,
observed: DomainDownloadStatus,
): ContractDownloadState {
const next = TO_CONTRACT[observed];
const keep =
((current.status === "importing" || current.status === "imported") && observed === "completed") ||
(current.status === "stalled" && observed === "downloading") ||
(current.status === "submitting" && observed === "queued");
if (keep) return { ...current };
if (
current.status === "paused" &&
observed === "paused_by_user" &&
(current.pauseReason === "policy" || current.pauseReason === "agent")
) {
return { ...current };
}
return { ...next };
}