Traverse the node tree and update the nodes

i’m currently looking for a method to traverse my entire doc and update the nodes contained within. my specific use case is to randomize the order of the children of each node contained within the doc (doc included). i have a working implementation, but i’m not sure if this is really the “prosemirror way” to solve such a problem. my solution is a custom version of the descendants and nodesBetween functions, which also allows the callback to return a new node instance, instead of just the usual boolean

function nodesBetweenUpdate(this: Node, from: number, to: number,
  f: (node: Node, start: number, parent: Node | null, index: number) => boolean | Node | void,
  nodeStart = 0,
  parent?: Node,
) {

  for (let i = 0, pos = 0; pos < to; i++) {
    let child = this.content.content[i], end = pos + child.nodeSize
    let res = f(child, nodeStart + pos, parent || null, i);
    if (end > from && res !== false && child.content.size) {
      child = (res instanceof Node ? res : child);
      let start = pos + 1
      child.nodesBetweenUpdate(Math.max(0, from - start),
        Math.min(child.content.size, to - start),
        f, nodeStart + start)
    }
    pos = end
  }
}

function descendantsUpdate(this: Node, f: (node: Node, pos: number, parent: Node | null, index: number) => void | boolean, root = true) {
  if (root) {
    let res = f(this, 0, null, 0);
    this.content = (res instanceof Node ? res : this).content
  }
  this.nodesBetweenUpdate(0, this.content.size, f)
}

so by extending the Node prototype you can call like:

// shuffle all nodes

const { tr } = VIEW.state

tr.doc.descendantsUpdate((node, pos, parent) => {
  if (node.childCount <= 1) {
    return false
  }

  let indexMap: number[] = []

  node.forEach((_, __, i) => indexMap.push(i))

  shuffle(indexMap)

  let content = node.content

  indexMap.forEach((i, ii) => { content = content.replaceChild(ii, node.content.child(i)) })

  const newNode = node.copy(content)

  tr.replaceWith(pos, pos + node.content.size, newNode)

  return newNode
})

VIEW.dispatch(tr)

if anyone is interested, heres my full repo with an example setup to test with