Execute a previewed batch with a resumable ledger
zotExecute.RdRecalculates the plan's digest and requires it to match both the preview and its approval, refuses a ledger written for a different batch, and skips the entries an earlier run completed. Every row is recorded before and after its effect, so an interrupted batch leaves a ledger that names the row whose outcome is unknown.
Arguments
- config
A
zotConfig()object.- preview
The
zotPreview()being executed.- approval
Its
zotApprove()record.- ledgerPath
JSON ledger file; reuse the same path to resume.
Details
Patch operations read the current item version, use a conditional write,
and compare requested fields with a subsequent API read. Create and attach
operations persist the new key as "created" before reading it back. Trash operations use the status
returned by zotTrash(). The approval record is application metadata; this
function does not independently determine who authorized the batch.
The digest is recalculated from the plan being executed, so a preview whose
rows changed after approval is refused instead of written under the old
consent. Before each effect the row is written as "attempt" and the ledger
is replaced atomically; the result overwrites it immediately afterwards. A
resumed batch that meets an "attempt" stops with a zotLedgerError: that
row's effect is unknown, and only the caller can reconcile it against the
library before retrying.
If a create read-back fails, its "created" entry retains the new key and
error. Resuming only reads that key and confirms the payload; it never posts
the item again. A transport exception during POST remains an "attempt"
because its effect is unknown. A server failure or a response without a
recognized result also remains an attempt. A rejected create response can be retried.
An attach row first recalculates the MD5 of its local file and refuses a
file changed since approval with a zotApprovalError, recorded as
"failed" before any request. It then creates the attachment item like a
create row and saves its key as "created". On that key it runs Zotero's
upload protocol (authorize, where "exists" counts as uploaded; upload;
register) through the configuration's file transport, then confirms the
item's MD5, filename, content type and parent. A failed upload leaves the
entry "created" with its error; resuming repeats the upload on the same
key, after checking the file again, and never creates a second item.
New ledgers have schema = 2 and identify existing items as library:key;
creates retain create-<row> for block-move plans. Ledgers without a schema
keep their original key-only format when every id selects exactly one plan
row. An ambiguous legacy ledger is refused without rewriting it. A legacy
failed create without a retained key also requires caller reconciliation:
older executors could lose the key after a successful POST.
See also
zotPreview(), zotApprove(), and zotVerify().
Examples
Store <- new.env(parent = emptyenv())
Store$version <- 1L
Store$title <- "Before"
Performer <- function(config, method, path, query, body, version) {
if (method == "GET") {
return(list(
status = 200L, headers = list(),
body = list(version = Store$version, data = list(title = Store$title))
))
}
Store$title <- body$title
Store$version <- Store$version + 1L
list(status = 204L, headers = list(), body = NULL)
}
Config <- structure(
list(userID = "42", key = "example", apiBase = "https://example.invalid",
performer = Performer),
class = "zotConfig"
)
Plan <- data.table::data.table(
action = "patch", library = "user", itemKey = "K1",
payload = list(list(title = "After"))
)
Preview <- zotPreview(Plan, describe = "Synthetic offline patch")
Approval <- zotApprove(Preview, notes = "Documentation example")
LedgerPath <- tempfile(fileext = ".json")
Ledger <- zotExecute(Config, Preview, Approval, ledgerPath = LedgerPath)
Ledger$items[["user:K1"]]$status
#> [1] "done"
unlink(LedgerPath)