sqlnotebook

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package sqlnotebook is the GoFastr SQL notebook plugin: a real SQLite engine (sql.js, SQLite compiled to WebAssembly) running INSIDE the opaque-origin sandboxed iframe, queried from a host-page notebook UI.

The interesting constraint is the cage itself. The framed CSP fixes connect-src 'none', so the frame cannot fetch anything — not even its own engine. sql.js's documented locateFile option dies exactly there ("both async and sync fetching of the wasm failed"), so the architecture splits the engine in two the way pdf splits document bytes:

  • sql-wasm.js (the JS glue) is a plain same-origin script the frame loads by tag; it is loadable from the frame's own origin.
  • sql-wasm.wasm (the engine) is fetched by the HOST adapter — which is not framed and may fetch — and handed to the frame as BYTES over the postMessage bridge. The frame calls initSqlJs({ wasmBinary }) and never touches the network.

Compiling the wasm at all needs the narrow CSP tier: this is the first plugin in the repo whose manifest declares CSP: ["'wasm-unsafe-eval'"]. That keyword grants WebAssembly compilation and nothing else — string eval stays an EvalError, connect-src stays 'none', the origin stays opaque — and it only takes effect because Plugin.Init threads the manifest's own CSP slice into pluginhost.NewAssetServer(...).WithCSP (gofastr#300: a manifest that validates but is never threaded produces a frame whose wasm refuses to compile, silently). The regression test for that lives in plugin_test.go and asserts the SERVED header, not the manifest.

The wire protocol is versioned and deliberately NOT the generic broker RPC (there is no host capability for the frame to call — it has no network and needs none). Host to frame: sqlnb/init carries the wasm bytes plus the seed SQL; sqlnb/query carries a query. Frame to host: sqlnb/ready, sqlnb/result (capped at 500 rows, truncated flag when the query produced more), sqlnb/error. Unknown types and mismatched versions are ignored silently on both sides. Results are computed in the frame and never persisted server-side: the notebook is session state, so this plugin registers no RPC routes at all — handlers.go is asset routes and the demo page only.

Index

Constants

View Source
const (
	SQLiteVersion = "3.49.1"
	SqlJsVersion  = "1.14.2"
)

SQLiteVersion is the SQLite release compiled into the embedded wasm, and SqlJsVersion the sql.js release the two dist files came from. Both are stated on the demo page, and TestDemoPageStatesTheBundledLibraryVersions requires them to match the lockfile and the wasm's own version string — mermaid's page shipped a version twelve releases stale because nothing checks prose.

View Source
const (
	Name          = "sqlnotebook"
	Version       = "0.1.0"
	SchemaVersion = "sqlnotebook.v1"
	RoutePrefix   = "/__gofastr/plugin/sqlnotebook"

	FrameHTMLURL     = RoutePrefix + "/frame.html"
	NotebookJSURL    = RoutePrefix + "/notebook.js"
	SqlWasmJSURL     = RoutePrefix + "/sql-wasm.js"
	SqlWasmWasmURL   = RoutePrefix + "/sql-wasm.wasm"
	AdapterScriptURL = RoutePrefix + "/adapter.js"
	DemoURL          = "/sqlnotebook"
)

Identity and route constants. Both this plugin and host/adapter.js hard-code these exactly. The demo lives at /sqlnotebook.

These mirror the sqlnotebook row in plugins.json; internal/registry tests pin Name + RoutePrefix against that row, so they MUST NOT drift.

View Source
const DefaultSeed = `` /* 588-byte string literal not displayed */

DefaultSeed is the SQL the frame runs at init before the first query, so the notebook opens on a table that exists rather than an empty engine. It seeds a small plugins table mirroring this repo's real registry rows (name + isolation, values as plugins.json has them), which gives the demo something true to query: a two-value isolation column worth grouping by. Kept ASCII and well under 15 rows on purpose.

Variables

This section is empty.

Functions

func DefaultCapabilities

func DefaultCapabilities() []string

DefaultCapabilities is the always-on grant set advertised to the frame: theme:read only, matching the adapter's broker registration exactly. The database lives inside the frame; no host resource is ever pulled, so even document:read would be a promise of nothing — the seed and the engine bytes are PUSHED in over sqlnb/init, and results never leave the frame's page.

func Mount

func Mount(cfg MountConfig) render.HTML

Mount renders the generic mount marker. The host adapter scans it, builds the sandboxed frame, fetches the wasm bytes same-origin and relays them with the seed over the bridge.

The seed rides the marker's generic data-fui-plugin-doc attribute, JSON-encoded so the adapter decodes it unambiguously (it also accepts raw SQL and {"sql": ...}, but the JSON string is the form this side emits). The demo page passes Plugin.Seed here, which is how WithSeed reaches the frame; a host mounting its own markers passes MountConfig.Seed per mount. An empty Seed produces no attribute, and the adapter then falls back to its own built-in default so the notebook is never empty. There is deliberately no hidden form field: the notebook is session state, nothing about it POSTs back. All interpolated values are HTML-escaped via render.Escape inside pluginhost.MountMarker.

func UIHostOption

func UIHostOption() uihost.Option

UIHostOption injects the platform broker and this plugin's adapter (the broker first — the adapter registers with it). There is no config.js: the only instance state, the seed, rides the mount marker (see Mount), which is the channel the adapter reads.

Types

type MountConfig

type MountConfig struct {
	DocID     string // mount key (default "demo"); the notebook has no persisted doc to round-trip
	Seed      string // SQL the frame executes at init; empty means the adapter's built-in default seed
	MinHeight string // initial iframe height before first resize (default "360px")
}

MountConfig configures Mount.

type Option

type Option func(*Plugin)

Option configures a Plugin.

func WithDemoPage

func WithDemoPage() Option

WithDemoPage registers the self-contained themed demo page at DemoURL.

func WithDevGrantAll

func WithDevGrantAll() Option

WithDevGrantAll short-circuits the capability gate (Phase-0 demo / tests). This plugin mounts no capability-gated RPC routes — the frame exchanges data only over the postMessage bridge and the asset routes are public static bytes — so the flag currently gates nothing; it is kept for harness parity with every other plugin here and for the routes a persistence layer would add.

func WithSeed

func WithSeed(sql string) Option

WithSeed overrides the SQL the frame executes at init (default: DefaultSeed). The frame runs it as a multi-statement script before the first query, so the notebook opens on tables the seed created. The value reaches the frame through the demo page's mount marker (data-fui-plugin-doc, JSON-encoded); a host mounting its own markers passes the seed per mount via MountConfig.Seed. An empty or whitespace-only seed panics at New: it is almost always a bug (a source that failed to load), and an engine with no schema is a blank page wearing a SQL badge. A host that truly wants an empty engine can seed a comment.

type Plugin

type Plugin struct {
	// contains filtered or unexported fields
}

Plugin is the SQL notebook plugin. It implements framework.Plugin and mirrors the pdf/calendar shape (opaque-origin sandboxed iframe, go:embed'd frame bundle, capability-gated grant set) with one first in the repo: the manifest declares the 'wasm-unsafe-eval' CSP tier, and Init threads it into the AssetServer so the frame can actually compile SQLite.

func New

func New(opts ...Option) *Plugin

New constructs a Plugin. Validation runs here so a misconfiguration aborts construction rather than mounting a frame whose engine cannot compile.

func (*Plugin) Capabilities

func (p *Plugin) Capabilities() []string

Capabilities returns the grant set this plugin advertises to the frame.

func (*Plugin) Init

func (p *Plugin) Init(app *framework.App) error

Init registers every asset route (and the demo page, when requested) on the app's router.

func (*Plugin) Manifest

func (p *Plugin) Manifest() pluginhost.Manifest

func (*Plugin) Name

func (p *Plugin) Name() string

func (*Plugin) Seed

func (p *Plugin) Seed() string

Seed returns the SQL the frame executes at init.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL