import type { TaprootDb } from '../db/client.js'; import type { ContentStatus } from '../db/schema.js'; /** * Applying one change across a whole branch. * * Superseding a catalog year is the case: ~280 items want unpublishing, or want `noIndex` set so the * old edition stops competing with the new one in search results. One at a time is not a workflow * anybody completes. * * **Everything goes through `updateItem`, never around it.** A direct `update content_items set * status` would be faster and would skip the cascading path rules, the revision, the derived * indexes, the audit trail and the cache purge — a second write implementation for the operation * most likely to touch hundreds of rows at once. Same rule `publishRelease` follows for the same * reason. * * Chunked and resumable exactly as `duplicateSubtree` is, and for the same reason: each item is its * own batch, and a few hundred of them do not fit in one Worker request. */ export interface BulkSubtreeInput { /** The new status, or omitted to leave it alone. */ status?: ContentStatus; /** Whether items should ask search engines not to index them, or omitted to leave it alone. */ noIndex?: boolean; /** Include the root itself. Off by default, matching `pathPrefix`. */ includeRoot?: boolean; /** How many items to write before returning. A caller loops until `remaining` is 0. */ limit?: number; /** Who is doing it, for `updated_by` and the revision. */ userId?: string | null; /** * Whether *this actor* may make this move, asked per item. * * **A callback because the answer lives in the studio and this does not.** `canChangeStatus` * needs a role, and roles are the server's; core owns only the half that is a fact about the * graph — `archived → published` is an arrow that does not exist, which is refused for an admin * too and is checked below regardless. The same split `resolveMenu` makes with `termHref`: the * part core can answer, plus a callback for the part it cannot. * * Omitted, only legality is enforced — which is right for a script or a migration, and wrong for * a request. A route must always pass it. */ canChange?: (from: ContentStatus, to: ContentStatus) => boolean; } export interface BulkSubtreeResult { changed: number; /** * Each changed item's id and the status it came *from*. * * Carried out rather than derived, because publication is judged by **crossing the boundary**, not * by the destination: `published → archived` is an unpublish and `draft → archived` is not, and a * caller checking `status === 'published'` gets that wrong — which is the mistake `canChangeStatus` * exists because somebody made three times. `publicationEvents` needs the `from`, and once this * function has returned it is the only place left holding it. */ touched: Map; /** Items matching the filter that this call did not reach. */ remaining: number; /** * Items the actor was not allowed to change, with the reason. * * **Reported rather than thrown**, because one refusal must not sink the batch: a contributor * running "unpublish this year" over 280 items should move the 277 they may move and be told * about the three they may not. Same shape as `publishRelease`'s `failed`, and the same argument * a per-file upload failure makes against refusing the whole request. */ refused: { id: string; title: string; reason: string; }[]; } export declare class BulkSubtreeError extends Error { readonly code: 'not_found' | 'nothing_to_do'; name: string; constructor(message: string, code?: 'not_found' | 'nothing_to_do'); } /** * Apply a status and/or a `noIndex` flag to everything under a path. * * The `noIndex` half is the one worth having beside the status: an archived catalog year usually * *should* stay readable — students hold rights to it — while no longer competing with the current * edition in search results. "Unpublish it" and "stop indexing it" are different intentions and a * bulk tool that only offered the first would force the wrong one. */ export declare function updateSubtree(handle: TaprootDb, rootId: string, input: BulkSubtreeInput): Promise; /** * How many items under a path a visitor can currently see. * * What a confirmation screen needs before somebody unpublishes a year: "this will take 188 pages off * the site" is a different sentence from "this will change 280 rows", and the second is the one that * gets read past. */ export declare function visibleCountUnder(handle: TaprootDb, rootId: string): Promise;