UNPKG

@opentui/core

Version:

OpenTUI is a TypeScript library for building terminal user interfaces (TUIs)

863 lines (854 loc) 31.6 kB
// @bun var __require = import.meta.require; // src/lib/tree-sitter/parser.worker.ts import { Parser, Query, Language } from "web-tree-sitter"; import { mkdir as mkdir2 } from "fs/promises"; import * as path2 from "path"; // src/lib/tree-sitter/download-utils.ts import { mkdir, writeFile } from "fs/promises"; import * as path from "path"; class DownloadUtils { static hashUrl(url) { let hash = 0; for (let i = 0;i < url.length; i++) { const char = url.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return Math.abs(hash).toString(16); } static async downloadOrLoad(source, cacheDir, cacheSubdir, fileExtension, useHashForCache = true, filetype) { const isUrl = source.startsWith("http://") || source.startsWith("https://"); if (isUrl) { let cacheFileName; if (useHashForCache) { const hash = this.hashUrl(source); cacheFileName = filetype ? `${filetype}-${hash}${fileExtension}` : `${hash}${fileExtension}`; } else { cacheFileName = path.basename(source); } const cacheFile = path.join(cacheDir, cacheSubdir, cacheFileName); await mkdir(path.dirname(cacheFile), { recursive: true }); try { const cachedContent = await Bun.file(cacheFile).arrayBuffer(); if (cachedContent.byteLength > 0) { console.log(`Loaded from cache: ${cacheFile} (${source})`); return { content: cachedContent, filePath: cacheFile }; } } catch (error) {} try { console.log(`Downloading from URL: ${source}`); const response = await fetch(source); if (!response.ok) { return { error: `Failed to fetch from ${source}: ${response.statusText}` }; } const content = await response.arrayBuffer(); try { await writeFile(cacheFile, Buffer.from(content)); console.log(`Cached: ${source}`); } catch (cacheError) { console.warn(`Failed to cache: ${cacheError}`); } return { content, filePath: cacheFile }; } catch (error) { return { error: `Error downloading from ${source}: ${error}` }; } } else { try { console.log(`Loading from local path: ${source}`); const content = await Bun.file(source).arrayBuffer(); return { content, filePath: source }; } catch (error) { return { error: `Error loading from local path ${source}: ${error}` }; } } } static async downloadToPath(source, targetPath) { const isUrl = source.startsWith("http://") || source.startsWith("https://"); await mkdir(path.dirname(targetPath), { recursive: true }); if (isUrl) { try { console.log(`Downloading from URL: ${source}`); const response = await fetch(source); if (!response.ok) { return { error: `Failed to fetch from ${source}: ${response.statusText}` }; } const content = await response.arrayBuffer(); await writeFile(targetPath, Buffer.from(content)); console.log(`Downloaded: ${source} -> ${targetPath}`); return { content, filePath: targetPath }; } catch (error) { return { error: `Error downloading from ${source}: ${error}` }; } } else { try { console.log(`Copying from local path: ${source}`); const content = await Bun.file(source).arrayBuffer(); await writeFile(targetPath, Buffer.from(content)); return { content, filePath: targetPath }; } catch (error) { return { error: `Error copying from local path ${source}: ${error}` }; } } } static async fetchHighlightQueries(sources, cacheDir, filetype) { const queryPromises = sources.map((source) => this.fetchHighlightQuery(source, cacheDir, filetype)); const queryResults = await Promise.all(queryPromises); const validQueries = queryResults.filter((query) => query.trim().length > 0); return validQueries.join(` `); } static async fetchHighlightQuery(source, cacheDir, filetype) { const result = await this.downloadOrLoad(source, cacheDir, "queries", ".scm", true, filetype); if (result.error) { console.error(`Error fetching highlight query from ${source}:`, result.error); return ""; } if (result.content) { return new TextDecoder().decode(result.content); } return ""; } } // src/lib/tree-sitter/parser.worker.ts import { isMainThread } from "worker_threads"; // src/lib/bunfs.ts import { basename as basename2, join as join2 } from "path"; function isBunfsPath(path2) { return path2.includes("$bunfs") || /^B:[\\/]~BUN/i.test(path2); } function getBunfsRootPath() { return process.platform === "win32" ? "B:\\~BUN\\root" : "/$bunfs/root"; } function normalizeBunfsPath(fileName) { return join2(getBunfsRootPath(), basename2(fileName)); } // src/lib/tree-sitter/parser.worker.ts var self = globalThis; class ParserWorker { bufferParsers = new Map; filetypeParserOptions = new Map; filetypeParsers = new Map; filetypeParserPromises = new Map; reusableParsers = new Map; reusableParserPromises = new Map; initializePromise; performance; dataPath; tsDataPath; initialized = false; constructor() { this.performance = { averageParseTime: 0, parseTimes: [], averageQueryTime: 0, queryTimes: [] }; } async fetchQueries(sources, filetype) { if (!this.tsDataPath) { return ""; } return DownloadUtils.fetchHighlightQueries(sources, this.tsDataPath, filetype); } async initialize({ dataPath }) { if (this.initializePromise) { return this.initializePromise; } this.initializePromise = new Promise(async (resolve, reject) => { this.dataPath = dataPath; this.tsDataPath = path2.join(dataPath, "tree-sitter"); try { await mkdir2(path2.join(this.tsDataPath, "languages"), { recursive: true }); await mkdir2(path2.join(this.tsDataPath, "queries"), { recursive: true }); let { default: treeWasm } = await import("web-tree-sitter/tree-sitter.wasm", { with: { type: "wasm" } }); if (isBunfsPath(treeWasm)) { treeWasm = normalizeBunfsPath(path2.parse(treeWasm).base); } await Parser.init({ locateFile() { return treeWasm; } }); this.initialized = true; resolve(); } catch (error) { reject(error); } }); return this.initializePromise; } addFiletypeParser(filetypeParser) { this.filetypeParserOptions.set(filetypeParser.filetype, filetypeParser); } async createQueries(filetypeParser, language) { try { const highlightQueryContent = await this.fetchQueries(filetypeParser.queries.highlights, filetypeParser.filetype); if (!highlightQueryContent) { console.error("Failed to fetch highlight queries for:", filetypeParser.filetype); return; } const highlightsQuery = new Query(language, highlightQueryContent); const result = { highlights: highlightsQuery }; if (filetypeParser.queries.injections && filetypeParser.queries.injections.length > 0) { const injectionQueryContent = await this.fetchQueries(filetypeParser.queries.injections, filetypeParser.filetype); if (injectionQueryContent) { result.injections = new Query(language, injectionQueryContent); } } return result; } catch (error) { console.error("Error creating queries for", filetypeParser.filetype, filetypeParser.queries); console.error(error); return; } } async loadLanguage(languageSource) { if (!this.initialized || !this.tsDataPath) { return; } const result = await DownloadUtils.downloadOrLoad(languageSource, this.tsDataPath, "languages", ".wasm", false); if (result.error) { console.error(`Error loading language ${languageSource}:`, result.error); return; } if (!result.filePath) { return; } const normalizedPath = result.filePath.replaceAll("\\", "/"); try { const language = await Language.load(normalizedPath); return language; } catch (error) { console.error(`Error loading language from ${normalizedPath}:`, error); return; } } async resolveFiletypeParser(filetype) { if (this.filetypeParsers.has(filetype)) { return this.filetypeParsers.get(filetype); } if (this.filetypeParserPromises.has(filetype)) { return this.filetypeParserPromises.get(filetype); } const loadingPromise = this.loadFiletypeParser(filetype); this.filetypeParserPromises.set(filetype, loadingPromise); try { const result = await loadingPromise; if (result) { this.filetypeParsers.set(filetype, result); } return result; } finally { this.filetypeParserPromises.delete(filetype); } } async loadFiletypeParser(filetype) { const filetypeParserOptions = this.filetypeParserOptions.get(filetype); if (!filetypeParserOptions) { return; } const language = await this.loadLanguage(filetypeParserOptions.wasm); if (!language) { return; } const queries = await this.createQueries(filetypeParserOptions, language); if (!queries) { console.error("Failed to create queries for:", filetype); return; } const filetypeParser = { ...filetypeParserOptions, queries, language }; return filetypeParser; } async preloadParser(filetype) { return this.resolveFiletypeParser(filetype); } async getReusableParser(filetype) { if (this.reusableParsers.has(filetype)) { return this.reusableParsers.get(filetype); } if (this.reusableParserPromises.has(filetype)) { return this.reusableParserPromises.get(filetype); } const creationPromise = this.createReusableParser(filetype); this.reusableParserPromises.set(filetype, creationPromise); try { const result = await creationPromise; if (result) { this.reusableParsers.set(filetype, result); } return result; } finally { this.reusableParserPromises.delete(filetype); } } async createReusableParser(filetype) { const filetypeParser = await this.resolveFiletypeParser(filetype); if (!filetypeParser) { return; } const parser = new Parser; parser.setLanguage(filetypeParser.language); const reusableState = { parser, filetypeParser, queries: filetypeParser.queries }; return reusableState; } async handleInitializeParser(bufferId, version, content, filetype, messageId) { const filetypeParser = await this.resolveFiletypeParser(filetype); if (!filetypeParser) { self.postMessage({ type: "PARSER_INIT_RESPONSE", bufferId, messageId, hasParser: false, warning: `No parser available for filetype ${filetype}` }); return; } const parser = new Parser; parser.setLanguage(filetypeParser.language); const tree = parser.parse(content); if (!tree) { self.postMessage({ type: "PARSER_INIT_RESPONSE", bufferId, messageId, hasParser: false, error: "Failed to parse buffer" }); return; } const parserState = { parser, tree, queries: filetypeParser.queries, filetype, content, injectionMapping: filetypeParser.injectionMapping }; this.bufferParsers.set(bufferId, parserState); self.postMessage({ type: "PARSER_INIT_RESPONSE", bufferId, messageId, hasParser: true }); const highlights = await this.initialQuery(parserState); self.postMessage({ type: "HIGHLIGHT_RESPONSE", bufferId, version, ...highlights }); } async initialQuery(parserState) { const query = parserState.queries.highlights; const matches = query.captures(parserState.tree.rootNode); let injectionRanges = new Map; if (parserState.queries.injections) { const injectionResult = await this.processInjections(parserState); matches.push(...injectionResult.captures); injectionRanges = injectionResult.injectionRanges; } return this.getHighlights(parserState, matches, injectionRanges); } getNodeText(node, content) { return content.substring(node.startIndex, node.endIndex); } async processInjections(parserState) { const injectionMatches = []; const injectionRanges = new Map; if (!parserState.queries.injections) { return { captures: injectionMatches, injectionRanges }; } const content = parserState.content; const injectionCaptures = parserState.queries.injections.captures(parserState.tree.rootNode); const languageGroups = new Map; const injectionMapping = parserState.injectionMapping; for (const capture of injectionCaptures) { const captureName = capture.name; if (captureName === "injection.content" || captureName.includes("injection")) { const nodeType = capture.node.type; let targetLanguage; if (injectionMapping?.nodeTypes && injectionMapping.nodeTypes[nodeType]) { targetLanguage = injectionMapping.nodeTypes[nodeType]; } else if (nodeType === "code_fence_content") { const parent = capture.node.parent; if (parent) { const infoString = parent.children.find((child) => child.type === "info_string"); if (infoString) { const languageNode = infoString.children.find((child) => child.type === "language"); if (languageNode) { const languageName = this.getNodeText(languageNode, content); if (injectionMapping?.infoStringMap && injectionMapping.infoStringMap[languageName]) { targetLanguage = injectionMapping.infoStringMap[languageName]; } else { targetLanguage = languageName; } } } } } if (targetLanguage) { if (!languageGroups.has(targetLanguage)) { languageGroups.set(targetLanguage, []); } languageGroups.get(targetLanguage).push({ node: capture.node, name: capture.name }); } } } for (const [language, captures] of languageGroups.entries()) { const injectedParser = await this.getReusableParser(language); if (!injectedParser) { console.warn(`No parser found for injection language: ${language}`); continue; } if (!injectionRanges.has(language)) { injectionRanges.set(language, []); } const parser = injectedParser.parser; for (const { node: injectionNode } of captures) { try { injectionRanges.get(language).push({ start: injectionNode.startIndex, end: injectionNode.endIndex }); const injectionContent = this.getNodeText(injectionNode, content); const tree = parser.parse(injectionContent); if (tree) { const matches = injectedParser.queries.highlights.captures(tree.rootNode); for (const match of matches) { const offsetCapture = { name: match.name, patternIndex: match.patternIndex, _injectedQuery: injectedParser.queries.highlights, node: { ...match.node, startPosition: { row: match.node.startPosition.row + injectionNode.startPosition.row, column: match.node.startPosition.row === 0 ? match.node.startPosition.column + injectionNode.startPosition.column : match.node.startPosition.column }, endPosition: { row: match.node.endPosition.row + injectionNode.startPosition.row, column: match.node.endPosition.row === 0 ? match.node.endPosition.column + injectionNode.startPosition.column : match.node.endPosition.column }, startIndex: match.node.startIndex + injectionNode.startIndex, endIndex: match.node.endIndex + injectionNode.startIndex } }; injectionMatches.push(offsetCapture); } tree.delete(); } } catch (error) { console.error(`Error processing injection for language ${language}:`, error); } } } return { captures: injectionMatches, injectionRanges }; } editToRange(edit) { return { startPosition: { column: edit.startPosition.column, row: edit.startPosition.row }, endPosition: { column: edit.newEndPosition.column, row: edit.newEndPosition.row }, startIndex: edit.startIndex, endIndex: edit.newEndIndex }; } async handleEdits(bufferId, content, edits) { const parserState = this.bufferParsers.get(bufferId); if (!parserState) { return { warning: "No parser state found for buffer" }; } parserState.content = content; for (const edit of edits) { parserState.tree.edit(edit); } const startParse = performance.now(); const newTree = parserState.parser.parse(content, parserState.tree); const endParse = performance.now(); const parseTime = endParse - startParse; this.performance.parseTimes.push(parseTime); if (this.performance.parseTimes.length > 10) { this.performance.parseTimes.shift(); } this.performance.averageParseTime = this.performance.parseTimes.reduce((acc, time) => acc + time, 0) / this.performance.parseTimes.length; if (!newTree) { return { error: "Failed to parse buffer" }; } const changedRanges = parserState.tree.getChangedRanges(newTree); parserState.tree = newTree; const startQuery = performance.now(); const matches = []; if (changedRanges.length === 0) { edits.forEach((edit) => { const range = this.editToRange(edit); changedRanges.push(range); }); } for (const range of changedRanges) { let node = parserState.tree.rootNode.descendantForPosition(range.startPosition, range.endPosition); if (!node) { continue; } if (node.equals(parserState.tree.rootNode)) { const rangeCaptures = parserState.queries.highlights.captures(node, { startIndex: range.startIndex - 100, endIndex: range.endIndex + 1000 }); matches.push(...rangeCaptures); continue; } while (node && !this.nodeContainsRange(node, range)) { node = node.parent; } if (!node) { node = parserState.tree.rootNode; } const nodeCaptures = parserState.queries.highlights.captures(node); matches.push(...nodeCaptures); } let injectionRanges = new Map; if (parserState.queries.injections) { const injectionResult = await this.processInjections(parserState); matches.push(...injectionResult.captures); injectionRanges = injectionResult.injectionRanges; } const endQuery = performance.now(); const queryTime = endQuery - startQuery; this.performance.queryTimes.push(queryTime); if (this.performance.queryTimes.length > 10) { this.performance.queryTimes.shift(); } this.performance.averageQueryTime = this.performance.queryTimes.reduce((acc, time) => acc + time, 0) / this.performance.queryTimes.length; return this.getHighlights(parserState, matches, injectionRanges); } nodeContainsRange(node, range) { return node.startPosition.row <= range.startPosition.row && node.endPosition.row >= range.endPosition.row && (node.startPosition.row < range.startPosition.row || node.startPosition.column <= range.startPosition.column) && (node.endPosition.row > range.endPosition.row || node.endPosition.column >= range.endPosition.column); } getHighlights(parserState, matches, injectionRanges) { const lineHighlights = new Map; const droppedHighlights = new Map; for (const match of matches) { const node = match.node; const startLine = node.startPosition.row; const endLine = node.endPosition.row; const highlight = { startCol: node.startPosition.column, endCol: node.endPosition.column, group: match.name }; if (!lineHighlights.has(startLine)) { lineHighlights.set(startLine, new Map); droppedHighlights.set(startLine, new Map); } if (lineHighlights.get(startLine)?.has(node.id)) { droppedHighlights.get(startLine)?.set(node.id, lineHighlights.get(startLine)?.get(node.id)); } lineHighlights.get(startLine)?.set(node.id, highlight); if (startLine !== endLine) { for (let line = startLine + 1;line <= endLine; line++) { if (!lineHighlights.has(line)) { lineHighlights.set(line, new Map); } const hl = { startCol: 0, endCol: node.endPosition.column, group: match.name }; lineHighlights.get(line)?.set(node.id, hl); } } } return { highlights: Array.from(lineHighlights.entries()).map(([line, lineHighlights2]) => ({ line, highlights: Array.from(lineHighlights2.values()), droppedHighlights: droppedHighlights.get(line) ? Array.from(droppedHighlights.get(line).values()) : [] })) }; } getSimpleHighlights(matches, injectionRanges) { const highlights = []; const flatInjectionRanges = []; for (const [lang, ranges] of injectionRanges.entries()) { for (const range of ranges) { flatInjectionRanges.push({ ...range, lang }); } } for (const match of matches) { const node = match.node; let isInjection = false; let injectionLang; let containsInjection = false; for (const injRange of flatInjectionRanges) { if (node.startIndex >= injRange.start && node.endIndex <= injRange.end) { isInjection = true; injectionLang = injRange.lang; break; } else if (node.startIndex <= injRange.start && node.endIndex >= injRange.end) { containsInjection = true; break; } } const matchQuery = match._injectedQuery; const patternProperties = matchQuery?.setProperties?.[match.patternIndex]; const concealValue = patternProperties?.conceal ?? match.setProperties?.conceal; const concealLines = patternProperties?.conceal_lines ?? match.setProperties?.conceal_lines; const meta = {}; if (isInjection && injectionLang) { meta.isInjection = true; meta.injectionLang = injectionLang; } if (containsInjection) { meta.containsInjection = true; } if (concealValue !== undefined) { meta.conceal = concealValue; } if (concealLines !== undefined) { meta.concealLines = concealLines; } if (Object.keys(meta).length > 0) { highlights.push([node.startIndex, node.endIndex, match.name, meta]); } else { highlights.push([node.startIndex, node.endIndex, match.name]); } } highlights.sort((a, b) => a[0] - b[0]); return highlights; } async handleResetBuffer(bufferId, version, content) { const parserState = this.bufferParsers.get(bufferId); if (!parserState) { return { warning: "No parser state found for buffer" }; } parserState.content = content; const newTree = parserState.parser.parse(content); if (!newTree) { return { error: "Failed to parse buffer during reset" }; } parserState.tree = newTree; const matches = parserState.queries.highlights.captures(parserState.tree.rootNode); let injectionRanges = new Map; if (parserState.queries.injections) { const injectionResult = await this.processInjections(parserState); matches.push(...injectionResult.captures); injectionRanges = injectionResult.injectionRanges; } return this.getHighlights(parserState, matches, injectionRanges); } disposeBuffer(bufferId) { const parserState = this.bufferParsers.get(bufferId); if (!parserState) { return; } parserState.tree.delete(); parserState.parser.delete(); this.bufferParsers.delete(bufferId); } async handleOneShotHighlight(content, filetype, messageId) { const reusableState = await this.getReusableParser(filetype); if (!reusableState) { self.postMessage({ type: "ONESHOT_HIGHLIGHT_RESPONSE", messageId, hasParser: false, warning: `No parser available for filetype ${filetype}` }); return; } const parseContent = filetype === "markdown" && content.endsWith("```") ? content + ` ` : content; const tree = reusableState.parser.parse(parseContent); if (!tree) { self.postMessage({ type: "ONESHOT_HIGHLIGHT_RESPONSE", messageId, hasParser: false, error: "Failed to parse content" }); return; } try { const matches = reusableState.filetypeParser.queries.highlights.captures(tree.rootNode); let injectionRanges = new Map; if (reusableState.filetypeParser.queries.injections) { const parserState = { parser: reusableState.parser, tree, queries: reusableState.filetypeParser.queries, filetype, content, injectionMapping: reusableState.filetypeParser.injectionMapping }; const injectionResult = await this.processInjections(parserState); matches.push(...injectionResult.captures); injectionRanges = injectionResult.injectionRanges; } const highlights = this.getSimpleHighlights(matches, injectionRanges); self.postMessage({ type: "ONESHOT_HIGHLIGHT_RESPONSE", messageId, hasParser: true, highlights }); } finally { tree.delete(); } } async updateDataPath(dataPath) { this.dataPath = dataPath; this.tsDataPath = path2.join(dataPath, "tree-sitter"); try { await mkdir2(path2.join(this.tsDataPath, "languages"), { recursive: true }); await mkdir2(path2.join(this.tsDataPath, "queries"), { recursive: true }); } catch (error) { throw new Error(`Failed to update data path: ${error}`); } } async clearCache() { if (!this.dataPath || !this.tsDataPath) { throw new Error("No data path configured"); } const { rm } = await import("fs/promises"); try { const treeSitterPath = path2.join(this.dataPath, "tree-sitter"); await rm(treeSitterPath, { recursive: true, force: true }); await mkdir2(path2.join(treeSitterPath, "languages"), { recursive: true }); await mkdir2(path2.join(treeSitterPath, "queries"), { recursive: true }); this.filetypeParsers.clear(); this.filetypeParserPromises.clear(); this.reusableParsers.clear(); this.reusableParserPromises.clear(); } catch (error) { throw new Error(`Failed to clear cache: ${error}`); } } } if (!isMainThread) { let logMessage = function(type, ...args) { self.postMessage({ type: "WORKER_LOG", logType: type, data: args }); }; const worker = new ParserWorker; console.log = (...args) => logMessage("log", ...args); console.error = (...args) => logMessage("error", ...args); console.warn = (...args) => logMessage("warn", ...args); self.onmessage = async (e) => { const { type, bufferId, version, content, filetype, edits, filetypeParser, messageId, dataPath } = e.data; try { switch (type) { case "INIT": try { await worker.initialize({ dataPath }); self.postMessage({ type: "INIT_RESPONSE" }); } catch (error) { self.postMessage({ type: "INIT_RESPONSE", error: error instanceof Error ? error.stack || error.message : String(error) }); } break; case "ADD_FILETYPE_PARSER": worker.addFiletypeParser(filetypeParser); break; case "PRELOAD_PARSER": const maybeParser = await worker.preloadParser(filetype); self.postMessage({ type: "PRELOAD_PARSER_RESPONSE", messageId, hasParser: !!maybeParser }); break; case "INITIALIZE_PARSER": await worker.handleInitializeParser(bufferId, version, content, filetype, messageId); break; case "HANDLE_EDITS": const response = await worker.handleEdits(bufferId, content, edits); if (response.highlights && response.highlights.length > 0) { self.postMessage({ type: "HIGHLIGHT_RESPONSE", bufferId, version, ...response }); } else if (response.warning) { self.postMessage({ type: "WARNING", bufferId, warning: response.warning }); } else if (response.error) { self.postMessage({ type: "ERROR", bufferId, error: response.error }); } break; case "GET_PERFORMANCE": self.postMessage({ type: "PERFORMANCE_RESPONSE", performance: worker.performance, messageId }); break; case "RESET_BUFFER": const resetResponse = await worker.handleResetBuffer(bufferId, version, content); if (resetResponse.highlights && resetResponse.highlights.length > 0) { self.postMessage({ type: "HIGHLIGHT_RESPONSE", bufferId, version, ...resetResponse }); } else if (resetResponse.warning) { self.postMessage({ type: "WARNING", bufferId, warning: resetResponse.warning }); } else if (resetResponse.error) { self.postMessage({ type: "ERROR", bufferId, error: resetResponse.error }); } break; case "DISPOSE_BUFFER": worker.disposeBuffer(bufferId); self.postMessage({ type: "BUFFER_DISPOSED", bufferId }); break; case "ONESHOT_HIGHLIGHT": await worker.handleOneShotHighlight(content, filetype, messageId); break; case "UPDATE_DATA_PATH": try { await worker.updateDataPath(dataPath); self.postMessage({ type: "UPDATE_DATA_PATH_RESPONSE", messageId }); } catch (error) { self.postMessage({ type: "UPDATE_DATA_PATH_RESPONSE", messageId, error: error instanceof Error ? error.message : String(error) }); } break; case "CLEAR_CACHE": try { await worker.clearCache(); self.postMessage({ type: "CLEAR_CACHE_RESPONSE", messageId }); } catch (error) { self.postMessage({ type: "CLEAR_CACHE_RESPONSE", messageId, error: error instanceof Error ? error.message : String(error) }); } break; default: self.postMessage({ type: "ERROR", bufferId, error: `Unknown message type: ${type}` }); } } catch (error) { self.postMessage({ type: "ERROR", bufferId, error: error instanceof Error ? error.stack || error.message : String(error) }); } }; } //# debugId=A12A2CF1481F706164756E2164756E21 //# sourceMappingURL=parser.worker.js.map