Skip to content

feat(tx): owned write transactions and browser transactions (#824) - #885

Merged
adityamukho merged 2 commits into
v3from
feat/824-binding-transactions
Nov 11, 2026
Merged

adityamukho merged 2 commits into
v3from
feat/824-binding-transactions

Conversation

@adityamukho

Copy link
Copy Markdown
Collaborator

Core part of #824: explicit write transactions that language bindings can hold.

What changes

  • Minigraf::begin_write_owned() / begin_write_owned_with(TxOptions) → OwnedWriteTransaction: Send + 'static, keeps the database open, execute / commit / rollback, drop rolls back. It may begin on one thread and run, commit, roll back or be dropped on another.
  • Same staging and commit as WriteTransaction (both share TxState; small staging is Staged). The borrowed transaction's public API is unchanged (cargo semver-checks: no change required).
  • Write lock: Mutex<WriteSlot> + Condvar. While an owned transaction holds it, other writers (writes, rules, checkpoints, verify, rebuild_indexes, both begins) wait in Inner::lock_write; queries don't take it.
  • INT-001: the calls a WriteTransaction refuses on its thread are refused on an owned transaction's begin thread (per database), and on the thread running one of its statements, so a user function calling back gets INT-001 (QRY-014 for the query) instead of deadlocking.
  • Browser: BrowserDb.beginWrite() → BrowserWriteTransaction (sync execute, commit() Promise resolved after the IndexedDB flush, sync rollback; freeing it open rolls back). While open, BrowserDb's execute, query, prepare, checkpoint, importGraph, another beginWrite and prepared runs are [INT-001]; a finished transaction is [API-018].
  • No new error codes, no format change. INT-001's text now says "a write transaction" (CHANGELOG).
  • Docs: CHANGELOG, README, CLAUDE.md, TEST_COVERAGE, ERROR_REFERENCE (INT-001, API-018), design spec in docs/superpowers/specs/2026-11-10-824-binding-transactions-design.md.

Tests

  • tests/owned_tx_test.rs (16): equivalence with the borrowed transaction (5 sequences × commit/rollback/drop × small/large × memory/file, + reopen), cross-thread commit/rollback/drop, guards, waiting writers, non-waiting readers, other databases unaffected, begin refusals, user functions off the begin thread.
  • src/db.rs (2): a commit refused at commit time releases the lock; a poisoned lock still ends the transaction.
  • Fault matrix (A7): the four large write-transaction cases and the three mixed-sync-mode commits run again through the owned transaction (7 tests).
  • src/browser/tx_tests.rs (12 wasm-bindgen): staging, a differential check against the native transaction, IndexedDB commit/rollback/drop + reopen, failed flush, guards, finished transactions, user functions.

Verification: full default suite 2,138 passed / 21 ignored; headless Firefox 80 passed; clippy (all-features, both wasm targets) clean; dist binary 1,268,984 B on rustc 1.99.1 (gate 1,269,760 B).

Binding, template and docs-site PRs follow, pinned to this branch until it merges.

Refs #824

🤖 Generated with Claude Code

https://claude.ai/code/session_01H2a7QouMzKp3tAWxgVsxRf

adityamukho and others added 2 commits November 10, 2026 21:45
Language bindings need a write transaction they can hold in an object
that outlives a borrow and is used, committed or freed on any thread.
The borrowed WriteTransaction borrows the database, is not Send and is
tracked by a thread-local flag, so no binding can hold it.

Minigraf::begin_write_owned / begin_write_owned_with(TxOptions) return
an OwnedWriteTransaction: Send + 'static, keeps the database open, and
stages, reads and commits exactly as WriteTransaction does (both now
share TxState and the small-transaction Staged buffer). The write lock
becomes Mutex<WriteSlot> + Condvar: while an owned transaction holds
it, every other writer waits in Inner::lock_write. Its begin thread is
recorded per database, and the calls WriteTransaction refuses on its
thread are INT-001 there, whichever thread uses the transaction.

BrowserDb.beginWrite returns a BrowserWriteTransaction (sync execute,
commit() Promise resolved after the IndexedDB flush, rollback). While
it is open, BrowserDb's read/write calls and prepared runs are INT-001;
a finished transaction is API-018.

No new error codes and no format change; INT-001's text now says
"write transaction".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H2a7QouMzKp3tAWxgVsxRf
…tement (#824)

An owned transaction's statement holds the write mutex while it runs.
On a thread other than the begin thread, a user function calling back
into the database passed the begin-thread guard: a write locked the
mutex its own thread held (a permanent deadlock), and a query read
outside the transaction. The running thread now sets the reentry flag
for the statement's duration, so such calls are INT-001 and the query
fails with QRY-014, as on the begin thread.

BrowserDb.beginWrite checks for an open transaction before its mutable
borrow, so a user function calling it during a transaction's query is
refused instead of panicking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H2a7QouMzKp3tAWxgVsxRf
@codecov

codecov Bot commented Nov 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.81022% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/db.rs 97.32% 7 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@adityamukho
adityamukho marked this pull request as ready for review November 11, 2026 02:04
@adityamukho
adityamukho merged commit 32d71ce into v3 Nov 11, 2026
23 checks passed
@adityamukho
adityamukho deleted the feat/824-binding-transactions branch November 11, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant