opfsvfs

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Mar 21, 2026 License: MIT Imports: 3 Imported by: 0

README

go-sqlite3-opfs

OPFS VFS for ncruces/go-sqlite3 — SQLite persistence in browser WASM via the Origin Private File System.

Installation

go get github.com/danmestas/go-sqlite3-opfs

Usage

Go
import (
    "database/sql"

    _ "github.com/danmestas/go-sqlite3-opfs"
    _ "github.com/ncruces/go-sqlite3/driver"

)

db, err := sql.Open("sqlite3", "file:mydb.db?vfs=opfs")
if err != nil {
    log.Fatal(err)
}
defer db.Close()
db.SetMaxOpenConns(1) // Required: one connection per Worker
JavaScript Worker
const root = await navigator.storage.getDirectory();
const dir = await root.getDirectoryHandle("sqlite3-opfs", { create: true });

const handles = {};
for (const suffix of ["", "-journal", "-wal"]) {
    const name = "mydb.db" + suffix;
    const fh = await dir.getFileHandle(name, { create: true });
    handles[name] = await fh.createSyncAccessHandle();
}

importScripts("wasm_exec.js");
const go = new Go();
const result = await WebAssembly.instantiateStreaming(fetch("app.wasm"), go.importObject);
go.run(result.instance);
_opfs_init(handles);

WAL Mode

db.SetMaxOpenConns(1)
db.Exec("PRAGMA locking_mode=EXCLUSIVE")
db.Exec("PRAGMA journal_mode=WAL")

WAL requires exclusive locking mode because OPFS does not support shared memory for multi-connection concurrency.

Playground

Interactive SQL playground with FTS5, JSON, triggers, views, and more:

go run ./playground

Options

import opfsvfs "github.com/danmestas/go-sqlite3-opfs"

opfsvfs.New(opfsvfs.Options{
    Name:     "opfs",       // VFS name (default: "opfs")
    Observer: myObserver,   // Optional I/O observer
})

Validated Features

All features tested in-browser and verified against native SQLite:

FTS5, foreign keys (CASCADE, SET NULL), triggers, JSON functions, generated columns, views, recursive CTEs, window functions, BLOB storage, indexes, transactions, WAL mode.

Limitations

  • One connection per Workerdb.SetMaxOpenConns(1)
  • One database per VFS — each VFS instance manages a single database
  • Worker-onlyFileSystemSyncAccessHandle unavailable on main thread
  • COOP/COEP headers requiredCross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp
  • No shared memory — WAL requires PRAGMA locking_mode=EXCLUSIVE

Browser Compatibility

Chrome/Edge 102+, Firefox 111+, Safari 16.4+

License

MIT

Documentation

Overview

Package opfsvfs implements an OPFS VFS for github.com/ncruces/go-sqlite3, enabling SQLite persistence in browser WebAssembly applications via the Origin Private File System.

Usage

Blank-import to register the "opfs" VFS, then open databases with the vfs=opfs query parameter:

import (
    "database/sql"
    _ "github.com/danmestas/go-sqlite3-opfs"
    _ "github.com/ncruces/go-sqlite3/driver"
)

db, err := sql.Open("sqlite3", "file:mydb.db?vfs=opfs")
db.SetMaxOpenConns(1)

JavaScript Worker Setup

OPFS sync access handles must be pre-opened in a dedicated Worker before the Go WASM binary starts. The Worker creates named file handles and passes them to Go via the _opfs_init callback:

const handles = {};
for (const suffix of ["", "-journal", "-wal"]) {
    const fh = await dir.getFileHandle("mydb.db" + suffix, { create: true });
    handles["mydb.db" + suffix] = await fh.createSyncAccessHandle();
}
_opfs_init(handles);

WAL Mode

WAL mode is supported with exclusive locking (required because OPFS does not provide shared memory):

db.SetMaxOpenConns(1)
db.Exec("PRAGMA locking_mode=EXCLUSIVE")
db.Exec("PRAGMA journal_mode=WAL")

Requirements

  • GOOS=js GOARCH=wasm build target
  • Dedicated Worker (FileSystemSyncAccessHandle is Worker-only)
  • COOP/COEP headers (required by wazero's SharedArrayBuffer usage)
  • Chrome 102+, Firefox 111+, Safari 16.4+

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type FlushEvent

type FlushEvent struct {
	File     string
	Duration time.Duration
	Err      error
}

type Handle

type Handle interface {
	Read(buf []byte, offset int64) (int, error)
	Write(buf []byte, offset int64) (int, error)
	GetSize() (int64, error)
	Truncate(size int64) error
	Flush() error
	Close() error
}

Handle abstracts a FileSystemSyncAccessHandle. On js builds, wraps syscall/js. All methods are synchronous.

type Observer

type Observer interface {
	OnRead(file string, offset int64, bytes int, duration time.Duration, err error)
	OnWrite(file string, offset int64, bytes int, duration time.Duration, err error)
	OnFlush(file string, duration time.Duration, err error)
}

Observer receives callbacks for I/O operations. Pass nil for no-op. Implementations must be safe for concurrent use.

type OpfsError

type OpfsError struct {
	Op     string // read, write, truncate, flush, getSize, close
	File   string // OPFS filename (e.g. "test.db", "test.db-wal")
	Offset int64  // byte offset, -1 if not applicable
	Size   int    // requested bytes, -1 if not applicable
	Err    error  // underlying error
}

OpfsError provides structured context for OPFS I/O failures.

func (*OpfsError) Error

func (e *OpfsError) Error() string

func (*OpfsError) Unwrap

func (e *OpfsError) Unwrap() error

type ReadEvent

type ReadEvent struct {
	File     string
	Offset   int64
	Bytes    int
	Duration time.Duration
	Err      error
}

type RecordingObserver

type RecordingObserver struct {
	Reads   []ReadEvent
	Writes  []WriteEvent
	Flushes []FlushEvent
}

RecordingObserver captures events for test assertions. Stops recording after maxRecordedEvents to prevent unbounded growth.

func (*RecordingObserver) OnFlush

func (r *RecordingObserver) OnFlush(file string, dur time.Duration, err error)

func (*RecordingObserver) OnRead

func (r *RecordingObserver) OnRead(file string, offset int64, bytes int, dur time.Duration, err error)

func (*RecordingObserver) OnWrite

func (r *RecordingObserver) OnWrite(file string, offset int64, bytes int, dur time.Duration, err error)

type Stats

type Stats struct {
	Reads        atomic.Int64
	Writes       atomic.Int64
	Flushes      atomic.Int64
	BytesRead    atomic.Int64
	BytesWritten atomic.Int64
	ReadTimeNs   atomic.Int64
	WriteTimeNs  atomic.Int64
	FlushTimeNs  atomic.Int64
}

Stats holds atomic performance counters. Zero allocation on read. Snapshot and Reset are best-effort (not atomic across fields) — acceptable because Go WASM is single-threaded, so no concurrent modifications occur.

func (*Stats) Reset

func (s *Stats) Reset()

Reset zeroes all counters.

func (*Stats) Snapshot

func (s *Stats) Snapshot() StatsSnapshot

Snapshot returns a point-in-time copy of the counters.

type StatsSnapshot

type StatsSnapshot struct {
	Reads, Writes, Flushes               int64
	BytesRead, BytesWritten              int64
	ReadTimeNs, WriteTimeNs, FlushTimeNs int64
}

StatsSnapshot is a non-atomic copy of all counters.

type WriteEvent

type WriteEvent struct {
	File     string
	Offset   int64
	Bytes    int
	Duration time.Duration
	Err      error
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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