fix(scheduler): различать неизвестную историю и подтверждённые запуски
This commit is contained in:
1 parent
933a4eaf57
commit
1f75bc5cea
6 files changed
+95
-18
No files matched your search
@@ -11,6 +11,11 @@ const runSchema = z.object({ id: z.string().min(1), jobKey: key, startedAt: time
|
||||
const runsSchema = z.object({ runs: z.array(runSchema).refine(rows => new Set(rows.map(row => row.id)).size === rows.length, "Повторяющиеся записи планировщика") })
|
||||
const receiptSchema = z.object({ status: z.enum(["ok", "error"]), error: z.string().nullable(), result: z.unknown(), eventError: z.string().optional() }).refine(receipt => receipt.status === "ok" ? receipt.error === null : !!receipt.error, "Статус запуска не согласован с ошибкой")
|
||||
const responseSchema = z.object({ ok: z.boolean(), result: receiptSchema, status: statusSchema.nullable(), statusError: z.string().optional() }).refine(response => response.ok === (response.result.status === "ok") && (response.status !== null || !!response.statusError), "Подтверждение запуска не согласовано с результатом")
|
||||
function confirmed<T>(schema: z.ZodType<T>, raw: unknown): T {
|
||||
const parsed = schema.safeParse(raw)
|
||||
if (!parsed.success) throw new Error("Ответ планировщика не подтверждён: получены некорректные данные")
|
||||
return parsed.data
|
||||
}
|
||||
function options(init?: RequestInit): RequestInit {
|
||||
return { ...init, signal: AbortSignal.any([...(init?.signal ? [init.signal] : []), AbortSignal.timeout(30_000)]) }
|
||||
}
|
||||
@@ -18,20 +23,20 @@ async function json(baseUrl: string, path: string, init?: RequestInit) {
|
||||
return requestJson<unknown>(baseUrl, path, options(init))
|
||||
}
|
||||
export async function getSchedulerStatus(baseUrl: string, init?: RequestInit) {
|
||||
return statusSchema.parse(await json(baseUrl, "/api/scheduler/status", { ...init, method: "GET" }))
|
||||
return confirmed(statusSchema, await json(baseUrl, "/api/scheduler/status", { ...init, method: "GET" }))
|
||||
}
|
||||
export async function listSchedulerRuns(baseUrl: string, filter: { jobKey?: SchedulerJobKey; limit?: number; offset?: number } = {}, init?: RequestInit) {
|
||||
const query = z.object({ jobKey: key.optional(), limit: count.min(1).max(200).optional(), offset: count.optional() }).parse(filter)
|
||||
const params = new URLSearchParams({ limit: String(query.limit ?? 80) })
|
||||
if (query.jobKey) params.set("jobKey", query.jobKey)
|
||||
if (query.offset !== undefined) params.set("offset", String(query.offset))
|
||||
const result = runsSchema.parse(await json(baseUrl, "/api/scheduler/runs?" + params.toString(), { ...init, method: "GET" }))
|
||||
const result = confirmed(runsSchema, await json(baseUrl, "/api/scheduler/runs?" + params.toString(), { ...init, method: "GET" }))
|
||||
if (query.jobKey && result.runs.some(run => run.jobKey !== query.jobKey)) throw new Error("Журнал относится к другой задаче планировщика")
|
||||
return result
|
||||
}
|
||||
export async function runSchedulerJobNow(baseUrl: string, jobKey: SchedulerJobKey, init?: RequestInit) {
|
||||
const target = key.parse(jobKey)
|
||||
const response = responseSchema.parse(await json(baseUrl, "/api/scheduler/jobs/" + encodeURIComponent(target) + "/run-now", { ...init, method: "POST" }))
|
||||
const response = confirmed(responseSchema, await json(baseUrl, "/api/scheduler/jobs/" + encodeURIComponent(target) + "/run-now", { ...init, method: "POST" }))
|
||||
const snapshot = parseSchedulerRunSnapshot(response.result.result)
|
||||
if (!snapshot || snapshot.job !== target) throw new Error("Результат запуска не подтверждён для выбранной задачи планировщика")
|
||||
const snapshotHasErrors = ("fatalError" in snapshot && !!snapshot.fatalError) || ("errors" in snapshot && !!snapshot.errors?.length) || (snapshot.job === "backups" && (!!snapshot.failures || !!snapshot.uploadFailures || !!snapshot.pruneFailures || !!snapshot.metadataError || !!snapshot.eventErrors?.length))
|
||||
@@ -39,5 +44,5 @@ export async function runSchedulerJobNow(baseUrl: string, jobKey: SchedulerJobKe
|
||||
return { ...response, result: { ...response.result, result: snapshot } }
|
||||
}
|
||||
export async function refreshScheduler(baseUrl: string, init?: RequestInit) {
|
||||
return z.object({ ok: z.literal(true), status: statusSchema }).parse(await json(baseUrl, "/api/scheduler/refresh", { ...init, method: "POST" }))
|
||||
return confirmed(z.object({ ok: z.literal(true), status: statusSchema }), await json(baseUrl, "/api/scheduler/refresh", { ...init, method: "POST" }))
|
||||
}
|
||||
Reference in new issue
Block a user