slackmark-node 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"renderers.js","names":["DEFAULT_MAX_SOURCE_CHARS","DEFAULT_MAX_OUTPUT_BYTES","DEFAULT_MAX_WIDTH","DEFAULT_MAX_HEIGHT","DEFAULT_DEVICE_SCALE_FACTOR","DEFAULT_BACKGROUND_COLOR","DEFAULT_VIEWPORT_WIDTH","DEFAULT_VIEWPORT_HEIGHT","browserGlobal","validateOptions","validatePositiveInteger","validatePositiveFinite","browserGlobal"],"sources":["../src/errors.ts","../src/image-output.ts","../src/node-options.ts","../src/katex-renderer.ts","../src/mermaid-renderer.ts"],"sourcesContent":["import {\n isOperationError,\n type OperationError,\n type StructuredRendererError,\n type UploadReceipt,\n} from \"slackmark\";\n\nimport type { NodeConverterStatus } from \"./status.js\";\n\nexport const NodeRenderErrorCode = {\n AssetMissing: \"asset_missing\",\n BrowserBinaryMissing: \"browser_binary_missing\",\n BrowserDisconnected: \"browser_disconnected\",\n BrowserFailed: \"browser_failed\",\n CleanupFailed: \"cleanup_failed\",\n CloseFailed: \"close_failed\",\n ContextFailed: \"context_failed\",\n DimensionLimit: \"dimension_limit\",\n OutputLimit: \"output_limit\",\n QueueFull: \"queue_full\",\n RenderFailed: \"render_failed\",\n SourceLimit: \"source_limit\",\n} as const;\n\nexport type NodeRenderErrorCode = (typeof NodeRenderErrorCode)[keyof typeof NodeRenderErrorCode];\n\nexport const NodeRenderStage = {\n Admission: \"admission\",\n AssetLoad: \"asset_load\",\n Browser: \"browser\",\n BrowserCleanup: \"browser_cleanup\",\n BrowserClose: \"browser_close\",\n BrowserLaunch: \"browser_launch\",\n ContextCleanup: \"context_cleanup\",\n ContextCreate: \"context_create\",\n Layout: \"layout\",\n OutputValidation: \"output_validation\",\n Render: \"render\",\n SourceValidation: \"source_validation\",\n} as const;\n\nexport type NodeRenderStage = (typeof NodeRenderStage)[keyof typeof NodeRenderStage];\n\nexport interface NodeRenderErrorOptions {\n readonly stage: NodeRenderStage;\n readonly retryable: boolean;\n readonly rendererId?: string | undefined;\n readonly cause?: unknown;\n}\n\nexport class NodeShutdownTimeoutError extends Error {\n override readonly name: string;\n readonly code: \"shutdown_timeout\";\n readonly stage: \"close\";\n readonly retryable: true;\n readonly status: NodeConverterStatus;\n readonly receipts: ReadonlyArray<UploadReceipt>;\n readonly settledReceipts: Promise<ReadonlyArray<UploadReceipt>>;\n\n constructor(\n status: NodeConverterStatus,\n receipts: ReadonlyArray<UploadReceipt>,\n settledReceipts: Promise<ReadonlyArray<UploadReceipt>>,\n ) {\n super(\"Node converter shutdown timed out\");\n this.name = \"NodeShutdownTimeoutError\";\n this.code = \"shutdown_timeout\";\n this.stage = \"close\";\n this.retryable = true;\n this.status = status;\n this.receipts = Object.freeze([...receipts]);\n this.settledReceipts = settledReceipts;\n }\n}\n\n/**\n * Sanitized failure from the Node rendering boundary.\n */\nexport class NodeRenderError extends Error implements StructuredRendererError {\n readonly code: NodeRenderErrorCode;\n readonly stage: NodeRenderStage;\n readonly retryable: boolean;\n readonly rendererId: string | undefined;\n\n constructor(\n code: NodeRenderErrorCode,\n message: string,\n options: NodeRenderErrorOptions = {\n stage: \"render\",\n retryable: false,\n },\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"NodeRenderError\";\n this.code = code;\n this.stage = options.stage;\n this.retryable = options.retryable;\n this.rendererId = options.rendererId;\n }\n}\n\nexport function normalizeNodeRenderError(\n error: unknown,\n code: NodeRenderErrorCode,\n message: string,\n stage: NodeRenderStage = NodeRenderStage.Render,\n retryable = false,\n): NodeRenderError | OperationError {\n return error instanceof NodeRenderError || isOperationError(error)\n ? error\n : new NodeRenderError(code, message, { stage, retryable, cause: error });\n}\n","import { createHash } from \"node:crypto\";\n\nimport { NodeRenderError, NodeRenderErrorCode, NodeRenderStage } from \"./errors.js\";\n\nconst PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);\nconst PNG_WIDTH_OFFSET = 16;\nconst PNG_HEIGHT_OFFSET = 20;\nconst PNG_HEADER_BYTES = 24;\nconst PNG_IHDR = new Uint8Array([73, 72, 68, 82]);\nconst PNG_IHDR_OFFSET = 12;\n\nexport interface ImageLimits {\n readonly maxOutputBytes: number;\n readonly maxWidth: number;\n readonly maxHeight: number;\n}\n\nexport function deterministicPngFilename(\n prefix: string,\n source: string,\n optionsFingerprint: string,\n): string {\n const digest = createHash(\"sha256\")\n .update(prefix)\n .update(\"\\0\")\n .update(optionsFingerprint)\n .update(\"\\0\")\n .update(source)\n .digest(\"hex\")\n .slice(0, 16);\n return `${prefix}-${digest}.png`;\n}\n\nexport function assertSourceLimit(source: string, maxSourceChars: number): void {\n if (source.length > maxSourceChars) {\n throw new NodeRenderError(\n NodeRenderErrorCode.SourceLimit,\n `Render source exceeds ${String(maxSourceChars)} characters`,\n { stage: NodeRenderStage.SourceValidation, retryable: false },\n );\n }\n}\n\nexport function assertPngLimits(data: Uint8Array, limits: ImageLimits): void {\n if (data.byteLength > limits.maxOutputBytes) {\n throw new NodeRenderError(\n NodeRenderErrorCode.OutputLimit,\n `Rendered image exceeds ${String(limits.maxOutputBytes)} bytes`,\n { stage: NodeRenderStage.OutputValidation, retryable: false },\n );\n }\n const dimensions = readPngDimensions(data);\n if (dimensions === undefined) {\n throw new NodeRenderError(\n NodeRenderErrorCode.RenderFailed,\n \"Renderer returned invalid PNG data\",\n { stage: NodeRenderStage.OutputValidation, retryable: false },\n );\n }\n if (dimensions.width > limits.maxWidth || dimensions.height > limits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n `Rendered image exceeds ${String(limits.maxWidth)}x${String(limits.maxHeight)} pixels`,\n { stage: NodeRenderStage.Layout, retryable: false },\n );\n }\n}\n\nfunction readPngDimensions(\n data: Uint8Array,\n): { readonly width: number; readonly height: number } | undefined {\n if (data.byteLength < PNG_HEADER_BYTES) {\n return undefined;\n }\n for (const [index, byte] of PNG_SIGNATURE.entries()) {\n if (data[index] !== byte) {\n return undefined;\n }\n }\n for (const [index, byte] of PNG_IHDR.entries()) {\n if (data[PNG_IHDR_OFFSET + index] !== byte) {\n return undefined;\n }\n }\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const width = view.getUint32(PNG_WIDTH_OFFSET);\n const height = view.getUint32(PNG_HEIGHT_OFFSET);\n return width > 0 && height > 0 ? { width, height } : undefined;\n}\n","import { ConfigurationError } from \"slackmark\";\n\nexport function snapshotNodeOptions<T extends object>(\n options: T,\n allowed: ReadonlyArray<string>,\n label: string,\n): T {\n if (!isPlainNodeObject(options)) {\n throw invalidNodeOptions(`${label} options must be a plain object`);\n }\n const allowedKeys = new Set(allowed);\n const snapshot: Record<string, unknown> = {};\n let keys: ReadonlyArray<string>;\n try {\n keys = Object.keys(options);\n } catch (cause) {\n throw invalidNodeOptions(`${label} options could not be inspected`, cause);\n }\n for (const key of keys) {\n if (!allowedKeys.has(key)) {\n throw invalidNodeOptions(`Unknown ${label} option: ${key}`);\n }\n try {\n snapshot[key] = Reflect.get(options, key);\n } catch (cause) {\n throw invalidNodeOptions(`${label}.${key} could not be read`, cause);\n }\n }\n return Object.freeze(snapshot) as T;\n}\n\nexport function snapshotOpenNodeOptions<T extends object>(options: T, label: string): T {\n let keys: ReadonlyArray<string>;\n try {\n keys = Object.keys(options);\n } catch (cause) {\n throw invalidNodeOptions(`${label} could not be inspected`, cause);\n }\n return snapshotNodeOptions(options, keys, label);\n}\n\nexport function assertNodeMethod(collaborator: unknown, method: string, label: string): void {\n try {\n if (\n (typeof collaborator !== \"object\" && typeof collaborator !== \"function\") ||\n collaborator === null ||\n typeof Reflect.get(collaborator, method) !== \"function\"\n ) {\n throw new TypeError(`${method} must be a function`);\n }\n } catch (cause) {\n throw invalidNodeOptions(`${label} collaborator is invalid`, cause);\n }\n}\n\nexport function invalidNodeOptions(message: string, cause?: unknown): ConfigurationError {\n return new ConfigurationError(message, {\n code: \"invalid_node_configuration\",\n stage: \"configuration\",\n cause,\n });\n}\n\nfunction isPlainNodeObject(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n let prototype: object | null;\n try {\n prototype = Object.getPrototypeOf(value);\n } catch {\n return false;\n }\n return prototype === Object.prototype || prototype === null;\n}\n","import {\n isOperationError,\n type BytesImage,\n type BytesImageRenderer,\n type OperationContext,\n type RenderRequest,\n} from \"slackmark\";\n\nimport { NodeRenderError, NodeRenderErrorCode, NodeRenderStage } from \"./errors.js\";\nimport { assertPngLimits, assertSourceLimit, deterministicPngFilename } from \"./image-output.js\";\nimport type { ImageLimits } from \"./image-output.js\";\nimport type { KatexAssetLoader } from \"./katex-assets.js\";\nimport { assertNodeMethod, invalidNodeOptions, snapshotNodeOptions } from \"./node-options.js\";\nimport type { RenderRuntime } from \"./puppeteer-render-runtime.js\";\n\nconst DEFAULT_MAX_SOURCE_CHARS = 20_000;\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_MAX_WIDTH = 4096;\nconst DEFAULT_MAX_HEIGHT = 4096;\nconst DEFAULT_MAX_EXPAND = 1000;\nconst DEFAULT_MAX_SIZE = 50;\nconst DEFAULT_FONT_SIZE_PX = 20;\nconst DEFAULT_PADDING_PX = 16;\nconst DEFAULT_DEVICE_SCALE_FACTOR = 2;\nconst DEFAULT_BACKGROUND_COLOR = \"white\";\nconst DEFAULT_VIEWPORT_WIDTH = 1280;\nconst DEFAULT_VIEWPORT_HEIGHT = 720;\nconst KATEX_LANGUAGES: ReadonlySet<string> = new Set([\"katex\", \"latex\", \"math\", \"tex\"]);\nconst KATEX_SHELL =\n \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><meta http-equiv=\\\"Content-Security-Policy\\\" content=\\\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; font-src data:; img-src 'none'; connect-src 'none'; worker-src 'none';\\\"></head><body><div id=\\\"math\\\"></div></body></html>\";\nconst KATEX_LAYOUT_CSS =\n \"html,body{margin:0;padding:0}#math{display:inline-block;box-sizing:border-box}.katex-display{margin:0}\";\n\nexport interface KatexRendererOptions {\n readonly runtime: RenderRuntime;\n readonly assets: KatexAssetLoader;\n readonly backgroundColor?: string | undefined;\n readonly deviceScaleFactor?: number | undefined;\n readonly fontSizePx?: number | undefined;\n readonly paddingPx?: number | undefined;\n readonly maxExpand?: number | undefined;\n readonly maxSize?: number | undefined;\n readonly maxSourceChars?: number | undefined;\n readonly maxOutputBytes?: number | undefined;\n readonly maxWidth?: number | undefined;\n readonly maxHeight?: number | undefined;\n}\n\n/**\n * Renders display TeX with KaTeX, local fonts, and an isolated browser screenshot.\n */\nexport class KatexRenderer implements BytesImageRenderer {\n readonly id: string;\n readonly output: \"bytes\";\n private readonly runtime: RenderRuntime;\n private readonly assets: KatexAssetLoader;\n private readonly backgroundColor: string;\n private readonly deviceScaleFactor: number;\n private readonly fontSizePx: number;\n private readonly paddingPx: number;\n private readonly maxExpand: number;\n private readonly maxSize: number;\n private readonly maxSourceChars: number;\n private readonly imageLimits: ImageLimits;\n private readonly optionsFingerprint: string;\n\n constructor(options: KatexRendererOptions) {\n const validated = snapshotNodeOptions(\n options,\n [\n \"runtime\",\n \"assets\",\n \"backgroundColor\",\n \"deviceScaleFactor\",\n \"fontSizePx\",\n \"paddingPx\",\n \"maxExpand\",\n \"maxSize\",\n \"maxSourceChars\",\n \"maxOutputBytes\",\n \"maxWidth\",\n \"maxHeight\",\n ],\n \"KatexRenderer\",\n );\n assertNodeMethod(validated.runtime, \"run\", \"RenderRuntime\");\n assertNodeMethod(validated.assets, \"loadScript\", \"KatexAssetLoader\");\n assertNodeMethod(validated.assets, \"loadCss\", \"KatexAssetLoader\");\n this.id = \"katex\";\n this.output = \"bytes\";\n this.runtime = validated.runtime;\n this.assets = validated.assets;\n this.backgroundColor = validated.backgroundColor ?? DEFAULT_BACKGROUND_COLOR;\n this.deviceScaleFactor = validated.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR;\n this.fontSizePx = validated.fontSizePx ?? DEFAULT_FONT_SIZE_PX;\n this.paddingPx = validated.paddingPx ?? DEFAULT_PADDING_PX;\n this.maxExpand = validated.maxExpand ?? DEFAULT_MAX_EXPAND;\n this.maxSize = validated.maxSize ?? DEFAULT_MAX_SIZE;\n this.maxSourceChars = validated.maxSourceChars ?? DEFAULT_MAX_SOURCE_CHARS;\n this.imageLimits = {\n maxOutputBytes: validated.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,\n maxWidth: validated.maxWidth ?? DEFAULT_MAX_WIDTH,\n maxHeight: validated.maxHeight ?? DEFAULT_MAX_HEIGHT,\n };\n this.optionsFingerprint = [\n this.backgroundColor,\n String(this.deviceScaleFactor),\n String(this.fontSizePx),\n String(this.paddingPx),\n String(this.maxExpand),\n String(this.maxSize),\n String(this.maxSourceChars),\n String(this.imageLimits.maxOutputBytes),\n String(this.imageLimits.maxWidth),\n String(this.imageLimits.maxHeight),\n ].join(\":\");\n\n validateOptions({\n backgroundColor: this.backgroundColor,\n deviceScaleFactor: this.deviceScaleFactor,\n fontSizePx: this.fontSizePx,\n paddingPx: this.paddingPx,\n maxExpand: this.maxExpand,\n maxSize: this.maxSize,\n maxSourceChars: this.maxSourceChars,\n imageLimits: this.imageLimits,\n });\n }\n\n canRender(request: RenderRequest): boolean {\n return KATEX_LANGUAGES.has(request.lang.trim().toLowerCase());\n }\n\n async render(request: RenderRequest, context: OperationContext): Promise<BytesImage | null> {\n if (!this.canRender(request)) {\n return null;\n }\n assertSourceLimit(request.source, this.maxSourceChars);\n\n let data: Uint8Array;\n try {\n data = await this.runtime.run(async (context) => {\n const script = await this.assets.loadScript();\n const css = await this.assets.loadCss();\n const page = await context.newPage();\n await page.setViewport({\n width: DEFAULT_VIEWPORT_WIDTH,\n height: DEFAULT_VIEWPORT_HEIGHT,\n deviceScaleFactor: this.deviceScaleFactor,\n });\n await page.setContent(KATEX_SHELL, { waitUntil: \"domcontentloaded\" });\n await page.addStyleTag({ content: `${css}\\n${KATEX_LAYOUT_CSS}` });\n await page.addScriptTag({ content: script });\n await page.$eval(\n \"body\",\n (body, backgroundColor) => {\n body.style.background = backgroundColor;\n },\n this.backgroundColor,\n );\n await page.$eval(\n \"#math\",\n (container, values) => {\n const browserGlobal = globalThis as typeof globalThis & {\n katex?: {\n renderToString(source: string, options: Readonly<Record<string, unknown>>): string;\n };\n };\n const katex = browserGlobal.katex;\n if (katex === undefined) {\n throw new Error(\"KaTeX browser harness failed to initialize\");\n }\n container.innerHTML = katex.renderToString(values.source, {\n displayMode: true,\n globalGroup: false,\n maxExpand: values.maxExpand,\n maxSize: values.maxSize,\n output: \"html\",\n strict: \"ignore\",\n throwOnError: true,\n trust: false,\n });\n container.setAttribute(\n \"style\",\n `font-size:${String(values.fontSizePx)}px;padding:${String(values.paddingPx)}px`,\n );\n },\n {\n source: request.source,\n maxExpand: this.maxExpand,\n maxSize: this.maxSize,\n fontSizePx: this.fontSizePx,\n paddingPx: this.paddingPx,\n },\n );\n await page.evaluate(async () => {\n await document.fonts.ready;\n });\n\n const element = await page.$(\"#math\");\n if (element === null) {\n throw new Error(\"KaTeX screenshot element is missing\");\n }\n const box = await element.boundingBox();\n if (box === null) {\n throw new Error(\"KaTeX screenshot element has no dimensions\");\n }\n const pixelWidth = Math.ceil(box.width * this.deviceScaleFactor);\n const pixelHeight = Math.ceil(box.height * this.deviceScaleFactor);\n if (pixelWidth > this.imageLimits.maxWidth || pixelHeight > this.imageLimits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n `Rendered image exceeds ${String(this.imageLimits.maxWidth)}x${String(this.imageLimits.maxHeight)} pixels`,\n { stage: NodeRenderStage.Layout, retryable: false },\n );\n }\n return element.screenshot({\n type: \"png\",\n omitBackground: this.backgroundColor === \"transparent\",\n });\n }, context);\n } catch (error) {\n if (isOperationError(error)) {\n throw error;\n }\n if (error instanceof NodeRenderError) {\n const sanitizedRenderFailure =\n error.code === NodeRenderErrorCode.RenderFailed && error.cause === undefined;\n if (error.code !== NodeRenderErrorCode.RenderFailed || sanitizedRenderFailure) {\n throw error;\n }\n }\n throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, \"KaTeX rendering failed\", {\n stage: \"render\",\n retryable: false,\n });\n }\n assertPngLimits(data, this.imageLimits);\n\n return {\n kind: \"bytes\",\n data,\n mimeType: \"image/png\",\n filename: deterministicPngFilename(\"math\", request.source, this.optionsFingerprint),\n altText: request.altText,\n };\n }\n}\n\nfunction validateOptions(renderer: {\n readonly backgroundColor: string;\n readonly deviceScaleFactor: number;\n readonly fontSizePx: number;\n readonly paddingPx: number;\n readonly maxExpand: number;\n readonly maxSize: number;\n readonly maxSourceChars: number;\n readonly imageLimits: ImageLimits;\n}): void {\n if (typeof renderer.backgroundColor !== \"string\" || renderer.backgroundColor.length === 0) {\n throw invalidNodeOptions(\"backgroundColor must be a nonempty string\");\n }\n validatePositiveFinite(renderer.deviceScaleFactor, \"deviceScaleFactor\");\n validatePositiveFinite(renderer.fontSizePx, \"fontSizePx\");\n validateNonNegativeFinite(renderer.paddingPx, \"paddingPx\");\n validatePositiveInteger(renderer.maxExpand, \"maxExpand\");\n validatePositiveFinite(renderer.maxSize, \"maxSize\");\n validatePositiveInteger(renderer.maxSourceChars, \"maxSourceChars\");\n validatePositiveInteger(renderer.imageLimits.maxOutputBytes, \"maxOutputBytes\");\n validatePositiveInteger(renderer.imageLimits.maxWidth, \"maxWidth\");\n validatePositiveInteger(renderer.imageLimits.maxHeight, \"maxHeight\");\n}\n\nfunction validatePositiveInteger(value: number, name: string): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be a positive integer`);\n }\n}\n\nfunction validatePositiveFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be positive`);\n }\n}\n\nfunction validateNonNegativeFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throw invalidNodeOptions(`${name} must be non-negative`);\n }\n}\n","import type { HTTPRequest } from \"puppeteer\";\nimport {\n isOperationError,\n type BytesImage,\n type BytesImageRenderer,\n type OperationContext,\n type RenderRequest,\n} from \"slackmark\";\n\nimport { NodeRenderError, NodeRenderErrorCode } from \"./errors.js\";\nimport { assertPngLimits, assertSourceLimit, deterministicPngFilename } from \"./image-output.js\";\nimport type { ImageLimits } from \"./image-output.js\";\nimport type { MermaidAssetLoader } from \"./mermaid-assets.js\";\nimport { assertNodeMethod, invalidNodeOptions, snapshotNodeOptions } from \"./node-options.js\";\nimport type { RenderRuntime } from \"./puppeteer-render-runtime.js\";\n\nconst DEFAULT_MAX_SOURCE_CHARS = 50_000;\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_MAX_WIDTH = 4096;\nconst DEFAULT_MAX_HEIGHT = 4096;\nconst DEFAULT_MAX_EDGES = 500;\nconst DEFAULT_DEVICE_SCALE_FACTOR = 2;\nconst DEFAULT_BACKGROUND_COLOR = \"white\";\nconst DEFAULT_VIEWPORT_WIDTH = 1280;\nconst DEFAULT_VIEWPORT_HEIGHT = 720;\nconst MERMAID_SHELL =\n \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><meta http-equiv=\\\"Content-Security-Policy\\\" content=\\\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'none'; font-src 'none'; connect-src 'none'; worker-src 'none'; object-src 'none'; frame-src 'none';\\\"></head><body><div id=\\\"diagram\\\"></div></body></html>\";\nconst SECURE_MERMAID_KEYS = [\n \"secure\",\n \"securityLevel\",\n \"startOnLoad\",\n \"maxTextSize\",\n \"suppressErrorRendering\",\n \"maxEdges\",\n \"deterministicIds\",\n \"deterministicIDSeed\",\n \"layout\",\n];\n\nexport type MermaidTheme = \"base\" | \"dark\" | \"default\" | \"forest\" | \"neutral\";\nconst MERMAID_THEMES: ReadonlySet<string> = new Set([\n \"base\",\n \"dark\",\n \"default\",\n \"forest\",\n \"neutral\",\n]);\n\nexport interface MermaidRendererOptions {\n readonly runtime: RenderRuntime;\n readonly assets: MermaidAssetLoader;\n readonly theme?: MermaidTheme | undefined;\n readonly backgroundColor?: string | undefined;\n readonly deviceScaleFactor?: number | undefined;\n readonly maxSourceChars?: number | undefined;\n readonly maxEdges?: number | undefined;\n readonly maxOutputBytes?: number | undefined;\n readonly maxWidth?: number | undefined;\n readonly maxHeight?: number | undefined;\n}\n\nexport class MermaidRenderer implements BytesImageRenderer {\n readonly id: string;\n readonly output: \"bytes\";\n private readonly runtime: RenderRuntime;\n private readonly assets: MermaidAssetLoader;\n private readonly theme: MermaidTheme;\n private readonly backgroundColor: string;\n private readonly deviceScaleFactor: number;\n private readonly maxSourceChars: number;\n private readonly maxEdges: number;\n private readonly imageLimits: ImageLimits;\n private readonly optionsFingerprint: string;\n\n constructor(options: MermaidRendererOptions) {\n const validated = snapshotNodeOptions(\n options,\n [\n \"runtime\",\n \"assets\",\n \"theme\",\n \"backgroundColor\",\n \"deviceScaleFactor\",\n \"maxSourceChars\",\n \"maxEdges\",\n \"maxOutputBytes\",\n \"maxWidth\",\n \"maxHeight\",\n ],\n \"MermaidRenderer\",\n );\n assertNodeMethod(validated.runtime, \"run\", \"RenderRuntime\");\n assertNodeMethod(validated.assets, \"loadScript\", \"MermaidAssetLoader\");\n this.id = \"mermaid\";\n this.output = \"bytes\";\n this.runtime = validated.runtime;\n this.assets = validated.assets;\n this.theme = validated.theme ?? \"default\";\n this.backgroundColor = validated.backgroundColor ?? DEFAULT_BACKGROUND_COLOR;\n this.deviceScaleFactor = validated.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR;\n this.maxSourceChars = validated.maxSourceChars ?? DEFAULT_MAX_SOURCE_CHARS;\n this.maxEdges = validated.maxEdges ?? DEFAULT_MAX_EDGES;\n this.imageLimits = {\n maxOutputBytes: validated.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,\n maxWidth: validated.maxWidth ?? DEFAULT_MAX_WIDTH,\n maxHeight: validated.maxHeight ?? DEFAULT_MAX_HEIGHT,\n };\n this.optionsFingerprint = [\n this.theme,\n this.backgroundColor,\n String(this.deviceScaleFactor),\n String(this.maxSourceChars),\n String(this.maxEdges),\n String(this.imageLimits.maxOutputBytes),\n String(this.imageLimits.maxWidth),\n String(this.imageLimits.maxHeight),\n ].join(\":\");\n validateOptions({\n theme: this.theme,\n backgroundColor: this.backgroundColor,\n deviceScaleFactor: this.deviceScaleFactor,\n maxSourceChars: this.maxSourceChars,\n maxEdges: this.maxEdges,\n imageLimits: this.imageLimits,\n });\n }\n\n canRender(request: RenderRequest): boolean {\n return request.lang.trim().toLowerCase() === \"mermaid\";\n }\n\n async render(request: RenderRequest, context: OperationContext): Promise<BytesImage | null> {\n if (!this.canRender(request)) {\n return null;\n }\n assertSourceLimit(request.source, this.maxSourceChars);\n\n let data: Uint8Array;\n try {\n data = await this.runtime.run(async (context) => {\n const script = await this.assets.loadScript();\n const page = await context.newPage();\n await page.setRequestInterception(true);\n page.on(\"request\", blockRequest);\n await page.setViewport({\n width: DEFAULT_VIEWPORT_WIDTH,\n height: DEFAULT_VIEWPORT_HEIGHT,\n deviceScaleFactor: this.deviceScaleFactor,\n });\n await page.setContent(MERMAID_SHELL, { waitUntil: \"domcontentloaded\" });\n await page.addScriptTag({ content: script });\n await page.evaluate(\n async (input) => {\n const browserGlobal = globalThis as typeof globalThis & {\n mermaid?: {\n initialize(config: Readonly<Record<string, unknown>>): void;\n parse(source: string, options: { suppressErrors: boolean }): Promise<unknown>;\n render(\n id: string,\n source: string,\n container: Element,\n ): Promise<{ readonly svg: string }>;\n };\n };\n const mermaid = browserGlobal.mermaid;\n const container = document.querySelector(\"#diagram\");\n if (mermaid === undefined || container === null) {\n throw new Error(\"Mermaid browser harness failed to initialize\");\n }\n mermaid.initialize({\n deterministicIds: true,\n deterministicIDSeed: \"slackmark-node\",\n layout: \"dagre\",\n logLevel: \"fatal\",\n maxEdges: input.maxEdges,\n maxTextSize: input.maxSourceChars,\n secure: input.secureKeys,\n securityLevel: \"strict\",\n startOnLoad: false,\n suppressErrorRendering: true,\n theme: input.theme,\n });\n await mermaid.parse(input.source, { suppressErrors: false });\n const rendered = await mermaid.render(\"slackmark-diagram\", input.source, container);\n container.innerHTML = rendered.svg;\n document.body.style.background = input.backgroundColor;\n },\n {\n source: request.source,\n theme: this.theme,\n backgroundColor: this.backgroundColor,\n maxSourceChars: this.maxSourceChars,\n maxEdges: this.maxEdges,\n secureKeys: SECURE_MERMAID_KEYS,\n },\n );\n const element = await page.$(\"#diagram svg\");\n if (element === null) {\n throw new Error(\"Mermaid produced no SVG\");\n }\n const box = await element.boundingBox();\n if (box === null) {\n throw new Error(\"Mermaid SVG has no dimensions\");\n }\n const pixelWidth = Math.ceil(box.width * this.deviceScaleFactor);\n const pixelHeight = Math.ceil(box.height * this.deviceScaleFactor);\n if (pixelWidth > this.imageLimits.maxWidth || pixelHeight > this.imageLimits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n \"Rendered Mermaid image exceeds configured dimensions\",\n { stage: \"layout\", retryable: false },\n );\n }\n return element.screenshot({\n type: \"png\",\n omitBackground: this.backgroundColor === \"transparent\",\n });\n }, context);\n } catch (error) {\n if (isOperationError(error)) {\n throw error;\n }\n if (error instanceof NodeRenderError) {\n if (error.code !== NodeRenderErrorCode.RenderFailed) {\n throw error;\n }\n }\n throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, \"Mermaid rendering failed\", {\n stage: \"render\",\n retryable: false,\n });\n }\n assertPngLimits(data, this.imageLimits);\n return {\n kind: \"bytes\",\n data,\n mimeType: \"image/png\",\n filename: deterministicPngFilename(\"mermaid\", request.source, this.optionsFingerprint),\n altText: request.altText,\n };\n }\n}\n\nfunction blockRequest(request: HTTPRequest): void {\n void request.abort(\"blockedbyclient\");\n}\n\nfunction validateOptions(renderer: {\n readonly theme: MermaidTheme;\n readonly backgroundColor: string;\n readonly deviceScaleFactor: number;\n readonly maxSourceChars: number;\n readonly maxEdges: number;\n readonly imageLimits: ImageLimits;\n}): void {\n if (typeof renderer.theme !== \"string\" || !MERMAID_THEMES.has(renderer.theme)) {\n throw invalidNodeOptions(\"theme must be base, dark, default, forest, or neutral\");\n }\n if (typeof renderer.backgroundColor !== \"string\" || renderer.backgroundColor.length === 0) {\n throw invalidNodeOptions(\"backgroundColor must be a nonempty string\");\n }\n validatePositiveFinite(renderer.deviceScaleFactor, \"deviceScaleFactor\");\n validatePositiveInteger(renderer.maxSourceChars, \"maxSourceChars\");\n validatePositiveInteger(renderer.maxEdges, \"maxEdges\");\n validatePositiveInteger(renderer.imageLimits.maxOutputBytes, \"maxOutputBytes\");\n validatePositiveInteger(renderer.imageLimits.maxWidth, \"maxWidth\");\n validatePositiveInteger(renderer.imageLimits.maxHeight, \"maxHeight\");\n}\n\nfunction validatePositiveInteger(value: number, name: string): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be a positive integer`);\n }\n}\n\nfunction validatePositiveFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be positive`);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"renderers.js","names":["DEFAULT_MAX_SOURCE_CHARS","DEFAULT_MAX_OUTPUT_BYTES","DEFAULT_MAX_WIDTH","DEFAULT_MAX_HEIGHT","DEFAULT_DEVICE_SCALE_FACTOR","DEFAULT_BACKGROUND_COLOR","DEFAULT_VIEWPORT_WIDTH","DEFAULT_VIEWPORT_HEIGHT","browserGlobal","validateOptions","validatePositiveInteger","validatePositiveFinite","browserGlobal"],"sources":["../src/errors.ts","../src/image-output.ts","../src/node-options.ts","../src/katex-renderer.ts","../src/mermaid-renderer.ts"],"sourcesContent":["import {\n isOperationError,\n type OperationError,\n type StructuredRendererError,\n type UploadReceipt,\n} from \"slackmark\";\n\nimport type { NodeConverterStatus } from \"./status.js\";\n\n/**\n * Registry-keyed marker, not a module-local symbol.\n *\n * Each published entry (`slackmark-node`, `slackmark-node/renderers`,\n * `slackmark-node/puppeteer`) is bundled without code splitting, so every entry carries\n * its own copy of {@link NodeRenderError} and `instanceof` is false for an error that\n * crosses an entry boundary. A renderer always crosses one: the renderers live in\n * `/renderers` and the runtime that throws at them lives in `/puppeteer`. `Symbol.for`\n * keys are shared by every copy, which is what makes {@link isNodeRenderError} reliable.\n */\nconst NODE_RENDER_ERROR_BRAND = Symbol.for(\"slackmark.error.nodeRender\");\n\nexport const NodeRenderErrorCode = {\n AssetMissing: \"asset_missing\",\n BrowserBinaryMissing: \"browser_binary_missing\",\n BrowserDisconnected: \"browser_disconnected\",\n BrowserFailed: \"browser_failed\",\n CleanupFailed: \"cleanup_failed\",\n CloseFailed: \"close_failed\",\n ContextFailed: \"context_failed\",\n DimensionLimit: \"dimension_limit\",\n OutputLimit: \"output_limit\",\n QueueFull: \"queue_full\",\n RenderFailed: \"render_failed\",\n SourceLimit: \"source_limit\",\n} as const;\n\nexport type NodeRenderErrorCode = (typeof NodeRenderErrorCode)[keyof typeof NodeRenderErrorCode];\n\nexport const NodeRenderStage = {\n Admission: \"admission\",\n AssetLoad: \"asset_load\",\n Browser: \"browser\",\n BrowserCleanup: \"browser_cleanup\",\n BrowserClose: \"browser_close\",\n BrowserLaunch: \"browser_launch\",\n ContextCleanup: \"context_cleanup\",\n ContextCreate: \"context_create\",\n Layout: \"layout\",\n OutputValidation: \"output_validation\",\n Render: \"render\",\n SourceValidation: \"source_validation\",\n} as const;\n\nexport type NodeRenderStage = (typeof NodeRenderStage)[keyof typeof NodeRenderStage];\n\nexport interface NodeRenderErrorOptions {\n readonly stage: NodeRenderStage;\n readonly retryable: boolean;\n readonly rendererId?: string | undefined;\n readonly cause?: unknown;\n}\n\nexport class NodeShutdownTimeoutError extends Error {\n override readonly name: string;\n readonly code: \"shutdown_timeout\";\n readonly stage: \"close\";\n readonly retryable: true;\n readonly status: NodeConverterStatus;\n readonly receipts: ReadonlyArray<UploadReceipt>;\n readonly settledReceipts: Promise<ReadonlyArray<UploadReceipt>>;\n\n constructor(\n status: NodeConverterStatus,\n receipts: ReadonlyArray<UploadReceipt>,\n settledReceipts: Promise<ReadonlyArray<UploadReceipt>>,\n ) {\n super(\"Node converter shutdown timed out\");\n this.name = \"NodeShutdownTimeoutError\";\n this.code = \"shutdown_timeout\";\n this.stage = \"close\";\n this.retryable = true;\n this.status = status;\n this.receipts = Object.freeze([...receipts]);\n this.settledReceipts = settledReceipts;\n }\n}\n\n/**\n * Sanitized failure from the Node rendering boundary.\n */\nexport class NodeRenderError extends Error implements StructuredRendererError {\n static {\n Object.defineProperty(this.prototype, NODE_RENDER_ERROR_BRAND, {\n configurable: false,\n enumerable: false,\n value: true,\n writable: false,\n });\n }\n\n readonly code: NodeRenderErrorCode;\n readonly stage: NodeRenderStage;\n readonly retryable: boolean;\n readonly rendererId: string | undefined;\n\n constructor(\n code: NodeRenderErrorCode,\n message: string,\n options: NodeRenderErrorOptions = {\n stage: \"render\",\n retryable: false,\n },\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"NodeRenderError\";\n this.code = code;\n this.stage = options.stage;\n this.retryable = options.retryable;\n this.rendererId = options.rendererId;\n }\n}\n\n/**\n * Identifies a {@link NodeRenderError} from any entry of this package. Prefer this to\n * `instanceof`, which is false across an entry boundary.\n */\nexport function isNodeRenderError(value: unknown): value is NodeRenderError {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n try {\n return Reflect.get(value, NODE_RENDER_ERROR_BRAND) === true;\n } catch {\n // A hostile getter must not escape the guard.\n return false;\n }\n}\n\nexport function normalizeNodeRenderError(\n error: unknown,\n code: NodeRenderErrorCode,\n message: string,\n stage: NodeRenderStage = NodeRenderStage.Render,\n retryable = false,\n): NodeRenderError | OperationError {\n return isNodeRenderError(error) || isOperationError(error)\n ? error\n : new NodeRenderError(code, message, { stage, retryable, cause: error });\n}\n","import { createHash } from \"node:crypto\";\n\nimport { NodeRenderError, NodeRenderErrorCode, NodeRenderStage } from \"./errors.js\";\n\nconst PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);\nconst PNG_WIDTH_OFFSET = 16;\nconst PNG_HEIGHT_OFFSET = 20;\nconst PNG_HEADER_BYTES = 24;\nconst PNG_IHDR = new Uint8Array([73, 72, 68, 82]);\nconst PNG_IHDR_OFFSET = 12;\n\nexport interface ImageLimits {\n readonly maxOutputBytes: number;\n readonly maxWidth: number;\n readonly maxHeight: number;\n}\n\nexport function deterministicPngFilename(\n prefix: string,\n source: string,\n optionsFingerprint: string,\n): string {\n const digest = createHash(\"sha256\")\n .update(prefix)\n .update(\"\\0\")\n .update(optionsFingerprint)\n .update(\"\\0\")\n .update(source)\n .digest(\"hex\")\n .slice(0, 16);\n return `${prefix}-${digest}.png`;\n}\n\nexport function assertSourceLimit(source: string, maxSourceChars: number): void {\n if (source.length > maxSourceChars) {\n throw new NodeRenderError(\n NodeRenderErrorCode.SourceLimit,\n `Render source exceeds ${String(maxSourceChars)} characters`,\n { stage: NodeRenderStage.SourceValidation, retryable: false },\n );\n }\n}\n\nexport function assertPngLimits(data: Uint8Array, limits: ImageLimits): void {\n if (data.byteLength > limits.maxOutputBytes) {\n throw new NodeRenderError(\n NodeRenderErrorCode.OutputLimit,\n `Rendered image exceeds ${String(limits.maxOutputBytes)} bytes`,\n { stage: NodeRenderStage.OutputValidation, retryable: false },\n );\n }\n const dimensions = readPngDimensions(data);\n if (dimensions === undefined) {\n throw new NodeRenderError(\n NodeRenderErrorCode.RenderFailed,\n \"Renderer returned invalid PNG data\",\n { stage: NodeRenderStage.OutputValidation, retryable: false },\n );\n }\n if (dimensions.width > limits.maxWidth || dimensions.height > limits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n `Rendered image exceeds ${String(limits.maxWidth)}x${String(limits.maxHeight)} pixels`,\n { stage: NodeRenderStage.Layout, retryable: false },\n );\n }\n}\n\nfunction readPngDimensions(\n data: Uint8Array,\n): { readonly width: number; readonly height: number } | undefined {\n if (data.byteLength < PNG_HEADER_BYTES) {\n return undefined;\n }\n for (const [index, byte] of PNG_SIGNATURE.entries()) {\n if (data[index] !== byte) {\n return undefined;\n }\n }\n for (const [index, byte] of PNG_IHDR.entries()) {\n if (data[PNG_IHDR_OFFSET + index] !== byte) {\n return undefined;\n }\n }\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const width = view.getUint32(PNG_WIDTH_OFFSET);\n const height = view.getUint32(PNG_HEIGHT_OFFSET);\n return width > 0 && height > 0 ? { width, height } : undefined;\n}\n","import { ConfigurationError } from \"slackmark\";\n\nexport function snapshotNodeOptions<T extends object>(\n options: T,\n allowed: ReadonlyArray<string>,\n label: string,\n): T {\n if (!isPlainNodeObject(options)) {\n throw invalidNodeOptions(`${label} options must be a plain object`);\n }\n const allowedKeys = new Set(allowed);\n const snapshot: Record<string, unknown> = {};\n let keys: ReadonlyArray<string>;\n try {\n keys = Object.keys(options);\n } catch (cause) {\n throw invalidNodeOptions(`${label} options could not be inspected`, cause);\n }\n for (const key of keys) {\n if (!allowedKeys.has(key)) {\n throw invalidNodeOptions(`Unknown ${label} option: ${key}`);\n }\n try {\n snapshot[key] = Reflect.get(options, key);\n } catch (cause) {\n throw invalidNodeOptions(`${label}.${key} could not be read`, cause);\n }\n }\n return Object.freeze(snapshot) as T;\n}\n\nexport function snapshotOpenNodeOptions<T extends object>(options: T, label: string): T {\n let keys: ReadonlyArray<string>;\n try {\n keys = Object.keys(options);\n } catch (cause) {\n throw invalidNodeOptions(`${label} could not be inspected`, cause);\n }\n return snapshotNodeOptions(options, keys, label);\n}\n\nexport function assertNodeMethod(collaborator: unknown, method: string, label: string): void {\n try {\n if (\n (typeof collaborator !== \"object\" && typeof collaborator !== \"function\") ||\n collaborator === null ||\n typeof Reflect.get(collaborator, method) !== \"function\"\n ) {\n throw new TypeError(`${method} must be a function`);\n }\n } catch (cause) {\n throw invalidNodeOptions(`${label} collaborator is invalid`, cause);\n }\n}\n\nexport function invalidNodeOptions(message: string, cause?: unknown): ConfigurationError {\n return new ConfigurationError(message, {\n code: \"invalid_node_configuration\",\n stage: \"configuration\",\n cause,\n });\n}\n\nfunction isPlainNodeObject(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n let prototype: object | null;\n try {\n prototype = Object.getPrototypeOf(value);\n } catch {\n return false;\n }\n return prototype === Object.prototype || prototype === null;\n}\n","import {\n isOperationError,\n type BytesImage,\n type BytesImageRenderer,\n type OperationContext,\n type RenderRequest,\n} from \"slackmark\";\n\nimport {\n isNodeRenderError,\n NodeRenderError,\n NodeRenderErrorCode,\n NodeRenderStage,\n} from \"./errors.js\";\nimport { assertPngLimits, assertSourceLimit, deterministicPngFilename } from \"./image-output.js\";\nimport type { ImageLimits } from \"./image-output.js\";\nimport type { KatexAssetLoader } from \"./katex-assets.js\";\nimport { assertNodeMethod, invalidNodeOptions, snapshotNodeOptions } from \"./node-options.js\";\nimport type { RenderRuntime } from \"./puppeteer-render-runtime.js\";\n\nconst DEFAULT_MAX_SOURCE_CHARS = 20_000;\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_MAX_WIDTH = 4096;\nconst DEFAULT_MAX_HEIGHT = 4096;\nconst DEFAULT_MAX_EXPAND = 1000;\nconst DEFAULT_MAX_SIZE = 50;\nconst DEFAULT_FONT_SIZE_PX = 20;\nconst DEFAULT_PADDING_PX = 16;\nconst DEFAULT_DEVICE_SCALE_FACTOR = 2;\nconst DEFAULT_BACKGROUND_COLOR = \"white\";\nconst DEFAULT_VIEWPORT_WIDTH = 1280;\nconst DEFAULT_VIEWPORT_HEIGHT = 720;\nconst KATEX_LANGUAGES: ReadonlySet<string> = new Set([\"katex\", \"latex\", \"math\", \"tex\"]);\nconst KATEX_SHELL =\n \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><meta http-equiv=\\\"Content-Security-Policy\\\" content=\\\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; font-src data:; img-src 'none'; connect-src 'none'; worker-src 'none';\\\"></head><body><div id=\\\"math\\\"></div></body></html>\";\nconst KATEX_LAYOUT_CSS =\n \"html,body{margin:0;padding:0}#math{display:inline-block;box-sizing:border-box}.katex-display{margin:0}\";\n\nexport interface KatexRendererOptions {\n readonly runtime: RenderRuntime;\n readonly assets: KatexAssetLoader;\n readonly backgroundColor?: string | undefined;\n readonly deviceScaleFactor?: number | undefined;\n readonly fontSizePx?: number | undefined;\n readonly paddingPx?: number | undefined;\n readonly maxExpand?: number | undefined;\n readonly maxSize?: number | undefined;\n readonly maxSourceChars?: number | undefined;\n readonly maxOutputBytes?: number | undefined;\n readonly maxWidth?: number | undefined;\n readonly maxHeight?: number | undefined;\n}\n\n/**\n * Renders display TeX with KaTeX, local fonts, and an isolated browser screenshot.\n */\nexport class KatexRenderer implements BytesImageRenderer {\n readonly id: string;\n readonly output: \"bytes\";\n private readonly runtime: RenderRuntime;\n private readonly assets: KatexAssetLoader;\n private readonly backgroundColor: string;\n private readonly deviceScaleFactor: number;\n private readonly fontSizePx: number;\n private readonly paddingPx: number;\n private readonly maxExpand: number;\n private readonly maxSize: number;\n private readonly maxSourceChars: number;\n private readonly imageLimits: ImageLimits;\n private readonly optionsFingerprint: string;\n\n constructor(options: KatexRendererOptions) {\n const validated = snapshotNodeOptions(\n options,\n [\n \"runtime\",\n \"assets\",\n \"backgroundColor\",\n \"deviceScaleFactor\",\n \"fontSizePx\",\n \"paddingPx\",\n \"maxExpand\",\n \"maxSize\",\n \"maxSourceChars\",\n \"maxOutputBytes\",\n \"maxWidth\",\n \"maxHeight\",\n ],\n \"KatexRenderer\",\n );\n assertNodeMethod(validated.runtime, \"run\", \"RenderRuntime\");\n assertNodeMethod(validated.assets, \"loadScript\", \"KatexAssetLoader\");\n assertNodeMethod(validated.assets, \"loadCss\", \"KatexAssetLoader\");\n this.id = \"katex\";\n this.output = \"bytes\";\n this.runtime = validated.runtime;\n this.assets = validated.assets;\n this.backgroundColor = validated.backgroundColor ?? DEFAULT_BACKGROUND_COLOR;\n this.deviceScaleFactor = validated.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR;\n this.fontSizePx = validated.fontSizePx ?? DEFAULT_FONT_SIZE_PX;\n this.paddingPx = validated.paddingPx ?? DEFAULT_PADDING_PX;\n this.maxExpand = validated.maxExpand ?? DEFAULT_MAX_EXPAND;\n this.maxSize = validated.maxSize ?? DEFAULT_MAX_SIZE;\n this.maxSourceChars = validated.maxSourceChars ?? DEFAULT_MAX_SOURCE_CHARS;\n this.imageLimits = {\n maxOutputBytes: validated.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,\n maxWidth: validated.maxWidth ?? DEFAULT_MAX_WIDTH,\n maxHeight: validated.maxHeight ?? DEFAULT_MAX_HEIGHT,\n };\n this.optionsFingerprint = [\n this.backgroundColor,\n String(this.deviceScaleFactor),\n String(this.fontSizePx),\n String(this.paddingPx),\n String(this.maxExpand),\n String(this.maxSize),\n String(this.maxSourceChars),\n String(this.imageLimits.maxOutputBytes),\n String(this.imageLimits.maxWidth),\n String(this.imageLimits.maxHeight),\n ].join(\":\");\n\n validateOptions({\n backgroundColor: this.backgroundColor,\n deviceScaleFactor: this.deviceScaleFactor,\n fontSizePx: this.fontSizePx,\n paddingPx: this.paddingPx,\n maxExpand: this.maxExpand,\n maxSize: this.maxSize,\n maxSourceChars: this.maxSourceChars,\n imageLimits: this.imageLimits,\n });\n }\n\n canRender(request: RenderRequest): boolean {\n return KATEX_LANGUAGES.has(request.lang.trim().toLowerCase());\n }\n\n async render(request: RenderRequest, context: OperationContext): Promise<BytesImage | null> {\n if (!this.canRender(request)) {\n return null;\n }\n assertSourceLimit(request.source, this.maxSourceChars);\n\n let data: Uint8Array;\n try {\n data = await this.runtime.run(async (context) => {\n const script = await this.assets.loadScript();\n const css = await this.assets.loadCss();\n const page = await context.newPage();\n await page.setViewport({\n width: DEFAULT_VIEWPORT_WIDTH,\n height: DEFAULT_VIEWPORT_HEIGHT,\n deviceScaleFactor: this.deviceScaleFactor,\n });\n await page.setContent(KATEX_SHELL, { waitUntil: \"domcontentloaded\" });\n await page.addStyleTag({ content: `${css}\\n${KATEX_LAYOUT_CSS}` });\n await page.addScriptTag({ content: script });\n await page.$eval(\n \"body\",\n (body, backgroundColor) => {\n body.style.background = backgroundColor;\n },\n this.backgroundColor,\n );\n await page.$eval(\n \"#math\",\n (container, values) => {\n const browserGlobal = globalThis as typeof globalThis & {\n katex?: {\n renderToString(source: string, options: Readonly<Record<string, unknown>>): string;\n };\n };\n const katex = browserGlobal.katex;\n if (katex === undefined) {\n throw new Error(\"KaTeX browser harness failed to initialize\");\n }\n container.innerHTML = katex.renderToString(values.source, {\n displayMode: true,\n globalGroup: false,\n maxExpand: values.maxExpand,\n maxSize: values.maxSize,\n output: \"html\",\n strict: \"ignore\",\n throwOnError: true,\n trust: false,\n });\n container.setAttribute(\n \"style\",\n `font-size:${String(values.fontSizePx)}px;padding:${String(values.paddingPx)}px`,\n );\n },\n {\n source: request.source,\n maxExpand: this.maxExpand,\n maxSize: this.maxSize,\n fontSizePx: this.fontSizePx,\n paddingPx: this.paddingPx,\n },\n );\n await page.evaluate(async () => {\n await document.fonts.ready;\n });\n\n const element = await page.$(\"#math\");\n if (element === null) {\n throw new Error(\"KaTeX screenshot element is missing\");\n }\n const box = await element.boundingBox();\n if (box === null) {\n throw new Error(\"KaTeX screenshot element has no dimensions\");\n }\n const pixelWidth = Math.ceil(box.width * this.deviceScaleFactor);\n const pixelHeight = Math.ceil(box.height * this.deviceScaleFactor);\n if (pixelWidth > this.imageLimits.maxWidth || pixelHeight > this.imageLimits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n `Rendered image exceeds ${String(this.imageLimits.maxWidth)}x${String(this.imageLimits.maxHeight)} pixels`,\n { stage: NodeRenderStage.Layout, retryable: false },\n );\n }\n return element.screenshot({\n type: \"png\",\n omitBackground: this.backgroundColor === \"transparent\",\n });\n }, context);\n } catch (error) {\n if (isOperationError(error)) {\n throw error;\n }\n if (isNodeRenderError(error)) {\n const sanitizedRenderFailure =\n error.code === NodeRenderErrorCode.RenderFailed && error.cause === undefined;\n if (error.code !== NodeRenderErrorCode.RenderFailed || sanitizedRenderFailure) {\n throw error;\n }\n }\n throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, \"KaTeX rendering failed\", {\n stage: \"render\",\n retryable: false,\n });\n }\n assertPngLimits(data, this.imageLimits);\n\n return {\n kind: \"bytes\",\n data,\n mimeType: \"image/png\",\n filename: deterministicPngFilename(\"math\", request.source, this.optionsFingerprint),\n altText: request.altText,\n };\n }\n}\n\nfunction validateOptions(renderer: {\n readonly backgroundColor: string;\n readonly deviceScaleFactor: number;\n readonly fontSizePx: number;\n readonly paddingPx: number;\n readonly maxExpand: number;\n readonly maxSize: number;\n readonly maxSourceChars: number;\n readonly imageLimits: ImageLimits;\n}): void {\n if (typeof renderer.backgroundColor !== \"string\" || renderer.backgroundColor.length === 0) {\n throw invalidNodeOptions(\"backgroundColor must be a nonempty string\");\n }\n validatePositiveFinite(renderer.deviceScaleFactor, \"deviceScaleFactor\");\n validatePositiveFinite(renderer.fontSizePx, \"fontSizePx\");\n validateNonNegativeFinite(renderer.paddingPx, \"paddingPx\");\n validatePositiveInteger(renderer.maxExpand, \"maxExpand\");\n validatePositiveFinite(renderer.maxSize, \"maxSize\");\n validatePositiveInteger(renderer.maxSourceChars, \"maxSourceChars\");\n validatePositiveInteger(renderer.imageLimits.maxOutputBytes, \"maxOutputBytes\");\n validatePositiveInteger(renderer.imageLimits.maxWidth, \"maxWidth\");\n validatePositiveInteger(renderer.imageLimits.maxHeight, \"maxHeight\");\n}\n\nfunction validatePositiveInteger(value: number, name: string): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be a positive integer`);\n }\n}\n\nfunction validatePositiveFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be positive`);\n }\n}\n\nfunction validateNonNegativeFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throw invalidNodeOptions(`${name} must be non-negative`);\n }\n}\n","import type { HTTPRequest } from \"puppeteer\";\nimport {\n isOperationError,\n type BytesImage,\n type BytesImageRenderer,\n type OperationContext,\n type RenderRequest,\n} from \"slackmark\";\n\nimport { isNodeRenderError, NodeRenderError, NodeRenderErrorCode } from \"./errors.js\";\nimport { assertPngLimits, assertSourceLimit, deterministicPngFilename } from \"./image-output.js\";\nimport type { ImageLimits } from \"./image-output.js\";\nimport type { MermaidAssetLoader } from \"./mermaid-assets.js\";\nimport { assertNodeMethod, invalidNodeOptions, snapshotNodeOptions } from \"./node-options.js\";\nimport type { RenderRuntime } from \"./puppeteer-render-runtime.js\";\n\nconst DEFAULT_MAX_SOURCE_CHARS = 50_000;\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_MAX_WIDTH = 4096;\nconst DEFAULT_MAX_HEIGHT = 4096;\nconst DEFAULT_MAX_EDGES = 500;\nconst DEFAULT_DEVICE_SCALE_FACTOR = 2;\nconst DEFAULT_BACKGROUND_COLOR = \"white\";\nconst DEFAULT_VIEWPORT_WIDTH = 1280;\nconst DEFAULT_VIEWPORT_HEIGHT = 720;\nconst MERMAID_SHELL =\n \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><meta http-equiv=\\\"Content-Security-Policy\\\" content=\\\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'none'; font-src 'none'; connect-src 'none'; worker-src 'none'; object-src 'none'; frame-src 'none';\\\"></head><body><div id=\\\"diagram\\\"></div></body></html>\";\nconst SECURE_MERMAID_KEYS = [\n \"secure\",\n \"securityLevel\",\n \"startOnLoad\",\n \"maxTextSize\",\n \"suppressErrorRendering\",\n \"maxEdges\",\n \"deterministicIds\",\n \"deterministicIDSeed\",\n \"layout\",\n];\n\nexport type MermaidTheme = \"base\" | \"dark\" | \"default\" | \"forest\" | \"neutral\";\nconst MERMAID_THEMES: ReadonlySet<string> = new Set([\n \"base\",\n \"dark\",\n \"default\",\n \"forest\",\n \"neutral\",\n]);\n\nexport interface MermaidRendererOptions {\n readonly runtime: RenderRuntime;\n readonly assets: MermaidAssetLoader;\n readonly theme?: MermaidTheme | undefined;\n readonly backgroundColor?: string | undefined;\n readonly deviceScaleFactor?: number | undefined;\n readonly maxSourceChars?: number | undefined;\n readonly maxEdges?: number | undefined;\n readonly maxOutputBytes?: number | undefined;\n readonly maxWidth?: number | undefined;\n readonly maxHeight?: number | undefined;\n}\n\nexport class MermaidRenderer implements BytesImageRenderer {\n readonly id: string;\n readonly output: \"bytes\";\n private readonly runtime: RenderRuntime;\n private readonly assets: MermaidAssetLoader;\n private readonly theme: MermaidTheme;\n private readonly backgroundColor: string;\n private readonly deviceScaleFactor: number;\n private readonly maxSourceChars: number;\n private readonly maxEdges: number;\n private readonly imageLimits: ImageLimits;\n private readonly optionsFingerprint: string;\n\n constructor(options: MermaidRendererOptions) {\n const validated = snapshotNodeOptions(\n options,\n [\n \"runtime\",\n \"assets\",\n \"theme\",\n \"backgroundColor\",\n \"deviceScaleFactor\",\n \"maxSourceChars\",\n \"maxEdges\",\n \"maxOutputBytes\",\n \"maxWidth\",\n \"maxHeight\",\n ],\n \"MermaidRenderer\",\n );\n assertNodeMethod(validated.runtime, \"run\", \"RenderRuntime\");\n assertNodeMethod(validated.assets, \"loadScript\", \"MermaidAssetLoader\");\n this.id = \"mermaid\";\n this.output = \"bytes\";\n this.runtime = validated.runtime;\n this.assets = validated.assets;\n this.theme = validated.theme ?? \"default\";\n this.backgroundColor = validated.backgroundColor ?? DEFAULT_BACKGROUND_COLOR;\n this.deviceScaleFactor = validated.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR;\n this.maxSourceChars = validated.maxSourceChars ?? DEFAULT_MAX_SOURCE_CHARS;\n this.maxEdges = validated.maxEdges ?? DEFAULT_MAX_EDGES;\n this.imageLimits = {\n maxOutputBytes: validated.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,\n maxWidth: validated.maxWidth ?? DEFAULT_MAX_WIDTH,\n maxHeight: validated.maxHeight ?? DEFAULT_MAX_HEIGHT,\n };\n this.optionsFingerprint = [\n this.theme,\n this.backgroundColor,\n String(this.deviceScaleFactor),\n String(this.maxSourceChars),\n String(this.maxEdges),\n String(this.imageLimits.maxOutputBytes),\n String(this.imageLimits.maxWidth),\n String(this.imageLimits.maxHeight),\n ].join(\":\");\n validateOptions({\n theme: this.theme,\n backgroundColor: this.backgroundColor,\n deviceScaleFactor: this.deviceScaleFactor,\n maxSourceChars: this.maxSourceChars,\n maxEdges: this.maxEdges,\n imageLimits: this.imageLimits,\n });\n }\n\n canRender(request: RenderRequest): boolean {\n return request.lang.trim().toLowerCase() === \"mermaid\";\n }\n\n async render(request: RenderRequest, context: OperationContext): Promise<BytesImage | null> {\n if (!this.canRender(request)) {\n return null;\n }\n assertSourceLimit(request.source, this.maxSourceChars);\n\n let data: Uint8Array;\n try {\n data = await this.runtime.run(async (context) => {\n const script = await this.assets.loadScript();\n const page = await context.newPage();\n await page.setRequestInterception(true);\n page.on(\"request\", blockRequest);\n await page.setViewport({\n width: DEFAULT_VIEWPORT_WIDTH,\n height: DEFAULT_VIEWPORT_HEIGHT,\n deviceScaleFactor: this.deviceScaleFactor,\n });\n await page.setContent(MERMAID_SHELL, { waitUntil: \"domcontentloaded\" });\n await page.addScriptTag({ content: script });\n await page.evaluate(\n async (input) => {\n const browserGlobal = globalThis as typeof globalThis & {\n mermaid?: {\n initialize(config: Readonly<Record<string, unknown>>): void;\n parse(source: string, options: { suppressErrors: boolean }): Promise<unknown>;\n render(\n id: string,\n source: string,\n container: Element,\n ): Promise<{ readonly svg: string }>;\n };\n };\n const mermaid = browserGlobal.mermaid;\n const container = document.querySelector(\"#diagram\");\n if (mermaid === undefined || container === null) {\n throw new Error(\"Mermaid browser harness failed to initialize\");\n }\n mermaid.initialize({\n deterministicIds: true,\n deterministicIDSeed: \"slackmark-node\",\n layout: \"dagre\",\n logLevel: \"fatal\",\n maxEdges: input.maxEdges,\n maxTextSize: input.maxSourceChars,\n secure: input.secureKeys,\n securityLevel: \"strict\",\n startOnLoad: false,\n suppressErrorRendering: true,\n theme: input.theme,\n });\n await mermaid.parse(input.source, { suppressErrors: false });\n const rendered = await mermaid.render(\"slackmark-diagram\", input.source, container);\n container.innerHTML = rendered.svg;\n document.body.style.background = input.backgroundColor;\n },\n {\n source: request.source,\n theme: this.theme,\n backgroundColor: this.backgroundColor,\n maxSourceChars: this.maxSourceChars,\n maxEdges: this.maxEdges,\n secureKeys: SECURE_MERMAID_KEYS,\n },\n );\n const element = await page.$(\"#diagram svg\");\n if (element === null) {\n throw new Error(\"Mermaid produced no SVG\");\n }\n const box = await element.boundingBox();\n if (box === null) {\n throw new Error(\"Mermaid SVG has no dimensions\");\n }\n const pixelWidth = Math.ceil(box.width * this.deviceScaleFactor);\n const pixelHeight = Math.ceil(box.height * this.deviceScaleFactor);\n if (pixelWidth > this.imageLimits.maxWidth || pixelHeight > this.imageLimits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n \"Rendered Mermaid image exceeds configured dimensions\",\n { stage: \"layout\", retryable: false },\n );\n }\n return element.screenshot({\n type: \"png\",\n omitBackground: this.backgroundColor === \"transparent\",\n });\n }, context);\n } catch (error) {\n if (isOperationError(error)) {\n throw error;\n }\n if (isNodeRenderError(error)) {\n if (error.code !== NodeRenderErrorCode.RenderFailed) {\n throw error;\n }\n }\n throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, \"Mermaid rendering failed\", {\n stage: \"render\",\n retryable: false,\n });\n }\n assertPngLimits(data, this.imageLimits);\n return {\n kind: \"bytes\",\n data,\n mimeType: \"image/png\",\n filename: deterministicPngFilename(\"mermaid\", request.source, this.optionsFingerprint),\n altText: request.altText,\n };\n }\n}\n\nfunction blockRequest(request: HTTPRequest): void {\n void request.abort(\"blockedbyclient\");\n}\n\nfunction validateOptions(renderer: {\n readonly theme: MermaidTheme;\n readonly backgroundColor: string;\n readonly deviceScaleFactor: number;\n readonly maxSourceChars: number;\n readonly maxEdges: number;\n readonly imageLimits: ImageLimits;\n}): void {\n if (typeof renderer.theme !== \"string\" || !MERMAID_THEMES.has(renderer.theme)) {\n throw invalidNodeOptions(\"theme must be base, dark, default, forest, or neutral\");\n }\n if (typeof renderer.backgroundColor !== \"string\" || renderer.backgroundColor.length === 0) {\n throw invalidNodeOptions(\"backgroundColor must be a nonempty string\");\n }\n validatePositiveFinite(renderer.deviceScaleFactor, \"deviceScaleFactor\");\n validatePositiveInteger(renderer.maxSourceChars, \"maxSourceChars\");\n validatePositiveInteger(renderer.maxEdges, \"maxEdges\");\n validatePositiveInteger(renderer.imageLimits.maxOutputBytes, \"maxOutputBytes\");\n validatePositiveInteger(renderer.imageLimits.maxWidth, \"maxWidth\");\n validatePositiveInteger(renderer.imageLimits.maxHeight, \"maxHeight\");\n}\n\nfunction validatePositiveInteger(value: number, name: string): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be a positive integer`);\n }\n}\n\nfunction validatePositiveFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be positive`);\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slackmark-node",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Node.js image renderers for slackmark diagrams and display math",
5
5
  "keywords": [
6
6
  "block kit",
@@ -44,21 +44,11 @@
44
44
  "publishConfig": {
45
45
  "access": "public"
46
46
  },
47
- "scripts": {
48
- "build": "tsdown && tsc -p tsconfig.build.json",
49
- "test": "vitest run --exclude '**/*.integration.test.ts'",
50
- "test:integration": "vitest run test/rendering.integration.test.ts",
51
- "example:smoke": "pnpm run build && node --experimental-strip-types examples/render-agent-markdown.ts && node --experimental-strip-types examples/custom-renderers.ts",
52
- "probe:slack-file": "pnpm run build && node --experimental-strip-types scripts/live-slack-file-probe.ts",
53
- "test:watch": "vitest",
54
- "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json --noEmit",
55
- "typecheck:src": "tsc -p tsconfig.json --noEmit"
56
- },
57
47
  "dependencies": {
58
48
  "katex": "0.18.1",
59
49
  "mermaid": "11.16.0",
60
50
  "puppeteer": "25.3.0",
61
- "slackmark": "workspace:*"
51
+ "slackmark": "0.6.1"
62
52
  },
63
53
  "devDependencies": {
64
54
  "@slack/web-api": "^8.0.0",
@@ -68,5 +58,15 @@
68
58
  },
69
59
  "engines": {
70
60
  "node": ">=22.12.0"
61
+ },
62
+ "scripts": {
63
+ "build": "tsdown && tsc -p tsconfig.build.json",
64
+ "test": "vitest run --exclude '**/*.integration.test.ts'",
65
+ "test:integration": "vitest run test/rendering.integration.test.ts",
66
+ "example:smoke": "pnpm run build && node --experimental-strip-types examples/render-agent-markdown.ts && node --experimental-strip-types examples/custom-renderers.ts",
67
+ "probe:slack-file": "pnpm run build && node --experimental-strip-types scripts/live-slack-file-probe.ts",
68
+ "test:watch": "vitest",
69
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json --noEmit",
70
+ "typecheck:src": "tsc -p tsconfig.json --noEmit"
71
71
  }
72
- }
72
+ }