// Generates the vectors that hold the Rust core to this module's result // (spec ยง2.8, Docs' rule adopted unchanged). For each data/.json โ€” // {guid, ops} โ€” a fresh Y.Doc with that guid and clientID 1 takes the // ops through applyOps; vectors/.yjs is the whole state as a v1 // update and vectors/.view.json is this side's view of it. // internal/slides/core/vectors_test.go loads each .yjs through the core // and compares views, and applies the same ops through the core's own // Ops and compares again. // // These are the contract: never edit a vector to make a test pass. A // deliberate rule change regenerates them in the same commit. The ids // come from the data files so a run is the same bytes every time. import * as Y from 'yjs' import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs' import { join, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { applyOps, view } from './ops.mjs' import { checkDoc } from './check.mjs' const here = dirname(fileURLToPath(import.meta.url)) export const dataDir = join(here, 'data') export const vectorsDir = join(here, 'vectors') /** JSON with every object's keys sorted, pretty-printed, newline-terminated. */ export function sortedJSON (v) { const sort = x => { if (Array.isArray(x)) return x.map(sort) if (x !== null && typeof x === 'object') { const out = {} for (const k of Object.keys(x).sort()) out[k] = sort(x[k]) return out } return x } return JSON.stringify(sort(v), null, 2) + '\n' } /** The update and view for one data file's ops. */ export function buildVector (name, data) { if (data.guid !== name) throw new Error(`${name}.json: guid ${JSON.stringify(data.guid)} must equal the file name`) const doc = new Y.Doc({ guid: data.guid }) doc.clientID = 1 applyOps(doc, data.ops) // What the ops write must pass the accepted-shape check the server // gates a posted update on (check.mjs, core/src/check.rs) โ€” a vector // that did not would be a contract the wire refuses. const problem = checkDoc(doc) if (problem) throw new Error(`${name}: the ops produced text outside the schema: ${problem}`) return { update: Y.encodeStateAsUpdate(doc), view: view(doc) } } export function generate () { mkdirSync(vectorsDir, { recursive: true }) const names = readdirSync(dataDir).filter(f => f.endsWith('.json')).map(f => f.slice(0, -5)).sort() for (const name of names) { const data = JSON.parse(readFileSync(join(dataDir, `${name}.json`), 'utf8')) const { update, view: v } = buildVector(name, data) writeFileSync(join(vectorsDir, `${name}.yjs`), Buffer.from(update)) writeFileSync(join(vectorsDir, `${name}.view.json`), sortedJSON(v)) console.log(`${name}: ${data.ops.length} ops, ${update.length} bytes`) } } if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) generate()