Validate a PDF reorder as a permutation before you test the export A developer working with Yan Wang / LensUp published a standalone JavaScript validator that checks whether a PDF page-reorder plan is a true permutation of the source document's physical page indexes, rejecting duplicates, out-of-range values, non-integers and sparse-array holes that a simple length check would miss. The function, validateReorder(order, pageCount), returns a defensive copy of the accepted plan and is paired with Node assert tests covering duplicate, out-of-range, string, fractional and sparse inputs. The author stresses the validator confirms only the request's structure, not the user's intended reading order or whether the exporter actually honored it. AI and affiliation disclosure: Codex generated this article and its JavaScript example for Yan Wang / LensUp. Codex ran the local checks described below; this does not claim personal execution or independent validation by the human account owner. The example is a standalone plan validator, not a production patch or a PDF exporter. A reorder UI can display the expected number of thumbnails while its export plan contains the same source page twice. Checking the plan's length catches a missing slot, but not a duplicate that replaced another page. For a pure reorder of one document, the useful input contract is narrow: every original physical page index appears exactly once. Deletion, repetition and insertion are different operations. They need different validators. The example accepts zero-based physical page indexes. For a four-page document, the allowed set is 0 through 3. It does not interpret printed numbers or PDF page labels. The array 3, 0, 1, 2 means “take original physical page four first.” It is a valid permutation. It may still be the wrong reading order for the user. Validation can confirm the structure of the request, not its intent. export function validateReorder order, pageCount { if Number.isSafeInteger pageCount || pageCount < 1 { throw new RangeError "pageCount must be a positive safe integer" ; } if Array.isArray order || order.length == pageCount { throw new TypeError "order must contain one slot per source page" ; } const seen = new Set ; for let i = 0; i < order.length; i += 1 { const page = order i ; if Number.isInteger page || page < 0 || page = pageCount || seen.has page { throw new RangeError "order must be a permutation of source indexes" ; } seen.add page ; } return order.slice ; } The indexed loop is deliberate. An ordinary sparse array has a length, but a missing slot reads as undefined. The integer check rejects it. Array methods that skip holes can make an incomplete plan look more complete than it is. The returned array is a copy, so later changes to the caller's array don't change the accepted plan. This is not a deep-copy utility or a guard against hostile proxies and accessors; it expects ordinary application arrays containing numbers. python import assert from "node:assert/strict"; assert.deepEqual validateReorder 3, 0, 1, 2 , 4 , 3, 0, 1, 2 ; assert.throws = validateReorder 0, 1, 1, 3 , 4 ; assert.throws = validateReorder 0, 1, 2, 4 , 4 ; assert.throws = validateReorder 0, 1, 2, "3" , 4 ; assert.throws = validateReorder 0, 1, , 3 , 4 ; const requested = 1, 0 ; const accepted = validateReorder requested, 2 ; requested.reverse ; assert.deepEqual accepted, 1, 0 ; These assertions can follow the function in one ES module. The harness used for this article also checks invalid counts, non-array inputs, negative and fractional indexes, NaN, Infinity and shorter plans. Results and the actual runtime are retained with the manuscript; they do not constitute a browser or PDF-format compatibility test. Use the accepted plan as the input to the export job. If the user can continue editing while a save runs, decide how that job's snapshot is isolated from later UI changes. Copying this one array does not snapshot the entire document or solve asynchronous state management. After export, open the saved PDF and check identifiable page content against the expected physical sequence. The validator never reads that PDF. It cannot detect an exporter using a stale array, writing pages in a different order, dropping content, breaking a link or changing a form. So there are two separate questions: is this a structurally valid reorder request, and did this particular saved artifact implement it? A valid permutation with the wrong intended sequence should pass the first test and fail the user's acceptance check. Keeping that distinction explicit gives each failure somewhere useful to go. The article's two JavaScript blocks were executed together as an ES module with Node.js v22.23.4 on macOS. To reproduce the displayed checks, save them together as reorder-check.mjs and run node reorder-check.mjs. The separate retained harness adds the wider counterexample set described above. This example intentionally has no “skip invalid entries” mode. Silently repairing 0, 1, 1, 3 would require deciding what the user meant. Fail the plan and return control to the UI instead. It also does not claim that source indexes survive a mutation to the source document. Bind the plan to the document version being exported. A plan that was valid for an earlier page set is not automatically valid for a later one. References: Number.isInteger https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global Objects/Number/isInteger , Array holes and iteration https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global Objects/Array array methods and empty slots .