Render BPMN 2.0 diagram interchange data directly in a dedicated edge Worker with bounded XML parsing, core BPMN shapes, labels, flows, and deterministic SVG. Remove BPMN from Browser Run and the bpmn-js client package, update catalog and normalization identity, and cover the production fixture and hostile XML inputs.
- commit
- 2d0881334ce2af1bd0c0266c610fcf32c6a42989
- parent
- 820567b5b7b420c083fc3c256988b49b666a1dbe
- author
- Dami <dami.etoile@gmail.com>
@@ -41,7 +41,6 @@ DiagramZip product, repository, or hosted service.
4141 | Renderer paths or deployable unit | Third-party material | License and effective boundary | Detailed record or full text |4242 | --- | --- | --- | --- |4343 | `renderers/client/mermaid/` | Mermaid 11.17.0 and its emitted browser dependencies | Mermaid is MIT; browser bundle is delivered to recipients | [Mermaid text](licenses/mermaid-license.txt), package lock |44-| `renderers/client/bpmn/` | bpmn-js 18.25.1 | Custom bpmn.io MIT-like terms, including its watermark condition; browser bundle is delivered to recipients | [bpmn.io terms](licenses/bpmn-license.txt) |4544 | `renderers/client/diagramsnet/` | diagrams.net 29.6.1 export runtime, shapes, stencils, images, and math assets | Application code is Apache-2.0; visual assets retain the additional diagrams.net terms. The built client publishes both beside the bundle | [renderer notice](renderers/client/diagramsnet/README.md), nested `vendor/assets/*/LICENSE` files |4645 | `renderers/client/tikz/` | `@planktimerr/tikzjax` 1.0.63 plus TeX/TikZ assets | The deployable browser unit is GPL-3.0-or-later; bundled TeX/TikZ material is LPPL-1.3c. Corresponding source and terms are published on the renderer origin | [renderer notice](renderers/client/tikz/README.md), `vendor-licenses/` |4746 | `renderers/python/wireviz-translator/` | Vendored WireViz release source and PyYAML | The in-process combined Worker unit is GPL-3.0-only; PyYAML is MIT. Separate services reached over HTTP or a service binding are outside this Python program | [unit scope](renderers/python/wireviz-translator/README.md), [notices](renderers/python/wireviz-translator/THIRD_PARTY_NOTICES.md) |@@ -260,6 +260,7 @@ test('normalizes CSS-driven Mermaid and BPMN neutral structures', () => {
260260 assert.match(bpmn, /data-dz-stroke="line"/)261261 assert.equal((bpmn.match(/data-dz-fill="ink"/g) ?? []).length, 2)262262 assert.doesNotMatch(bpmn.match(/<rect class="djs-hit"[^>]*>/)?.[0] ?? '', /data-dz-/)263+ assert.equal(normalizationFor('bpmn', 'diagramzip-bpmn-svg@1').profile, 'neutral-svg-semantic-2')263264 })264265 265266 test('creates addressable surfaces for closed Pikchr and line-built Svgbob boxes', () => {@@ -1,23 +0,0 @@
1-Copyright (c) 2014-present Camunda Services GmbH2-3-Permission is hereby granted, free of charge, to any person obtaining a copy of4-this software and associated documentation files (the "Software"), to deal in the5-Software without restriction, including without limitation the rights to use, copy,6-modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,7-and to permit persons to whom the Software is furnished to do so, subject to the8-following conditions:9-10-The above copyright notice and this permission notice shall be included in all11-copies or substantial portions of the Software.12-13-The source code responsible for displaying the bpmn.io project watermark that14-links back to https://bpmn.io as part of rendered diagrams MUST NOT be15-removed or changed. When this software is being used in a website or application,16-the watermark must stay fully visible and not visually overlapped by other elements.17-18-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,19-INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR20-PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE21-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,22-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE23-OR OTHER DEALINGS IN THE SOFTWARE.@@ -1,10 +1,9 @@
11 export const CHANNEL = 'diagram.zip:renderer:v1' as const22 3-export const ENGINES = ['bpmn', 'mermaid', 'excalidraw', 'diagramsnet', 'tikz'] as const3+export const ENGINES = ['mermaid', 'excalidraw', 'diagramsnet', 'tikz'] as const44 export type Engine = (typeof ENGINES)[number]55 66 export const FRAME_URLS: Readonly<Record<Engine, string>> = Object.freeze({7- bpmn: 'https://bpmn.render.diagram.zip/index.html?v=1',87 mermaid: 'https://mermaid.render.diagram.zip/index.html?v=1',98 excalidraw: 'https://excalidraw.render.diagram.zip/index.html?v=4',109 diagramsnet: 'https://diagramsnet.render.diagram.zip/index.html?v=1',@@ -86,7 +86,7 @@ export class PuppeteerExecutor implements BrowserExecutor {
8686 this.browser = await puppeteer.launch(this.endpoint, {8787 keep_alive: 60_000,8888 guardrails: {89- allowedDomains: ['bpmn.render.diagram.zip', 'mermaid.render.diagram.zip', 'excalidraw.render.diagram.zip', 'diagramsnet.render.diagram.zip', 'tikz.render.diagram.zip'],89+ allowedDomains: ['mermaid.render.diagram.zip', 'excalidraw.render.diagram.zip', 'diagramsnet.render.diagram.zip', 'tikz.render.diagram.zip'],9090 },9191 })9292 this.page = undefined@@ -3,9 +3,9 @@ import { FRAME_URLS, RenderInputError, validateRenderRequest } from '../src/cont
33 44 describe('browser-run render contract', () => {55 it('accepts only the five pinned engines and validates the source', () => {6- const request = validateRenderRequest({ engine: 'bpmn', requestId: 'r-1', source: '<definitions />' })7- expect(request.engine).toBe('bpmn')8- expect(FRAME_URLS.bpmn).toBe('https://bpmn.render.diagram.zip/index.html?v=1')6+ const request = validateRenderRequest({ engine: 'mermaid', requestId: 'r-1', source: 'graph TD' })7+ expect(request.engine).toBe('mermaid')8+ expect(FRAME_URLS.mermaid).toBe('https://mermaid.render.diagram.zip/index.html?v=1')99 })1010 1111 it('rejects unknown engines, malformed ids, and oversized input', () => {@@ -7,11 +7,11 @@ describe('browser page executor', () => {
77 const fakeWindow = {88 addEventListener: (_type: string, listener: (event: MessageEvent) => void) => listeners.add(listener),99 removeEventListener: (_type: string, listener: (event: MessageEvent) => void) => listeners.delete(listener),10- postMessage: (data: unknown) => queueMicrotask(() => listeners.forEach(listener => listener({ source: globalThis, data: { channel: 'diagram.zip:renderer:v1', type: 'result', requestId: (data as any).requestId, ok: true, svg: '<svg/>', version: 'v', build: 'b', pipeline: ['bpmn'] } } as unknown as MessageEvent))),10+ postMessage: (data: unknown) => queueMicrotask(() => listeners.forEach(listener => listener({ source: globalThis, data: { channel: 'diagram.zip:renderer:v1', type: 'result', requestId: (data as any).requestId, ok: true, svg: '<svg/>', version: 'v', build: 'b', pipeline: ['mermaid'] } } as unknown as MessageEvent))),1111 }1212 const oldMethods = { addEventListener: (globalThis as any).addEventListener, removeEventListener: (globalThis as any).removeEventListener, postMessage: (globalThis as any).postMessage }1313 Object.assign(globalThis, fakeWindow)14- await expect(browserHarness({ engine: 'bpmn', requestId: 'r', source: '<xml/>' })).resolves.toMatchObject({ ok: true, svg: '<svg/>' })14+ await expect(browserHarness({ engine: 'mermaid', requestId: 'r', source: 'graph TD' })).resolves.toMatchObject({ ok: true, svg: '<svg/>' })1515 Object.assign(globalThis, oldMethods)1616 })1717 @@ -33,6 +33,6 @@ describe('browser page executor', () => {
3333 async goto() {},3434 async evaluate<T>(): Promise<T> { return { ok: true, svg: 'x'.repeat(4_194_305), version: 'v', build: 'b', pipeline: [] } as T },3535 }36- await expect(createPageExecutor(page).render({ engine: 'bpmn', requestId: 'r', source: '' })).rejects.toThrow(/output is too large/)36+ await expect(createPageExecutor(page).render({ engine: 'mermaid', requestId: 'r', source: '' })).rejects.toThrow(/output is too large/)3737 })3838 })@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
22 vi.mock('cloudflare:workers', () => ({ DurableObject: class {} }))33 import worker, { responseFromRenderResult } from '../src/index'44 5-function environment(response = { ok: true, svg: '<svg/>', version: 'v', build: 'b', pipeline: ['bpmn'] }) {5+function environment(response = { ok: true, svg: '<svg/>', version: 'v', build: 'b', pipeline: ['mermaid'] }) {66 const forwarded: Request[] = []77 const stub = { fetch: vi.fn(async (request: Request) => { forwarded.push(request); return Response.json(response) }) }88 return {@@ -26,13 +26,13 @@ describe('private Worker boundary', () => {
2626 2727 it('requires auth, validates JSON, and forwards the exact render contract to the DO', async () => {2828 const setup = environment()29- const body = JSON.stringify({ engine: 'bpmn', requestId: 'request-1', source: '<definitions/>' })29+ const body = JSON.stringify({ engine: 'mermaid', requestId: 'request-1', source: 'graph TD' })3030 expect((await worker.fetch(new Request('https://bridge.example/render', { method: 'POST', body }), setup.env)).status).toBe(401)3131 const request = new Request('https://bridge.example/render', { method: 'POST', body, headers: { Authorization: 'Bearer secret' } })3232 expect((await worker.fetch(request, setup.env)).status).toBe(200)33- expect(setup.env.BROWSER_SESSIONS.getByName).toHaveBeenCalledWith('renderer:bpmn')33+ expect(setup.env.BROWSER_SESSIONS.getByName).toHaveBeenCalledWith('renderer:mermaid')3434 expect(setup.forwarded).toHaveLength(1)35- expect(await setup.forwarded[0]!.json()).toEqual({ engine: 'bpmn', requestId: 'request-1', source: '<definitions/>' })35+ expect(await setup.forwarded[0]!.json()).toEqual({ engine: 'mermaid', requestId: 'request-1', source: 'graph TD' })3636 })3737 3838 it('maps a frame failure to a non-success structured error', async () => {@@ -22,7 +22,7 @@ const targetRuntime: Record<EngineId, EngineRuntime> = {
2222 nwdiag: 'edge-python',2323 packetdiag: 'edge-python',2424 rackdiag: 'edge-python',25- bpmn: 'browser-run',25+ bpmn: 'edge-js',2626 bytefield: 'edge-js',2727 dbml: 'edge-wasm',2828 diagramsnet: 'browser-run',@@ -47,7 +47,7 @@ const activeRuntime: Record<EngineId, EngineRuntime> = {
4747 plantuml: 'edge-wasm',4848 c4plantuml: 'edge-wasm',4949 mermaid: 'browser-run',50- bpmn: 'browser-run',50+ bpmn: 'edge-js',5151 bytefield: 'edge-js',5252 diagramsnet: 'browser-run',5353 excalidraw: 'browser-run',@@ -80,7 +80,7 @@ const versions: Record<EngineId, string> = {
8080 plantuml: 'plantuml@1.2026.6/edge-wasm-1',8181 c4plantuml: 'c4plantuml@2.7.0-lowered+plantuml@1.2026.6/edge-wasm-1',8282 mermaid: 'mermaid@11.17.0',83- bpmn: 'bpmn-js@18.25.1',83+ bpmn: 'diagramzip-bpmn-svg@1',8484 diagramsnet: 'diagrams.net@29.6.1',8585 bytefield: 'bytefield-svg@1.11.0',8686 excalidraw: '@excalidraw/excalidraw@0.18.1',@@ -119,6 +119,12 @@ describe('engine catalog', () => {
119119 expect(diagramsnet.version).toContain('diagrams.net@29.6.1')120120 })121121 122+ it('marks BPMN as a direct edge renderer', () => {123+ const bpmn = ENGINE_CATALOG.find(engine => engine.id === 'bpmn')!124+ expect(bpmn.activeRuntime).toBe('edge-js')125+ expect(bpmn.version).toBe('diagramzip-bpmn-svg@1')126+ })127+122128 it('marks TikZ as a bundled browser renderer and UMLet as a direct edge translation', () => {123129 const tikz = ENGINE_CATALOG.find(engine => engine.id === 'tikz')!124130 const umlet = ENGINE_CATALOG.find(engine => engine.id === 'umlet')!@@ -1,730 +0,0 @@
1-{2- "name": "@diagramzip/renderer-bpmn",3- "version": "1.0.0",4- "lockfileVersion": 3,5- "requires": true,6- "packages": {7- "": {8- "name": "@diagramzip/renderer-bpmn",9- "version": "1.0.0",10- "dependencies": {11- "bpmn-js": "18.25.1"12- },13- "devDependencies": {14- "esbuild": "0.28.1"15- },16- "engines": {17- "node": ">=24"18- }19- },20- "node_modules/@bpmn-io/diagram-js-ui": {21- "version": "0.2.4",22- "resolved": "https://registry.npmjs.org/@bpmn-io/diagram-js-ui/-/diagram-js-ui-0.2.4.tgz",23- "integrity": "sha512-I678ZGtoH7z7FgzFhzzppM9ThsJ3Lh83kW3GjhjG3xyYciIPYHMaHK1MABP8F69H9pFZpViM6t4qUo8dwrVz0w==",24- "license": "MIT",25- "dependencies": {26- "htm": "^3.1.1",27- "preact": "^10.29.2"28- }29- },30- "node_modules/@esbuild/aix-ppc64": {31- "version": "0.28.1",32- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",33- "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",34- "cpu": [35- "ppc64"36- ],37- "dev": true,38- "license": "MIT",39- "optional": true,40- "os": [41- "aix"42- ],43- "engines": {44- "node": ">=18"45- }46- },47- "node_modules/@esbuild/android-arm": {48- "version": "0.28.1",49- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",50- "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",51- "cpu": [52- "arm"53- ],54- "dev": true,55- "license": "MIT",56- "optional": true,57- "os": [58- "android"59- ],60- "engines": {61- "node": ">=18"62- }63- },64- "node_modules/@esbuild/android-arm64": {65- "version": "0.28.1",66- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",67- "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",68- "cpu": [69- "arm64"70- ],71- "dev": true,72- "license": "MIT",73- "optional": true,74- "os": [75- "android"76- ],77- "engines": {78- "node": ">=18"79- }80- },81- "node_modules/@esbuild/android-x64": {82- "version": "0.28.1",83- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",84- "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",85- "cpu": [86- "x64"87- ],88- "dev": true,89- "license": "MIT",90- "optional": true,91- "os": [92- "android"93- ],94- "engines": {95- "node": ">=18"96- }97- },98- "node_modules/@esbuild/darwin-arm64": {99- "version": "0.28.1",100- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",101- "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",102- "cpu": [103- "arm64"104- ],105- "dev": true,106- "license": "MIT",107- "optional": true,108- "os": [109- "darwin"110- ],111- "engines": {112- "node": ">=18"113- }114- },115- "node_modules/@esbuild/darwin-x64": {116- "version": "0.28.1",117- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",118- "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",119- "cpu": [120- "x64"121- ],122- "dev": true,123- "license": "MIT",124- "optional": true,125- "os": [126- "darwin"127- ],128- "engines": {129- "node": ">=18"130- }131- },132- "node_modules/@esbuild/freebsd-arm64": {133- "version": "0.28.1",134- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",135- "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",136- "cpu": [137- "arm64"138- ],139- "dev": true,140- "license": "MIT",141- "optional": true,142- "os": [143- "freebsd"144- ],145- "engines": {146- "node": ">=18"147- }148- },149- "node_modules/@esbuild/freebsd-x64": {150- "version": "0.28.1",151- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",152- "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",153- "cpu": [154- "x64"155- ],156- "dev": true,157- "license": "MIT",158- "optional": true,159- "os": [160- "freebsd"161- ],162- "engines": {163- "node": ">=18"164- }165- },166- "node_modules/@esbuild/linux-arm": {167- "version": "0.28.1",168- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",169- "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",170- "cpu": [171- "arm"172- ],173- "dev": true,174- "license": "MIT",175- "optional": true,176- "os": [177- "linux"178- ],179- "engines": {180- "node": ">=18"181- }182- },183- "node_modules/@esbuild/linux-arm64": {184- "version": "0.28.1",185- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",186- "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",187- "cpu": [188- "arm64"189- ],190- "dev": true,191- "license": "MIT",192- "optional": true,193- "os": [194- "linux"195- ],196- "engines": {197- "node": ">=18"198- }199- },200- "node_modules/@esbuild/linux-ia32": {201- "version": "0.28.1",202- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",203- "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",204- "cpu": [205- "ia32"206- ],207- "dev": true,208- "license": "MIT",209- "optional": true,210- "os": [211- "linux"212- ],213- "engines": {214- "node": ">=18"215- }216- },217- "node_modules/@esbuild/linux-loong64": {218- "version": "0.28.1",219- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",220- "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",221- "cpu": [222- "loong64"223- ],224- "dev": true,225- "license": "MIT",226- "optional": true,227- "os": [228- "linux"229- ],230- "engines": {231- "node": ">=18"232- }233- },234- "node_modules/@esbuild/linux-mips64el": {235- "version": "0.28.1",236- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",237- "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",238- "cpu": [239- "mips64el"240- ],241- "dev": true,242- "license": "MIT",243- "optional": true,244- "os": [245- "linux"246- ],247- "engines": {248- "node": ">=18"249- }250- },251- "node_modules/@esbuild/linux-ppc64": {252- "version": "0.28.1",253- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",254- "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",255- "cpu": [256- "ppc64"257- ],258- "dev": true,259- "license": "MIT",260- "optional": true,261- "os": [262- "linux"263- ],264- "engines": {265- "node": ">=18"266- }267- },268- "node_modules/@esbuild/linux-riscv64": {269- "version": "0.28.1",270- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",271- "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",272- "cpu": [273- "riscv64"274- ],275- "dev": true,276- "license": "MIT",277- "optional": true,278- "os": [279- "linux"280- ],281- "engines": {282- "node": ">=18"283- }284- },285- "node_modules/@esbuild/linux-s390x": {286- "version": "0.28.1",287- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",288- "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",289- "cpu": [290- "s390x"291- ],292- "dev": true,293- "license": "MIT",294- "optional": true,295- "os": [296- "linux"297- ],298- "engines": {299- "node": ">=18"300- }301- },302- "node_modules/@esbuild/linux-x64": {303- "version": "0.28.1",304- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",305- "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",306- "cpu": [307- "x64"308- ],309- "dev": true,310- "license": "MIT",311- "optional": true,312- "os": [313- "linux"314- ],315- "engines": {316- "node": ">=18"317- }318- },319- "node_modules/@esbuild/netbsd-arm64": {320- "version": "0.28.1",321- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",322- "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",323- "cpu": [324- "arm64"325- ],326- "dev": true,327- "license": "MIT",328- "optional": true,329- "os": [330- "netbsd"331- ],332- "engines": {333- "node": ">=18"334- }335- },336- "node_modules/@esbuild/netbsd-x64": {337- "version": "0.28.1",338- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",339- "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",340- "cpu": [341- "x64"342- ],343- "dev": true,344- "license": "MIT",345- "optional": true,346- "os": [347- "netbsd"348- ],349- "engines": {350- "node": ">=18"351- }352- },353- "node_modules/@esbuild/openbsd-arm64": {354- "version": "0.28.1",355- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",356- "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",357- "cpu": [358- "arm64"359- ],360- "dev": true,361- "license": "MIT",362- "optional": true,363- "os": [364- "openbsd"365- ],366- "engines": {367- "node": ">=18"368- }369- },370- "node_modules/@esbuild/openbsd-x64": {371- "version": "0.28.1",372- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",373- "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",374- "cpu": [375- "x64"376- ],377- "dev": true,378- "license": "MIT",379- "optional": true,380- "os": [381- "openbsd"382- ],383- "engines": {384- "node": ">=18"385- }386- },387- "node_modules/@esbuild/openharmony-arm64": {388- "version": "0.28.1",389- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",390- "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",391- "cpu": [392- "arm64"393- ],394- "dev": true,395- "license": "MIT",396- "optional": true,397- "os": [398- "openharmony"399- ],400- "engines": {401- "node": ">=18"402- }403- },404- "node_modules/@esbuild/sunos-x64": {405- "version": "0.28.1",406- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",407- "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",408- "cpu": [409- "x64"410- ],411- "dev": true,412- "license": "MIT",413- "optional": true,414- "os": [415- "sunos"416- ],417- "engines": {418- "node": ">=18"419- }420- },421- "node_modules/@esbuild/win32-arm64": {422- "version": "0.28.1",423- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",424- "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",425- "cpu": [426- "arm64"427- ],428- "dev": true,429- "license": "MIT",430- "optional": true,431- "os": [432- "win32"433- ],434- "engines": {435- "node": ">=18"436- }437- },438- "node_modules/@esbuild/win32-ia32": {439- "version": "0.28.1",440- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",441- "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",442- "cpu": [443- "ia32"444- ],445- "dev": true,446- "license": "MIT",447- "optional": true,448- "os": [449- "win32"450- ],451- "engines": {452- "node": ">=18"453- }454- },455- "node_modules/@esbuild/win32-x64": {456- "version": "0.28.1",457- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",458- "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",459- "cpu": [460- "x64"461- ],462- "dev": true,463- "license": "MIT",464- "optional": true,465- "os": [466- "win32"467- ],468- "engines": {469- "node": ">=18"470- }471- },472- "node_modules/bpmn-js": {473- "version": "18.25.1",474- "resolved": "https://registry.npmjs.org/bpmn-js/-/bpmn-js-18.25.1.tgz",475- "integrity": "sha512-wT/TjzUQw3o5vU6nI2tytOhOm9atf2tOgCdCKF6sQUvokZ0nOEH7yfE9IvwKXclfdUxvdawgU80h909hvQJ9hw==",476- "license": "SEE LICENSE IN LICENSE",477- "dependencies": {478- "bpmn-moddle": "^10.0.0",479- "diagram-js": "^15.24.0",480- "diagram-js-direct-editing": "^3.5.1",481- "ids": "^3.0.2",482- "inherits-browser": "^0.1.0",483- "min-dash": "^5.1.0",484- "min-dom": "^5.3.0",485- "tiny-svg": "^4.1.4"486- },487- "engines": {488- "node": "*"489- }490- },491- "node_modules/bpmn-moddle": {492- "version": "10.1.0",493- "resolved": "https://registry.npmjs.org/bpmn-moddle/-/bpmn-moddle-10.1.0.tgz",494- "integrity": "sha512-k9Vl97hiFwlr4bJTQ5+pZp21bmrQwil5jnMQ30wF8vney2/WnIcJmWwYzRTAKFVoViTGowtDfbO5rGEF4viO0w==",495- "license": "MIT",496- "dependencies": {497- "min-dash": "^5.1.0",498- "moddle": "^8.2.1",499- "moddle-xml": "^12.1.0"500- },501- "engines": {502- "node": ">= 20.12"503- }504- },505- "node_modules/clsx": {506- "version": "2.1.1",507- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",508- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",509- "license": "MIT",510- "engines": {511- "node": ">=6"512- }513- },514- "node_modules/diagram-js": {515- "version": "15.24.1",516- "resolved": "https://registry.npmjs.org/diagram-js/-/diagram-js-15.24.1.tgz",517- "integrity": "sha512-0zlPffk8pGdbUmt0RQH32N7dIbgYuLwkFlZTCi28kRSkb37GdEjONjXKs+NluZvWeLsY1hqSyq4le19ZeFE19Q==",518- "license": "MIT",519- "dependencies": {520- "@bpmn-io/diagram-js-ui": "^0.2.4",521- "clsx": "^2.1.1",522- "didi": "^11.0.0",523- "inherits-browser": "^0.1.0",524- "min-dash": "^5.1.0",525- "min-dom": "^5.3.0",526- "object-refs": "^0.4.0",527- "path-intersection": "^4.1.0",528- "tiny-svg": "^4.1.4"529- },530- "engines": {531- "node": "*"532- }533- },534- "node_modules/diagram-js-direct-editing": {535- "version": "3.5.1",536- "resolved": "https://registry.npmjs.org/diagram-js-direct-editing/-/diagram-js-direct-editing-3.5.1.tgz",537- "integrity": "sha512-fEQCQ/x5oKVV0m7Nmgv7i85Vhw+kJkKNXF8zcV5Vm0KwF8x/8e3Ax0aJgdXt8/fDPymEkY8HmZweXIipCeeh+g==",538- "license": "MIT",539- "dependencies": {540- "min-dash": "^5.0.0",541- "min-dom": "^5.3.0"542- },543- "engines": {544- "node": "*"545- },546- "peerDependencies": {547- "diagram-js": "*"548- }549- },550- "node_modules/didi": {551- "version": "11.0.0",552- "resolved": "https://registry.npmjs.org/didi/-/didi-11.0.0.tgz",553- "integrity": "sha512-PzCfRzQttvFpVcYMbSF7h8EsWjeJpVjWH4qDhB5LkMi1ILvHq4Ob0vhM2wLFziPkbUBi+PAo7ODbe2sacR7nJQ==",554- "license": "MIT",555- "engines": {556- "node": ">= 20.12"557- }558- },559- "node_modules/domify": {560- "version": "3.0.0",561- "resolved": "https://registry.npmjs.org/domify/-/domify-3.0.0.tgz",562- "integrity": "sha512-bs2yO68JDFOm6rKv8f0EnrM2cENduhRkpqOtt/s5l5JBA/eqGBZCzLPmdYoHtJ6utgLGgcBajFsEQbl12pT0lQ==",563- "license": "MIT",564- "engines": {565- "node": ">=20"566- },567- "funding": {568- "url": "https://github.com/sponsors/sindresorhus"569- }570- },571- "node_modules/esbuild": {572- "version": "0.28.1",573- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",574- "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",575- "dev": true,576- "hasInstallScript": true,577- "license": "MIT",578- "bin": {579- "esbuild": "bin/esbuild"580- },581- "engines": {582- "node": ">=18"583- },584- "optionalDependencies": {585- "@esbuild/aix-ppc64": "0.28.1",586- "@esbuild/android-arm": "0.28.1",587- "@esbuild/android-arm64": "0.28.1",588- "@esbuild/android-x64": "0.28.1",589- "@esbuild/darwin-arm64": "0.28.1",590- "@esbuild/darwin-x64": "0.28.1",591- "@esbuild/freebsd-arm64": "0.28.1",592- "@esbuild/freebsd-x64": "0.28.1",593- "@esbuild/linux-arm": "0.28.1",594- "@esbuild/linux-arm64": "0.28.1",595- "@esbuild/linux-ia32": "0.28.1",596- "@esbuild/linux-loong64": "0.28.1",597- "@esbuild/linux-mips64el": "0.28.1",598- "@esbuild/linux-ppc64": "0.28.1",599- "@esbuild/linux-riscv64": "0.28.1",600- "@esbuild/linux-s390x": "0.28.1",601- "@esbuild/linux-x64": "0.28.1",602- "@esbuild/netbsd-arm64": "0.28.1",603- "@esbuild/netbsd-x64": "0.28.1",604- "@esbuild/openbsd-arm64": "0.28.1",605- "@esbuild/openbsd-x64": "0.28.1",606- "@esbuild/openharmony-arm64": "0.28.1",607- "@esbuild/sunos-x64": "0.28.1",608- "@esbuild/win32-arm64": "0.28.1",609- "@esbuild/win32-ia32": "0.28.1",610- "@esbuild/win32-x64": "0.28.1"611- }612- },613- "node_modules/htm": {614- "version": "3.1.1",615- "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz",616- "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==",617- "license": "Apache-2.0"618- },619- "node_modules/ids": {620- "version": "3.0.2",621- "resolved": "https://registry.npmjs.org/ids/-/ids-3.0.2.tgz",622- "integrity": "sha512-t6YJP4mdC+GHF96Nbis/4FEANhP/8VWmYMvUuYpXvSdrhg5hpIVbq2XZlOA3UWTbtdwPCi0q7jEXOdHkAnqOnw==",623- "license": "MIT",624- "engines": {625- "node": ">= 20.12"626- }627- },628- "node_modules/inherits-browser": {629- "version": "0.1.0",630- "resolved": "https://registry.npmjs.org/inherits-browser/-/inherits-browser-0.1.0.tgz",631- "integrity": "sha512-CJHHvW3jQ6q7lzsXPpapLdMx5hDpSF3FSh45pwsj6bKxJJ8Nl8v43i5yXnr3BdfOimGHKyniewQtnAIp3vyJJw==",632- "license": "ISC"633- },634- "node_modules/min-dash": {635- "version": "5.1.0",636- "resolved": "https://registry.npmjs.org/min-dash/-/min-dash-5.1.0.tgz",637- "integrity": "sha512-HAvN6XzmCQj+js43G9sJRRw8kj/7DvoxN7qhoIN9Xo5ZN1NMljtpyKs/NunXmw0QSWwlgZzNv6tLeEa4xaZ0tg==",638- "license": "MIT"639- },640- "node_modules/min-dom": {641- "version": "5.3.0",642- "resolved": "https://registry.npmjs.org/min-dom/-/min-dom-5.3.0.tgz",643- "integrity": "sha512-0w5FEBgPAyHhmFojW3zxd7we3D+m5XYS3E/06OyvxmbHJoiQVa4Nagj6RWvoAKYRw5xth6cP5TMePc5cR1M9hA==",644- "license": "MIT",645- "dependencies": {646- "domify": "^3.0.0",647- "min-dash": "^5.0.0"648- }649- },650- "node_modules/moddle": {651- "version": "8.2.1",652- "resolved": "https://registry.npmjs.org/moddle/-/moddle-8.2.1.tgz",653- "integrity": "sha512-20qzO/ZpMGO9j9gdQgXk9bfGxb9wA3JKnGth/21YZKODLrZE4RW/uVMIL5pTskYm34Z61DucBEPkq93qByNsBQ==",654- "license": "MIT",655- "dependencies": {656- "min-dash": "^5.1.0"657- }658- },659- "node_modules/moddle-xml": {660- "version": "12.1.0",661- "resolved": "https://registry.npmjs.org/moddle-xml/-/moddle-xml-12.1.0.tgz",662- "integrity": "sha512-+m50j9/MFGhvWbLJoKSSjQj3uv7SYm2w5i2lfv6goWarlWW1Nd0A31NYUXmKuWnEZ8Mm/bH1RZYtuf4x8ptY7g==",663- "license": "MIT",664- "dependencies": {665- "min-dash": "^5.1.0",666- "saxen": "^11.1.0"667- },668- "engines": {669- "node": ">= 18"670- },671- "peerDependencies": {672- "moddle": ">= 6.2.0"673- }674- },675- "node_modules/object-refs": {676- "version": "0.4.0",677- "resolved": "https://registry.npmjs.org/object-refs/-/object-refs-0.4.0.tgz",678- "integrity": "sha512-6kJqKWryKZmtte6QYvouas0/EIJKPI1/MMIuRsiBlNuhIMfqYTggzX2F1AJ2+cDs288xyi9GL7FyasHINR98BQ==",679- "license": "MIT",680- "engines": {681- "node": "*"682- }683- },684- "node_modules/path-intersection": {685- "version": "4.1.0",686- "resolved": "https://registry.npmjs.org/path-intersection/-/path-intersection-4.1.0.tgz",687- "integrity": "sha512-urUP6WvhnxbHPdHYl6L7Yrc6+1ny6uOFKPCzPxTSUSYGHG0o94RmI7SvMMaScNAM5RtTf08bg4skc6/kjfne3A==",688- "license": "MIT",689- "engines": {690- "node": ">= 14.20"691- }692- },693- "node_modules/preact": {694- "version": "10.29.8",695- "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz",696- "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==",697- "license": "MIT",698- "funding": {699- "type": "opencollective",700- "url": "https://opencollective.com/preact"701- },702- "peerDependencies": {703- "preact-render-to-string": ">=5"704- },705- "peerDependenciesMeta": {706- "preact-render-to-string": {707- "optional": true708- }709- }710- },711- "node_modules/saxen": {712- "version": "11.1.1",713- "resolved": "https://registry.npmjs.org/saxen/-/saxen-11.1.1.tgz",714- "integrity": "sha512-J4BkmJFaM7VgE7pgkFGsNEcqqM3h7+Mz80vfLWFhx7uNOCOXIu6LLjQHYWNejdst3pf/3JUaBIG9+pkk1umlow==",715- "license": "MIT",716- "engines": {717- "node": ">= 20.12"718- }719- },720- "node_modules/tiny-svg": {721- "version": "4.1.4",722- "resolved": "https://registry.npmjs.org/tiny-svg/-/tiny-svg-4.1.4.tgz",723- "integrity": "sha512-cBaEACCbouYrQc9RG+eTXnPYosX1Ijqty/I6DdXovwDd89Pwu4jcmpOR7BuFEF9YCcd7/AWwasE0207WMK7hdw==",724- "license": "MIT",725- "engines": {726- "node": ">= 20"727- }728- }729- }730-}@@ -1,17 +0,0 @@
1-{2- "name": "@diagramzip/renderer-bpmn",3- "version": "1.0.0",4- "private": true,5- "type": "module",6- "engines": { "node": ">=24" },7- "scripts": {8- "build": "node scripts/build.mjs",9- "deploy": "../../edge/node_modules/.bin/wrangler deploy --config wrangler.jsonc"10- },11- "dependencies": {12- "bpmn-js": "18.25.1"13- },14- "devDependencies": {15- "esbuild": "0.28.1"16- }17-}@@ -1,22 +0,0 @@
1-import { cp, mkdir, rm } from 'node:fs/promises'2-import { build } from 'esbuild'3-4-const output = new URL('../dist/', import.meta.url)5-6-await rm(output, { recursive: true, force: true })7-await mkdir(output, { recursive: true })8-await Promise.all([9- cp(new URL('../unit/index.html', import.meta.url), new URL('index.html', output)),10- cp(new URL('../unit/_headers', import.meta.url), new URL('_headers', output)),11-])12-13-await build({14- entryPoints: [new URL('../src/frame.js', import.meta.url).pathname],15- bundle: true,16- minify: true,17- format: 'iife',18- platform: 'browser',19- outfile: new URL('frame-v1.js', output).pathname,20- sourcemap: false,21- treeShaking: true,22-})@@ -1,63 +0,0 @@
1-import BpmnViewer from 'bpmn-js/lib/Viewer'2-3-const CHANNEL = 'diagram.zip:renderer:v1'4-const ENGINE = 'bpmn'5-const VERSION = 'bpmn-js@18.25.1'6-const BUILD = 'bpmn-js-18.25.1-client-unit-1'7-const MAX_SOURCE_LENGTH = 524_2888-9-let queue = Promise.resolve()10-11-async function render(message) {12- if (typeof message.source !== 'string' || message.source.length > MAX_SOURCE_LENGTH) {13- throw new Error('BPMN source must be a string no larger than 512 KiB.')14- }15- const container = document.createElement('div')16- Object.assign(container.style, { width: '1280px', height: '800px' })17- document.body.append(container)18- const viewer = new BpmnViewer({ container })19- try {20- await viewer.importXML(message.source)21- const { svg } = await viewer.saveSVG()22- return svg23- } finally {24- viewer.destroy()25- container.remove()26- }27-}28-29-window.addEventListener('message', event => {30- const message = event.data31- if (32- event.source !== parent33- || message?.channel !== CHANNEL34- || message?.type !== 'render'35- || message?.engine !== ENGINE36- || typeof message?.requestId !== 'string'37- ) return38-39- queue = queue.then(async () => {40- try {41- parent.postMessage({42- channel: CHANNEL,43- type: 'result',44- requestId: message.requestId,45- ok: true,46- svg: await render(message),47- version: VERSION,48- build: BUILD,49- pipeline: [ENGINE],50- }, '*')51- } catch (error) {52- parent.postMessage({53- channel: CHANNEL,54- type: 'result',55- requestId: message.requestId,56- ok: false,57- error: error instanceof Error ? error.message.slice(0, 500) : 'BPMN rendering failed.',58- }, '*')59- }60- })61-})62-63-parent.postMessage({ channel: CHANNEL, type: 'ready', engine: ENGINE, version: VERSION, build: BUILD }, '*')@@ -1,12 +0,0 @@
1-/*2- Access-Control-Allow-Origin: *3- Cross-Origin-Resource-Policy: cross-origin4- Referrer-Policy: no-referrer5- X-Content-Type-Options: nosniff6-7-/index.html8- Cache-Control: public, max-age=300, no-transform9- Content-Security-Policy: default-src 'none'; script-src https://bpmn.render.diagram.zip; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; connect-src 'none'; worker-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'10-11-/frame-v1.js12- Cache-Control: public, max-age=31536000, immutable@@ -1,14 +0,0 @@
1-2-<html lang="en">3- <head>4- <meta charset="UTF-8">5- <meta6- http-equiv="Content-Security-Policy"7- content="default-src 'none'; script-src https://bpmn.render.diagram.zip; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; connect-src 'none'; worker-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'"8- >9- <title>BPMN renderer</title>10- </head>11- <body>12- <script src="/frame-v1.js"></script>13- </body>14-</html>@@ -1,3 +0,0 @@
1-import { createBrowserRendererUnit } from '../../../shared/browser-unit.ts'2-3-export default createBrowserRendererUnit({ id: 'bpmn', kind: 'render', version: 'bpmn-js@18.25.1', build: 'bpmn-js-18.25.1-browser-run-unit-1', pipeline: ['bpmn'], frame: '/index.html?v=1', knownLosses: ['External links and resource-loading elements are removed from exported SVG.'] })@@ -1,16 +0,0 @@
1-{2- "$schema": "../../edge/node_modules/wrangler/config-schema.json",3- "name": "diagramzip-render-bpmn",4- "main": "./unit/worker.js",5- "compatibility_date": "2026-08-20",6- "services": [{ "binding": "BROWSER_RENDERER", "service": "diagramzip-browser-run" }],7- "assets": {8- "directory": "./dist",9- "binding": "ASSETS",10- "html_handling": "none",11- "not_found_handling": "none",12- "run_worker_first": ["/v1/*"]13- },14- "observability": { "enabled": true },15- "routes": [{ "pattern": "bpmn.render.diagram.zip", "custom_domain": true }]16-}@@ -0,0 +1,9 @@
1+{2+ "$schema": "../../node_modules/wrangler/config-schema.json",3+ "name": "diagramzip-render-bpmn",4+ "main": "../../src/units/bpmn.ts",5+ "compatibility_date": "2026-08-20",6+ "observability": { "enabled": true, "logs": { "head_sampling_rate": 1 } },7+ "vars": { "RENDERER_BUILD": "bpmn-direct-svg-1" },8+ "routes": [{ "pattern": "bpmn.render.diagram.zip", "custom_domain": true }]9+}@@ -0,0 +1,123 @@
1+import { SaxesParser, type SaxesTagPlain } from 'saxes'2+import { RenderError } from '../runtime/errors'3+import type { EngineId, RendererAdapter, RenderRequest } from '../runtime/types'4+import { edgeResult } from './types'5+6+const VERSION = 'diagramzip-bpmn-svg@1'7+const MAX_SOURCE = 512 * 10248+const MAX_NODES = 20_0009+const MAX_COORD = 32_76810+const MAX_OUTPUT = 4 * 1024 * 102411+12+type Node = { name: string; attrs: Record<string, string>; text: string; children: Node[] }13+type Box = { x: number; y: number; w: number; h: number }14+type Shape = Box & { id: string; type: string; name: string; label?: Box }15+type Edge = { id: string; type: string; points: { x: number; y: number }[]; name: string; label?: Box }16+17+const local = (name: string) => name.includes(':') ? name.slice(name.indexOf(':') + 1) : name18+const esc = (value: string) => value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''')19+const attr = (node: Node, key: string) => node.attrs[key] ?? node.attrs[Object.keys(node.attrs).find(k => local(k) === key) ?? '']20+const child = (node: Node, name: string) => node.children.find(value => local(value.name) === name)21+22+function fail(message: string, code = 'invalid_source'): never { throw new RenderError(422, code, message) }23+function number(value: string | undefined, name: string): number {24+ if (!value || !/^-?(?:\d+\.?\d*|\.\d+)$/.test(value.trim())) fail(`BPMN ${name} must be a finite number.`)25+ const result = Number(value)26+ if (!Number.isFinite(result) || Math.abs(result) > MAX_COORD) fail(`BPMN ${name} is outside the supported range.`)27+ return result28+}29+function parseXml(source: string): Node {30+ if (new TextEncoder().encode(source).byteLength > MAX_SOURCE) throw new RenderError(413, 'source_too_large', 'BPMN source may not exceed 512 KiB.')31+ if (/<!DOCTYPE|<!ENTITY/i.test(source)) fail('BPMN source may not contain a doctype or entity declaration.')32+ let root: Node | null = null; const stack: Node[] = []; let nodes = 0; let error: unknown33+ const parser = new SaxesParser({ xmlns: false })34+ parser.on('doctype', () => { error = new Error('DOCTYPE is not allowed') })35+ parser.on('processinginstruction', () => { error = new Error('Processing instructions are not allowed') })36+ parser.on('opentag', (tag: SaxesTagPlain) => {37+ if (++nodes > MAX_NODES) { error = new Error('Too many XML nodes'); return }38+ const attrs: Record<string, string> = {}39+ for (const [key, value] of Object.entries(tag.attributes)) attrs[key] = typeof value === 'string' ? value : String((value as { value?: string }).value ?? '')40+ const node: Node = { name: tag.name, attrs, text: '', children: [] }; const parent = stack.at(-1)41+ if (parent) parent.children.push(node); else if (root) error = new Error('Multiple XML roots'); else root = node42+ stack.push(node)43+ })44+ const append = (value: string) => { const node = stack.at(-1); if (node) node.text += value; else if (value.trim()) error = new Error('Text outside root') }45+ parser.on('text', append); parser.on('cdata', append); parser.on('closetag', () => { stack.pop() }); parser.on('error', value => { error = value })46+ try { parser.write(source).close() } catch (value) { error = value }47+ if (error || !root) fail('BPMN source is not valid bounded XML.')48+ return root49+}50+51+function box(node: Node, allowEmpty = false): Box {52+ const x = number(attr(node, 'x'), 'x coordinate'); const y = number(attr(node, 'y'), 'y coordinate')53+ const w = number(attr(node, 'width'), 'width'); const h = number(attr(node, 'height'), 'height')54+ if (allowEmpty ? w < 0 || h < 0 : w <= 0 || h <= 0) fail(`BPMN DI dimensions must be ${allowEmpty ? 'non-negative' : 'positive'}.`)55+ return { x, y, w, h }56+}57+58+export function parseBpmn(source: string): { shapes: Shape[]; edges: Edge[]; width: number; height: number } {59+ const root = parseXml(source); if (local(root.name) !== 'definitions') fail('BPMN source must have a definitions root.')60+ const semantic = new Map<string, { type: string; name: string }>(); const di = new Map<string, Node>(); const labels = new Map<string, Box>()61+ const walk = (node: Node) => {62+ const name = local(node.name); const id = attr(node, 'id')63+ if (id && (name !== 'BPMNShape' && name !== 'BPMNEdge' && name !== 'BPMNLabel')) semantic.set(id, { type: name, name: attr(node, 'name') ?? '' })64+ if (id && (name === 'BPMNShape' || name === 'BPMNEdge')) di.set(id, node)65+ for (const value of node.children) walk(value)66+ }67+ // A second traversal keeps the DI parent id while remaining independent of XML prefixes.68+ const collect = (node: Node, parentDi?: string) => {69+ const name = local(node.name); const id = attr(node, 'id'); const element = attr(node, 'bpmnElement')70+ if (id && (name === 'BPMNShape' || name === 'BPMNEdge')) di.set(id, node)71+ if (name === 'BPMNLabel' && parentDi) { const b = child(node, 'Bounds'); if (b) labels.set(parentDi, box(b, true)) }72+ const next = id && (name === 'BPMNShape' || name === 'BPMNEdge') ? id : parentDi73+ for (const value of node.children) collect(value, next)74+ }75+ walk(root); collect(root)76+ const shapes: Shape[] = []; const edges: Edge[] = []77+ for (const [diId, node] of di) {78+ const element = attr(node, 'bpmnElement'); if (!element) continue79+ const sem = semantic.get(element); if (!sem) continue80+ if (local(node.name) === 'BPMNShape') shapes.push({ ...box(child(node, 'Bounds') ?? fail(`BPMN shape ${diId} has no bounds.`)), id: element, type: sem.type, name: sem.name, label: labels.get(diId) })81+ else {82+ const points = node.children.filter(value => local(value.name) === 'waypoint').map(value => ({ x: number(attr(value, 'x'), 'waypoint x'), y: number(attr(value, 'y'), 'waypoint y') }))83+ if (points.length < 2) fail(`BPMN edge ${diId} needs at least two waypoints.`)84+ edges.push({ id: element, type: sem.type, points, name: sem.name, label: labels.get(diId) })85+ }86+ }87+ if (!shapes.length) fail('BPMN diagram contains no rendered shapes.')88+ const maxX = Math.max(...shapes.map(s => s.x + s.w), ...edges.flatMap(e => e.points.map(p => p.x)), 1)89+ const maxY = Math.max(...shapes.map(s => s.y + s.h), ...edges.flatMap(e => e.points.map(p => p.y)), 1)90+ return { shapes, edges, width: maxX + 20, height: maxY + 20 }91+}92+93+function label(value: string, b: Box, anchor = 'middle'): string {94+ if (!value) return ''95+ const lines = value.split(/\r?\n/).slice(0, 8); const x = anchor === 'start' ? b.x + 4 : b.x + b.w / 296+ return `<text x="${x}" y="${b.y + 14}" text-anchor="${anchor}" font-family="Arial,sans-serif" font-size="12" fill="#111827">${lines.map((line, i) => `<tspan x="${x}" dy="${i ? 14 : 0}">${esc(line.slice(0, 240))}</tspan>`).join('')}</text>`97+}98+function shapeSvg(shape: Shape): string {99+ const { x, y, w, h } = shape; const t = shape.type.toLowerCase(); const stroke = '#334155'; const fill = '#fff'100+ let body: string101+ if (t.includes('event')) body = `<circle cx="${x + w / 2}" cy="${y + h / 2}" r="${Math.min(w, h) / 2 - 2}" fill="${fill}" stroke="${stroke}" stroke-width="2"/>`102+ else if (t.includes('gateway')) body = `<path d="M${x + w / 2} ${y}L${x + w} ${y + h / 2}L${x + w / 2} ${y + h}L${x} ${y + h / 2}Z" fill="${fill}" stroke="${stroke}" stroke-width="2"/>`103+ else if (t === 'participant' || t === 'lane') body = `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="#f8fafc" stroke="${stroke}"/><path d="M${x + 28} ${y}V${y + h}" stroke="${stroke}"/>`104+ else if (t.includes('dataobject')) body = `<path d="M${x} ${y}H${x + w - 12}L${x + w} ${y + 12}V${y + h}H${x}Z" fill="${fill}" stroke="${stroke}"/><path d="M${x + w - 12} ${y}V${y + 12}H${x + w}" fill="none" stroke="${stroke}"/>`105+ else body = `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${t.includes('subprocess') ? 2 : 8}" fill="${fill}" stroke="${stroke}"${t.includes('subprocess') ? ' stroke-dasharray="6 4"' : ''}/>`106+ const b = shape.label ?? { x, y: y + h / 2 - 10, w, h: 20 }107+ return `<g data-bpmn-id="${esc(shape.id)}">${body}${label(shape.name, b, shape.type.toLowerCase() === 'participant' || shape.type.toLowerCase() === 'lane' ? 'start' : 'middle')}</g>`108+}109+export function renderBpmn(source: string): string {110+ const diagram = parseBpmn(source); const defs = '<defs><marker id="bpmn-arrow" markerWidth="10" markerHeight="10" refX="9" refY="4" orient="auto"><path d="M0 0L9 4L0 8Z" fill="#334155"/></marker></defs>'111+ const edges = diagram.edges.map(edge => { const first = edge.points[0]!; return `<g data-bpmn-id="${esc(edge.id)}"><polyline points="${edge.points.map(p => `${p.x},${p.y}`).join(' ')}" fill="none" stroke="#334155" stroke-width="1.5" marker-end="url(#bpmn-arrow)"${edge.type.toLowerCase() === 'association' ? ' stroke-dasharray="5 4"' : ''}/>${label(edge.name, edge.label ?? { x: first.x, y: first.y, w: 80, h: 20 })}</g>` }).join('')112+ const body = `<svg xmlns="http://www.w3.org/2000/svg" width="${diagram.width}" height="${diagram.height}" viewBox="0 0 ${diagram.width} ${diagram.height}">${defs}${edges}${diagram.shapes.map(shapeSvg).join('')}</svg>`113+ if (body.length > MAX_OUTPUT) throw new RenderError(413, 'output_too_large', 'BPMN SVG output exceeds 4 MiB.')114+ return body115+}116+117+export const bpmnAdapter: RendererAdapter = {118+ id: 'bpmn' as EngineId, runtime: 'edge-js', version: VERSION,119+ render(request: RenderRequest, signal: AbortSignal) {120+ if (signal.aborted) throw signal.reason121+ return Promise.resolve(edgeResult('bpmn' as EngineId, VERSION, renderBpmn(request.source)))122+ },123+}@@ -0,0 +1,14 @@
1+import { bpmnAdapter } from '../adapters/bpmn'2+import { createRendererUnit } from '../runtime/unit'3+import type { EngineId } from '../runtime/types'4+5+export default createRendererUnit({6+ id: 'bpmn' as EngineId,7+ kind: 'render',8+ adapter: bpmnAdapter,9+ knownLosses: [10+ 'The bounded SVG renderer uses BPMN DI coordinates and a deliberately finite core symbol vocabulary; vendor-specific rendering extensions are ignored.',11+ 'Text uses deterministic browser-independent metrics and is not pixel-identical to bpmn-js.',12+ 'External resources, scripts, hyperlinks, and interactive overlays are not rendered.',13+ ],14+})@@ -0,0 +1,28 @@
1+import { describe, expect, it } from 'vitest'2+import fixture from './fixtures/bpmn/core.bpmn?raw'3+import productionFixture from '../../../examples/diagrams/example.bpmn?raw'4+import { parseBpmn, renderBpmn } from '../src/adapters/bpmn'5+6+describe('bounded BPMN DI-to-SVG adapter', () => {7+ it('renders core shapes, connections, labels, and dimensions', () => {8+ const parsed = parseBpmn(fixture); expect(parsed.shapes).toHaveLength(5); expect(parsed.edges).toHaveLength(4)9+ const svg = renderBpmn(fixture)10+ expect(svg).toContain('viewBox="0 0 606 121"'); expect(svg).toContain('Review order'); expect(svg).toContain('Approved?')11+ expect(svg).toContain('marker-end="url(#bpmn-arrow)"'); expect(svg).toContain('data-bpmn-id="Gateway"')12+ })13+ it('rejects active XML, malformed DI, and oversized coordinates', () => {14+ expect(() => renderBpmn('<!DOCTYPE definitions [<!ENTITY x "y">]><definitions/>')).toThrow(/doctype|entity/i)15+ expect(() => renderBpmn('<definitions xmlns="x"><process id="p"><task id="t"/></process><bpmndi:BPMNDiagram xmlns:bpmndi="d"><bpmndi:BPMNPlane><bpmndi:BPMNShape bpmnElement="t"/></bpmndi:BPMNPlane></bpmndi:BPMNDiagram></definitions>')).toThrow(/bounds|shapes/i)16+ expect(() => renderBpmn(fixture.replace('x="20"', 'x="999999"'))).toThrow(/range|coordinate/i)17+ expect(() => renderBpmn('x'.repeat(512 * 1024 + 1))).toThrow(/512 KiB/i)18+ })19+ it('escapes labels and does not emit active content', () => {20+ const svg = renderBpmn(fixture.replace('name="Review order"', 'name="<x>&danger</x>"'))21+ expect(svg).toContain('<x>&danger</x>'); expect(svg).not.toContain('<x>'); expect(svg).not.toContain('foreignObject')22+ })23+ it('renders the production fixture including empty BPMN label bounds', () => {24+ const svg = renderBpmn(productionFixture)25+ expect(svg).toContain('Examine Situation')26+ expect(svg).toContain('Notification Sent')27+ })28+})@@ -0,0 +1,15 @@
1+<?xml version="1.0" encoding="UTF-8"?>2+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI">3+ <process id="Process_1"><startEvent id="Start" name="Start"/><task id="Task" name="Review order"/><exclusiveGateway id="Gateway" name="Approved?"/><subProcess id="Sub" name="Fulfil"><task id="Inner" name="Pack"/></subProcess><endEvent id="End" name="Done"/><sequenceFlow id="F1" sourceRef="Start" targetRef="Task"/><sequenceFlow id="F2" name="yes" sourceRef="Task" targetRef="Gateway"/><sequenceFlow id="F3" sourceRef="Gateway" targetRef="Sub"/><sequenceFlow id="F4" sourceRef="Sub" targetRef="End"/></process>4+ <bpmndi:BPMNDiagram id="D"><bpmndi:BPMNPlane id="P" bpmnElement="Process_1">5+ <bpmndi:BPMNShape id="S1" bpmnElement="Start"><dc:Bounds x="20" y="50" width="36" height="36"/></bpmndi:BPMNShape>6+ <bpmndi:BPMNShape id="S2" bpmnElement="Task"><dc:Bounds x="100" y="40" width="120" height="56"/></bpmndi:BPMNShape>7+ <bpmndi:BPMNShape id="S3" bpmnElement="Gateway"><dc:Bounds x="270" y="42" width="52" height="52"/><bpmndi:BPMNLabel><dc:Bounds x="250" y="100" width="92" height="20"/></bpmndi:BPMNLabel></bpmndi:BPMNShape>8+ <bpmndi:BPMNShape id="S4" bpmnElement="Sub"><dc:Bounds x="370" y="35" width="130" height="66"/></bpmndi:BPMNShape>9+ <bpmndi:BPMNShape id="S5" bpmnElement="End"><dc:Bounds x="550" y="50" width="36" height="36"/></bpmndi:BPMNShape>10+ <bpmndi:BPMNEdge id="E1" bpmnElement="F1"><di:waypoint x="56" y="68"/><di:waypoint x="100" y="68"/></bpmndi:BPMNEdge>11+ <bpmndi:BPMNEdge id="E2" bpmnElement="F2"><di:waypoint x="220" y="68"/><di:waypoint x="270" y="68"/><bpmndi:BPMNLabel><dc:Bounds x="226" y="78" width="36" height="18"/></bpmndi:BPMNLabel></bpmndi:BPMNEdge>12+ <bpmndi:BPMNEdge id="E3" bpmnElement="F3"><di:waypoint x="322" y="68"/><di:waypoint x="370" y="68"/></bpmndi:BPMNEdge>13+ <bpmndi:BPMNEdge id="E4" bpmnElement="F4"><di:waypoint x="500" y="68"/><di:waypoint x="550" y="68"/></bpmndi:BPMNEdge>14+ </bpmndi:BPMNPlane></bpmndi:BPMNDiagram>15+</definitions>@@ -66,7 +66,8 @@ const structurizrEngines = new Set(['structurizr'])
6666 const d2Engines = new Set(['d2'])6767 const symbolatorEngines = new Set(['symbolator'])6868 const umletEngines = new Set(['umlet'])69-const browserRunEngines = new Set(['mermaid', 'bpmn', 'excalidraw', 'diagramsnet', 'tikz'])69+const browserRunEngines = new Set(['mermaid', 'excalidraw', 'diagramsnet', 'tikz'])70+const bpmnEngines = new Set(['bpmn'])7071 7172 async function smoke([engine, filename]) {7273 const source = await readFile(resolve(diagramDirectory, filename), 'utf8')@@ -111,6 +112,10 @@ async function smoke([engine, filename]) {
111112 if (browserRunEngines.has(engine) && response.headers.get('X-Diagram-Renderer') !== 'browser-run') {112113 throw new Error(`${engine}: expected browser-run renderer, received ${response.headers.get('X-Diagram-Renderer')}`)113114 }115+ if (bpmnEngines.has(engine)) {116+ if (response.headers.get('X-Diagram-Renderer') !== 'edge-js') throw new Error(`${engine}: expected edge-js renderer, received ${response.headers.get('X-Diagram-Renderer')}`)117+ if (!response.headers.get('X-Renderer-Build')?.startsWith('bpmn-direct-svg-')) throw new Error(`${engine}: unexpected BPMN Worker build ${response.headers.get('X-Renderer-Build')}`)118+ }114119 if (pythonEngines.has(engine)) {115120 if (response.headers.get('X-Diagram-Renderer') !== 'edge-python') {116121 throw new Error(`${engine}: expected edge-python renderer, received ${response.headers.get('X-Diagram-Renderer')}`)@@ -140,7 +140,7 @@ const VERSION_PATTERNS = Object.freeze({
140140 rackdiag: /(?:rackdiag@)?3\.0\.0/,141141 bytefield: /bytefield-svg@1\.11\.0/,142142 mermaid: /mermaid@11\.17\.0/,143- bpmn: /bpmn-js@18\.25\.1/,143+ bpmn: /(?:bpmn-js@18\.25\.1|diagramzip-bpmn-svg@1)/,144144 nomnoml: /nomnoml@1\.7\.0/,145145 pikchr: /pikchr@85e65b9686/,146146 symbolator: /(?:symbolator@)?1\.2\.2/,