oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1// Generates the vectors that hold the Rust core to this module's result
2// (spec §2.8, Docs' rule adopted unchanged). For each data/<name>.json —
3// {guid, ops} — a fresh Y.Doc with that guid and clientID 1 takes the
4// ops through applyOps; vectors/<name>.yjs is the whole state as a v1
5// update and vectors/<name>.view.json is this side's view of it.
6// internal/slides/core/vectors_test.go loads each .yjs through the core
7// and compares views, and applies the same ops through the core's own
8// Ops and compares again.
9//
10// These are the contract: never edit a vector to make a test pass. A
11// deliberate rule change regenerates them in the same commit. The ids
12// come from the data files so a run is the same bytes every time.
13import * as Y from 'yjs'
14import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
15import { join, dirname } from 'node:path'
16import { fileURLToPath } from 'node:url'
17import { applyOps, view } from './ops.mjs'
18import { checkDoc } from './check.mjs'
19
20const here = dirname(fileURLToPath(import.meta.url))
21export const dataDir = join(here, 'data')
22export const vectorsDir = join(here, 'vectors')
23
24/** JSON with every object's keys sorted, pretty-printed, newline-terminated. */
25export function sortedJSON (v) {
26 const sort = x => {
27 if (Array.isArray(x)) return x.map(sort)
28 if (x !== null && typeof x === 'object') {
29 const out = {}
30 for (const k of Object.keys(x).sort()) out[k] = sort(x[k])
31 return out
32 }
33 return x
34 }
35 return JSON.stringify(sort(v), null, 2) + '\n'
36}
37
38/** The update and view for one data file's ops. */
39export function buildVector (name, data) {
40 if (data.guid !== name) throw new Error(`${name}.json: guid ${JSON.stringify(data.guid)} must equal the file name`)
41 const doc = new Y.Doc({ guid: data.guid })
42 doc.clientID = 1
43 applyOps(doc, data.ops)
44 // What the ops write must pass the accepted-shape check the server
45 // gates a posted update on (check.mjs, core/src/check.rs) — a vector
46 // that did not would be a contract the wire refuses.
47 const problem = checkDoc(doc)
48 if (problem) throw new Error(`${name}: the ops produced text outside the schema: ${problem}`)
49 return { update: Y.encodeStateAsUpdate(doc), view: view(doc) }
50}
51
52export function generate () {
53 mkdirSync(vectorsDir, { recursive: true })
54 const names = readdirSync(dataDir).filter(f => f.endsWith('.json')).map(f => f.slice(0, -5)).sort()
55 for (const name of names) {
56 const data = JSON.parse(readFileSync(join(dataDir, `${name}.json`), 'utf8'))
57 const { update, view: v } = buildVector(name, data)
58 writeFileSync(join(vectorsDir, `${name}.yjs`), Buffer.from(update))
59 writeFileSync(join(vectorsDir, `${name}.view.json`), sortedJSON(v))
60 console.log(`${name}: ${data.ops.length} ops, ${update.length} bytes`)
61 }
62}
63
64if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) generate()
65