sql-workbench-embedded
Version:
Lightweight JavaScript library that transforms static SQL code blocks into interactive, browser-based SQL execution environments using DuckDB WASM
786 lines (616 loc) • 21.1 kB
Markdown
A lightweight JavaScript library that transforms static SQL code blocks into interactive, browser-based SQL execution environments using DuckDB WASM.
**Repository**: [https://github.com/tobilg/sql-workbench-embedded](https://github.com/tobilg/sql-workbench-embedded)
- **Zero Backend Required**: All SQL execution happens in the browser
- **Lightweight**: 9.54 kB gzipped bundle (29.81 kB minified)
- **Easy Integration**: Just add a CSS class to your code blocks
- **Interactive Editing**: Edit SQL queries with real-time syntax highlighting
- **Framework Agnostic**: Works with vanilla JS, React, Vue, and more
- **Privacy-Focused**: No data transmission to external servers
- **Lazy Loading**: DuckDB WASM loads only when needed
- **Init Queries**: Execute initialization queries once for extension management
- **Path Resolution**: Automatic resolution of relative file paths in SQL queries
- **Flexible Theming**: Three-tier priority system (data-attribute > config > default)
- **Custom Themes**: Create themes that extend built-ins or define new color schemes
- **Typography Customization**: Customize fonts and sizes per theme
## Quick Start
### Via CDN
#### unpkg
```html
<!DOCTYPE html>
<html>
<head>
<title>SQL Workbench Example</title>
</head>
<body>
<pre class="sql-workbench-embedded">
<code>SELECT 'Hello, World!' AS greeting;</code>
</pre>
<!-- Latest version -->
<script src="https://unpkg.com/sql-workbench-embedded@latest"></script>
<!-- Specific version (recommended for production) -->
<script src="https://unpkg.com/sql-workbench-embedded@0.1.4"></script>
</body>
</html>
```
```html
<!DOCTYPE html>
<html>
<head>
<title>SQL Workbench Example</title>
</head>
<body>
<pre class="sql-workbench-embedded">
<code>SELECT 'Hello, World!' AS greeting;</code>
</pre>
<!-- Latest version -->
<script src="https://cdn.jsdelivr.net/npm/sql-workbench-embedded@latest"></script>
<!-- Specific version (recommended for production) -->
<script src="https://cdn.jsdelivr.net/npm/sql-workbench-embedded@0.1.4"></script>
</body>
</html>
```
```bash
npm install
npm run dev
npm run build
npm run preview:prod
```
- **Basic Example**: [examples/index.html](examples/index.html) - Vanilla JavaScript integration
- **Init Queries Example**: [examples/init-queries.html](examples/init-queries.html) - DuckDB extension management with initQueries
- **unpkg CDN (UMD)**: [examples/unpkg.html](examples/unpkg.html) - Loading from unpkg as UMD module
- **unpkg CDN (ESM)**: [examples/unpkg-esm.html](examples/unpkg-esm.html) - Loading from unpkg as ES module
- **Typography Example**: [examples/typography.html](examples/typography.html) - Font customization examples
- **React Example**: [examples/react.html](examples/react.html) - React component integration
- **Vue Example**: [examples/vue.html](examples/vue.html) - Vue 3 component integration
## Usage
### Automatic Initialization
By default, the library automatically scans for elements with the class `sql-workbench-embedded` and transforms them:
```html
<pre class="sql-workbench-embedded">
<code>
SELECT * FROM generate_series(1, 10);
</code>
</pre>
<!-- You can also set the theme directly on the element -->
<pre class="sql-workbench-embedded" data-theme="dark">
<code>
SELECT * FROM users WHERE active = true;
</code>
</pre>
```
```javascript
import { SQLWorkbench } from 'sql-workbench-embedded';
// Configure globally
SQLWorkbench.config({
selector: '.my-sql-code',
theme: 'dark',
baseUrl: 'https://my-data-server.com',
});
// Initialize all embeds
SQLWorkbench.init();
// Or create a single embed programmatically
const embed = new SQLWorkbench.Embedded(element, {
editable: true,
theme: 'light',
});
```
```html
<!-- Using unpkg -->
<script src="https://unpkg.com/sql-workbench-embedded@0.1.4"></script>
<!-- Or using jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/sql-workbench-embedded@0.1.4"></script>
<script>
// Configure globally
SQLWorkbench.config({
selector: '.my-sql-code',
theme: 'dark',
baseUrl: 'https://my-data-server.com',
});
// Initialize all embeds
SQLWorkbench.init();
// Or create a single embed programmatically
const embed = new SQLWorkbench.Embedded(element, {
editable: true,
theme: 'light',
});
</script>
```
```javascript
SQLWorkbench.config({
selector: 'pre.sql-workbench-embedded, .sql-workbench-embedded pre', // CSS selector for auto-discovery
baseUrl: 'https://data.sql-workbench.com', // Base URL for file paths
theme: 'auto', // 'light', 'dark', or 'auto'
autoInit: true, // Auto-initialize on DOMContentLoaded
duckdbVersion: '1.31.1-dev1.0', // DuckDB version
duckdbCDN: 'https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm',
editable: true, // Allow code editing
showOpenButton: true, // Show "Open in SQL Workbench" button
initQueries: [], // Initialization queries to execute once before first user query
});
```
```javascript
const embed = new SQLWorkbench.Embedded(element, {
initialCode: 'SELECT 1;',
theme: 'dark',
editable: false,
showOpenButton: false, // Hide "Open in SQL Workbench" button for this instance
});
```
The `initQueries` configuration allows you to execute SQL queries once before any user query runs. This is perfect for installing and loading DuckDB extensions, setting configuration options, or creating user-defined functions.
- ✅ Executes only once across all embeds on the page
- ✅ Runs sequentially in array order
- ✅ Lazy execution - only when the first "Run" button is pressed
- ✅ All embeds share the same DuckDB instance and extensions
```javascript
SQLWorkbench.config({
initQueries: [
"INSTALL spatial",
"LOAD spatial",
"INSTALL a5 FROM community",
"LOAD a5"
]
});
```
Now all embeds can use spatial functions and community extensions:
```html
<pre class="sql-workbench-embedded">
SELECT ST_Distance(
ST_Point(-74.0060, 40.7128),
ST_Point(-118.2437, 34.0522)
) / 1000 AS distance_km;
</pre>
<pre class="sql-workbench-embedded">
-- Generate GeoJSON for A5 cell
SELECT ST_AsGeoJSON(
ST_MakePolygon(
ST_MakeLine(
list_transform(
a5_cell_to_boundary(a5_lonlat_to_cell(-3.7037, 40.41677, 10)),
x -> ST_Point(x[1], x[2])
)
)
)
) as geojson;
</pre>
```
```javascript
SQLWorkbench.config({
initQueries: [
"SET memory_limit='2GB'",
"SET threads=4"
]
});
```
```javascript
SQLWorkbench.config({
initQueries: [
"CREATE MACRO add_tax(price, rate) AS price * (1 + rate)",
"CREATE MACRO full_name(first, last) AS first || ' ' || last"
]
});
```
If an initialization query fails:
- The error is shown to the user
- The user query is not executed
- The init query state is reset, allowing retry on next run
See [examples/init-queries.html](examples/init-queries.html) for a complete working example with the spatial and a5 community extensions.
Themes are resolved in the following priority order (highest to lowest):
1. **HTML `data-theme` attribute** - `<pre data-theme="ocean">` (highest)
2. **Per-instance options** - `new Embedded(element, { theme: 'dark' })`
3. **Global configuration** - `SQLWorkbench.config({ theme: 'auto' })` (lowest)
You can define custom themes that either extend existing light/dark themes or define completely new color schemes.
Custom themes are automatically inherited by programmatic embeds, so you only need to define them once globally.
```javascript
SQLWorkbench.config({
customThemes: {
// Extend existing theme with custom colors
ocean: {
extends: 'dark',
config: {
primaryBg: '#0ea5e9',
primaryHover: '#0284c7',
editorBg: '#1e3a5f',
// Optionally customize syntax highlighting
syntaxKeyword: '#4fc3f7',
syntaxString: '#80cbc4'
}
},
// Standalone theme with all colors defined
sunset: {
config: {
bgColor: '#fef3c7',
textColor: '#92400e',
borderColor: '#f59e0b',
editorBg: '#fef7cd',
editorText: '#92400e',
editorFocusBg: '#fef3c7',
controlsBg: '#fef7cd',
primaryBg: '#f97316',
primaryText: '#ffffff',
primaryHover: '#ea580c',
secondaryBg: '#f59e0b',
secondaryText: '#92400e',
secondaryHover: '#d97706',
mutedText: '#a16207',
errorText: '#dc2626',
errorBg: '#fef2f2',
errorBorder: '#f87171',
tableHeaderBg: '#fef3c7',
tableHeaderText: '#92400e',
tableHover: '#fef7cd'
}
}
},
theme: 'ocean' // Global default theme
});
// Use via data-attribute (highest priority)
// <pre class="sql-workbench-embedded" data-theme="sunset">
// Or programmatically (inherits customThemes automatically)
new SQLWorkbench.Embedded(element, {
theme: 'ocean' // Overrides global default
});
```
When defining custom themes, you can override any of these properties:
**Color Properties:**
- `bgColor` - Main background color
- `textColor` - Primary text color
- `borderColor` - Border color
- `editorBg` - Editor background
- `editorText` - Editor text color
- `editorFocusBg` - Editor focus background
- `controlsBg` - Controls background
- `primaryBg` - Primary button background
- `primaryText` - Primary button text
- `primaryHover` - Primary button hover
- `secondaryBg` - Secondary button background
- `secondaryText` - Secondary button text
- `secondaryHover` - Secondary button hover
- `mutedText` - Muted text color
- `errorText` - Error text color
- `errorBg` - Error background
- `errorBorder` - Error border color
- `tableHeaderBg` - Table header background
- `tableHeaderText` - Table header text
- `tableHover` - Table row hover background
**Syntax Highlighting Colors (Optional):**
- `syntaxKeyword` - SQL keywords (SELECT, FROM, WHERE, etc.)
- `syntaxString` - String literals
- `syntaxNumber` - Numeric values
- `syntaxComment` - SQL comments
- `syntaxFunction` - Function names
- `syntaxOperator` - Operators (+, -, =, etc.)
**Typography Properties (Optional):**
- `fontFamily` - Font family for container and UI elements
- `editorFontFamily` - Font family for the SQL editor
- `fontSize` - Base font size (e.g., '14px', '1rem')
- `editorFontSize` - Font size for the SQL editor
- `buttonFontSize` - Font size for buttons
- `metadataFontSize` - Font size for metadata text
### Typography Customization
You can customize font families and sizes in your themes:
```javascript
SQLWorkbench.config({
customThemes: {
// Large accessible theme for better readability
'large-accessible': {
extends: 'light',
config: {
fontSize: '18px',
editorFontSize: '16px',
buttonFontSize: '16px',
metadataFontSize: '14px',
}
},
// Custom editor font with ligatures
'fira-code': {
extends: 'dark',
config: {
editorFontFamily: '"Fira Code", "JetBrains Mono", monospace',
editorFontSize: '15px',
}
},
// Compact theme for dense displays
'compact': {
extends: 'dark',
config: {
fontSize: '12px',
editorFontSize: '12px',
buttonFontSize: '12px',
metadataFontSize: '10px',
}
}
}
});
```
See [examples/typography.html](examples/typography.html) for a complete demonstration of typography customization.
- **With `extends`**: Only define the properties you want to override. The base theme provides defaults for all others.
- **Without `extends`**: You must define all required color properties. The library will throw an error if any are missing.
The [sql-workbench-embedded-themes](https://github.com/tobilg/sql-workbench-embedded-themes) package provides 66 ready-to-use themes converted from popular CodeMirror 5 themes (50 dark + 16 light themes).
```bash
npm install sql-workbench-embedded-themes
```
```javascript
import { SQLWorkbench } from 'sql-workbench-embedded';
import { dracula, monokai, elegant } from 'sql-workbench-embedded-themes';
// Configure with pre-built themes
SQLWorkbench.config({
customThemes: {
dracula: {
config: dracula.config
},
monokai: {
config: monokai.config
}
},
theme: 'dracula'
});
```
For browser usage without a bundler, load individual theme bundles (~1.4KB each):
```html
<!DOCTYPE html>
<html>
<head>
<title>SQL Workbench with Themes</title>
</head>
<body>
<pre class="sql-workbench-embedded">
<code>SELECT * FROM generate_series(1, 10);</code>
</pre>
<!-- Load SQL Workbench -->
<script src="https://unpkg.com/sql-workbench-embedded@latest"></script>
<!-- Load specific themes from CDN -->
<script src="https://unpkg.com/sql-workbench-embedded-themes/dist/umd/themes/dracula.js"></script>
<script src="https://unpkg.com/sql-workbench-embedded-themes/dist/umd/themes/monokai.js"></script>
<script>
// Themes are available on window.SQLWorkbenchThemes
SQLWorkbench.config({
customThemes: {
dracula: {
config: window.SQLWorkbenchThemes.dracula.config
},
monokai: {
config: window.SQLWorkbenchThemes.monokai.config
}
},
theme: 'dracula'
});
</script>
</body>
</html>
```
The package includes 66 themes categorized as:
- **Dark themes (50)**: dracula, monokai, material, nord, oceanic-next, and many more
- **Light themes (16)**: elegant, idea, neat, paraiso-light, and more
For a complete list of available themes, visit the [sql-workbench-embedded-themes repository](https://github.com/tobilg/sql-workbench-embedded-themes).
## Path Resolution
The library automatically resolves relative file paths in SQL queries:
```sql
-- Relative path
SELECT * FROM 'data.parquet';
-- Resolves to: https://data.sql-workbench.com/data.parquet
-- Dot-relative path
SELECT * FROM './path/to/data.parquet';
-- Resolves to: https://data.sql-workbench.com/path/to/data.parquet
-- Absolute path
SELECT * FROM '/data.parquet';
-- Resolves to: https://your-domain.com/data.parquet
-- Already absolute URL (unchanged)
SELECT * FROM 'https://example.com/data.parquet';
```
Configure the base URL:
```javascript
SQLWorkbench.config({
baseUrl: 'https://my-data-cdn.com',
});
```
Each embed includes an "Open in SQL Workbench" button (enabled by default) that opens the current query in the full [SQL Workbench](https://sql-workbench.com) web application. The query is encoded in the URL hash using URL-safe Base64 encoding for sharing and persistence.
To disable this button globally:
```javascript
SQLWorkbench.config({
showOpenButton: false,
});
```
Or for a specific instance:
```javascript
const embed = new SQLWorkbench.Embedded(element, {
showOpenButton: false,
});
```
- **Ctrl+Enter** (Mac: **Cmd+Enter**): Execute query
- **Ctrl+Shift+Enter** (Mac: **Cmd+Shift+Enter**): Open in SQL Workbench
- **Ctrl+Backspace** (Mac: **Cmd+Backspace**): Reset to original code
- **Tab**: Navigate between buttons
Initialize all embeds matching the configured selector.
Destroy all embeds and cleanup resources.
Set global configuration options.
Class for creating individual embeds.
```javascript
const embed = new SQLWorkbench.Embedded(element, options);
// Methods
embed.run(); // Execute query
embed.destroy(); // Cleanup
embed.isDestroyed(); // Check if destroyed
embed.getContainer(); // Get container element
```
**Option 1: CDN (Recommended for quick setup)**
```html
<!-- Add to your HTML head -->
<script type="module" src="https://unpkg.com/sql-workbench-embedded@0.1.4/dist/sql-workbench-embedded.esm.js"></script>
```
**Option 2: npm**
```bash
npm install sql-workbench-embedded
```
```jsx
import { useRef, useEffect } from 'react';
function SQLWorkbenchEmbedded({ code, options }) {
const containerRef = useRef(null);
const embedRef = useRef(null);
useEffect(() => {
if (containerRef.current && window.SQLWorkbench) {
// Create a pre element with the SQL code
const preElement = document.createElement('pre');
preElement.className = 'sql-workbench-embedded';
preElement.innerHTML = `<code>${code}</code>`;
containerRef.current.appendChild(preElement);
// Initialize the embed
embedRef.current = new window.SQLWorkbench.Embedded(preElement, options);
}
return () => {
embedRef.current?.destroy();
};
}, [code, options]);
return <div ref={containerRef} />;
}
// Usage in your app
function App() {
return (
<SQLWorkbenchEmbedded
code="SELECT 'Hello, World!' AS greeting;"
options={{ editable: true, theme: 'light' }}
/>
);
}
```
```vue
<template>
<div ref="container"></div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue';
export default {
props: {
code: String,
options: Object
},
setup(props) {
const containerRef = ref(null);
const embedRef = ref(null);
onMounted(() => {
if (containerRef.value && window.SQLWorkbench) {
// Create a pre element with the SQL code
const preElement = document.createElement('pre');
preElement.className = 'sql-workbench-embedded';
preElement.innerHTML = `<code>${props.code}</code>`;
containerRef.value.appendChild(preElement);
// Initialize the embed
embedRef.value = new window.SQLWorkbench.Embedded(preElement, props.options);
}
});
onUnmounted(() => {
embedRef.value?.destroy();
});
return {
containerRef
};
}
};
</script>
```
```vue
<template>
<div ref="container"></div>
</template>
<script>
export default {
props: ['code', 'options'],
mounted() {
if (this.$refs.container && window.SQLWorkbench) {
const element = document.createElement('pre');
element.className = 'sql-workbench-embedded';
element.innerHTML = `<code>${this.code}</code>`;
this.$refs.container.appendChild(element);
this.embed = new window.SQLWorkbench.Embedded(element, this.options);
}
},
beforeUnmount() {
this.embed?.destroy();
},
};
</script>
```
The library is optimized for production with minimal bundle size:
- **UMD Bundle**: 29.81 kB minified, 9.54 kB gzipped
- **ES Module**: 30.59 kB minified, 9.59 kB gzipped
- **DuckDB WASM**: Loaded separately from CDN (~5MB on first use)
### Size Breakdown
- **SQL Workbench Embed**: ~20 kB (UI, syntax highlighting, path resolution, theming)
- **DuckDB Client Library**: ~6 kB (minimal DuckDB bindings)
- **Build Overhead**: ~3 kB (UMD wrapper, utilities)
### Performance Impact
- **Initial Load**: 9.54 kB gzipped (extremely lightweight)
- **First Query**: Additional ~5MB for DuckDB WASM binary (cached thereafter)
- **Subsequent Loads**: Only 9.54 kB (DuckDB cached)
The production build maintains a compact size while providing full functionality including flexible theming and typography customization.
## Browser Support
- Chrome/Edge 88+
- Firefox 89+
- Safari 15+
Requires: WebAssembly, Web Workers, ES2018+
## Development
### Project Structure
```
src/
├── index.ts # Main entry point
├── embedded.ts # Core Embedded class
├── types.ts # TypeScript definitions
├── duckdb-manager.ts # DuckDB connection management
├── path-resolver.ts # File path resolution
├── syntax-highlight.ts # SQL syntax highlighting
└── styles.ts # CSS injection
examples/
├── index.html # Basic vanilla JS example
├── init-queries.html # Init queries / extension management example
├── unpkg.html # unpkg CDN example (UMD)
├── unpkg-esm.html # unpkg CDN example (ESM)
├── typography.html # Typography customization examples
├── react.html # React integration example
└── vue.html # Vue 3 integration example
```
## License
MIT License - see [LICENSE](LICENSE) file for details.