export class NotebookNotEmptyError extends Error { readonly code = "notebook_not_empty"; constructor() { super("notebook_not_empty"); this.name = "NotebookNotEmptyError"; } } export const isNotebookNotEmptyError = (error: unknown) => { if (error instanceof NotebookNotEmptyError) return true; if (typeof error === "object" && error !== null && "code" in error && (error as { code?: unknown }).code === "notebook_not_empty") { return true; } return error instanceof Error && error.message === "notebook_not_empty"; }; export const notebookDeleteIdsFromPayload = ( payload: { notebookId?: unknown; notebookIds?: unknown } | null | undefined, fallbackId: string, ) => { const listed = Array.isArray(payload?.notebookIds) ? payload.notebookIds.filter((id): id is string => typeof id === "string" && id.length > 0) : []; if (listed.length > 0) return listed; const notebookId = typeof payload?.notebookId === "string" && payload.notebookId.length > 0 ? payload.notebookId : fallbackId; return notebookId ? [notebookId] : []; }; // A pending delete of a parent must also hide descendants that the server // still lists, including when the parent itself is already absent. export const notebooksBlockedByPendingDeletes = ( notebooks: Array<{ id: string; parentId?: string | null }>, pendingDeleteIds: ReadonlySet, ) => { const parentById = new Map(notebooks.map((notebook) => [notebook.id, notebook.parentId ?? null])); const blocked = new Set(); for (const notebook of notebooks) { const seen = new Set(); let currentId: string | null = notebook.id; while (currentId && !seen.has(currentId)) { if (pendingDeleteIds.has(currentId)) { blocked.add(notebook.id); break; } seen.add(currentId); const parentId: string | null = parentById.get(currentId) ?? null; if (parentId && pendingDeleteIds.has(parentId)) { blocked.add(notebook.id); break; } currentId = parentId; } } return blocked; };