- commit
- fd1429883bb8ce8b04e004bdfa182af4031c97da
- parent
- b8c1a30e30b9f981ee0db3e965e43cf78c070975
- author
- Dami <dami.etoile@gmail.com>
@@ -41,9 +41,10 @@ bundles, CSPs, deployments, and subdomains.
4141 4242 A unit may expose multiple catalog hostnames when those engines share the same4343 dependency stack. BlockDiag, SeqDiag, ActDiag, NwDiag, PacketDiag, and RackDiag44-share `blockdiag-family`; PlantUML and C4 PlantUML share `plantuml-family`;45-Vega and Vega-Lite share `vega-family`. Requests are still selected only by a46-dedicated hostname, so a caller cannot choose an unrelated engine in the body.44+share `blockdiag-family`; GraphViz and the ERD-to-DOT translator share45+`graphviz-family`; PlantUML and C4 PlantUML share `plantuml-family`; Vega and46+Vega-Lite share `vega-family`. Requests are still selected only by a dedicated47+hostname, so a caller cannot choose an unrelated engine in the body.4748 4849 A unit may translate into another renderer. Translation is declared as an4950 ordered pipeline whose first stage is the deployable unit. Vega-Lite therefore@@ -55,16 +56,16 @@ reports `vega-family,vega`; `X-Diagram-Engine` still reports `vegalite`.
5556 | --- | --- |5657 | Worker JavaScript | Bytefield, Nomnoml, Vega family (Vega and Vega-Lite → Vega), WaveDrom |5758 | Worker Python | BlockDiag family (BlockDiag, SeqDiag, ActDiag, NwDiag, PacketDiag, RackDiag) |59+| Worker WebAssembly | GraphViz family (GraphViz and ERD → DOT → GraphViz) |5860 | Sandboxed browser unit | Mermaid, BPMN, Excalidraw |59-| Compatibility unit | 15 dependency units covering the remaining 16 engines |61+| Compatibility unit | 13 dependency units covering the remaining 14 engines |6062 61-The current split is 14/30 engines off Fly and 16/30 still dependent on it.63+The current split is 16/30 engines off Fly and 14/30 still dependent on it.6264 Compatibility units are extraction seams, not final runtimes or fallbacks for6365 an engine after cutover. DBML6466 currently proxies because its published package mixes module formats. GraphViz65-currently proxies because the published Viz.js wrapper performs runtime66-WebAssembly instantiation; its unit still needs a direct precompiled module67-adapter.67+uses a precompiled Worker module; ERD lowers its upstream-compatible source68+language to DOT and reuses the same in-process GraphViz runtime.6869 6970 All returned SVG passes through the same sanitizer. Scripts, event handlers,7071 external resources, and active embedded HTML are removed. Mermaid's@@ -8,6 +8,7 @@
88 "name": "diagramzip-render",99 "version": "0.2.1",1010 "dependencies": {11+ "@viz-js/viz": "3.29.0",1112 "bytefield-svg": "1.11.0",1213 "json5": "2.2.3",1314 "nomnoml": "1.7.0",@@ -2058,6 +2059,12 @@
20582059 "url": "https://opencollective.com/vitest"20592060 }20602061 },2062+ "node_modules/@viz-js/viz": {2063+ "version": "3.29.0",2064+ "resolved": "https://registry.npmjs.org/@viz-js/viz/-/viz-3.29.0.tgz",2065+ "integrity": "sha512-iq4MTqNCr8pinP4C73JRFx/h2w4QbtHvXINNiPeBv3/C5tSamXwakst6ODlR/l6uh3/SGr7IAGIfSk2qMekiNw==",2066+ "license": "MIT"2067+ },20612068 "node_modules/ansi-regex": {20622069 "version": "5.0.1",20632070 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",@@ -2,8 +2,8 @@ import { spawn } from 'node:child_process'
22 import { resolve } from 'node:path'33 44 const units = [5- 'plantuml-family', 'graphviz', 'd2',6- 'dbml', 'diagramsnet', 'ditaa', 'erd', 'goat', 'pikchr',5+ 'plantuml-family', 'd2',6+ 'dbml', 'diagramsnet', 'ditaa', 'goat', 'pikchr',77 'structurizr', 'svgbob', 'symbolator', 'tikz', 'umlet', 'wireviz',88 ]99 const wrangler = resolve(import.meta.dirname, '../node_modules/.bin/wrangler')@@ -6,13 +6,12 @@ const configs = [
66 'wrangler.units/nomnoml.jsonc',77 'wrangler.units/vega-family.jsonc',88 'wrangler.units/wavedrom.jsonc',9+ 'wrangler.units/graphviz-family.jsonc',910 'wrangler.compatibility/plantuml-family.jsonc',10- 'wrangler.compatibility/graphviz.jsonc',1111 'wrangler.compatibility/d2.jsonc',1212 'wrangler.compatibility/dbml.jsonc',1313 'wrangler.compatibility/diagramsnet.jsonc',1414 'wrangler.compatibility/ditaa.jsonc',15- 'wrangler.compatibility/erd.jsonc',1615 'wrangler.compatibility/goat.jsonc',1716 'wrangler.compatibility/pikchr.jsonc',1817 'wrangler.compatibility/structurizr.jsonc',@@ -41,11 +41,14 @@ const sharedUnits = {
4141 nwdiag: 'blockdiag-family',4242 packetdiag: 'blockdiag-family',4343 rackdiag: 'blockdiag-family',44+ graphviz: 'graphviz-family',45+ erd: 'graphviz-family',4446 vega: 'vega-family',4547 vegalite: 'vega-family',4648 }4749 4850 const pythonEngines = new Set(['blockdiag', 'seqdiag', 'actdiag', 'nwdiag', 'packetdiag', 'rackdiag'])51+const graphvizEngines = new Set(['graphviz', 'erd'])4952 5053 async function smoke([engine, filename]) {5154 const source = await readFile(resolve(fixtureDirectory, filename), 'utf8')@@ -69,7 +72,13 @@ async function smoke([engine, filename]) {
6972 throw new Error(`${engine}: expected unit ${expectedUnit}, received ${response.headers.get('X-Diagram-Unit')}`)7073 }7174 const pipeline = response.headers.get('X-Diagram-Pipeline')72- const expectedPipeline = engine === 'vegalite' ? `${expectedUnit},vega` : expectedUnit75+ const expectedPipeline = engine === 'vegalite'76+ ? `${expectedUnit},vega`77+ : engine === 'graphviz'78+ ? `${expectedUnit},graphviz`79+ : engine === 'erd'80+ ? `${expectedUnit},erd,graphviz`81+ : expectedUnit7382 if (pipeline !== expectedPipeline) {7483 throw new Error(`${engine}: unexpected pipeline ${pipeline}`)7584 }@@ -81,6 +90,14 @@ async function smoke([engine, filename]) {
8190 throw new Error(`${engine}: unexpected Python Worker build ${response.headers.get('X-Renderer-Build')}`)8291 }8392 }93+ if (graphvizEngines.has(engine)) {94+ if (response.headers.get('X-Diagram-Renderer') !== 'edge-wasm') {95+ throw new Error(`${engine}: expected edge-wasm renderer, received ${response.headers.get('X-Diagram-Renderer')}`)96+ }97+ if (!response.headers.get('X-Renderer-Build')?.startsWith('graphviz-15.1.1-family-edge-wasm-')) {98+ throw new Error(`${engine}: unexpected GraphViz-family Worker build ${response.headers.get('X-Renderer-Build')}`)99+ }100+ }84101 return `${engine.padEnd(10)} ${response.headers.get('X-Diagram-Cache')?.padEnd(4)} ${body.length} bytes`85102 }86103 @@ -1,5 +1,7 @@
11 import type { EngineId, RendererAdapter } from '../types'22 import { bytefieldAdapter } from './edge/bytefield'3+import { erdAdapter } from './edge/erd'4+import { graphvizAdapter } from './edge/graphviz'35 import { nomnomlAdapter } from './edge/nomnoml'46 import { vegaAdapter } from './edge/vega'57 import { vegaliteAdapter } from './edge/vegalite'@@ -8,6 +10,8 @@ import type { EdgeEngineId } from './edge/types'
810 911 const adapters: Record<EdgeEngineId, RendererAdapter> = {1012 bytefield: bytefieldAdapter,13+ erd: erdAdapter,14+ graphviz: graphvizAdapter,1115 nomnoml: nomnomlAdapter,1216 vega: vegaAdapter,1317 vegalite: vegaliteAdapter,@@ -0,0 +1,29 @@
1+import { erdToDot, ErdSyntaxError, parseErd } from '../../erd'2+import { RenderError } from '../../errors'3+import type { RendererAdapter } from '../../types'4+import { renderGraphvizSource } from './graphviz'5+import { edgeResult } from './types'6+7+const VERSION = 'erd@0.2.1.0+graphviz@15.1.1'8+9+export const erdAdapter: RendererAdapter = {10+ id: 'erd',11+ runtime: 'edge-wasm',12+ version: VERSION,13+ async render(request, signal) {14+ if (signal.aborted) throw signal.reason15+ if (Object.keys(request.options).length > 0) {16+ throw new RenderError(400, 'invalid_options', 'ERD does not accept renderer options.')17+ }18+ try {19+ const dot = erdToDot(parseErd(request.source))20+ if (signal.aborted) throw signal.reason21+ return edgeResult('erd', VERSION, await renderGraphvizSource(dot), 'edge-wasm')22+ } catch (error) {23+ if (error instanceof RenderError) throw error24+ if (error instanceof ErdSyntaxError) throw new RenderError(422, 'render_failed', error.message)25+ const message = error instanceof Error ? error.message : String(error)26+ throw new RenderError(422, 'render_failed', message.slice(0, 500) || 'ERD could not render this source.')27+ }28+ },29+}@@ -0,0 +1,114 @@
1+import Module from '../../vendor/graphviz-backend'2+import { renderInput } from '../../vendor/graphviz-wrapper'3+import { RenderError } from '../../errors'4+import type { RendererAdapter } from '../../types'5+import { edgeResult } from './types'6+7+const VERSION = 'graphviz@15.1.1'8+const LAYOUT_ENGINES = new Set(['circo', 'dot', 'fdp', 'neato', 'nop', 'nop1', 'nop2', 'osage', 'patchwork', 'sfdp', 'twopi'])9+const RESOURCE_ATTRIBUTES = new Set(['fontpath', 'image', 'imagepath', 'shapefile', 'stylesheet'])10+11+let modulePromise: ReturnType<typeof Module> | undefined12+let renderTail: Promise<void> = Promise.resolve()13+14+export function graphvizModule(): ReturnType<typeof Module> {15+ modulePromise ??= Module()16+ return modulePromise17+}18+19+async function withGraphvizLock<T>(operation: () => T): Promise<T> {20+ const previous = renderTail21+ let release!: () => void22+ renderTail = new Promise(resolve => { release = resolve })23+ await previous24+ try {25+ return operation()26+ } finally {27+ release()28+ }29+}30+31+function withoutQuotedContent(source: string): string {32+ let result = ''33+ let quote = ''34+ let escaped = false35+ for (const char of source) {36+ if (quote) {37+ if (escaped) escaped = false38+ else if (char === '\\') escaped = true39+ else if (char === quote) {40+ quote = ''41+ result += char42+ }43+ continue44+ }45+ if (char === '"' || char === "'") quote = char46+ result += char47+ }48+ return result49+}50+51+function hasResourceAttribute(source: string): boolean {52+ return /\b(?:fontpath|image|imagepath|shapefile|stylesheet)\s*=|<\s*img\b/i.test(withoutQuotedContent(source))53+}54+55+export function optionsFor(options: Record<string, string>): Record<string, unknown> {56+ const engine = options.layout ?? 'dot'57+ if (!LAYOUT_ENGINES.has(engine)) throw new RenderError(400, 'invalid_options', `Unsupported GraphViz layout engine: ${engine}.`)58+ const graphAttributes: Record<string, string> = {}59+ const nodeAttributes: Record<string, string> = {}60+ const edgeAttributes: Record<string, string> = {}61+ for (const [name, value] of Object.entries(options)) {62+ if (name === 'layout') continue63+ if (name === 'scale') throw new RenderError(400, 'unsupported_options', 'GraphViz scale is not supported by the edge-Wasm adapter.')64+ const attributeName = name.replace(/^(?:graph|node|edge)-attribute-/, '')65+ if (attributeName !== name && RESOURCE_ATTRIBUTES.has(attributeName.toLowerCase())) {66+ throw new RenderError(400, 'unsafe_options', `GraphViz resource attribute '${attributeName}' is not supported by the edge renderer.`)67+ }68+ if (name.startsWith('graph-attribute-')) graphAttributes[attributeName] = value69+ if (name.startsWith('node-attribute-')) nodeAttributes[attributeName] = value70+ if (name.startsWith('edge-attribute-')) edgeAttributes[attributeName] = value71+ if (!name.startsWith('graph-attribute-') && !name.startsWith('node-attribute-') && !name.startsWith('edge-attribute-')) {72+ throw new RenderError(400, 'invalid_options', `Unsupported GraphViz option: ${name}.`)73+ }74+ }75+ return {76+ engine,77+ format: 'svg',78+ yInvert: false,79+ reduce: false,80+ graphAttributes,81+ nodeAttributes,82+ edgeAttributes,83+ }84+}85+86+/** Shared in-process GraphViz runtime for translators such as ERD and WireViz. */87+export async function renderGraphvizSource(source: string, options: Record<string, string> = {}): Promise<string> {88+ const module = await graphvizModule()89+ const result: any = await withGraphvizLock(() => renderInput(module, source, ['svg'], optionsFor(options)))90+ if (result.status !== 'success' || typeof result.output?.svg !== 'string') {91+ const message = result.errors?.find((entry: { level?: string }) => entry.level === 'error')?.message ?? 'GraphViz could not render this source.'92+ throw new RenderError(422, 'render_failed', message)93+ }94+ return result.output.svg95+}96+97+export const graphvizAdapter: RendererAdapter = {98+ id: 'graphviz',99+ runtime: 'edge-wasm',100+ version: VERSION,101+ async render(request, signal) {102+ if (signal.aborted) throw signal.reason103+ if (hasResourceAttribute(request.source)) {104+ throw new RenderError(422, 'unsafe_include', 'GraphViz resource-loading attributes are not supported by the edge renderer.')105+ }106+ try {107+ return edgeResult('graphviz', VERSION, await renderGraphvizSource(request.source, request.options), 'edge-wasm')108+ } catch (error) {109+ if (error instanceof RenderError) throw error110+ const message = error instanceof Error ? error.message : String(error)111+ throw new RenderError(422, 'render_failed', message.slice(0, 500) || 'GraphViz could not render this source.')112+ }113+ },114+}@@ -1,13 +1,13 @@
11 import { RenderError } from '../../errors'22 import type { RenderResult } from '../../types'33 4-export type EdgeEngineId = 'bytefield' | 'nomnoml' | 'vega' | 'vegalite' | 'wavedrom'4+export type EdgeEngineId = 'bytefield' | 'erd' | 'graphviz' | 'nomnoml' | 'vega' | 'vegalite' | 'wavedrom'55 6-export function edgeResult(id: EdgeEngineId, version: string, body: string): RenderResult {6+export function edgeResult(id: EdgeEngineId, version: string, body: string, runtime: 'edge-js' | 'edge-wasm' = 'edge-js'): RenderResult {77 if (typeof body !== 'string' || body.trim() === '') {88 throw new RenderError(422, 'empty_render', `The ${id} renderer returned no SVG.`)99 }10- return { body, contentType: 'image/svg+xml', engineVersion: version, runtime: 'edge-js' }10+ return { body, contentType: 'image/svg+xml', engineVersion: version, runtime }1111 }1212 1313 export function edgeFailure(id: EdgeEngineId, error: unknown): never {@@ -26,7 +26,7 @@ const targetRuntime: Record<EngineId, EngineRuntime> = {
2626 dbml: 'edge-js',2727 diagramsnet: 'client',2828 ditaa: 'origin',29- erd: 'edge-js',29+ erd: 'edge-wasm',3030 excalidraw: 'client',3131 goat: 'edge-wasm',3232 nomnoml: 'edge-js',@@ -57,6 +57,8 @@ const activeRuntime: Partial<Record<EngineId, EngineRuntime>> = {
5757 nwdiag: 'edge-python',5858 packetdiag: 'edge-python',5959 rackdiag: 'edge-python',60+ graphviz: 'edge-wasm',61+ erd: 'edge-wasm',6062 }6163 6264 const versions: Partial<Record<EngineId, string>> = {@@ -74,14 +76,18 @@ const versions: Partial<Record<EngineId, string>> = {
7476 nwdiag: 'nwdiag@3.0.0/python-worker-1',7577 packetdiag: 'packetdiag@3.0.0/python-worker-1',7678 rackdiag: 'rackdiag@3.0.0/python-worker-1',79+ graphviz: 'graphviz@15.1.1/edge-wasm-1',80+ erd: 'erd@0.2.1.0+graphviz@15.1.1/edge-wasm-1',7781 }7882 7983 const losses: Partial<Record<EngineId, readonly string[]>> = {8084 mermaid: ['External links and resource-loading elements are removed; XHTML labels retain only safe text formatting.'],8185 bpmn: ['External links and resource-loading elements are removed from exported SVG.'],8286 graphviz: [83- 'The published Viz.js wrapper performs runtime WebAssembly instantiation, which Workers prohibit; GraphViz remains on the origin until a precompiled module adapter is built.',84- 'External links and resources are rejected during SVG sanitization.',87+ 'Only SVG is in the rendering contract.',88+ 'Resource-loading attributes such as image, imagepath, shapefile, fontpath, and stylesheet are rejected because the edge unit has no filesystem or network asset loader.',89+ 'The edge build uses GraphViz 15.1.1 versus the compatibility image GraphViz 14.1.3; output differences are possible.',90+ 'The GraphViz scale option is not supported by the edge-Wasm adapter.',8591 ],8692 bytefield: ['Only SVG is in the v2 rendering contract.'],8793 dbml: ['The upstream package mixes ESM files into a CommonJS package and remains on the origin until it can be safely repackaged.'],@@ -100,6 +106,10 @@ const losses: Partial<Record<EngineId, readonly string[]>> = {
100106 nwdiag: ['Remote and filesystem-backed images are rejected.', 'Only SVG output is supported.'],101107 packetdiag: ['Remote and filesystem-backed images are rejected.', 'Only SVG output is supported.'],102108 rackdiag: ['Remote and filesystem-backed images are rejected.', 'Only SVG output is supported.'],109+ erd: [110+ 'Only the source language and Kroki rendering mode are supported; ERD CLI and filesystem configuration modes are not exposed.',111+ 'The translated graph uses GraphViz 15.1.1 versus the compatibility image GraphViz 14.1.3; output differences are possible.',112+ ],103113 }104114 105115 export const ENGINE_CATALOG: readonly EngineCatalogEntry[] = ENGINE_IDS.map(id => ({@@ -0,0 +1,261 @@
1+/**2+ * Source-to-DOT lowering for BurntSushi/erd's small `.er` language.3+ *4+ * This module deliberately stops at DOT. GraphViz execution belongs to the5+ * renderer unit, which makes the parser/model useful in isolation and keeps6+ * user input away from the execution boundary.7+ */8+9+export type Cardinality = 'zero-one' | 'one' | 'zero-plus' | 'one-plus'10+11+export interface ErdOption { name: string; value: string }12+export interface ErdAttribute { field: string; pk: boolean; fk: boolean; options: ErdOption[] }13+export interface ErdEntity { name: string; attributes: ErdAttribute[]; headerOptions: ErdOption[]; options: ErdOption[] }14+export interface ErdRelation { entity1: string; entity2: string; card1: Cardinality; card2: Cardinality; options: ErdOption[] }15+export interface ErdDocument { entities: ErdEntity[]; relations: ErdRelation[]; titleOptions: ErdOption[]; headerOptions: ErdOption[]; entityOptions: ErdOption[]; relationOptions: ErdOption[] }16+17+export class ErdSyntaxError extends Error {18+ constructor(message: string, public readonly line: number) {19+ super(`ERD line ${line}: ${message}`)20+ this.name = 'ErdSyntaxError'21+ }22+}23+24+const CARDINALITIES: Record<string, Cardinality> = { '?': 'zero-one', '1': 'one', '*': 'zero-plus', '+': 'one-plus' }25+const ALLOWED_OPTIONS = new Set(['label', 'color', 'bgcolor', 'size', 'font', 'border', 'border-color', 'cellspacing', 'cellborder', 'cellpadding', 'text-alignment'])26+const INTEGER_OPTIONS = new Set(['border', 'cellspacing', 'cellborder', 'cellpadding'])27+28+function optionDefaults(name: 'title' | 'header' | 'entity' | 'relationship'): ErdOption[] {29+ if (name === 'title') return [{ name: 'size', value: '30' }]30+ if (name === 'header') return [{ name: 'size', value: '16' }]31+ if (name === 'entity') return [{ name: 'border', value: '0' }, { name: 'cellborder', value: '1' }, { name: 'cellspacing', value: '0' }, { name: 'cellpadding', value: '4' }, { name: 'font', value: 'Helvetica' }]32+ return []33+}34+35+function mergeOptions(specific: ErdOption[], inherited: ErdOption[], defaults: ErdOption[] = []): ErdOption[] {36+ const result = new Map<string, ErdOption>()37+ for (const option of [...defaults, ...inherited, ...specific]) result.set(option.name, option)38+ return [...result.values()]39+}40+41+function parseOptions(text: string, line: number): ErdOption[] {42+ const trimmed = text.trim()43+ if (!trimmed) return []44+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) throw new ErdSyntaxError('options must be enclosed in {...}', line)45+ const body = trimmed.slice(1, -1).trim()46+ if (!body) return []47+ const options: ErdOption[] = []48+ let i = 049+ while (i < body.length) {50+ while (/\s/.test(body[i] ?? '')) i++51+ const start = i52+ while (/[A-Za-z-]/.test(body[i] ?? '')) i++53+ const name = body.slice(start, i)54+ if (!ALLOWED_OPTIONS.has(name)) throw new ErdSyntaxError(`option '${name || body.slice(start)}' does not exist`, line)55+ while (/\s/.test(body[i] ?? '')) i++56+ if (body[i++] !== ':') throw new ErdSyntaxError(`expected ':' after option '${name}'`, line)57+ while (/\s/.test(body[i] ?? '')) i++58+ if (body[i++] !== '"') throw new ErdSyntaxError(`option '${name}' value must be quoted`, line)59+ let value = ''60+ while (i < body.length && body[i] !== '"') value += body[i++]61+ if (body[i++] !== '"') throw new ErdSyntaxError(`unterminated value for option '${name}'`, line)62+ if (!value) throw new ErdSyntaxError(`option '${name}' value cannot be empty`, line)63+ if (INTEGER_OPTIONS.has(name) && (!/^\d+$/.test(value) || Number(value) > 255)) throw new ErdSyntaxError(`option '${name}' must be an integer from 0 to 255`, line)64+ if (name === 'size' && (!/^\d+(?:\.\d+)?$/.test(value) || !Number.isFinite(Number(value)) || Number(value) <= 0)) throw new ErdSyntaxError("option 'size' must be a positive finite number", line)65+ if (name === 'text-alignment' && !['left', 'center', 'right'].includes(value.toLowerCase())) throw new ErdSyntaxError("option 'text-alignment' must be left, center, or right", line)66+ if (['color', 'bgcolor', 'border-color'].includes(name) && !/^[^\u0000-\u001f\u007f"<>&]+$/.test(value)) throw new ErdSyntaxError(`option '${name}' has an invalid color`, line)67+ options.push({ name, value })68+ while (/\s/.test(body[i] ?? '')) i++69+ if (i === body.length) break70+ if (body[i++] !== ',') throw new ErdSyntaxError(`expected ',' after option '${name}'`, line)71+ while (/\s/.test(body[i] ?? '')) i++72+ if (i === body.length) break73+ }74+ return options75+}76+77+function unquote(value: string, line: number): string {78+ const trimmed = value.trim()79+ if (!trimmed) throw new ErdSyntaxError('identifier cannot be empty', line)80+ const quote = trimmed[0]81+ if (quote === '"' || quote === "'" || quote === '`') {82+ if (trimmed.at(-1) !== quote || trimmed.length < 3) throw new ErdSyntaxError('unterminated quoted identifier', line)83+ return trimmed.slice(1, -1)84+ }85+ if (!/^[A-Za-z0-9_]+$/.test(trimmed)) throw new ErdSyntaxError(`invalid identifier '${trimmed}'`, line)86+ return trimmed87+}88+89+function splitOptions(text: string): [string, string] {90+ const index = text.indexOf('{')91+ return index < 0 ? [text.trim(), ''] : [text.slice(0, index).trim(), text.slice(index).trim()]92+}93+94+function entityClose(text: string, line: number): number {95+ let quote = ''96+ for (let index = 1; index < text.length; index++) {97+ const char = text[index]!98+ if (quote) {99+ if (char === quote) quote = ''100+ continue101+ }102+ if (char === '"' || char === "'" || char === '`') quote = char103+ else if (char === ']') return index104+ }105+ throw new ErdSyntaxError('unterminated entity declaration', line)106+}107+108+export function parseErd(source: string): ErdDocument {109+ const document: ErdDocument = { entities: [], relations: [], titleOptions: [], headerOptions: [], entityOptions: [], relationOptions: [] }110+ let current: ErdEntity | undefined111+ let inGlobalPreamble = true112+ const relationLines: number[] = []113+ for (const record of logicalRecords(source)) {114+ const lineNo = record.line115+ const raw = record.text116+ const directive = /^(title|header|entity|relationship)\b(.*)$/i.exec(raw)117+ if (inGlobalPreamble && directive) {118+ const options = parseOptions(directive[2]!.trim(), lineNo)119+ const target = ({ title: 'titleOptions', header: 'headerOptions', entity: 'entityOptions', relationship: 'relationOptions' } as const)[directive[1]!.toLowerCase() as 'title' | 'header' | 'entity' | 'relationship']120+ document[target] = options121+ continue122+ }123+ inGlobalPreamble = false124+ if (raw.startsWith('[')) {125+ const close = entityClose(raw, lineNo)126+ const entity = unquote(raw.slice(1, close), lineNo)127+ const [, optionText] = splitOptions(raw.slice(close + 1).trim())128+ const specific = parseOptions(optionText, lineNo)129+ current = {130+ name: entity,131+ attributes: [],132+ headerOptions: mergeOptions(specific, document.headerOptions, optionDefaults('header')),133+ options: mergeOptions(specific, document.entityOptions, optionDefaults('entity')),134+ }135+ document.entities.push(current)136+ continue137+ }138+ const relation = /^(.+?)\s*([?1*+])--([?1*+])\s*(.+?)(?:\s+(\{.*\}))?$/.exec(raw)139+ if (relation) {140+ const left = unquote(relation[1]!, lineNo)141+ const right = unquote(relation[4]!, lineNo)142+ const card1 = CARDINALITIES[relation[2]!]143+ const card2 = CARDINALITIES[relation[3]!]144+ if (!card1 || !card2) throw new ErdSyntaxError('invalid relationship cardinality', lineNo)145+ relationLines.push(lineNo)146+ document.relations.push({ entity1: left, entity2: right, card1, card2, options: mergeOptions(relation[5] ? parseOptions(relation[5], lineNo) : [], document.relationOptions) })147+ continue148+ }149+ if (!current) throw new ErdSyntaxError('attribute comes before first entity', lineNo)150+ const attrMatch = /^([*+\s]*)(?:"([^"]+)"|'([^']+)'|`([^`]+)`|([^\s{]+))(?:\s+(\{.*\}))?$/.exec(raw)151+ if (!attrMatch) throw new ErdSyntaxError('invalid attribute declaration', lineNo)152+ const field = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? attrMatch[5]153+ current.attributes.push({ field: field!, pk: attrMatch[1]!.includes('*'), fk: attrMatch[1]!.includes('+'), options: mergeOptions(attrMatch[6] ? parseOptions(attrMatch[6], lineNo) : [], [], [{ name: 'text-alignment', value: 'left' }]) })154+ }155+ const names = new Set(document.entities.map(entity => entity.name))156+ for (const [index, relation] of document.relations.entries()) {157+ const line = relationLines[index] ?? 0158+ if (!names.has(relation.entity1)) throw new ErdSyntaxError(`unknown entity '${relation.entity1}' in relationship`, line)159+ if (!names.has(relation.entity2)) throw new ErdSyntaxError(`unknown entity '${relation.entity2}' in relationship`, line)160+ }161+ return document162+}163+164+function logicalRecords(source: string): Array<{ text: string; line: number }> {165+ const records: Array<{ text: string; line: number }> = []166+ const lines = source.replaceAll('\r\n', '\n').replaceAll('\r', '\n').split('\n')167+ let buffer = ''168+ let startLine = 0169+ let quote = ''170+ let braces = 0171+ for (let index = 0; index < lines.length; index++) {172+ let fragment = ''173+ const physical = lines[index]!174+ for (let offset = 0; offset < physical.length; offset++) {175+ const char = physical[offset]!176+ if (quote) {177+ fragment += char178+ if (char === quote) quote = ''179+ continue180+ }181+ if (char === '"' || char === "'" || char === '`') {182+ quote = char183+ fragment += char184+ continue185+ }186+ if (char === '#') break187+ if (char === '{') braces++188+ if (char === '}') braces--189+ if (braces < 0) throw new ErdSyntaxError("unexpected '}'", index + 1)190+ fragment += char191+ }192+ const trimmed = fragment.trim()193+ if (trimmed) {194+ if (!buffer) startLine = index + 1195+ buffer += `${buffer ? ' ' : ''}${trimmed}`196+ }197+ if (buffer && braces === 0 && !quote) {198+ records.push({ text: buffer, line: startLine })199+ buffer = ''200+ }201+ }202+ if (buffer || braces > 0 || quote) throw new ErdSyntaxError('unterminated options or quoted value', startLine || lines.length)203+ return records204+}205+206+function dotQuote(value: string): string { return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('\n', '\\n')}"` }207+function html(value: string): string { return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"') }208+function option(options: ErdOption[], name: string): string | undefined { return options.find(item => item.name === name)?.value }209+function attrValue(options: ErdOption[], name: string, fallback: string): string { return option(options, name) ?? fallback }210+function cardinality(card: Cardinality): string { return ({ 'zero-one': '{0,1}', one: '1', 'zero-plus': '0..N', 'one-plus': '1..N' })[card] }211+function font(text: string, options: ErdOption[]): string {212+ const attrs = [213+ option(options, 'color') ? `COLOR="${html(option(options, 'color')!)}"` : '',214+ option(options, 'font') ? `FACE="${html(option(options, 'font')!)}"` : '',215+ option(options, 'size') ? `POINT-SIZE="${html(option(options, 'size')!)}"` : '',216+ ].filter(Boolean)217+ return attrs.length ? `<FONT ${attrs.join(' ')}>${text}</FONT>` : text218+}219+function align(value: string): string { return ({ left: 'LEFT', center: 'CENTER', right: 'RIGHT' })[value.toLowerCase()] ?? 'LEFT' }220+function cellAttributes(options: ErdOption[]): string {221+ return [222+ `ALIGN="${align(attrValue(options, 'text-alignment', 'left'))}"`,223+ option(options, 'bgcolor') ? `BGCOLOR="${html(option(options, 'bgcolor')!)}"` : '',224+ option(options, 'border') ? `BORDER="${html(option(options, 'border')!)}"` : '',225+ option(options, 'border-color') ? `COLOR="${html(option(options, 'border-color')!)}"` : '',226+ option(options, 'cellborder') ? `CELLBORDER="${html(option(options, 'cellborder')!)}"` : '',227+ option(options, 'cellpadding') ? `CELLPADDING="${html(option(options, 'cellpadding')!)}"` : '',228+ option(options, 'cellspacing') ? `CELLSPACING="${html(option(options, 'cellspacing')!)}"` : '',229+ ].filter(Boolean).join(' ')230+}231+232+function entityLabel(entity: ErdEntity): string {233+ const headerLabel = option(entity.headerOptions, 'label')234+ const header = font(`<B>${html(entity.name)}</B>${headerLabel ? ` ${html(headerLabel)}` : ''}`, entity.headerOptions)235+ const rows = [`<TR><TD COLSPAN="1">${header}</TD></TR>`]236+ for (const attr of entity.attributes) {237+ let text = html(attr.field)238+ if (attr.pk) text = `<U>${text}</U>`239+ if (attr.fk) text = `<I>${text}</I>`240+ text = font(text, attr.options)241+ const label = option(attr.options, 'label')242+ if (label) text += font(` [${html(label)}]`, attr.options)243+ rows.push(`<TR><TD ${cellAttributes(attr.options)}>${text}</TD></TR>`)244+ }245+ const table = `<TABLE BORDER="${attrValue(entity.options, 'border', '0')}"${option(entity.options, 'border-color') ? ` COLOR="${html(option(entity.options, 'border-color')!)}"` : ''}${option(entity.options, 'bgcolor') ? ` BGCOLOR="${html(option(entity.options, 'bgcolor')!)}"` : ''} CELLBORDER="${attrValue(entity.options, 'cellborder', '1')}" CELLSPACING="${attrValue(entity.options, 'cellspacing', '0')}" CELLPADDING="${attrValue(entity.options, 'cellpadding', '4')}">${rows.join('')}</TABLE>`246+ return font(table, entity.options)247+}248+249+export function erdToDot(document: ErdDocument): string {250+ const lines = ['graph ERD {', ' rankdir=LR;', ' splines=spline;', ' node [shape=plaintext];', ' edge [color=gray50, minlen=2, style=dashed];']251+ const title = option(document.titleOptions, 'label')252+ const titleOptions = mergeOptions(document.titleOptions, [], optionDefaults('title'))253+ if (title) lines.push(` graph [label=<${font(html(title), titleOptions)}>, labelloc=t, labeljust=l];`)254+ for (const entity of document.entities) lines.push(` ${dotQuote(entity.name)} [label=<${entityLabel(entity)}>];`)255+ for (const relation of document.relations) {256+ const label = option(relation.options, 'label')257+ const relationLabel = label ? font(html(` ${label} `), relation.options) : ''258+ lines.push(` ${dotQuote(relation.entity1)} -- ${dotQuote(relation.entity2)} [taillabel=<${font(cardinality(relation.card1), relation.options)}>, headlabel=<${font(cardinality(relation.card2), relation.options)}>${label ? `, label=<${relationLabel}>` : ''}];`)259+ }260+ return `${lines.join('\n')}\n}`261+}@@ -0,0 +1,28 @@
1+import { erdAdapter } from '../adapters/edge/erd'2+import { graphvizAdapter } from '../adapters/edge/graphviz'3+import { createRendererUnitGroup } from '../unit'4+5+export default createRendererUnitGroup('graphviz-family', [6+ {7+ id: 'graphviz',8+ kind: 'render',9+ adapter: graphvizAdapter,10+ pipeline: ['graphviz'],11+ knownLosses: [12+ 'Only SVG is in the rendering contract.',13+ 'GraphViz resource-loading attributes are rejected because the edge unit has no file or network asset loader.',14+ 'The edge build uses GraphViz 15.1.1 versus the compatibility image GraphViz 14.1.3; output differences are possible.',15+ 'The GraphViz scale option is not supported by the edge-Wasm adapter.',16+ ],17+ },18+ {19+ id: 'erd',20+ kind: 'translate',21+ adapter: erdAdapter,22+ pipeline: ['erd', 'graphviz'],23+ knownLosses: [24+ 'Only the source language and default Kroki rendering mode are supported; ERD CLI/config-file modes are not exposed.',25+ 'The translated graph uses GraphViz 15.1.1 versus the compatibility image GraphViz 14.1.3; output differences are possible.',26+ ],27+ },28+])@@ -21,3 +21,8 @@ declare module 'wavedrom/skins/lowkey.js' { const skins: { lowkey: unknown }; ex
2121 declare module 'wavedrom/skins/narrow.js' { const skins: { narrow: unknown }; export default skins }2222 declare module 'wavedrom/skins/narrower.js' { const skins: { narrower: unknown }; export default skins }2323 declare module 'wavedrom/skins/narrowerer.js' { const skins: { narrowerer: unknown }; export default skins }24+25+declare module '*.wasm' {26+ const module: WebAssembly.Module27+ export default module28+}@@ -0,0 +1,4 @@
1+// @ts-nocheck2+import wasmModule from './graphviz.wasm';3+async function Module(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;Module["agerrMessages"]=[];Module["stderrMessages"]=[];err=text=>Module["stderrMessages"].push(text);var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}function binaryDecode(bin){for(var i=0,l=bin.length,o=new Uint8Array(l),c;i<l;++i){c=bin.charCodeAt(i);o[i]=~c>>8&c}return o}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["A"]();FS.ignorePermissions=false}function postRun(){}function abort(what){what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return wasmModule}function getBinarySync(file){return file}async function getWasmBinary(binaryFile){return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result instanceof WebAssembly.Instance ? result : result["instance"])}var info=getWasmImports();wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr];case"i8":return HEAP8[ptr];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP64[ptr>>3];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var UTF8Decoder=new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>{if(!ptr)return"";var end=findStringEnd(HEAPU8,ptr,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(HEAPU8.subarray(ptr,end))};var ___assert_fail=(condition,filename,line,func)=>abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"]);class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;abort()};var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.slice(0,-1)}return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>view=>(crypto.getRandomValues(view),0);var randomFill=view=>(randomFill=initRandomFill())(view);var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!=="")break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push("..")}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join("/")}};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(heapOrArray.buffer?heapOrArray.subarray(idx,endPtr):new Uint8Array(heapOrArray.slice(idx,endPtr)))};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.codePointAt(i);if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i<length;i++){var result;try{result=stream.tty.ops.get_char(stream.tty)}catch(e){throw new FS.ErrnoError(29)}if(result===undefined&&bytesRead===0){throw new FS.ErrnoError(6)}if(result===null||result===undefined)break;bytesRead++;buffer[offset+i]=result}if(bytesRead){stream.node.atime=Date.now()}return bytesRead},write(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.put_char){throw new FS.ErrnoError(60)}try{for(var i=0;i<length;i++){stream.tty.ops.put_char(stream.tty,buffer[offset+i])}}catch(e){throw new FS.ErrnoError(29)}if(length){stream.node.mtime=stream.node.ctime=Date.now()}return i}},default_tty_ops:{get_char(tty){return FS_stdin_getChar()},put_char(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var zeroMemory=(ptr,size)=>HEAPU8.fill(0,ptr,ptr+size);var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var mmapAlloc=size=>{size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(ptr)zeroMemory(ptr,size);return ptr};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=MEMFS.emptyFileContents??=new Uint8Array(0)}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){return node.contents.subarray(0,node.usedBytes)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents.length;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity<CAPACITY_DOUBLING_MAX?2:1.125)>>>0);if(prevCapacity)newCapacity=Math.max(newCapacity,256);var oldContents=MEMFS.getFileDataAsTypedArray(node);node.contents=new Uint8Array(newCapacity);node.contents.set(oldContents)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;var oldContents=node.contents;node.contents=new Uint8Array(newSize);node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)));node.usedBytes=newSize},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]!=null){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){if(!MEMFS.doesNotExistError){MEMFS.doesNotExistError=new FS.ErrnoError(44);MEMFS.doesNotExistError.stack="<generic error, no stack>"}throw MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);buffer.set(contents.subarray(position,position+size),offset);return size},write(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HEAP8.buffer){canOwn=false}if(!length)return 0;var node=stream.node;node.mtime=node.ctime=Date.now();if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length}else if(node.usedBytes===0&&position===0){node.contents=buffer.slice(offset,offset+length);node.usedBytes=length}else{MEMFS.expandFileStorage(node,position+length);node.contents.set(buffer.subarray(offset,offset+length),position);node.usedBytes=Math.max(node.usedBytes,position+length)}return length},llseek(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.usedBytes}}if(position<0){throw new FS.ErrnoError(28)}return position},mmap(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&contents.buffer===HEAP8.buffer){allocated=false;ptr=contents.byteOffset}else{allocated=true;ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}if(contents){if(position>0||position+length<contents.length){if(contents.subarray){contents=contents.subarray(position,position+length)}else{contents=Array.prototype.slice.call(contents,position,position+length)}}HEAP8.set(contents,ptr)}}return{ptr,allocated}},msync(stream,buffer,offset,length,mmapFlags){MEMFS.stream_ops.write(stream,buffer,0,length,offset,false);return 0}}};var FS_modeStringToFlags=str=>{if(typeof str!="string")return str;var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_fileDataToTypedArray=data=>{if(typeof data=="string"){data=intArrayFromString(data,true)}if(!data.subarray){data=new Uint8Array(data)}return data};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>id;var runDependencies=0;var dependenciesFulfilled=null;var removeRunDependency=id=>{runDependencies--;if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++};var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!="undefined")Browser.init();for(var plugin of preloadPlugins){if(plugin["canHandle"](fullname)){return plugin["handle"](byteArray,fullname)}}return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);addRunDependency(dep);try{var byteArray=url;if(typeof url=="string"){byteArray=await asyncLoad(url)}byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}}finally{removeRunDependency(dep)}};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,filesystems:null,syncFSRequests:0,ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i<parts.length;i++){var islast=i===parts.length-1;if(islast&&opts.parent){break}if(parts[i]==="."){continue}if(parts[i]===".."){current_path=PATH.dirname(current_path);if(FS.isRoot(current)){path=current_path+"/"+parts.slice(i+1).join("/");nlinks--;continue linkloop}else{current=current.parent}continue}current_path=PATH.join2(current_path,parts[i]);try{current=FS.lookupNode(current,parts[i])}catch(e){if(e?.errno===44&&islast&&opts.noent_okay){return{path:current_path}}throw e}if(FS.isMountpoint(current)&&(!islast||opts.follow_mount)){current=current.mounted.root}if(FS.isLink(current.mode)&&(!islast||opts.follow)){if(!current.node_ops.readlink){throw new FS.ErrnoError(52)}var link=current.node_ops.readlink(current);if(!PATH.isAbs(link)){link=PATH.dirname(current_path)+"/"+link}path=link+"/"+parts.slice(i+1).join("/");continue linkloop}}return{path:current_path,node:current}}throw new FS.ErrnoError(32)},getPath(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName(parentid,name){var hash=0;for(var i=0;i<name.length;i++){hash=(hash<<5)-hash+name.charCodeAt(i)|0}return(parentid+hash>>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}if(perms.includes("w")&&!(node.mode&146)){return 2}if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else if(FS.isDir(node.mode)){return 31}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}var mode=FS.flagsToPermissionString(flags);if(FS.isDir(node.mode)){if(mode!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,mode)},checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);setattr(arg,attr)},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}for(var mount of mounts){if(mount.type.syncfs){mount.type.syncfs(mount,populate,done)}else{done(null)}}},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);for(var[hash,current]of Object.entries(FS.nameTable)){while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}}node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name){throw new FS.ErrnoError(28)}if(name==="."||name===".."){throw new FS.ErrnoError(20)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs){Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return link.node_ops.readlink(link)},stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&~4095,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var setattr=FS.checkOpExists(node.node_ops.setattr,63);setattr(node,{atime,mtime})},open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flags=FS_modeStringToFlags(flags);if(flags&64){mode=mode&4095|32768}else{mode=0}var node;var isDirPath;if(typeof path=="object"){node=path}else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else if(isDirPath){throw new FS.ErrnoError(31)}else{node=FS.mknod(path,mode|511,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(created){FS.chmod(node,mode&511)}return stream},close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){abort(`Invalid encoding type "${opts.encoding}"`)}var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){buf=UTF8ArrayToString(buf)}FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);data=FS_fileDataToTypedArray(data);FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn);FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){FS.initialized=true;FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var stream of FS.streams){if(stream){FS.close(stream)}}},findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){data=FS_fileDataToTypedArray(data);FS.chmod(node,mode|146);var stream=FS.open(node,577);FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}},createDevice(parent,name,input,output){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);FS.createDevice.major??=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open(stream){stream.seekable=false},close(stream){if(output?.buffer?.length){output(10)}},read(stream,buffer,offset,length,pos){var bytesRead=0;for(var i=0;i<length;i++){var result;try{result=input()}catch(e){throw new FS.ErrnoError(29)}if(result===undefined&&bytesRead===0){throw new FS.ErrnoError(6)}if(result===null||result===undefined)break;bytesRead++;buffer[offset+i]=result}if(bytesRead){stream.node.atime=Date.now()}return bytesRead},write(stream,buffer,offset,length,pos){for(var i=0;i<length;i++){try{output(buffer[offset+i])}catch(e){throw new FS.ErrnoError(29)}}if(length){stream.node.mtime=stream.node.ctime=Date.now()}return i}});return FS.mkdev(path,mode,dev)},forceLoadFile(obj){if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(globalThis.XMLHttpRequest){abort("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else{try{obj.contents=readBinary(obj.url)}catch(e){throw new FS.ErrnoError(29)}}},createLazyFile(parent,name,url,canRead,canWrite){class LazyUint8Array{lengthKnown=false;chunks=[];get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)abort("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};for(const[key,fn]of Object.entries(node.stream_ops)){stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}}function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i<size;i++){buffer[offset+i]=contents[position+i]}}else{for(var i=0;i<size;i++){buffer[offset+i]=contents.get(position+i)}}return size}stream_ops.read=(stream,buffer,offset,length,position)=>{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var SYSCALLS={calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();stream.flags|=arg;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{return SYSCALLS.writeStat(buf,FS.fstat(fd))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21537:case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.writeStat(buf,nofollow?FS.lstat(path):FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>abort("");var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];var ydayFromDate=date=>{var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>num<INT53_MIN||num>INT53_MAX?NaN:Number(num);function __localtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __mmap_js(len,prot,flags,fd,offset,allocated,addr){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,offset,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;HEAPU32[addr>>2]=ptr;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffset<winterOffset){stringToUTF8(winterName,std_name,17);stringToUTF8(summerName,dst_name,17)}else{stringToUTF8(winterName,dst_name,17);stringToUTF8(summerName,std_name,17)}};var _emscripten_get_now=()=>performance.now();var _emscripten_date_now=()=>Date.now();var nowIsMonotonic=1;var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;function _clock_time_get(clk_id,ignored_precision,ptime){ignored_precision=bigintToI53Checked(ignored_precision);if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=_emscripten_date_now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP64[ptime>>3]=BigInt(nsec);return 0}var getHeapMax=()=>2147483648;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var keepRuntimeAlive=()=>true;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr<len)break;if(typeof offset!="undefined"){offset+=curr}}return ret};function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr<len){break}if(typeof offset!="undefined"){offset+=curr}}return ret};function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var _random_get=(buffer,size)=>randomFill(HEAPU8.subarray(buffer,buffer+size));var getCFunc=ident=>{var func=Module["_"+ident];return func};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i<args.length;i++){var converter=toC[argTypes[i]];if(converter){if(stack===0)stack=stackSave();cArgs[i]=converter(args[i])}else{cArgs[i]=args[i]}}}var ret=func(...cArgs);function onDone(ret){if(stack!==0)stackRestore(stack);return convertReturnValue(ret)}ret=onDone(ret);return ret};FS.createPreloadedFile=FS_createPreloadedFile;FS.preloadFile=FS_preloadFile;FS.staticInit();{}Module["ccall"]=ccall;Module["getValue"]=getValue;Module["PATH"]=PATH;Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;Module["FS"]=FS;function viz_errorf(text){Module["agerrMessages"].push(UTF8ToString(text));return 0}var _viz_set_y_invert,_viz_set_reduce,_viz_get_graphviz_version,_free,_malloc,_viz_get_plugin_list,_viz_create_graph,_viz_read_one_graph,_viz_string_dup,_viz_string_dup_html,_viz_string_free,_viz_string_free_html,_viz_add_node,_viz_add_edge,_viz_add_subgraph,_viz_set_default_graph_attribute,_viz_set_default_node_attribute,_viz_set_default_edge_attribute,_viz_set_attribute,_viz_free_graph,_viz_create_context,_viz_free_context,_viz_layout,_viz_free_layout,_viz_reset_errors,_viz_render,_emscripten_builtin_memalign,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_viz_set_y_invert=Module["_viz_set_y_invert"]=wasmExports["B"];_viz_set_reduce=Module["_viz_set_reduce"]=wasmExports["C"];_viz_get_graphviz_version=Module["_viz_get_graphviz_version"]=wasmExports["D"];_free=Module["_free"]=wasmExports["E"];_malloc=Module["_malloc"]=wasmExports["F"];_viz_get_plugin_list=Module["_viz_get_plugin_list"]=wasmExports["G"];_viz_create_graph=Module["_viz_create_graph"]=wasmExports["H"];_viz_read_one_graph=Module["_viz_read_one_graph"]=wasmExports["I"];_viz_string_dup=Module["_viz_string_dup"]=wasmExports["J"];_viz_string_dup_html=Module["_viz_string_dup_html"]=wasmExports["K"];_viz_string_free=Module["_viz_string_free"]=wasmExports["L"];_viz_string_free_html=Module["_viz_string_free_html"]=wasmExports["M"];_viz_add_node=Module["_viz_add_node"]=wasmExports["N"];_viz_add_edge=Module["_viz_add_edge"]=wasmExports["O"];_viz_add_subgraph=Module["_viz_add_subgraph"]=wasmExports["P"];_viz_set_default_graph_attribute=Module["_viz_set_default_graph_attribute"]=wasmExports["Q"];_viz_set_default_node_attribute=Module["_viz_set_default_node_attribute"]=wasmExports["R"];_viz_set_default_edge_attribute=Module["_viz_set_default_edge_attribute"]=wasmExports["S"];_viz_set_attribute=Module["_viz_set_attribute"]=wasmExports["T"];_viz_free_graph=Module["_viz_free_graph"]=wasmExports["U"];_viz_create_context=Module["_viz_create_context"]=wasmExports["V"];_viz_free_context=Module["_viz_free_context"]=wasmExports["W"];_viz_layout=Module["_viz_layout"]=wasmExports["X"];_viz_free_layout=Module["_viz_free_layout"]=wasmExports["Y"];_viz_reset_errors=Module["_viz_reset_errors"]=wasmExports["Z"];_viz_render=Module["_viz_render"]=wasmExports["_"];_emscripten_builtin_memalign=wasmExports["$"];__emscripten_stack_restore=wasmExports["aa"];__emscripten_stack_alloc=wasmExports["ba"];_emscripten_stack_get_current=wasmExports["ca"];memory=wasmMemory=wasmExports["z"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={a:___assert_fail,b:___cxa_throw,y:___syscall_faccessat,f:___syscall_fcntl64,w:___syscall_fstat64,j:___syscall_ioctl,v:___syscall_newfstatat,l:___syscall_openat,i:__abort_js,o:__localtime_js,m:__mmap_js,n:__munmap_js,q:__tzset_js,x:_clock_time_get,g:_emscripten_date_now,k:_emscripten_resize_heap,s:_environ_get,t:_environ_sizes_get,h:_exit,e:_fd_close,d:_fd_read,r:_fd_seek,c:_fd_write,u:_random_get,p:viz_errorf};function run(){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);postRun()}{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})}4+;return moduleRtn}export default Module;@@ -0,0 +1,40 @@
1+// @ts-nocheck2+const errorPatterns = [3+ [/^Error: (.*)/, "error"],4+ [/^Warning: (.*)/, "warning"]5+];6+7+export function parseStderrMessages(messages) {8+ return messages.map(message => {9+ for (let i = 0; i < errorPatterns.length; i++) {10+ const [pattern, level] = errorPatterns[i];11+12+ let match;13+14+ if ((match = pattern.exec(message)) !== null) {15+ return { message: match[1].trimEnd(), level };16+ }17+ }18+19+ return { message: message.trimEnd() };20+ });21+}22+23+export function parseAgerrMessages(messages) {24+ const result = [];25+ let level = undefined;26+27+ for (let i = 0; i < messages.length; i++) {28+ if (messages[i] == "Error" && messages[i+1] == ": ") {29+ level = "error";30+ i += 1;31+ } else if (messages[i] == "Warning" && messages[i+1] == ": ") {32+ level = "warning";33+ i += 1;34+ } else {35+ result.push({ message: messages[i].trimEnd(), level });36+ }37+ }38+39+ return result;40+}\ No newline at end of file@@ -0,0 +1,294 @@
1+// @ts-nocheck2+import { parseStderrMessages, parseAgerrMessages } from "./graphviz-errors";3+4+export function getGraphvizVersion(module) {5+ const resultPointer = module.ccall("viz_get_graphviz_version", "number", [], []);6+ return module.UTF8ToString(resultPointer);7+}8+9+export function getPluginList(module, kind) {10+ const resultPointer = module.ccall("viz_get_plugin_list", "number", ["string"], [kind]);11+12+ if (resultPointer == 0) {13+ throw new Error(`couldn't get plugin list: ${kind}`);14+ }15+16+ const list = [];17+ let itemPointer = resultPointer;18+ let stringPointer;19+20+ while (stringPointer = module.getValue(itemPointer, "*")) {21+ list.push(module.UTF8ToString(stringPointer));22+ module.ccall("free", "number", ["number"], [stringPointer]);23+ itemPointer += 4;24+ }25+26+ module.ccall("free", "number", ["number"], [resultPointer]);27+28+ return list;29+}30+31+export function renderInput(module, input, formats, options) {32+ let graphPointer, contextPointer, resultPointer, imageFilePaths;33+34+ try {35+ module["agerrMessages"] = [];36+ module["stderrMessages"] = [];37+38+ imageFilePaths = createImageFiles(module, options.images);39+40+ if (typeof input === "string") {41+ graphPointer = readStringInput(module, input, options);42+ } else if (typeof input === "object") {43+ graphPointer = readObjectInput(module, input, options);44+ } else {45+ throw new Error("input must be a string or object");46+ }47+48+ if (graphPointer === 0) {49+ return {50+ status: "failure",51+ output: undefined,52+ errors: parseErrorMessages(module)53+ };54+ }55+56+ setDefaultAttributes(module, graphPointer, options);57+58+ module.ccall("viz_set_y_invert", "number", ["number"], [options.yInvert ? 1 : 0]);59+ module.ccall("viz_set_reduce", "number", ["number"], [options.reduce ? 1 : 0]);60+61+ contextPointer = module.ccall("viz_create_context");62+63+ module.ccall("viz_reset_errors");64+65+ let layoutError = module.ccall("viz_layout", "number", ["number", "number", "string"], [contextPointer, graphPointer, options.engine]);66+67+ if (layoutError !== 0) {68+ return {69+ status: "failure",70+ output: undefined,71+ errors: parseErrorMessages(module)72+ };73+ }74+75+ let output = {};76+77+ for (let format of formats) {78+ resultPointer = module.ccall("viz_render", "number", ["number", "number", "string"], [contextPointer, graphPointer, format]);79+80+ if (resultPointer === 0) {81+ return {82+ status: "failure",83+ output: undefined,84+ errors: parseErrorMessages(module)85+ };86+ } else {87+ output[format] = module.UTF8ToString(resultPointer);88+89+ module.ccall("free", "number", ["number"], [resultPointer]);90+ resultPointer = 0;91+ }92+ }93+94+ return {95+ status: "success",96+ output: output,97+ errors: parseErrorMessages(module)98+ };99+ } catch (error) {100+ if (/^exit\(\d+\)/.test(error)) {101+ return {102+ status: "failure",103+ output: undefined,104+ errors: parseErrorMessages(module)105+ };106+ } else {107+ throw error;108+ }109+ } finally {110+ if (contextPointer && graphPointer) {111+ module.ccall("viz_free_layout", "number", ["number"], [contextPointer, graphPointer]);112+ }113+114+ if (graphPointer) {115+ module.ccall("viz_free_graph", "number", ["number"], [graphPointer]);116+ }117+118+ if (contextPointer) {119+ module.ccall("viz_free_context", "number", ["number"], [contextPointer]);120+ }121+122+ if (resultPointer) {123+ module.ccall("free", "number", ["number"], [resultPointer]);124+ }125+126+ if (imageFilePaths) {127+ removeImageFiles(module, imageFilePaths);128+ }129+ }130+}131+132+function parseErrorMessages(module) {133+ return parseAgerrMessages(module["agerrMessages"]).concat(parseStderrMessages(module["stderrMessages"]));134+}135+136+function createImageFiles(module, images) {137+ if (!images) {138+ return [];139+ }140+141+ for (const image of images) {142+ if (typeof image.name !== "string") {143+ throw new Error("image name must be a string");144+ } else if (typeof image.width !== "number" && typeof image.width !== "string") {145+ throw new Error("image width must be a number or string");146+ } else if (typeof image.height !== "number" && typeof image.height !== "string") {147+ throw new Error("image height must be a number or string");148+ }149+ }150+151+ return images.map(image => {152+ const path = module.PATH.join("/", image.name);153+ const data = `<?xml version="1.0" encoding="UTF-8"?>154+<svg xmlns="http://www.w3.org/2000/svg" width="${image.width}" height="${image.height}"></svg>155+`;156+157+ module.FS.createPath("/", module.PATH.dirname(path));158+ module.FS.writeFile(path, data);159+160+ return path;161+ });162+}163+164+function removeImageFiles(module, imageFilePaths) {165+ for (const path of imageFilePaths) {166+ if (module.FS.analyzePath(path).exists) {167+ module.FS.unlink(path);168+ }169+ }170+}171+172+function readStringInput(module, src, options) {173+ let srcPointer;174+175+ try {176+ const srcLength = module.lengthBytesUTF8(src);177+ srcPointer = module.ccall("malloc", "number", ["number"], [srcLength + 1]);178+ module.stringToUTF8(src, srcPointer, srcLength + 1);179+180+ return module.ccall("viz_read_one_graph", "number", ["number"], [srcPointer]);181+ } finally {182+ if (srcPointer) {183+ module.ccall("free", "number", ["number"], [srcPointer]);184+ }185+ }186+}187+188+function readObjectInput(module, object, options) {189+ const graphPointer = module.ccall("viz_create_graph", "number", ["string", "number", "number"], [object.name, typeof object.directed !== "undefined" ? object.directed : true, typeof object.strict !== "undefined" ? object.strict : false]);190+191+ readGraph(module, graphPointer, object);192+193+ return graphPointer;194+}195+196+function readGraph(module, graphPointer, graphData) {197+ if (graphData.nodes) {198+ graphData.nodes.forEach(nodeData => {199+ if (typeof nodeData.name === "undefined") {200+ throw new Error("nodes must have a name");201+ }202+203+ const nodePointer = module.ccall("viz_add_node", "number", ["number", "string"], [graphPointer, String(nodeData.name)]);204+205+ if (nodeData.attributes) {206+ setAttributes(module, graphPointer, nodePointer, nodeData.attributes);207+ }208+ });209+ }210+211+ if (graphData.edges) {212+ graphData.edges.forEach(edgeData => {213+ if (typeof edgeData.tail === "undefined") {214+ throw new Error("edges must have a tail");215+ }216+217+ if (typeof edgeData.head === "undefined") {218+ throw new Error("edges must have a head");219+ }220+221+ const edgePointer = module.ccall("viz_add_edge", "number", ["number", "string", "string"], [graphPointer, String(edgeData.tail), String(edgeData.head)]);222+223+ if (edgeData.attributes) {224+ setAttributes(module, graphPointer, edgePointer, edgeData.attributes);225+ }226+ });227+ }228+229+ if (graphData.subgraphs) {230+ graphData.subgraphs.forEach(subgraphData => {231+ const subgraphPointer = module.ccall("viz_add_subgraph", "number", ["number", "string"], [graphPointer, typeof subgraphData.name !== "undefined" ? String(subgraphData.name) : 0]);232+233+ readGraph(module, subgraphPointer, subgraphData);234+ });235+ }236+237+ setDefaultAttributes(module, graphPointer, graphData);238+}239+240+function setDefaultAttributes(module, graphPointer, data) {241+ if (data.graphAttributes) {242+ for (const [name, value] of Object.entries(data.graphAttributes)) {243+ withStringPointer(module, graphPointer, value, stringPointer => {244+ module.ccall("viz_set_default_graph_attribute", "number", ["number", "string", "number"], [graphPointer, name, stringPointer]);245+ });246+ }247+ }248+249+ if (data.nodeAttributes) {250+ for (const [name, value] of Object.entries(data.nodeAttributes)) {251+ withStringPointer(module, graphPointer, value, stringPointer => {252+ module.ccall("viz_set_default_node_attribute", "number", ["number", "string", "number"], [graphPointer, name, stringPointer]);253+ });254+ }255+ }256+257+ if (data.edgeAttributes) {258+ for (const [name, value] of Object.entries(data.edgeAttributes)) {259+ withStringPointer(module, graphPointer, value, stringPointer => {260+ module.ccall("viz_set_default_edge_attribute", "number", ["number", "string", "number"], [graphPointer, name, stringPointer]);261+ });262+ }263+ }264+}265+266+function setAttributes(module, graphPointer, objectPointer, attributes) {267+ for (const [key, value] of Object.entries(attributes)) {268+ withStringPointer(module, graphPointer, value, stringPointer => {269+ module.ccall("viz_set_attribute", "number", ["number", "string", "number"], [objectPointer, key, stringPointer]);270+ });271+ }272+}273+274+function withStringPointer(module, graphPointer, value, callbackFn) {275+ let stringPointer;276+277+ if (typeof value === "object" && "html" in value) {278+ stringPointer = module.ccall("viz_string_dup_html", "number", ["number", "string"], [graphPointer, String(value.html)]);279+ } else {280+ stringPointer = module.ccall("viz_string_dup", "number", ["number", "string"], [graphPointer, String(value)]);281+ }282+283+ if (stringPointer == 0) {284+ throw new Error("couldn't dup string");285+ }286+287+ callbackFn(stringPointer);288+289+ if (typeof value === "object" && "html" in value) {290+ module.ccall("viz_string_free_html", "number", ["number", "number"], [graphPointer, stringPointer]);291+ } else {292+ module.ccall("viz_string_free", "number", ["number", "number"], [graphPointer, stringPointer]);293+ }294+}Binary file.
@@ -3,6 +3,8 @@ import { edgeAdapter } from '../src/adapters/edge'
33 import type { RenderRequest } from '../src/types'44 55 const fixtures = {6+ graphviz: 'digraph { a -> b }',7+ erd: '[Person]\n*name\n\n[Place]\n*id\n\nPerson *--1 Place',68 nomnoml: '[Alice]->[Bob]',79 bytefield: '[{:bits 8 :name "field"}]',810 vega: '{"width":100,"height":50,"marks":[{"type":"rect","encode":{"enter":{"x":{"value":0},"y":{"value":0},"width":{"value":100},"height":{"value":50}}}}]}',@@ -20,6 +20,15 @@ describe('engine catalog', () => {
2020 }2121 })2222 23+ it('marks GraphViz and ERD as one edge-Wasm family', () => {24+ const family = ENGINE_CATALOG.filter(engine => ['graphviz', 'erd'].includes(engine.id))25+ expect(family).toHaveLength(2)26+ for (const engine of family) {27+ expect(engine.activeRuntime).toBe('edge-wasm')28+ expect(engine.version).toContain('graphviz@15.1.1')29+ }30+ })31+2332 it('does not retain a silent origin fallback for any engine', () => {2433 for (const engine of ENGINE_CATALOG) {2534 if (engine.activeRuntime === 'origin') expect(engine.version).toBe('compatibility-origin')@@ -0,0 +1,91 @@
1+import { describe, expect, it } from 'vitest'2+import { ErdSyntaxError, erdToDot, parseErd } from '../src/erd'3+4+describe('ERD source lowering', () => {5+ it('parses the repository schema and emits entities, fields, and cardinalities', () => {6+ const doc = parseErd(`[Person]\n*name\nheight\nweight\n+birth_location_id\n\n[Location]\n*id\ncity\nstate\ncountry\n\nPerson *--1 Location`)7+ expect(doc.entities).toHaveLength(2)8+ expect(doc.entities[0]!.attributes[0]).toMatchObject({ field: 'name', pk: true, fk: false })9+ expect(doc.entities[0]!.attributes[3]).toMatchObject({ field: 'birth_location_id', pk: false, fk: true })10+ expect(doc.relations[0]).toMatchObject({ entity1: 'Person', entity2: 'Location', card1: 'zero-plus', card2: 'one' })11+ const dot = erdToDot(doc)12+ expect(dot).toContain('"Person" [label=<')13+ expect(dot).toContain('taillabel=<0..N>')14+ expect(dot).toContain('headlabel=<1>')15+ })16+17+ it('supports quoted identifiers, comments, directives, labels, and all cardinalities', () => {18+ const doc = parseErd(`# comment\ntitle { label: "People & places", size: "24" }\nheader { label: "header", color: "navy", font: "Georgia", size: "18" }\nentity { bgcolor: "#fff", border: "2", border-color: "red", cellborder: "1", cellspacing: "2", cellpadding: "3", color: "black", font: "Arial", size: "11" }\nrelationship { label: "lives at", color: "gray", font: "Arial", size: "10" }\n\n["People & Co"]\n*"display name" { label: "Name", color: "blue", font: "Verdana", size: "9", text-alignment: "center" }\n[Place]\n?code\n\n"People & Co" ?--+ Place # trailing comment`)19+ expect(doc.titleOptions).toContainEqual({ name: 'label', value: 'People & places' })20+ expect(doc.relations[0]).toMatchObject({ card1: 'zero-one', card2: 'one-plus' })21+ const dot = erdToDot(doc)22+ expect(dot).toContain('People & places')23+ expect(dot).toContain('People & Co')24+ expect(dot).toContain('{0,1}')25+ expect(dot).toContain('headlabel=<')26+ expect(dot).toContain('BGCOLOR="#fff"')27+ expect(dot).toContain('COLOR="red"')28+ expect(dot).toContain('POINT-SIZE="10"')29+ expect(dot).toContain(' [Name]')30+ expect(dot).toContain('graph [label=<<FONT POINT-SIZE="24">People & places</FONT>>, labelloc=t, labeljust=l];')31+ })32+33+ it('retains upstream defaults when a global directive overrides one property', () => {34+ const dot = erdToDot(parseErd('header { color: "navy" }\nentity { bgcolor: "#fff" }\n[A]\nid'))35+ expect(dot).toContain('POINT-SIZE="16"')36+ expect(dot).toContain('FACE="Helvetica"')37+ expect(dot).toContain('CELLBORDER="1"')38+ expect(dot).toContain('CELLPADDING="4"')39+ })40+41+ it('supports upstream multiline options, comments, and trailing commas', () => {42+ const doc = parseErd(`entity {43+ bgcolor: "#ececfc", # preserve the upstream comment form44+ size: "20",45+ }46+ [Person]47+ name {48+ label: "string",49+ color: "#3366ff",50+ }`)51+ const dot = erdToDot(doc)52+ expect(dot).toContain('BGCOLOR="#ececfc"')53+ expect(dot).toContain('POINT-SIZE="20"')54+ expect(dot).toContain('COLOR="#3366ff"')55+ expect(dot).toContain(' [string]')56+ })57+58+ it('treats directive names after the preamble as ordinary attributes', () => {59+ const doc = parseErd('[A]\nid\nheader { color: "red" }\n[B]\nid')60+ expect(doc.headerOptions).toEqual([])61+ expect(doc.entities[0]!.attributes[1]).toMatchObject({ field: 'header' })62+ expect(erdToDot(doc)).toContain('<FONT COLOR="red">header</FONT>')63+ })64+65+ it('escapes DOT and HTML metacharacters in labels', () => {66+ const dot = erdToDot(parseErd('["A]\\B"]\n"x < y & z"'))67+ expect(dot).toContain('"A]\\\\B"')68+ expect(dot).toContain('x < y & z')69+ })70+71+ it('applies supported HTML table styling to attributes', () => {72+ const dot = erdToDot(parseErd('[A]\nfield { bgcolor: "#ececfc", border: "2", border-color: "red", cellborder: "3", cellspacing: "4", cellpadding: "5" }'))73+ expect(dot).toContain('BGCOLOR="#ececfc"')74+ expect(dot).toContain('BORDER="2"')75+ expect(dot).toContain('COLOR="red"')76+ expect(dot).toContain('CELLBORDER="3"')77+ expect(dot).toContain('CELLSPACING="4"')78+ expect(dot).toContain('CELLPADDING="5"')79+ })80+81+ it('rejects malformed input and filesystem-style configuration', () => {82+ expect(() => parseErd('id')).toThrow(/attribute comes before first entity/)83+ expect(() => parseErd('[A]\nfield { config: "/tmp/erd.yaml" }')).toThrow(/option 'config' does not exist/)84+ expect(() => parseErd('[A]\nfoo\nA *--1 Missing')).toThrow(/unknown entity 'Missing'/)85+ expect(() => parseErd('[A]\nfoo { label: "unterminated }')).toThrow(ErdSyntaxError)86+ expect(() => parseErd('[A]\nfoo { border: "256" }')).toThrow(/0 to 255/)87+ expect(() => parseErd('[A]\nfoo { size: "0" }')).toThrow(/positive finite/)88+ expect(() => parseErd('[A]\nfoo { text-alignment: "diagonal" }')).toThrow(/text-alignment/)89+ expect(() => parseErd('[A]\nfoo { color: "red"')).toThrow(/unterminated options/)90+ })91+})@@ -0,0 +1,66 @@
1+import { describe, expect, it } from 'vitest'2+import { graphvizAdapter, optionsFor, renderGraphvizSource } from '../src/adapters/edge/graphviz'3+import type { RenderRequest } from '../src/types'4+5+describe('GraphViz option lowering', () => {6+ it('maps layout and default graph/node/edge attributes', () => {7+ expect(optionsFor({8+ layout: 'neato',9+ 'graph-attribute-rankdir': 'LR',10+ 'node-attribute-shape': 'box',11+ 'edge-attribute-color': 'gray50',12+ })).toMatchObject({13+ engine: 'neato',14+ graphAttributes: { rankdir: 'LR' },15+ nodeAttributes: { shape: 'box' },16+ edgeAttributes: { color: 'gray50' },17+ })18+ })19+20+ it('rejects scale and unknown options instead of silently dropping them', () => {21+ expect(() => optionsFor({ scale: '72' })).toThrow(/scale is not supported/)22+ expect(() => optionsFor({ engine: 'dot' })).toThrow(/Unsupported GraphViz option/)23+ })24+25+ it('rejects resource-loading attributes supplied through options', () => {26+ for (const option of [27+ 'graph-attribute-imagepath',28+ 'graph-attribute-fontpath',29+ 'graph-attribute-stylesheet',30+ 'node-attribute-image',31+ 'node-attribute-shapefile',32+ ]) {33+ expect(() => optionsFor({ [option]: '/tmp/resource' })).toThrow(/resource attribute/)34+ }35+ })36+37+ it('rejects DOT and HTML image loading forms', async () => {38+ const request = (source: string): RenderRequest => ({39+ engine: 'graphviz', source, format: 'svg', options: {},40+ metadata: { title: '', description: '' },41+ presentation: { background: '', padding: 0, frame: false },42+ })43+ await expect(graphvizAdapter.render(request('digraph { a [image="/tmp/a.png"] }'), new AbortController().signal)).rejects.toThrow(/not supported/)44+ await expect(graphvizAdapter.render(request('digraph { a [label=<<IMG SRC="a.png"/>>] }'), new AbortController().signal)).rejects.toThrow(/not supported/)45+ await expect(graphvizAdapter.render(request('digraph { a [label="image=descriptive text"] }'), new AbortController().signal)).resolves.toMatchObject({ runtime: 'edge-wasm' })46+ })47+48+ it('renders the HTML labels and ports needed by translated engines', async () => {49+ const svg = await renderGraphvizSource('digraph { a [shape=plain label=<<TABLE><TR><TD PORT="p">A</TD></TR></TABLE>>]; b [label="B"]; a:p -> b }')50+ expect(svg).toMatch(/<svg[\s>]/i)51+ expect(svg).toContain('A')52+ expect(svg).toContain('B')53+ })54+55+ it('serializes shared-module renders without leaking errors', async () => {56+ const results = await Promise.allSettled([57+ renderGraphvizSource('digraph { a -> b }'),58+ renderGraphvizSource('digraph { broken -> }'),59+ renderGraphvizSource('graph { "safe" -- "still safe" }'),60+ ])61+ expect(results[0]).toMatchObject({ status: 'fulfilled' })62+ expect(results[1]).toMatchObject({ status: 'rejected' })63+ expect(results[2]).toMatchObject({ status: 'fulfilled' })64+ if (results[2]!.status === 'fulfilled') expect(results[2]!.value).toContain('still safe')65+ })66+})@@ -0,0 +1,62 @@
1+@viz-js/viz 3.29.02+https://github.com/mdaines/viz-js/tree/96be4ed32789125f00cffa3ae390bd8809fc9c033+4+The GraphViz-family Worker vendors the Emscripten backend, wrapper, and5+WebAssembly payload from @viz-js/viz 3.29.0. The backend is mechanically6+adapted to import graphviz.wasm as a Cloudflare CompiledWasm module instead of7+embedding the bytes in JavaScript. The package provenance identifies:8+9+- GraphViz 15.1.1, source:10+ https://gitlab.com/graphviz/graphviz/-/tree/15.1.111+- Expat 2.8.1, source:12+ https://github.com/libexpat/libexpat/tree/R_2_8_113+- Emscripten SDK 5.0.514+15+GraphViz and Bytefield are licensed under EPL-2.0. The repository's shared full16+EPL-2.0 text is included at licenses/bytefield-license.txt. Expat and Viz.js17+use the MIT terms reproduced below.18+19+Viz.js MIT License20+21+Copyright (c) Michael Daines22+23+Permission is hereby granted, free of charge, to any person obtaining a copy24+of this software and associated documentation files (the "Software"), to deal25+in the Software without restriction, including without limitation the rights26+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell27+copies of the Software, and to permit persons to whom the Software is28+furnished to do so, subject to the following conditions:29+30+The above copyright notice and this permission notice shall be included in all31+copies or substantial portions of the Software.32+33+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR34+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,35+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE36+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER37+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,38+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE39+SOFTWARE.40+41+Expat MIT License42+43+Copyright (c) 1998-2000 Thai Open Source Software Center Ltd and Clark Cooper44+Copyright (c) 2001-2025 Expat maintainers45+46+Permission is hereby granted, free of charge, to any person obtaining a copy47+of this software and associated documentation files (the "Software"), to deal48+in the Software without restriction, including without limitation the rights49+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell50+copies of the Software, and to permit persons to whom the Software is51+furnished to do so, subject to the following conditions:52+53+The above copyright notice and this permission notice shall be included in all54+copies or substantial portions of the Software.55+56+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR57+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,58+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE59+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER60+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,61+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE62+SOFTWARE.@@ -0,0 +1,16 @@
1+{2+ "$schema": "../node_modules/wrangler/config-schema.json",3+ "name": "diagramzip-render-graphviz-family",4+ "main": "../src/units/graphviz-family.ts",5+ "compatibility_date": "2026-08-20",6+ "compatibility_flags": ["nodejs_compat"],7+ "rules": [8+ { "type": "CompiledWasm", "globs": ["**/*.wasm"], "fallthrough": false }9+ ],10+ "observability": { "enabled": true, "logs": { "head_sampling_rate": 1 } },11+ "vars": { "RENDERER_BUILD": "graphviz-15.1.1-family-edge-wasm-1" },12+ "routes": [13+ { "pattern": "erd.render.diagram.zip", "custom_domain": true },14+ { "pattern": "graphviz.render.diagram.zip", "custom_domain": true }15+ ]16+}@@ -32,6 +32,7 @@ export const NO_GATEWAY_FALLBACK_ENGINES = Object.freeze([
3232 'mermaid', 'bpmn', 'excalidraw',3333 'bytefield', 'nomnoml', 'vega', 'vegalite', 'wavedrom',3434 'blockdiag', 'seqdiag', 'actdiag', 'nwdiag', 'packetdiag', 'rackdiag',35+ 'graphviz', 'erd',3536 ])3637 3738 export function requiresDedicatedRenderer(engine) {@@ -54,17 +54,31 @@ test('keeps the compatibility gateway fallback for a residual engine', async ()
5454 })5555 5656 const response = await controller.renderThroughGateway(57- { ...request, type: 'graphviz' },57+ { ...request, type: 'd2' },5858 new AbortController().signal,5959 )6060 6161 assert.equal(response.status, 200)6262 assert.deepEqual(endpoints, [63- 'https://graphviz.render.diagram.zip/v1/svg',63+ 'https://d2.render.diagram.zip/v1/svg',6464 '/render/v1/svg',6565 ])6666 })6767 68+test('does not send a GraphViz-family failure to the gateway', async () => {69+ const endpoints = []70+ const controller = controllerWith(async endpoint => {71+ endpoints.push(endpoint)72+ return new Response('unavailable', { status: 503 })73+ })74+75+ await assert.rejects(76+ controller.renderThroughGateway({ ...request, type: 'erd' }, new AbortController().signal),77+ /erd renderer unit is unavailable/,78+ )79+ assert.deepEqual(endpoints, ['https://erd.render.diagram.zip/v1/svg'])80+})81+6882 test('does not send a failed client renderer to the gateway', async () => {6983 let gatewayCalls = 07084 const statuses = []@@ -23,9 +23,11 @@ test('requires every migrated engine to stay on its dedicated runtime', () => {
2323 'mermaid', 'bpmn', 'excalidraw',2424 'bytefield', 'nomnoml', 'vega', 'vegalite', 'wavedrom',2525 'blockdiag', 'seqdiag', 'actdiag', 'nwdiag', 'packetdiag', 'rackdiag',26+ 'graphviz', 'erd',2627 ]) {2728 assert.equal(requiresDedicatedRenderer(engine), true)2829 }2930 assert.equal(requiresDedicatedRenderer('plantuml'), false)30- assert.equal(requiresDedicatedRenderer('graphviz'), false)31+ assert.equal(requiresDedicatedRenderer('graphviz'), true)32+ assert.equal(requiresDedicatedRenderer('erd'), true)3133 })@@ -31,7 +31,7 @@ For each diagram family, choose the closest maintained rendering path in this or
3131 6. Use a narrowly scoped compatibility unit only until that engine's replacement3232 is live, then remove its origin path instead of retaining a silent fallback.3333 34-Workers should own routing, decoding, request validation, SVG sanitization, presentation, caching, and the edge-native engines. The browser should own interactive rendering wherever practical. Fly.io or Cloudflare Containers should temporarily retain engines whose real responsibility is a native toolchain, a JVM, Graphviz composition that cannot yet move to WebAssembly, or TeX. Python alone is no longer a reason to require an origin now that Cloudflare has Python Workers, but each dependency still needs a compatibility and performance spike.34+Workers should own routing, decoding, request validation, SVG sanitization, presentation, caching, and the edge-native engines. The browser should own interactive rendering wherever practical. Fly.io or Cloudflare Containers should temporarily retain engines whose real responsibility is a native toolchain, a JVM, an unproven WebAssembly port, or TeX. Python alone is no longer a reason to require an origin now that Cloudflare has Python Workers, but each dependency still needs a compatibility and performance spike.3535 3636 This gives v2 a coherent product during the extraction: all engines remain3737 available through a runtime adapter, while each completed cutover permanently@@ -90,7 +90,8 @@ Suggested deployable units:
9090 that accepts only residual origin engines.9191 - dependency-grouped JavaScript units: Bytefield, Nomnoml, WaveDrom, Vega,9292 and Vega-Lite, each reached only through its engine hostname.93-- `diagram-zip-render-wasm`: GraphViz, D2, Pikchr, and Svgbob after compatibility spikes.93+- `graphviz-family`: GraphViz and translators that lower to DOT, currently ERD and potentially WireViz after its own compatibility spike.94+- additional dependency-grouped WebAssembly units for D2, Pikchr, Svgbob, and GoAT only after their separate compatibility spikes.9495 - `diagram-zip-render-python`: the upstream BlockDiag suite, isolated because Pyodide, Pillow, fonts, and the Python package graph have a different bundle and startup profile.9596 - `diagram-zip-render-origin`: the remaining native/JVM/TeX renderers, initially consolidated in one Fly app or equivalent container group.9697 @@ -151,7 +152,7 @@ Kroki does not contain a separate ERD or BlockDiag renderer:
151152 152153 - `Erd.java` invokes the upstream `erd` executable with `--fmt=<format>`.153154 - `Blockdiag.java` invokes one bundled executable with `--module=blockdiag|seqdiag|actdiag|nwdiag|packetdiag|rackdiag`.154-- The server image takes ERD 0.2.3 from `ghcr.io/yuzutech/erd`.155+- The current Docker image's `erd` binary reports 0.2.1.0; the extraction tests use that containerized binary as the behavior reference.155156 - The server image downloads `yuzutech/blockdiag` 3.4.2, a maintained packaging/compatibility fork of the Apache-licensed BlockDiag core. Its release workflow installs the upstream SeqDiag, ActDiag, and NwDiag packages and compiles all six commands into one Nuitka binary.156157 157158 The existing Java code is therefore a process adapter. V2 can remove the Java/subprocess/container boundary while retaining the upstream parsers, models, layout, and SVG behavior.@@ -417,7 +418,7 @@ The origin is a compatibility bridge, not the target architecture for standardiz
417418 | WaveDrom | Edge JavaScript | Origin |418419 | WireViz | Origin; rewrite candidate | Origin |419420 420-The first coverage milestone puts all 30 catalog types behind one contract and keeps unsupported edge engines on the origin. Its first native cohort is Bytefield, Nomnoml, WaveDrom, Vega, and Vega-Lite. DBML remains on the origin because its published package mixes module formats; GraphViz remains there because the published Viz.js wrapper instantiates WebAssembly at runtime. D2, Pikchr, and Svgbob move only after direct precompiled-Wasm adapters are proven. Seven engines can later move interactive preview into the browser: Mermaid, PlantUML, C4-PlantUML, BPMN, Excalidraw, diagrams.net, and UMLet.421+The first coverage milestone puts all 30 catalog types behind one contract and keeps unsupported edge engines on the origin. Its first native cohort is Bytefield, Nomnoml, WaveDrom, Vega, and Vega-Lite. DBML remains on the origin because its published package mixes module formats. GraphViz now imports its WebAssembly as a precompiled Worker module, and ERD lowers into that same runtime. D2, Pikchr, and Svgbob move only after direct precompiled-Wasm adapters are proven. Seven engines can later move interactive preview into the browser: Mermaid, PlantUML, C4-PlantUML, BPMN, Excalidraw, diagrams.net, and UMLet.421422 422423 The upstream-reuse milestone then moves all six BlockDiag-family types into one Python Worker, moves ERD to an upstream-guided TypeScript/GraphViz implementation, and compiles Structurizr to PlantUML in TypeScript. At that point the target split is 17 edge-rendered types, eight client/pipeline-rendered types, and five types requiring a compatibility origin: Ditaa, GoAT, Symbolator, TikZ, and WireViz. A successful GoAT WebAssembly adapter reduces the origin set to four.423424 @@ -513,7 +514,7 @@ one dependency-family cutover.
513514 514515 ### Phase 4: upstream-guided ERD port515516 516-Port the upstream ERD grammar, model, defaults, validation, and DOT mapping to TypeScript. Import its tests and examples, compare generated DOT and SVG semantics with ERD 0.2.3, and render through the GraphViz WebAssembly adapter. Do not broaden the language during the compatibility phase.517+Port the upstream ERD grammar, model, defaults, validation, and DOT mapping to TypeScript. Import its tests and examples, compare generated DOT and SVG semantics with the containerized ERD 0.2.1.0 reference, and render through the GraphViz WebAssembly adapter. Do not broaden the language during the compatibility phase.517518 518519 ### Phase 5: client-native preview519520 @@ -544,6 +545,9 @@ Use production traffic and latency data to choose the remaining rewrites. WireVi
544545 - Vendored upstream renderer code records its exact version or commit, license, local patches, and imported upstream test corpus.545546 - The BlockDiag-family Worker passes structural coverage for all six hostnames,546547 declares its losses, and has no gateway/origin fallback.548+- The GraphViz-family Worker passes structural GraphViz and ERD fixtures under549+ real workerd, declares its version drift and unsupported asset-loading modes,550+ and has no gateway/origin fallback.547551 548552 ## Open design questions549553 @@ -554,10 +558,11 @@ Use production traffic and latency data to choose the remaining rewrites. WireVi
554558 555559 ## Implementation status — 2026-08-20556560 557-The coverage plane is implemented across dedicated renderer hostnames. Fourteen561+The coverage plane is implemented across dedicated renderer hostnames. Sixteen558562 of 30 catalog engines no longer depend on Fly: five execute in JavaScript559-Workers, six share the `blockdiag-family` Python Worker, and Mermaid, BPMN, and560-Excalidraw render in sandboxed browser units. The remaining 16 engines use 15563+Workers, six share the `blockdiag-family` Python Worker, GraphViz and ERD share564+the `graphviz-family` WebAssembly Worker, and Mermaid, BPMN, and Excalidraw565+render in sandboxed browser units. The remaining 14 engines use 13561566 dependency-grouped compatibility units. Repository and production smokes require562567 structurally valid SVG; pixel comparison remains intentionally deferred.563568 @@ -566,12 +571,19 @@ drawer/node/plugin registration, a bundled DejaVu Serif font, bounded streaming
566571 input, Cache API storage, SVG sanitization, and explicit remote/filesystem image567572 rejection. All six production hostnames report `edge-python` and the568573 `blockdiag-family` unit, and none can fall back through the gateway.569-The same hard boundary now applies to all fourteen migrated engines: direct574+The GraphViz family imports GraphViz 15.1.1 as a precompiled Worker module. ERD575+uses an upstream-guided TypeScript grammar/model/DOT lowering and calls the same576+in-process runtime rather than a second deployment. Both hostnames were checked577+under real workerd, including cache behavior, presentation, HTML labels and578+ports, malformed input, and unsafe image rejection. The family dry-run is about579+1.19 MB uncompressed and 0.47 MB compressed.580+581+The same hard boundary now applies to all sixteen migrated engines: direct570582 unit or client-renderer failure is surfaced to the editor, the gateway rejects571583 their engine IDs before consulting its cache or origin adapter, and every572584 catalog fallback is `null`.573585 574-The first feasibility findings are already reflected in routing and the loss ledger: DBML's package cannot currently be imported cleanly in the Worker runtime, and Viz.js attempts runtime WebAssembly compilation. Both remain fully covered through the origin rather than blocking the migration. The current dry-run bundle is approximately 4.4 MB uncompressed and 0.85 MB compressed.586+The first feasibility findings are already reflected in routing and the loss ledger: DBML's package cannot currently be imported cleanly in the Worker runtime. GraphViz's published wrapper required a small vendored backend change so Wrangler could bind the binary as a precompiled module; the renderer no longer depends on Fly. GraphViz version drift, unsupported `scale`, and the lack of an image asset loader are explicit losses rather than hidden fallbacks.575587 576588 The SVG compatibility boundary preserves CDATA-backed text and embedded font styles used by Ditaa, Symbolator, diagrams.net, and TikZ. Mermaid retains a narrow allowlist of XHTML label formatting inside `foreignObject`, while scripts, event handlers, external links, and resource-loading elements remain blocked. When a user selects a canvas color, known full-size white renderer backdrops are removed before the chosen background is applied; internal white diagram shapes are left intact.577589