| 1 | // The operations the inspector and the drive surface apply (spec §7.2), |
| 2 | // over a Y.Doc in the browser — the same vocabulary as core/src/ops.rs, |
| 3 | // with the same result. A new op is added to both or to neither; the |
| 4 | // vectors under vectors/ hold the two to one view. |
| 5 | // |
| 6 | // applyOps(ydoc, ops, origin) parses and checks the whole batch against a |
| 7 | // model of the deck first (ops-model.mjs) and throws an OpsError — with |
| 8 | // the core's own message, prefixed `op N:` — having written nothing; |
| 9 | // only then does it apply every op (ops-apply.mjs) inside one |
| 10 | // ydoc.transact with the given origin, so the undo manager and the |
| 11 | // save queue see one change. |
| 12 | // |
| 13 | // Ids: the core never draws entropy and neither does this module. Every |
| 14 | // new slide or block id arrives in the op (`id`, `new_id`, |
| 15 | // `new_block_ids`); the browser mints them with ids.mjs before calling. |
| 16 | import { roots } from './ydoc.mjs' |
| 17 | import { Model, OpsError, parseOp } from './ops-model.mjs' |
| 18 | import { applyOne } from './ops-apply.mjs' |
| 19 | |
| 20 | export { OpsError } from './ops-model.mjs' |
| 21 | export { view } from './view.mjs' |
| 22 | |
| 23 | function at (i, e, applying = false) { |
| 24 | const msg = e instanceof Error ? e.message : String(e) |
| 25 | return new OpsError(`op ${i}${applying ? ' (applying)' : ''}: ${msg}`, i) |
| 26 | } |
| 27 | |
| 28 | /** |
| 29 | * Applies ops (an array of §7.2 operations) to ydoc in one transaction. |
| 30 | * Throws OpsError, applying nothing, when any op is malformed or names |
| 31 | * something the deck (as the batch so far leaves it) does not have. |
| 32 | */ |
| 33 | export function applyOps (ydoc, ops, origin = 'ops') { |
| 34 | if (!Array.isArray(ops)) throw new OpsError('ops must be a JSON array', -1) |
| 35 | const parsed = ops.map((raw, i) => { |
| 36 | try { return parseOp(raw) } catch (e) { throw at(i, e) } |
| 37 | }) |
| 38 | const r = roots(ydoc) |
| 39 | const model = Model.read(r) |
| 40 | parsed.forEach((op, i) => { |
| 41 | try { model.check(op) } catch (e) { throw at(i, e) } |
| 42 | }) |
| 43 | ydoc.transact(() => { |
| 44 | parsed.forEach((op, i) => { |
| 45 | try { applyOne(r, op) } catch (e) { throw at(i, e, true) } |
| 46 | }) |
| 47 | }, origin) |
| 48 | } |
| 49 | |