nuthatch/web/src/workflows/recursive-delete.ts

89 lines
2.6 KiB
TypeScript

import type {
DeletePlanResponse,
ProductEndpoint,
Root,
SessionResponse
} from '../generated/contract.ts';
import { unfinishedDeleteItems, type PendingDelete } from '../mutations.ts';
import { requireValue } from '../transport.ts';
import type { RecursiveDeletePersistence } from './recursive-delete-persistence.ts';
export type RecursiveDeleteResult = {
deleted: number;
pages: number;
};
export class RecursiveDeletePageLimitError extends Error {
readonly pages: number;
constructor(pages: number) {
super(`Recursive delete stopped after ${pages} pages; run it again to continue.`);
this.name = 'RecursiveDeletePageLimitError';
this.pages = pages;
}
}
export function savedRecursiveDelete(
persistence: RecursiveDeletePersistence
): PendingDelete | null {
return persistence.read();
}
export async function runRecursiveDelete(
client: ProductEndpoint,
persistence: RecursiveDeletePersistence,
session: SessionResponse,
root: Root,
prefix: string,
nextOperationId: () => string,
pageLimit = 100
): Promise<RecursiveDeleteResult> {
let deleted = 0;
let completedPages = 0;
let continuation: string | null = null;
for (let page = 0; page < pageLimit; page += 1) {
let items = persistence.load(root, prefix);
let nextContinuation: string | null = null;
if (items.length === 0) {
const plan: DeletePlanResponse = requireValue(
await client.deletePlan({
root,
path: prefix,
...(continuation === null ? {} : { continuation })
})
);
if (plan.paths.length === 0) {
if (plan.continuation !== null) {
continuation = plan.continuation;
continue;
}
persistence.clear();
return { deleted, pages: completedPages };
}
nextContinuation = plan.continuation;
items = plan.paths.map((path) => ({
path,
operation_id: nextOperationId()
}));
persistence.save(root, prefix, items);
}
const response = requireValue(
await client.deleteBatch({
body: { root, csrf: session.csrf, items }
})
);
const unfinished = unfinishedDeleteItems(response.results, nextOperationId);
deleted += response.results.length - unfinished.length;
if (unfinished.length > 0) {
persistence.save(root, prefix, unfinished);
throw new Error(
`${unfinished.length} objects remain. Pending operations keep their IDs; terminal failures restart with new IDs.`
);
}
persistence.clear();
completedPages += 1;
continuation = nextContinuation;
}
throw new RecursiveDeletePageLimitError(pageLimit);
}