SqliteCrdtTable class final
A CrdtTable backed by SQLite (package:sqlite3).
Records live in a single crdt_records table keyed by (collection, key);
one SQLite database can host every collection. Values are stored as JSON
text. Ordering and sync filtering use hlc_key, a fixed-width sortable
encoding of the record's Hybrid Logical Clock, so WHERE hlc_key > ? is a
correct causal comparison.
Construct it with a path; the database is opened on the first init (which
Collection calls, since isPersistent is true). On native targets pass
an encryptionKey to use SQLCipher. The open fails closed: init throws
SqlCipherUnavailableException when a key is given but the linked native
library is not a SQLCipher build, so data is never silently written in
plaintext.
Wrong key versus corruption
An existing encrypted file that does not decrypt under encryptionKey
makes init throw WrongPassphraseException and leaves the file exactly
as it was — retrying with the right key succeeds. An encrypted file whose
damage makes it unreadable answers exactly like a mis-keyed one, so the
snapshot arbitrates: when <path>.snapshot opens cleanly under the same
key, the key is proven right, the file is treated as corrupt, and recovery
proceeds. Without a snapshot the ambiguity resolves to
WrongPassphraseException — the file is never destroyed on a guess, at
the price that an encrypted database without snapshots recovers from
header damage only by the user confirming the passphrase and deleting the
file. Corruption past the header (a damaged page under a valid key) fails
PRAGMA quick_check directly and recovers without arbitration.
Corruption recovery
init runs PRAGMA quick_check on open. If a corrupt file does not pass,
it is moved aside to <path>.corrupt-<timestamp> and the store is
rebuilt: from <path>.snapshot if a readable one exists, otherwise empty
(and recoveries emits so the sync layer can pull the data back from a
peer). Set snapshotInterval (or PassnoteConfig.snapshotInterval) to keep
those snapshots current.
- Implemented types
Constructors
-
SqliteCrdtTable(String _path, {List<
int> ? encryptionKey, Duration snapshotInterval = Duration.zero}) -
Creates a table for the database at
path, which is opened on the first init. Pass":memory:"for a transient database; pass anencryptionKeyto use SQLCipher on a SQLCipher build of the native library.snapshotIntervalsets how often a recovery snapshot is written (Duration.zero, the default, disables it).
Properties
- hashCode → int
-
The hash code for this object.
no setterinherited
- isPersistent → bool
-
Whether this table owns its own durability.
no setteroverride
- nodeId → String
-
The node id persisted in
crdt_meta, stable across restarts. Created on the first init of a fresh database. Throws StateError before init.no setteroverride -
recoveries
→ Stream<
CrdtRecovery> -
Emits once if init found the local store unreadable and rebuilt it
(from an on-disk snapshot, or empty).
no setteroverride
- recovery → CrdtRecovery?
-
Non-null once init has run if it had to rebuild an unreadable store.
no setter
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- snapshotInterval ← Duration
-
How often to write a snapshot of the store. Duration.zero disables
snapshots. Set before CrdtTable.init; later changes may not take effect.
no getteroverride
Methods
-
atomically(
Future< void> writes()) → Future<void> -
Runs
writesin one SQLite transaction: a process killed half-way through a document's records commits none of them, rather than leaving the document half-written on disk. -
changeset(
String table, {String? sinceHlc, String? untilHlc}) → Future< CrdtChangeset> -
Returns records in
tableinCrdtChangesetformat.override -
close(
) → Future< void> -
Closes the database and every watch stream. Safe to call more than once.
override
-
compact(
String table, {required Duration horizon}) → Future< int> -
Permanently removes tombstoned records whose wall-clock time is older
than
horizonand returns the number of reclaimed records.override -
docFields(
String table, Iterable< String> docIds) → Future<Map< String, Map< >String, Object?> > -
One indexed lookup per chunk of ids (
doc_id IN (...)), never a table scan.override -
ensureQueryIndex(
String table, String fieldPath) → Future< void> -
Creates a small partial index for
fieldPathintable: it covers only that field's live rows and indexes their extracted value, so an equality predicate on the path is an index search rather than a table scan.override -
explainQuery(
String table, QuerySpec spec) → String -
The
EXPLAIN QUERY PLANoutput for the SQL that queryDocIds would run forspec— one plan line per row. A diagnostic: use it to confirm a predicate on an indexed path searches its index instead of scanning. -
init(
) → Future< void> -
Initializes the table from storage (called by Collection during
startup when isPersistent is true).
override
-
liveFields(
String table, String prefix) → Future< Map< String, Object?> > -
Returns all live (non-tombstoned) records in
tablewhose key starts withprefix, as a flat map of key → value.override -
liveKeys(
String table, String prefix) → Set< String> -
All live (non-tombstoned) keys in
tablewhose key starts withprefix.override -
maxHlc(
String table) → String -
The highest HLC seen in
tableas a string, or the empty string when the table is empty.override -
merge(
CrdtChangeset changeset) → Future< void> -
Merges a
CrdtChangesetreceived from a peer or loaded from storage.override -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
put(
String table, String key, Object? value, {bool isDeleted = false, CrdtType crdtType = const LwwRegister()}) → Future< void> -
Writes or tombstones the record identified by
table/key.override -
queryDocIds(
String table, QuerySpec spec) → Future< List< QueryRow> > -
Returns the ids of the live documents in
tablethat satisfyspec, in the spec's total order —(order-by value, document id)— windowed by its limit and cursor.override -
rangeDigest(
String table, {String? fromHlc, String? toHlc}) → Future< RangeDigest> -
One indexed range scan over
(collection, hlc_key); O(1) memory.override -
recordHlc(
String table, String key) → Future< String?> -
The stored HLC string of the record at
table/key— tombstoned or live — ornullwhen no record exists.override -
rekey(
List< int> newKey) → Future<void> -
Re-encrypts the database under
newKeywith SQLCipher'sPRAGMA rekey. All data is preserved. Subsequent opens and recovery snapshots usenewKey, and an existing recovery snapshot is rewritten under it at once: a passphrase is usually rotated because the old one leaked, so a copy under the old key must not wait for the next snapshot tick. Quarantined<file>.corrupt-*copies keep the key they had; delete them once inspected.override -
snapshotNow(
) → void -
Writes a consistent copy of the store to
<path>.snapshot, replacing any previous one. Called by the periodic timer; also safe to call directly for a manual checkpoint. A no-op for an in-memory database. -
toString(
) → String -
A string representation of this object.
inherited
-
value(
String table, String key) → Future< Object?> -
Returns the live value for
keyintable, ornullif absent or tombstoned.override -
watch(
String table) → Stream< void> -
A broadcast stream that emits whenever any record in
tablechanges.override
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited
Static Methods
-
configureWeb(
{Uri? wasmUri}) → void -
Web-only configuration (a no-op on native): points the engine at the
sqlite3.wasmmodule. Call it once before opening a database. -
open(
String path, {List< int> ? encryptionKey}) → Future<SqliteCrdtTable> - Convenience for tests: constructs and inits in one step.