import type { MikrotikClient } from "./mikrotik.js" import { MikrotikError } from "./mikrotik.js" import { parseRecursiveCatalog } from "./recursive-route-catalog.js" export class RecursiveApplyError extends Error { constructor(message: string, public readonly receipt: { state: "partial" | "unknown" | "rejected" | "applied" confirmedWrites: number createdIds: string[] deletedIds: string[] needsReview: boolean metadataRecorded: boolean }) { super(message); this.name = "RecursiveApplyError" } } export async function executeRecursiveApply( client: Pick, payloads: Record[], persist: () => Promise, ) { // The editor replaces every owned row: reject unsupported ownership before any write. const existing = parseRecursiveCatalog(await client.get("/ip/route")) const createdIds: string[] = [], deletedIds: string[] = [] let nativeComplete = false try { for (const payload of payloads) { const result = await client.put("/ip/route", payload) if (!result || typeof result !== "object" || !(".id" in result) || typeof result[".id"] !== "string" || !result[".id"].trim()) throw new Error("RouterOS не подтвердил ID созданного маршрута") createdIds.push(result[".id"]) } for (const route of existing) { await client.delete("/ip/route/" + encodeURIComponent(route.id)) deletedIds.push(route.id) } nativeComplete = true await persist() return { pushed: createdIds.length, deleted: deletedIds.length } } catch (error) { const rejected = error instanceof MikrotikError && error.statusCode >= 400 && error.statusCode < 500 const confirmedWrites = createdIds.length + deletedIds.length const state = nativeComplete ? "applied" : rejected ? (confirmedWrites ? "partial" : "rejected") : "unknown" throw new RecursiveApplyError(`Подтверждено записей RouterOS: ${confirmedWrites}. Обновите каталог перед повторной попыткой. ${String(error)}`, { state, confirmedWrites, createdIds, deletedIds, needsReview: state !== "rejected", metadataRecorded: false, }) } }