clone

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

main
diagramzip / renderers / browser-run / src / contracts.ts
contracts.ts 2.4K
1export const CHANNEL = 'diagram.zip:renderer:v1' as const23export const ENGINES = ['mermaid', 'diagramsnet'] as const4export type Engine = (typeof ENGINES)[number]56export const FRAME_URLS: Readonly<Record<Engine, string>> = Object.freeze({7 mermaid: 'https://mermaid.render.diagram.zip/index.html?v=2',8 diagramsnet: 'https://diagramsnet.render.diagram.zip/index.html?v=1',9})1011export const LIMITS = Object.freeze({12 source: 524_288,13 output: 4_194_304,14 timeoutMs: 60_000,15})1617export interface RenderRequest {18 engine: Engine19 requestId: string20 source: string21}2223export interface RenderResult {24 ok: true25 svg: string26 version: string27 build: string28 pipeline: string[]29}3031export interface RenderFailure {32 ok: false33 error: string34}3536export type RenderResponse = RenderResult | RenderFailure3738export function isEngine(value: unknown): value is Engine {39 return typeof value === 'string' && (ENGINES as readonly string[]).includes(value)40}4142export function validateRenderRequest(value: unknown): RenderRequest {43 if (typeof value !== 'object' || value === null || Array.isArray(value)) {44 throw new RenderInputError('invalid_request', 'Request body must be a JSON object.')45 }46 const request = value as Record<string, unknown>47 if (!isEngine(request.engine)) throw new RenderInputError('invalid_engine', 'Unsupported renderer engine.')48 if (typeof request.requestId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(request.requestId)) {49 throw new RenderInputError('invalid_request_id', 'requestId must be 1-128 safe identifier characters.')50 }51 if (typeof request.source !== 'string') throw new RenderInputError('invalid_source', 'source must be a string.')52 if (request.source.length > LIMITS.source) throw new RenderInputError('source_too_large', 'Source exceeds the 512 KiB limit.')53 return { engine: request.engine, requestId: request.requestId, source: request.source }54}5556export class RenderInputError extends Error {57 constructor(readonly code: string, message: string) {58 super(message)59 this.name = 'RenderInputError'60 }61}6263export function errorResponse(error: unknown): Response {64 const input = error instanceof RenderInputError65 const code = input ? error.code : 'render_failed'66 const message = input ? error.message : error instanceof Error ? error.message.slice(0, 500) : 'Rendering failed.'67 return Response.json({ error: { code, message } }, { status: input ? 400 : 500 })68}