react-sql-workbench-embedded
Version:
React wrapper component for the sql-workbench-embedded package, which is a library for creating interactive SQL editors in the browser using DuckDB WASM.
8 lines (7 loc) • 32.4 kB
JavaScript
/**
* React Component for SQL Workbench Embedded v0.1.0
* React wrapper component for the sql-workbench-embedded package, which is a library for creating interactive SQL editors in the browser using DuckDB WASM.
* https://github.com/tobilg/react-sql-workbench-embedded
* (c) 2025 TobiLG <tobilg@gmail.com> - MIT License
*/
import React,{forwardRef,useRef,useImperativeHandle,useEffect,createContext,useState,useContext}from"react";const SQLWorkbenchEmbedded=forwardRef(({initialCode:e="",theme:t="auto",editable:n=!0,showOpenButton:o=!0,className:r="",style:s,onReady:i,onError:a},l)=>{const c=useRef(null),d=useRef(null),u=useRef(!1);return useImperativeHandle(l,()=>({getInstance:()=>d.current,getElement:()=>c.current})),useEffect(()=>{let r=!0;return(async()=>{if(c.current&&!u.current)try{if(await Promise.resolve().then(()=>sqlWorkbenchEmbedded_esm),!r||!c.current)return;if(!window.SQLWorkbench)throw new Error("SQLWorkbench not found on window object");window.SQLWorkbench.init();const s={initialCode:e,theme:t,editable:n,showOpenButton:o};d.current=new window.SQLWorkbench.Embedded(c.current,s),u.current=!0,r&&i&&d.current&&i(d.current)}catch(e){if(!r)return;const t=e instanceof Error?e:new Error(String(e));a&&a(t)}})(),()=>{r=!1,u.current=!1,d.current=null}},[e,t,n,o,i,a]),React.createElement("div",{className:r,style:s},React.createElement("div",{ref:c,className:"sql-workbench-embedded","data-theme":t},e))});SQLWorkbenchEmbedded.displayName="SQLWorkbenchEmbedded";const SQLWorkbenchContext=createContext(null);function SQLWorkbenchProvider({config:e,children:t,onReady:n,onError:o}){const[r,s]=useState(!1),[i,a]=useState(null);return useEffect(()=>{(async()=>{try{if(await Promise.resolve().then(()=>sqlWorkbenchEmbedded_esm),!window.SQLWorkbench)throw new Error("SQLWorkbench not found on window object");e&&window.SQLWorkbench.config({autoInit:!1,...e}),window.SQLWorkbench.init(),s(!0),n&&n()}catch(e){const t=e instanceof Error?e:new Error(String(e));a(t),o&&o(t)}})()},[e,n,o]),React.createElement(SQLWorkbenchContext.Provider,{value:{isReady:r,error:i}},t)}function useSQLWorkbench(){const e=useContext(SQLWorkbenchContext);return e||{isReady:!0,error:null}}const DEFAULT_CONFIG={selector:"pre.sql-workbench-embedded, .sql-workbench-embedded pre",baseUrl:"https://data.sql-workbench.com",theme:"auto",customThemes:{},autoInit:!0,duckdbVersion:"1.31.1-dev1.0",duckdbCDN:"https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm",editable:!0,showOpenButton:!0,initQueries:[]},SQL_KEYWORDS=new Set(["SELECT","FROM","WHERE","JOIN","INNER","LEFT","RIGHT","OUTER","FULL","ON","AND","OR","NOT","IN","IS","NULL","LIKE","BETWEEN","AS","ORDER","BY","GROUP","HAVING","LIMIT","OFFSET","DISTINCT","ALL","INSERT","INTO","VALUES","UPDATE","SET","DELETE","CREATE","DROP","ALTER","TABLE","DATABASE","INDEX","VIEW","UNION","CASE","WHEN","THEN","ELSE","END","EXISTS","WITH","RECURSIVE","CAST","DESC","ASC","COUNT","SUM","AVG","MIN","MAX","ROUND","FLOOR","CEIL","ABS","UPPER","LOWER","TRIM","SUBSTRING","CONCAT","LENGTH","COALESCE","PRIMARY","KEY","FOREIGN","REFERENCES","UNIQUE","CHECK","DEFAULT","AUTO_INCREMENT","CONSTRAINT","CASCADE","NULLS","FIRST","LAST","PARTITION","OVER","ROW_NUMBER","RANK","DENSE_RANK","LEAD","LAG","FIRST_VALUE","LAST_VALUE","WINDOW","ROWS","RANGE","PRECEDING","FOLLOWING","CURRENT","UNBOUNDED","EXTRACT","DATE","TIME","TIMESTAMP","INTERVAL","YEAR","MONTH","DAY","HOUR","MINUTE","SECOND","TRUE","FALSE","BOOLEAN","INTEGER","BIGINT","SMALLINT","DECIMAL","NUMERIC","REAL","DOUBLE","PRECISION","FLOAT","VARCHAR","CHAR","TEXT","BLOB","DATE","DATETIME","ARRAY","STRUCT","MAP"]);function escapeHtml(e){const t=document.createElement("div");t.textContent=e;let n=t.innerHTML;return n=n.replace(/ {2,}/g,e=>" ".repeat(e.length)),e.endsWith(" ")&&(n=n.replace(/ +$/,e=>" ".repeat(e.length))),e.startsWith(" ")&&(n=n.replace(/^ +/,e=>" ".repeat(e.length))),n}function highlightSQL(e){let t="",n=0;for(;n<e.length;){const o=e[n];if(o){if("-"===e[n]&&"-"===e[n+1]||"#"===e[n]){const o=n;for(;n<e.length&&"\n"!==e[n];)n++;t+=`<span class="sql-comment">${escapeHtml(e.substring(o,n))}</span>`;continue}if("/"===e[n]&&"*"===e[n+1]){const o=n;for(n+=2;n<e.length-1&&("*"!==e[n]||"/"!==e[n+1]);)n++;n+=2,t+=`<span class="sql-comment">${escapeHtml(e.substring(o,n))}</span>`;continue}if("'"===e[n]){const o=n;for(n++;n<e.length&&"'"!==e[n];)"\\"===e[n]&&n++,n++;n++,t+=`<span class="sql-string">${escapeHtml(e.substring(o,n))}</span>`;continue}if('"'===e[n]){const o=n;for(n++;n<e.length&&'"'!==e[n];)"\\"===e[n]&&n++,n++;n++,t+=escapeHtml(e.substring(o,n));continue}if("`"===e[n]){const o=n;for(n++;n<e.length&&"`"!==e[n];)n++;n++,t+=escapeHtml(e.substring(o,n));continue}if(/\d/.test(o)){const o=n;for(;n<e.length;){const t=e[n];if(!t||!/[\d.]/.test(t))break;n++}t+=`<span class="sql-number">${escapeHtml(e.substring(o,n))}</span>`;continue}if(/[+\-*/<>=!]/.test(o)){let r=o;n++;const s=e[n];n<e.length&&s&&/[=<>]/.test(s)&&(r+=s,n++),t+=`<span class="sql-operator">${escapeHtml(r)}</span>`;continue}if(/[a-zA-Z_]/.test(o)){const o=n;for(;n<e.length;){const t=e[n];if(!t||!/[a-zA-Z0-9_]/.test(t))break;n++}const r=e.substring(o,n),s=r.toUpperCase();SQL_KEYWORDS.has(s)?t+=`<span class="sql-keyword">${escapeHtml(r)}</span>`:t+=escapeHtml(r);continue}t+=escapeHtml(o),n++}else n++}return t}function debounce(e,t){let n=null;return function(...o){n&&clearTimeout(n),n=setTimeout(()=>e(...o),t)}}function resolvePath(e,t){return e.startsWith("http://")||e.startsWith("https://")?e:e.startsWith("/")?`${window.location.origin}${e}`:`${t.baseUrl.endsWith("/")?t.baseUrl.slice(0,-1):t.baseUrl}/${e.startsWith("./")?e.slice(2):e}`}function extractFilePaths(e){const t=[],n=/'([^']+\.(parquet|csv|json|arrow))'/gi;let o;for(;null!==(o=n.exec(e));)t.push(o[1]);const r=/"([^"]+\.(parquet|csv|json|arrow))"/gi;for(;null!==(o=r.exec(e));)t.push(o[1]);return[...new Set(t)]}function resolvePathsInSQL(e,t){const n=extractFilePaths(e),o=new Map;for(const e of n){const n=resolvePath(e,t);o.set(e,n)}return o}class DuckDBManager{constructor(){this.db=null,this.connection=null,this.initPromise=null,this.registeredFiles=new Set,this.duckdbModule=null,this.initQueriesExecuted=!1,this.initQueriesPromise=null,this.initQueries=[],this.config={version:"1.31.1-dev1.0",cdn:"https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm"}}configure(e){this.db||(this.config={...this.config,...e})}configureInitQueries(e){this.initQueriesExecuted||(this.initQueries=e)}async initialize(){if(!this.db)return this.initPromise||(this.initPromise=this.doInitialize()),this.initPromise}async loadDuckDBModule(){if(this.duckdbModule)return this.duckdbModule;if("undefined"!=typeof window&&window.duckdb)return this.duckdbModule=window.duckdb,this.duckdbModule;try{const e=await import("@duckdb/duckdb-wasm");return this.duckdbModule=e,this.duckdbModule}catch(e){try{const e=`https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@${this.config.version}/+esm`,t=await import(e);return this.duckdbModule=t,"undefined"!=typeof window&&(window.duckdb=t),this.duckdbModule}catch(t){throw new Error(`Failed to load DuckDB. Tried:\n1. Pre-loaded global: Not found\n2. Dynamic import: ${e instanceof Error?e.message:String(e)}\n3. CDN import: ${t instanceof Error?t.message:String(t)}`)}}}async doInitialize(){try{const e=await this.loadDuckDBModule(),t=new e.ConsoleLogger,n=e.getJsDelivrBundles(),o=await e.selectBundle(n),r=await fetch(o.mainWorker);if(!r.ok)throw new Error(`Failed to fetch worker: ${r.status} ${r.statusText}`);const s=await r.blob(),i=URL.createObjectURL(s),a=new Worker(i);this.db=new e.AsyncDuckDB(t,a),await this.db.instantiate(o.mainModule,o.mainWorker),this.connection=await this.db.connect(),URL.revokeObjectURL(i)}catch(e){if(this.initPromise=null,e instanceof Error&&e.message.includes("CORS"))throw new Error("Failed to initialize DuckDB: CORS policy blocked worker loading. This may be a browser security restriction in development mode.");throw new Error(`Failed to initialize DuckDB: ${e instanceof Error?e.message:String(e)}`)}}async executeInitQueries(){if(!this.initQueriesExecuted){if(this.initQueriesPromise)return this.initQueriesPromise;if(this.initQueries.length)return this.initQueriesPromise=(async()=>{try{for(let e=0;e<this.initQueries.length;e++){const t=this.initQueries[e];await this.connection.query(t)}this.initQueriesExecuted=!0}catch(e){throw this.initQueriesPromise=null,new Error(`Initialization query failed: ${e instanceof Error?e.message:String(e)}`)}})(),this.initQueriesPromise;this.initQueriesExecuted=!0}}async registerFile(e,t){if(await this.initialize(),!this.db)throw new Error("DuckDB not initialized");if(!this.registeredFiles.has(t))try{await this.db.registerFileURL(e,t,0,!1),this.registeredFiles.add(t)}catch(t){throw new Error(`Failed to register file ${e}: ${t instanceof Error?t.message:String(t)}`)}}async query(e){if(await this.initialize(),await this.executeInitQueries(),!this.connection)throw new Error("DuckDB connection not available");const t=performance.now();try{const n=await this.connection.query(e),o=performance.now()-t,r=n.schema.fields.map(e=>e.name),s=[];for(let e=0;e<n.numRows;e++){const t=[];for(let o=0;o<n.numCols;o++){const r=n.getChildAt(o);t.push(r?.get(e)??null)}s.push(t)}return{columns:r,rows:s,rowCount:n.numRows,executionTime:o}}catch(e){throw new Error(`Query execution failed: ${e instanceof Error?e.message:String(e)}`)}}async close(){this.connection&&(await this.connection.close(),this.connection=null),this.db&&(await this.db.terminate(),this.db=null),this.initPromise=null,this.registeredFiles.clear(),this.initQueriesExecuted=!1,this.initQueriesPromise=null,this.initQueries=[]}isInitialized(){return null!==this.db}}const duckDBManager=new DuckDBManager,LIGHT_THEME_CONFIG={bgColor:"#ffffff",textColor:"#333333",borderColor:"#e5e5e5",editorBg:"#ffffff",editorText:"#333333",editorFocusBg:"#f3f3f3",controlsBg:"#f3f3f3",primaryBg:"#007acc",primaryText:"#ffffff",primaryHover:"#005a9e",secondaryBg:"#e5e5e5",secondaryText:"#333333",secondaryHover:"#d4d4d4",mutedText:"#6a737d",errorText:"#e51400",errorBg:"#ffebe9",errorBorder:"#e51400",tableHeaderBg:"#f3f3f3",tableHeaderText:"#333333",tableHover:"#f0f0f0",syntaxKeyword:"#0000ff",syntaxString:"#a31515",syntaxNumber:"#098658",syntaxComment:"#008000",syntaxFunction:"#795e26",syntaxOperator:"#000000",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif',editorFontFamily:'"Monaco", "Menlo", "Ubuntu Mono", "Consolas", monospace',fontSize:"14px",editorFontSize:"13px",buttonFontSize:"13px",metadataFontSize:"12px"},DARK_THEME_CONFIG={bgColor:"#252526",textColor:"#d4d4d4",borderColor:"#3e3e42",editorBg:"#1e1e1e",editorText:"#d4d4d4",editorFocusBg:"#252526",controlsBg:"#2d2d30",primaryBg:"#007acc",primaryText:"#ffffff",primaryHover:"#0062a3",secondaryBg:"#3e3e42",secondaryText:"#d4d4d4",secondaryHover:"#4e4e52",mutedText:"#858585",errorText:"#f48771",errorBg:"#5a1d1d",errorBorder:"#f48771",tableHeaderBg:"#2d2d30",tableHeaderText:"#d4d4d4",tableHover:"#2a2d2e",syntaxKeyword:"#569cd6",syntaxString:"#ce9178",syntaxNumber:"#b5cea8",syntaxComment:"#6a9955",syntaxFunction:"#dcdcaa",syntaxOperator:"#d4d4d4",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif',editorFontFamily:'"Monaco", "Menlo", "Ubuntu Mono", "Consolas", monospace',fontSize:"14px",editorFontSize:"13px",buttonFontSize:"13px",metadataFontSize:"12px"},REQUIRED_THEME_KEYS=["bgColor","textColor","borderColor","editorBg","editorText","editorFocusBg","controlsBg","primaryBg","primaryText","primaryHover","secondaryBg","secondaryText","secondaryHover","mutedText","errorText","errorBg","errorBorder","tableHeaderBg","tableHeaderText","tableHover"];function validateThemeColors(e){const t=REQUIRED_THEME_KEYS.filter(t=>!(t in e));if(t.length>0)throw new Error(`Missing required theme variables: ${t.join(", ")}`)}function getThemeConfig(e,t={}){if("light"===e)return LIGHT_THEME_CONFIG;if("dark"===e)return DARK_THEME_CONFIG;const n=t[e];if(!n)throw new Error(`Unknown theme: ${e}`);return n.extends?{..."light"===n.extends?LIGHT_THEME_CONFIG:DARK_THEME_CONFIG,...n.config}:(validateThemeColors(n.config),n.config)}function applyThemeConfig(e,t){e.style.setProperty("--sw-bg-color",t.bgColor),e.style.setProperty("--sw-text-color",t.textColor),e.style.setProperty("--sw-border-color",t.borderColor),e.style.setProperty("--sw-editor-bg",t.editorBg),e.style.setProperty("--sw-editor-text",t.editorText),e.style.setProperty("--sw-editor-focus-bg",t.editorFocusBg),e.style.setProperty("--sw-controls-bg",t.controlsBg),e.style.setProperty("--sw-primary-bg",t.primaryBg),e.style.setProperty("--sw-primary-text",t.primaryText),e.style.setProperty("--sw-primary-hover",t.primaryHover),e.style.setProperty("--sw-secondary-bg",t.secondaryBg),e.style.setProperty("--sw-secondary-text",t.secondaryText),e.style.setProperty("--sw-secondary-hover",t.secondaryHover),e.style.setProperty("--sw-muted-text",t.mutedText),e.style.setProperty("--sw-error-text",t.errorText),e.style.setProperty("--sw-error-bg",t.errorBg),e.style.setProperty("--sw-error-border",t.errorBorder),e.style.setProperty("--sw-table-header-bg",t.tableHeaderBg),e.style.setProperty("--sw-table-header-text",t.tableHeaderText),e.style.setProperty("--sw-table-hover",t.tableHover),t.syntaxKeyword&&e.style.setProperty("--sw-syntax-keyword",t.syntaxKeyword),t.syntaxString&&e.style.setProperty("--sw-syntax-string",t.syntaxString),t.syntaxNumber&&e.style.setProperty("--sw-syntax-number",t.syntaxNumber),t.syntaxComment&&e.style.setProperty("--sw-syntax-comment",t.syntaxComment),t.syntaxFunction&&e.style.setProperty("--sw-syntax-function",t.syntaxFunction),t.syntaxOperator&&e.style.setProperty("--sw-syntax-operator",t.syntaxOperator),t.fontFamily&&e.style.setProperty("--sw-font-family",t.fontFamily),t.editorFontFamily&&e.style.setProperty("--sw-editor-font-family",t.editorFontFamily),t.fontSize&&e.style.setProperty("--sw-font-size",t.fontSize),t.editorFontSize&&e.style.setProperty("--sw-editor-font-size",t.editorFontSize),t.buttonFontSize&&e.style.setProperty("--sw-button-font-size",t.buttonFontSize),t.metadataFontSize&&e.style.setProperty("--sw-metadata-font-size",t.metadataFontSize)}const CSS_STYLES="\n.sql-workbench-container {\n font-family: var(--sw-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif);\n font-size: var(--sw-font-size, 14px);\n border: 1px solid var(--sw-border-color);\n border-radius: 8px;\n overflow: hidden;\n margin: 1rem 0;\n background: var(--sw-bg-color);\n color: var(--sw-text-color);\n}\n\n.sql-workbench-editor-wrapper {\n position: relative;\n background: var(--sw-editor-bg);\n}\n\n.sql-workbench-editor-header {\n position: absolute;\n top: 1rem;\n right: 1rem;\n display: flex;\n gap: 0.5rem;\n z-index: 10;\n}\n\n.sql-workbench-editor {\n font-family: var(--sw-editor-font-family, 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace);\n font-size: var(--sw-editor-font-size, 13px);\n line-height: 1.5;\n padding: 1rem;\n background: var(--sw-editor-bg);\n color: var(--sw-editor-text);\n min-height: 100px;\n overflow: auto;\n white-space: pre-wrap;\n tab-size: 2;\n outline: none;\n direction: ltr;\n text-align: left;\n}\n\n.sql-workbench-editor[contenteditable='true']:focus {\n background: var(--sw-editor-focus-bg);\n}\n\n.sql-workbench-button {\n padding: 0.375rem 0.75rem;\n font-size: var(--sw-button-font-size, 13px);\n font-weight: 500;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.2s;\n font-family: inherit;\n}\n\n.sql-workbench-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.sql-workbench-button-hidden {\n display: none;\n}\n\n.sql-workbench-button-primary {\n background: var(--sw-primary-bg);\n color: var(--sw-primary-text);\n}\n\n.sql-workbench-button-primary:hover:not(:disabled) {\n background: var(--sw-primary-hover);\n}\n\n.sql-workbench-button-secondary {\n background: var(--sw-secondary-bg);\n color: var(--sw-secondary-text);\n}\n\n.sql-workbench-button-secondary:hover:not(:disabled) {\n background: var(--sw-secondary-hover);\n}\n\n.sql-workbench-button-icon {\n padding: 0.375rem;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: auto;\n}\n\n.sql-workbench-button-icon svg {\n width: 16px;\n height: 16px;\n display: block;\n}\n\n.sql-workbench-output {\n padding: 1rem;\n min-height: 20px;\n border-top: 1px solid var(--sw-border-color);\n}\n\n.sql-workbench-output-hidden {\n display: none;\n}\n\n.sql-workbench-output-empty {\n color: var(--sw-muted-text);\n font-style: italic;\n}\n\n.sql-workbench-loading {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n color: var(--sw-muted-text);\n}\n\n.sql-workbench-spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--sw-border-color);\n border-top-color: var(--sw-primary-bg);\n border-radius: 50%;\n animation: sw-spin 0.8s linear infinite;\n}\n\n@keyframes sw-spin {\n to { transform: rotate(360deg); }\n}\n\n.sql-workbench-error {\n color: var(--sw-error-text);\n background: var(--sw-error-bg);\n padding: 0.75rem;\n border-radius: 4px;\n border-left: 3px solid var(--sw-error-border);\n}\n\n.sql-workbench-error-title {\n font-weight: 600;\n margin-bottom: 0.25rem;\n}\n\n.sql-workbench-result-table {\n width: 100%;\n border-collapse: collapse;\n overflow-x: auto;\n display: block;\n max-height: 400px;\n overflow-y: auto;\n}\n\n.sql-workbench-result-table table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.sql-workbench-result-table th {\n background: var(--sw-table-header-bg);\n color: var(--sw-table-header-text);\n font-weight: 600;\n text-align: left;\n padding: 0.5rem;\n border-bottom: 2px solid var(--sw-border-color);\n position: sticky;\n top: 0;\n}\n\n.sql-workbench-result-table td {\n padding: 0.5rem;\n border-bottom: 1px solid var(--sw-border-color);\n}\n\n.sql-workbench-result-table tr:hover {\n background: var(--sw-table-hover);\n}\n\n.sql-workbench-metadata {\n margin-top: 0.5rem;\n padding-top: 0.5rem;\n border-top: 1px solid var(--sw-border-color);\n font-size: var(--sw-metadata-font-size, 12px);\n color: var(--sw-muted-text);\n display: flex;\n gap: 1rem;\n}\n\n/* Responsive adjustments for mobile */\n@media (max-width: 480px) {\n .sql-workbench-editor-header {\n flex-direction: column;\n width: auto;\n }\n\n .sql-workbench-button {\n font-size: 12px;\n padding: 0.4rem 0.75rem;\n }\n\n .sql-workbench-metadata {\n flex-direction: column;\n gap: 0.25rem;\n }\n}\n\n/* Syntax highlighting classes */\n.sql-keyword { color: var(--sw-syntax-keyword, #0000ff); font-weight: 600; }\n.sql-string { color: var(--sw-syntax-string, #a31515); }\n.sql-number { color: var(--sw-syntax-number, #098658); }\n.sql-comment { color: var(--sw-syntax-comment, #008000); font-style: italic; }\n.sql-function { color: var(--sw-syntax-function, #795e26); }\n.sql-operator { color: var(--sw-syntax-operator, #000000); }\n";function injectStyles(){if("undefined"==typeof document)return;const e="sql-workbench-embedded-styles";if(document.getElementById(e))return;const t=document.createElement("style");t.id=e,t.textContent=CSS_STYLES,document.head.appendChild(t)}class Embedded{constructor(e,t={}){this.container=null,this.editorElement=null,this.outputElement=null,this.runButton=null,this.resetButton=null,this.openButton=null,this.state="idle",this.destroyed=!1,this.element=e;const n=e.getAttribute("data-theme");this.options={...DEFAULT_CONFIG,...t,theme:t.theme??n??DEFAULT_CONFIG.theme,initialCode:t.initialCode??this.extractInitialCode()},this.initialCode=this.options.initialCode,this.init()}extractInitialCode(){const e=this.element.querySelector("code");return e?e.textContent?.trim()??"":this.element.textContent?.trim()??""}init(){this.createUI(),this.attachEventListeners(),this.updateEditor()}createUI(){this.container=document.createElement("div"),this.container.className="sql-workbench-container";const e=this.resolveTheme();this.container.setAttribute("data-theme",e);try{const t=getThemeConfig(e,this.options.customThemes);applyThemeConfig(this.container,t)}catch(t){const n="dark"===e?"dark":"light";this.container.setAttribute("data-theme",n)}const t=document.createElement("div");t.className="sql-workbench-editor-wrapper";const n=document.createElement("div");n.className="sql-workbench-editor-header",this.options.showOpenButton&&(this.openButton=document.createElement("button"),this.openButton.className="sql-workbench-button sql-workbench-button-secondary sql-workbench-button-icon sql-workbench-button-open",this.openButton.setAttribute("aria-label","Open in SQL Workbench"),this.openButton.setAttribute("title","Open in SQL Workbench"),this.openButton.innerHTML='<svg fill="currentColor" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 209.322 209.322">\n <g>\n <path d="M105.572,101.811c9.889-6.368,27.417-16.464,28.106-42.166c0.536-20.278-9.971-49.506-49.155-50.878\n C53.041,7.659,39.9,28.251,36.071,46.739l-0.928-0.126c-1.932,0-3.438,1.28-5.34,2.889c-2.084,1.784-4.683,3.979-7.792,4.308\n c-3.573,0.361-8.111-1.206-11.698-2.449c-4.193-1.431-6.624-2.047-8.265-0.759c-1.503,1.163-2.178,3.262-2.028,6.226\n c0.331,6.326,4.971,18.917,16.016,25.778c7.67,4.765,16.248,5.482,20.681,5.482c0.006,0,0.006,0,0.006,0\n c2.37,0,4.945-0.239,7.388-0.726c2.741,4.218,5.228,7.476,6.037,9.752c2.054,5.851-27.848,25.087-27.848,55.01\n c0,29.916,22.013,48.475,56.727,48.475h55.004c30.593,0,70.814-29.908,75.291-92.48C180.781,132.191,167.028,98.15,105.572,101.811\n z M18.941,77.945C8.775,71.617,4.992,58.922,5.294,55.525c0.897,0.24,2.194,0.689,3.228,1.042\n c4.105,1.415,9.416,3.228,14.068,2.707c4.799-0.499,8.253-3.437,10.778-5.574c0.607-0.509,1.393-1.176,1.872-1.491\n c0.87,0.315,0.962,0.693,1.176,3.14c0.196,2.26,0.473,5.37,2.362,9.006c1.437,2.761,3.581,5.705,5.646,8.542\n c1.701,2.336,4.278,5.871,4.535,6.404c-0.445,1.184-4.907,3.282-12.229,3.282C30.177,82.591,23.69,80.904,18.941,77.945z\n M56.86,49.368c0-4.938,4.001-8.943,8.931-8.943c4.941,0,8.942,4.005,8.942,8.943c0,4.931-4.001,8.942-8.942,8.942\n C60.854,58.311,56.86,54.299,56.86,49.368z M149.159,155.398l-20.63,11.169l13.408,9.293c0,0-49.854,15.813-72.198-6.885\n c-11.006-11.16-13.06-28.533,4.124-38.84c17.184-10.312,84.609,3.943,84.609,3.943L134.295,147.8L149.159,155.398z"/>\n </g>\n </svg>',n.appendChild(this.openButton)),this.resetButton=document.createElement("button"),this.resetButton.className="sql-workbench-button sql-workbench-button-secondary sql-workbench-button-reset sql-workbench-button-hidden",this.resetButton.textContent="Reset",this.resetButton.setAttribute("aria-label","Reset to original code"),this.runButton=document.createElement("button"),this.runButton.className="sql-workbench-button sql-workbench-button-primary sql-workbench-button-run",this.runButton.textContent="Run",this.runButton.setAttribute("aria-label","Execute SQL query"),n.appendChild(this.resetButton),n.appendChild(this.runButton),this.editorElement=document.createElement("div"),this.editorElement.className="sql-workbench-editor",this.editorElement.contentEditable=String(this.options.editable),this.editorElement.spellcheck=!1,this.editorElement.setAttribute("role","textbox"),this.editorElement.setAttribute("aria-label","SQL Editor"),this.editorElement.setAttribute("aria-multiline","true"),this.editorElement.textContent=this.initialCode,t.appendChild(n),t.appendChild(this.editorElement),this.outputElement=document.createElement("div"),this.outputElement.className="sql-workbench-output sql-workbench-output-hidden",this.outputElement.setAttribute("role","region"),this.outputElement.setAttribute("aria-label","Query results"),this.outputElement.setAttribute("aria-live","polite"),this.container.appendChild(t),this.container.appendChild(this.outputElement),this.element.parentNode?.replaceChild(this.container,this.element)}resolveTheme(){return"light"===this.options.theme||"dark"===this.options.theme||this.options.theme&&"auto"!==this.options.theme?this.options.theme:"undefined"!=typeof window&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}attachEventListeners(){if(this.runButton?.addEventListener("click",()=>this.run()),this.resetButton?.addEventListener("click",()=>this.reset()),this.openButton?.addEventListener("click",()=>this.openInSQLWorkbench()),this.editorElement?.addEventListener("keydown",e=>{if((e.ctrlKey||e.metaKey)&&e.shiftKey&&"Enter"===e.key)return e.preventDefault(),void this.openInSQLWorkbench();if((e.ctrlKey||e.metaKey)&&"Enter"===e.key&&!e.shiftKey)return e.preventDefault(),void this.run();if((e.ctrlKey||e.metaKey)&&"Backspace"===e.key){if("loading"===this.state)return;return e.preventDefault(),void this.reset()}return"Enter"!==e.key||e.shiftKey||e.ctrlKey||e.metaKey?"Tab"===e.key?(e.preventDefault(),void this.insertText(" ")):void 0:(e.preventDefault(),void this.insertText("\n"))}),this.options.editable){const e=debounce(()=>this.updateEditor(),150);this.editorElement?.addEventListener("input",e)}}updateEditor(){if(!this.editorElement)return;const e=this.getCode(),t=window.getSelection();let n=0;if(this.options.editable&&t&&t.rangeCount>0){const e=t.getRangeAt(0),o=e.cloneRange();o.selectNodeContents(this.editorElement),o.setEnd(e.endContainer,e.endOffset),n=o.toString().length}const o=highlightSQL(e);if(this.editorElement.innerHTML=o,this.options.editable&&n>0)try{this.setCursorPosition(n)}catch{}}insertText(e){const t=window.getSelection();if(!t||!this.editorElement)return;t.isCollapsed||t.deleteFromDocument();const n=t.getRangeAt(0),o=n.cloneRange();o.selectNodeContents(this.editorElement),o.setEnd(n.endContainer,n.endOffset);const r=o.toString().length,s=this.getCode(),i=highlightSQL(s.substring(0,r)+e+s.substring(r));this.editorElement.innerHTML=i,this.setCursorPosition(r+e.length);const a=new Event("input",{bubbles:!0});this.editorElement.dispatchEvent(a)}setCursorPosition(e){if(!this.editorElement)return;const t=window.getSelection();if(!t)return;let n=0;const o=document.createNodeIterator(this.editorElement,NodeFilter.SHOW_TEXT);let r=o.nextNode(),s=!1;for(;r;){const i=r.textContent?.length??0;if(n+i>=e){const o=document.createRange(),a=Math.min(e-n,i);o.setStart(r,a),o.collapse(!0),t.removeAllRanges(),t.addRange(o),s=!0;break}n+=i,r=o.nextNode()}if(!s&&this.editorElement.lastChild){const e=document.createRange();e.selectNodeContents(this.editorElement),e.collapse(!1),t.removeAllRanges(),t.addRange(e)}}getCode(){return this.editorElement?.textContent??""}setCode(e){this.editorElement&&(this.editorElement.textContent=e,this.updateEditor())}async run(){if("loading"===this.state)return;const e=this.getCode();if(!e.trim())return void this.showError("No SQL query to execute");this.setState("loading"),this.showLoading();const t=performance.now();try{duckDBManager.isInitialized()||(duckDBManager.configure({version:this.options.duckdbVersion,cdn:this.options.duckdbCDN}),this.options.initQueries&&this.options.initQueries.length>0&&duckDBManager.configureInitQueries(this.options.initQueries));const n=resolvePathsInSQL(e,{baseUrl:this.options.baseUrl});for(const[e,t]of n.entries()){const n=e.split("/").pop()??e;await duckDBManager.registerFile(n,t)}const o=await duckDBManager.query(e),r=performance.now()-t;r<200&&await new Promise(e=>setTimeout(e,200-r)),this.setState("success"),this.showResult(o),this.showResetButton()}catch(e){const n=performance.now()-t;n<200&&await new Promise(e=>setTimeout(e,200-n)),this.setState("error"),this.showError(e instanceof Error?e.message:String(e)),this.showResetButton()}}reset(){this.setCode(this.initialCode),this.setState("idle"),this.outputElement&&(this.outputElement.className="sql-workbench-output sql-workbench-output-hidden",this.outputElement.textContent=""),this.resetButton&&this.resetButton.classList.add("sql-workbench-button-hidden")}setState(e){this.state=e,this.runButton&&this.resetButton&&(this.runButton.disabled="loading"===e,this.resetButton.disabled="loading"===e)}showResetButton(){this.resetButton&&this.resetButton.classList.remove("sql-workbench-button-hidden")}showLoading(){this.outputElement&&(this.outputElement.className="sql-workbench-output sql-workbench-loading",this.outputElement.innerHTML='\n <div class="sql-workbench-loading">\n <div class="sql-workbench-spinner"></div>\n <span>Executing query...</span>\n </div>\n ')}showError(e){this.outputElement&&(this.outputElement.className="sql-workbench-output",this.outputElement.innerHTML=`\n <div class="sql-workbench-error">\n <div class="sql-workbench-error-title">Error</div>\n <div>${this.escapeHtml(e)}</div>\n </div>\n `)}showResult(e){if(!this.outputElement)return;if(this.outputElement.className="sql-workbench-output",0===e.rowCount)return void(this.outputElement.innerHTML=`\n <div>Query executed successfully. No rows returned.</div>\n <div class="sql-workbench-metadata">\n <span>Execution time: ${e.executionTime.toFixed(2)}ms</span>\n </div>\n `);let t='<div class="sql-workbench-result-table"><table><thead><tr>';for(const n of e.columns)t+=`<th>${this.escapeHtml(n)}</th>`;t+="</tr></thead><tbody>";for(const n of e.rows){t+="<tr>";for(const e of n){const n=null==e?"NULL":String(e);t+=`<td>${this.escapeHtml(n)}</td>`}t+="</tr>"}t+="</tbody></table></div>",t+=`\n <div class="sql-workbench-metadata">\n <span>${e.rowCount} row${1===e.rowCount?"":"s"}</span>\n <span>${e.columns.length} column${1===e.columns.length?"":"s"}</span>\n <span>Execution time: ${e.executionTime.toFixed(2)}ms</span>\n </div>\n `,this.outputElement.innerHTML=t}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}encodeQueryForURL(e){return btoa(unescape(encodeURIComponent(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}openInSQLWorkbench(){const e=this.getCode();if(!e.trim())return;let t=e;this.options.initQueries&&this.options.initQueries.length>0&&(t=this.options.initQueries.join(";\n")+";\n\n"+e);const n=`https://sql-workbench.com/#queries=v1,${this.encodeQueryForURL(t)}`;window.open(n,"_blank","noopener")}destroy(){this.destroyed||(this.container?.remove(),this.container=null,this.editorElement=null,this.outputElement=null,this.runButton=null,this.resetButton=null,this.openButton=null,this.destroyed=!0)}isDestroyed(){return this.destroyed}getContainer(){return this.container}}const embedInstances=new WeakMap,allEmbeds=[];let globalConfig={...DEFAULT_CONFIG};function config(e){globalConfig={...globalConfig,...e}}function getConfig(){return{...globalConfig}}function init(){"undefined"!=typeof document&&(injectStyles(),document.querySelectorAll(globalConfig.selector).forEach(e=>{if(embedInstances.has(e))return;const{theme:t,...n}=globalConfig,o=new Embedded(e,n),r=o.getContainer();r&&(embedInstances.set(r,o),allEmbeds.push(o))}),setupMutationObserver())}function setupMutationObserver(){"undefined"!=typeof window&&window.MutationObserver&&new MutationObserver(e=>{for(const t of e)"childList"===t.type&&t.removedNodes.length>0&&t.removedNodes.forEach(e=>{if(e instanceof HTMLElement){const t=embedInstances.get(e);if(t&&!t.isDestroyed()){t.destroy();const e=allEmbeds.indexOf(t);e>-1&&allEmbeds.splice(e,1)}}})}).observe(document.body,{childList:!0,subtree:!0})}function destroy(){allEmbeds.forEach(e=>{e.isDestroyed()||e.destroy()}),allEmbeds.length=0,duckDBManager.close().catch(e=>{})}"undefined"!=typeof document&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{globalConfig.autoInit&&init()}):setTimeout(()=>{globalConfig.autoInit&&init()},0));const SQLWorkbench={Embedded:Embedded,init:init,destroy:destroy,config:config,getConfig:getConfig};"undefined"!=typeof window&&(window.SQLWorkbench=SQLWorkbench);const sqlWorkbenchEmbedded_esm=Object.freeze(Object.defineProperty({__proto__:null,Embedded:Embedded,SQLWorkbench:SQLWorkbench,default:SQLWorkbench},Symbol.toStringTag,{value:"Module"}));export{SQLWorkbenchEmbedded,SQLWorkbenchProvider,useSQLWorkbench};