import { GraphError } from '@graphora/core' import { createGraphEngine } from '@graphora/engine' import { CanvasGraphRenderer, GRAPHORA_STYLE_ATTRIBUTE, createViewport } from '@graphora/renderer' import './style.css' const container = document.querySelector('#graph') const status = document.querySelector('#status') const successButton = document.querySelector('#apply-success') const collectionButton = document.querySelector( '#highlight-downstream' ) const failureButton = document.querySelector('#apply-failure') if ( container === null || status === null || successButton === null || collectionButton === null || failureButton === null ) { throw new Error('Missing atomic batch example elements.') } const engine = createGraphEngine({ graph: { nodes: [ { id: 'api', label: 'API', position: { x: -90, y: 0 }, attributes: { [GRAPHORA_STYLE_ATTRIBUTE]: { fill: '#2563eb' } } }, { id: 'worker', label: 'Worker', position: { x: 20, y: 0 }, attributes: { [GRAPHORA_STYLE_ATTRIBUTE]: { fill: '#0f766e' } } } ], edges: [{ id: 'dispatch', source: 'api', target: 'worker' }] }, renderer: new CanvasGraphRenderer(), viewport: createViewport({ width: container.clientWidth, height: container.clientHeight }), theme: { node: { radius: 26 }, edge: { strokeWidth: 2 }, label: { fontSize: 13 } } }) engine.mount(container) successButton.addEventListener('click', () => { const result = engine.store.applyBatch( [ { type: 'add-node', node: { id: 'database', label: 'Database', position: { x: 120, y: 0 }, attributes: { [GRAPHORA_STYLE_ATTRIBUTE]: { fill: '#c2410c' } } } }, { type: 'add-edge', edge: { id: 'writes', source: 'worker', target: 'database' } }, { type: 'update-node-position', id: 'worker', position: { x: 10, y: -45 } }, { type: 'update-node-attributes', id: 'api', attributes: { [GRAPHORA_STYLE_ATTRIBUTE]: { fill: '#7c3aed' } } } ], { id: 'example-success' } ) container.dataset.nodeCount = String(engine.snapshot().nodes.length) status.textContent = `Committed ${result.operationCount} operations at version ${result.version}; one batch event and one change event.` successButton.disabled = true }) failureButton.addEventListener('click', () => { const before = JSON.stringify(engine.snapshot()) try { engine.store.applyBatch( [ { type: 'add-node', node: { id: 'ghost', position: { x: 0, y: 100 } } }, { type: 'add-edge', edge: { id: 'invalid', source: 'ghost', target: 'missing' } } ], { id: 'example-rejected' } ) } catch (error) { if (!(error instanceof GraphError)) throw error const unchanged = JSON.stringify(engine.snapshot()) === before container.dataset.rollback = unchanged ? 'true' : 'false' status.textContent = unchanged ? `Rejected ${error.code}; graph and version are unchanged.` : 'Rollback check failed.' } }) collectionButton.addEventListener('click', () => { const downstream = engine.store.nodes(['api']).neighbors('out') const result = downstream.updateAttributes({ [GRAPHORA_STYLE_ATTRIBUTE]: { fill: '#db2777' } }) status.textContent = `Highlighted ${downstream.ids().join(', ')} from API: ${result.operationCount} collection update at version ${result.version}.` })