clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

main
diagramzip / renderers / edge / src / adapters / vega-common.ts
vega-common.ts 1.7K
1import { parse, View, type Loader, type Spec } from 'vega'2import { expressionInterpreter } from 'vega-interpreter'34const disabledLoader: Loader = {5 load: async () => { throw new Error('External Vega data loading is disabled.') },6 http: async () => { throw new Error('External Vega HTTP loading is disabled.') },7 file: async () => { throw new Error('External Vega file loading is disabled.') },8 sanitize: async uri => ({ href: uri }),9}1011export function parsedSpecification(source: string): Record<string, unknown> {12 const parsed: unknown = JSON.parse(source)13 if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {14 throw new Error('Vega source must be a JSON object.')15 }16 return parsed as Record<string, unknown>17}1819export function containsUrl(value: unknown): boolean {20 const pending: unknown[] = [value]21 let visited = 022 while (pending.length > 0) {23 const current = pending.pop()24 if (++visited > 100_000) throw new Error('Vega specification is too complex.')25 if (Array.isArray(current)) {26 pending.push(...current)27 continue28 }29 if (typeof current !== 'object' || current === null) continue30 for (const [name, nested] of Object.entries(current)) {31 if (name.toLowerCase() === 'url' && nested !== undefined && nested !== null && nested !== '') return true32 pending.push(nested)33 }34 }35 return false36}3738export async function renderVegaSpecification(specification: Spec): Promise<string> {39 const view = new View(parse(specification, undefined, { ast: true }), {40 expr: expressionInterpreter,41 loader: disabledLoader,42 renderer: 'none',43 })44 try {45 return await view.toSVG()46 } finally {47 view.finalize()48 }49}