Skip to contents

Recalculates 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.

Usage

zotExecute(config, preview, approval, ledgerPath)

Arguments

config

A zotConfig() object.

preview

The zotPreview() being executed.

approval

Its zotApprove() record.

ledgerPath

JSON ledger file; reuse the same path to resume.

Value

The ledger as a list (class zotLedger).

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.

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)