durability

package
v5.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Overview

Package durability selects and reports a client-side storage tier (plan item P3.6).

P3.6's three criteria are all about states and transitions rather than about file syscalls:

  • incremental durability, verified by kill-and-reopen
  • a second tab reaches a READ-ONLY state without throwing, and the app is told
  • when OPFS is unavailable the IndexedDB tier is used, with a diagnostic and no app-visible error

So the selection and the lock model live here, in a package with no engine and no syscall/js, and are tested natively. The browser half — opening an OPFS sync access handle, catching the lock error a second tab gets — is injected through Capabilities and Locker, and is NOT verified by these tests. That boundary is stated rather than implied: what is proven here is that the right state is entered and reported, not that the browser behaves as described.

The whole point of separating them is that the state machine is where the bugs that matter live. A second tab that throws instead of degrading takes down a user's other window; a fallback that reports an error where it should report a diagnostic makes a working app look broken.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoTierAvailable = errors.New("durability: no storage tier is available")

ErrNoTierAvailable is returned when nothing can be used.

View Source
var ErrReadOnly = errors.New("durability: this database handle is read-only")

ErrReadOnly is returned when a write is attempted on a read-only handle.

A distinct sentinel so an app can present "this window is read-only because another tab has the database" rather than a generic failure — which is the difference between an explanation and a bug report.

Functions

This section is empty.

Types

type Access

type Access string

Access describes what the holder may do with the database.

const (
	// AccessReadWrite is exclusive ownership.
	AccessReadWrite Access = "read-write"
	// AccessReadOnly means another context holds the writer lock.
	//
	// A state, not an error. OPFS allows one sync access handle per file, so a
	// second tab CANNOT write — and throwing there would break a window the user
	// did not touch, over a condition that is completely normal.
	AccessReadOnly Access = "read-only"
	// AccessNone means the database could not be opened at all.
	AccessNone Access = "none"
)

type Capabilities

type Capabilities struct {
	// HasOPFS reports that the Origin Private File System is reachable AND that
	// sync access handles are usable, which requires a worker context.
	HasOPFS bool
	// HasIndexedDB reports that IndexedDB is reachable.
	HasIndexedDB bool
}

Capabilities reports what the environment supports.

Injected rather than probed here, so the state machine is testable without a browser and so a caller can force a tier in a test.

type Handle

type Handle struct {
	Name      string
	Selection Selection
	Access    Access
	// ReadOnlyReason explains a read-only handle, for the app to surface.
	//
	// P3.6 requires the second tab to reach read-only "without throwing and
	// surface it to the app". A read-only handle with no reason is surfaceable
	// only as a mystery.
	ReadOnlyReason string
	// contains filtered or unexported fields
}

Handle is an opened database's tier and access state.

func Open

func Open(parseName string, parseRequest Request, parseCapabilities Capabilities, parseLocker Locker) (*Handle, error)

Open selects a tier and attempts to take the writer lock.

A second tab does NOT get an error. It gets a read-only handle with a reason, because that is the state the user's second window is actually in, and throwing would break a window they did not touch over an entirely normal condition.

func (*Handle) CanWrite

func (parseHandle *Handle) CanWrite() bool

CanWrite reports whether writes are permitted.

func (*Handle) CheckWrite

func (parseHandle *Handle) CheckWrite() error

CheckWrite reports whether a write may proceed.

func (*Handle) Close

func (parseHandle *Handle) Close() error

Close releases the writer lock if this handle holds it.

Releasing a lock this handle never held would take it from whoever does, so the check is on `held` rather than on the access mode — a handle can be read-only precisely because someone else is holding what it would release.

func (*Handle) Promote

func (parseHandle *Handle) Promote() (bool, error)

Promote retries the writer lock on a read-only handle.

For the tab that was second and is now alone, after the first one closed. Without it a user's remaining window stays read-only until they reload it, which reads as the app being broken.

type Locker

type Locker interface {
	// Acquire attempts to take the writer lock.
	//
	// Returns (true, nil) on success, (false, nil) when another context holds
	// it, and (false, err) when something actually went wrong.
	Acquire(parseName string) (bool, error)
	// Release drops the lock.
	Release(parseName string) error
}

Locker acquires the single-writer lock for a database.

Injected because the real implementation is an OPFS sync access handle, which exists only in a browser worker. The contract is narrow on purpose: acquiring either succeeds, or fails because someone else holds it, and those are different outcomes rather than one error to be string-matched.

type Request

type Request struct {
	// Preferred is the tier the caller wants.
	Preferred Tier
	// AllowFallback permits a lower tier when the preferred one is unavailable.
	//
	// True is the right default for durability, and false is the right choice
	// for a caller who would rather fail loudly than write somewhere the user
	// did not expect.
	AllowFallback bool
}

Request is what the application asked for.

type Selection

type Selection struct {
	// Tier is what will actually be used.
	Tier Tier
	// Requested is what the caller asked for.
	Requested Tier
	// Degraded reports that the tier is lower than requested.
	Degraded bool
	// Diagnostic explains a degradation in one sentence, empty otherwise.
	//
	// P3.6 requires a diagnostic and NO app-visible error for the OPFS →
	// IndexedDB fallback, so the two are separate fields rather than one error:
	// an error would force every caller to decide whether to abort, over a
	// condition where continuing is correct.
	Diagnostic string
}

Selection is the outcome of choosing a tier.

func Select

func Select(parseRequest Request, parseCapabilities Capabilities) (Selection, error)

Select chooses a tier.

The fallback order is OPFS → IndexedDB and stops there. Memory is never a silent fallback: a caller who asked for durability and receives memory storage would lose the user's data on reload, and would have been told about it only in a diagnostic nobody reads. Falling back to memory therefore requires asking for memory.

func (Selection) Durable

func (parseSelection Selection) Durable() bool

Durable reports whether data survives a reload.

func (Selection) Incremental

func (parseSelection Selection) Incremental() bool

Incremental reports whether the tier makes writes durable without rewriting the whole database image.

The property that distinguishes the tiers in practice: a 50,000-row import against IndexedDB rewrites the image on every flush, which is what makes incremental durability worth the complexity of OPFS.

type Tier

type Tier string

Tier names a storage backend.

const (
	// TierOPFS is the Origin Private File System, accessed through sync access
	// handles in a worker. Incremental: a write reaches disk without rewriting
	// the database image.
	TierOPFS Tier = "opfs"
	// TierIndexedDB snapshots the whole database image on flush. Universally
	// available, and the cost is that durability is all-or-nothing per flush.
	TierIndexedDB Tier = "indexeddb"
	// TierMemory does not survive a reload. The correct tier when the caller
	// asked for it, and never a silent fallback — a user who expects their data
	// to persist must not be given memory storage quietly.
	TierMemory Tier = "memory"
)

Jump to

Keyboard shortcuts

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