pondpilot-widget
v1.4.0
Published
Transform static SQL code blocks into interactive snippets powered by DuckDB WASM
Downloads
103
Maintainers
Readme
PondPilot Widget
Transform static SQL code blocks into interactive snippets powered by DuckDB WASM.
🎮 Try it live - Interactive examples and demos
Features
- 🦆 DuckDB in the Browser - Full SQL analytics engine running locally
- ✨ Zero Backend - Everything runs client-side in WebAssembly
- 🎨 Syntax Highlighting - Beautiful SQL code formatting with cursor preservation
- ⚡ Instant Results - Execute queries with one click or Ctrl/Cmd+Enter
- 🔧 Easy Integration - Works with any static site or documentation
- 🧩 Configurable - Tweak selectors, auto-init, editable mode, and UI affordances
- 🗂️ Init Queries - Install DuckDB extensions or run setup SQL once per page
- ♻️ Reset Queries - Optionally run cleanup SQL when the editor resets
- 🎨 Custom Themes - Extend light/dark defaults or register fully custom palettes
- ♿ Accessible - Full ARIA support and keyboard navigation
- 🎯 Lightweight - Only ~22KB minified, loads DuckDB on-demand
- 🌙 Dark Mode - Automatic theme detection or manual control
- 🔒 Secure - No data leaves the browser, CSP-compatible
- 📁 Relative Paths - Automatic resolution of relative parquet file paths
Installation
CDN (Recommended)
The easiest way to get started is via CDN:
<!-- Latest version -->
<script src="https://unpkg.com/pondpilot-widget"></script>
<!-- Specific version (recommended for production) -->
<script src="https://unpkg.com/[email protected]"></script>
<!-- Alternative CDN -->
<script src="https://cdn.jsdelivr.net/npm/pondpilot-widget"></script>Package Manager
For projects using a bundler:
NPM
npm install pondpilot-widgetYarn
yarn add pondpilot-widgetQuick Start
1. Add the Script Tag
<script src="https://unpkg.com/pondpilot-widget"></script>2. Mark Your SQL Code Blocks
<pre class="pondpilot-snippet">
SELECT
'Hello World' as greeting,
42 as answer,
CURRENT_DATE as today;
</pre>3. That's It!
The widget automatically transforms your static code blocks into interactive SQL editors.
Alternative: Using a Bundler
If you're using a bundler like Webpack, Vite, or Parcel:
import "pondpilot-widget";Usage Examples
Basic Configuration
// Configure before initialization
window.PondPilot.config.theme = 'dark';
window.PondPilot.config.showPoweredBy = false;
// Or initialize with options
new PondPilot.Widget(element, {
theme: 'dark',
editable: false,
showPoweredBy: false
});Framework Integration
React
import { useEffect, useRef } from "react";
import "pondpilot-widget";
function SQLEditor({ sql, ...options }) {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
const widget = new window.PondPilot.Widget(ref.current, options);
return () => widget.destroy();
}
}, []);
return (
<pre ref={ref} className="pondpilot-snippet">
{sql}
</pre>
);
}Advanced Configuration
The runtime exposes a global API with ergonomic helpers:
const { config, init, create, destroy, registerTheme, getConfig } = window.PondPilot;
// Merge default configuration
config({
selector: "pre.sql-demo",
baseUrl: "https://cdn.example.com/datasets",
autoInit: false,
initQueries: ["INSTALL httpfs", "LOAD httpfs"],
resetQueries: ["DROP TABLE IF EXISTS scratch_table;"],
});
// Register a custom theme and apply it via data-theme or options.theme
registerTheme("sunset", {
extends: "light",
config: {
bgColor: "#ffedd5",
textColor: "#7c2d12",
borderColor: "#f97316",
editorBg: "#fff7ed",
editorText: "#7c2d12",
editorFocusBg: "#fed7aa",
controlsBg: "rgba(249, 115, 22, 0.12)",
primaryBg: "#f97316",
primaryText: "#ffffff",
primaryHover: "#ea580c",
secondaryBg: "rgba(249, 115, 22, 0.16)",
secondaryText: "#7c2d12",
secondaryHover: "rgba(249, 115, 22, 0.28)",
mutedText: "#9a3412",
errorText: "#dc2626",
errorBg: "rgba(220, 38, 38, 0.08)",
errorBorder: "rgba(220, 38, 38, 0.2)",
tableHeaderBg: "rgba(249, 115, 22, 0.16)",
tableHeaderText: "#7c2d12",
tableHover: "rgba(249, 115, 22, 0.12)",
syntaxKeyword: "#c2410c",
syntaxString: "#047857",
syntaxNumber: "#7c3aed",
syntaxComment: "#9a3412",
syntaxSpecial: "#dc2626",
syntaxIdentifier: "#facc15",
fontFamily: "Inter, system-ui, sans-serif",
editorFontFamily: "'JetBrains Mono', monospace",
fontSize: "14px",
editorFontSize: "13px",
buttonFontSize: "13px",
metadataFontSize: "12px",
},
});
// Manually mount widgets when needed
document.querySelectorAll("pre.sql-demo").forEach((node) => {
window.PondPilot.create(node, { theme: "sunset" });
});Data attributes
Per-snippet overrides can be expressed declaratively:
<pre
class="pondpilot-snippet"
data-theme="sunset"
data-base-url="https://cdn.example.com/data"
data-init-queries='["LOAD httpfs"]'
>
<code>SELECT * FROM 'sales.parquet';</code>
</pre>Supported attributes include:
data-themedata-base-urldata-editabledata-show-powered-bydata-init-queries(semicolon or JSON array)
API Summary
PondPilot.init(target?, options?)– initialize matching elements (defaults toconfig.selector)PondPilot.create(element, options?)– mount a single instance on demandPondPilot.destroy(target?)– remove widgets (defaults to all)PondPilot.config(partial)– merge configurationPondPilot.getConfig()– inspect current configurationPondPilot.registerTheme(name, definition)– extend theming systemPondPilot.Widget– constructor for manual control (new PondPilot.Widget(element, options))
Vue
<template>
<pre ref="editor" class="pondpilot-snippet">{{ sql }}</pre>
</template>
<script>
import 'pondpilot-widget';
export default {
props: ['sql', 'options'],
mounted() {
this.widget = new window.PondPilot.Widget(this.$refs.editor, this.options);
},
beforeUnmount() {
this.widget?.destroy();
}
}
</script>Markdown/Documentation Sites
Docusaurus
// In docusaurus.config.js
module.exports = {
scripts: [
'https://unpkg.com/pondpilot-widget'
],
// ...
};VitePress
// In .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme';
import 'pondpilot-widget';
export default DefaultTheme;Local Examples (in this repo)
You can run a small examples site from the examples/ folder:
- Serve the repository root (so examples/ is web‑served):
python3 -m http.server 8080
# then open http://localhost:8080/examples/index.html- Local .duckdb database demo (optional):
# create a tiny demo DB at examples/data/blog.duckdb
uv run examples/create-blog-db.py # or: pip install duckdb pandas && python3 examples/create-blog-db.py
# open the demo page
open http://localhost:8080/examples/local-duckdb.html- Relative Parquet path handling demo (optional):
cd examples/relative-paths
python3 create-test-data.py # creates analytics.parquet in this directory
cd ../..
open http://localhost:8080/examples/relative-paths/test-relative-paths.html- A5 Geospatial Extension demo with interactive map:
# No setup required - just open the demo
open http://localhost:8080/examples/a5-geospatial-demo.htmlNotes:
- The example pages in this repo load the widget from ../src/ for local development. You can switch them to use the CDN by replacing the script tag with the CDN snippet from Installation.
- The local .duckdb example shows how to provide an external DuckDB WASM instance to the widget and open a DB file served over HTTP.
- The relative‑paths example demonstrates how the widget auto‑resolves relative parquet file paths in queries.
- The A5 demo shows how to use DuckDB community extensions for geospatial analysis with interactive Leaflet map visualization.
Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| selector | string | "pre.pondpilot-snippet, .pondpilot-snippet pre" | CSS selector for SQL blocks |
| theme | string | "light" | Theme: "light" or "dark" |
| editable | boolean | true | Allow editing SQL code |
| showPoweredBy | boolean | true | Show "Powered by PondPilot" branding |
| baseUrl | string | "http://localhost:5173" | PondPilot instance URL |
| autoInit | boolean | true | Auto-initialize on DOM ready |
API Reference
Global Object
window.PondPilot = {
init(), // Initialize all widgets
destroy(), // Clean up all widgets
config: {}, // Global configuration
Widget: class {} // Widget constructor
};Widget Instance
const widget = new PondPilot.Widget(element, options);
// Methods
await widget.run(); // Execute SQL query
widget.reset(); // Reset to original code
await widget.cleanup(); // Clean up resources
widget.destroy(); // Destroy widgetSecurity Considerations
Content Security Policy (CSP)
Content-Security-Policy: script-src 'self' https://unpkg.com https://cdn.jsdelivr.net; worker-src 'self' blob: https://cdn.jsdelivr.net; connect-src 'self' https://cdn.jsdelivr.net;Subresource Integrity (SRI)
<script src="https://unpkg.com/[email protected]" integrity="sha384-[hash]" crossorigin="anonymous"> </script>
Browser Support
- Chrome/Edge 88+
- Firefox 89+
- Safari 15+
Requires:
- WebAssembly
- Web Workers
- ES2018+
Working with Parquet Files
The widget supports loading parquet files from various sources:
Direct HTTP URLs
SELECT * FROM 'https://example.com/data.parquet' LIMIT 10;Relative Paths (New in v1.1.0)
The widget automatically resolves relative paths to absolute URLs:
-- All of these formats are supported:
SELECT * FROM 'data.parquet';
SELECT * FROM './data.parquet';
SELECT * FROM '/data.parquet';When using relative paths:
- Files must be accessible via HTTP from the same server
- The widget resolves paths relative to the current page URL
- For local development, ensure your files are served by a web server
Performance Tips
- Lazy Loading: DuckDB is only loaded when first query runs
- Shared Instance: Multiple widgets share the same DuckDB instance
- Resource Cleanup: Widgets automatically clean up when removed from DOM
- File Caching: Registered parquet files are cached to avoid duplicate downloads
Changelog
See CHANGELOG.md for release history.
License
MIT © PondPilot
Contributing
Contributions welcome! Please read our contributing guide.
Local development
npm install # install dependencies
npm run format # apply Prettier formatting
npm test # run the Vitest suitePrefer just? We provide a lightweight justfile with the same commands (just format, just test, etc.).
