pub struct Store { /* private fields */ }Expand description
The SQLite database, and every query the server makes against it.
Implementations§
Source§impl Store
impl Store
Sourcepub fn audited<T>(
&self,
write: impl FnOnce(&Transaction<'_>, &str) -> Result<Outcome<T>>,
build_leaf: impl FnOnce(u64, &str, &T) -> Vec<u8>,
) -> Result<T>
pub fn audited<T>( &self, write: impl FnOnce(&Transaction<'_>, &str) -> Result<Outcome<T>>, build_leaf: impl FnOnce(u64, &str, &T) -> Vec<u8>, ) -> Result<T>
Runs write inside one transaction and, only if it commits,
appends the leaf build_leaf makes from the position that leaf
will hold (its seq), its at, and write’s own result — then,
and only then, the in-memory tree learns about it.
write is given the same at, so a row it stamps and the leaf
that records it carry one time. Both are taken under the lock the
whole transaction holds, which is what makes at rise with seq.
This and Store::audited_each are the only places a leaf is ever
appended, so “every authenticated state change appends its leaf, in
the same transaction as the change” is true by construction: nothing
calls INSERT INTO audit_log any other way, and a write that never
commits — because write returned Outcome::Refuse or an error —
leaves neither the state change nor a leaf behind.
Sourcepub fn audited_each<T>(
&self,
write: impl FnOnce(&Transaction<'_>, &str) -> Result<Outcome<T>>,
build_leaves: impl FnOnce(u64, &str, &T) -> Vec<Vec<u8>>,
) -> Result<T>
pub fn audited_each<T>( &self, write: impl FnOnce(&Transaction<'_>, &str) -> Result<Outcome<T>>, build_leaves: impl FnOnce(u64, &str, &T) -> Vec<Vec<u8>>, ) -> Result<T>
Store::audited, for a write that records any number of changes
at once: build_leaves returns one leaf per change, the first
taking seq and each after it the next, all appended in write’s
transaction. The sweep is the one user: every device it removes has
a leaf, and they commit together with the removals.
Sourcepub fn audit_append(
&self,
build_leaf: impl FnOnce(u64, &str) -> Vec<u8>,
) -> Result<u64>
pub fn audit_append( &self, build_leaf: impl FnOnce(u64, &str) -> Vec<u8>, ) -> Result<u64>
Appends a leaf with no other state to change: a pull, the server’s
own start. Still one transaction (of one statement), so it shares
Store::audited’s all-or-nothing behaviour rather than being a
special case.
Returns the leaf’s seq, read out of the same closure audited
calls under its lock — not by asking the tree its size again
afterwards, which another append could have moved on by then.
Sourcepub fn audit_checkpoint(&self) -> (u64, Hash)
pub fn audit_checkpoint(&self) -> (u64, Hash)
The tree’s size and root, for GET /v1/audit/checkpoint and the
Recall-Audit-Checkpoint header every pull carries.
Sourcepub fn audit_entries(
&self,
start: u64,
end: u64,
max_bytes: usize,
) -> Result<Vec<AuditEntry>>
pub fn audit_entries( &self, start: u64, end: u64, max_bytes: usize, ) -> Result<Vec<AuditEntry>>
Leaves start to end - 1, stopping early once they come to more
than max_bytes together — though never before the first, however
large, so a caller paging on from the last seq it got always
moves. The caller (the route handler) is responsible for
end <= tree_size and the 1,000-row page limit.
Sourcepub fn audit_consistency(
&self,
first: u64,
second: u64,
) -> Result<Result<Vec<Hash>, ConsistencyError>>
pub fn audit_consistency( &self, first: u64, second: u64, ) -> Result<Result<Vec<Hash>, ConsistencyError>>
The RFC 9162 §2.1.4 proof that the tree at second extends the one
at first, from the tree in memory: a few hundred hashes at most,
and no read of the database, however long the log.
Source§impl Store
impl Store
Sourcepub fn create_enrollment(
&self,
e: &NewEnrollment<'_>,
max_pending: usize,
max_per_address: usize,
) -> Result<Created>
pub fn create_enrollment( &self, e: &NewEnrollment<'_>, max_pending: usize, max_per_address: usize, ) -> Result<Created>
Stores a pending enrolment, unless its code is in use by another
one still waiting, max_pending are waiting already, or
max_per_address from its address are.
Sourcepub fn poll_enrollment(
&self,
enrollment_id: &str,
now: OffsetDateTime,
interval: Duration,
) -> Result<Poll>
pub fn poll_enrollment( &self, enrollment_id: &str, now: OffsetDateTime, interval: Duration, ) -> Result<Poll>
Answers a machine’s poll, and records when it asked.
A poll sooner than interval after the last one is told to slow
down. A second of slack keeps a client that sleeps exactly the
interval from being told so because its previous request spent a
moment in flight.
Sourcepub fn approve_enrollment_audited(
&self,
user_code: &str,
device_id: &str,
scope: &str,
now: &str,
expected_fingerprint: Option<&str>,
build_leaf: impl FnOnce(u64, &str, &Device) -> Vec<u8>,
) -> Result<Decision<Device>>
pub fn approve_enrollment_audited( &self, user_code: &str, device_id: &str, scope: &str, now: &str, expected_fingerprint: Option<&str>, build_leaf: impl FnOnce(u64, &str, &Device) -> Vec<u8>, ) -> Result<Decision<Device>>
Approves the enrolment waiting with user_code, making it device
device_id, and appends the approve leaf build_leaf makes in
the same transaction. When expected_fingerprint is given, the
code’s key must have exactly that fingerprint.
build_leaf is called only once the device is real, so it can carry
the device’s own public key: the leaf that lets a signature of this
device’s be checked after the row itself is gone. A refusal
(NotFound, Expired, KeyMismatch, NameTaken, or
AlreadyDecided) rolls back and appends nothing.
Sourcepub fn deny_enrollment_audited(
&self,
user_code: &str,
now: &str,
build_leaf: impl FnOnce(u64, &str, &str) -> Vec<u8>,
) -> Result<Decision<String>>
pub fn deny_enrollment_audited( &self, user_code: &str, now: &str, build_leaf: impl FnOnce(u64, &str, &str) -> Vec<u8>, ) -> Result<Decision<String>>
Denies the enrolment waiting with user_code, answering with the
name it asked for, and appends the deny leaf in the same
transaction.
Sourcepub fn pending_enrollment(
&self,
user_code: &str,
now: &str,
) -> Result<Decision<Waiting>>
pub fn pending_enrollment( &self, user_code: &str, now: &str, ) -> Result<Decision<Waiting>>
What the enrolment waiting with user_code asked for, judged the
way approving it would be, so a lookup and the approval after it
never disagree about whether the code is still good.
Sourcepub fn enroll_device_audited(
&self,
d: &NewDevice<'_>,
max_for_key: Option<u32>,
build_leaf: impl FnOnce(u64, &str, &Device) -> Vec<u8>,
) -> Result<Inserted>
pub fn enroll_device_audited( &self, d: &NewDevice<'_>, max_for_key: Option<u32>, build_leaf: impl FnOnce(u64, &str, &Device) -> Vec<u8>, ) -> Result<Inserted>
Stores a device an authkey enrolled, and the enroll leaf
build_leaf makes for it, in one transaction — unless an unrevoked
device has its name or, when max_for_key is given, its authkey
already has that many unrevoked devices, both checked in the same
transaction, which then appends nothing.
The one way a device comes to exist without an approval, so its leaf is what carries its public key into the log: without it, nothing the device later signs could be checked offline once it is swept.
Sourcepub fn enrolled_worker(&self) -> Result<Option<Device>>
pub fn enrolled_worker(&self) -> Result<Option<Device>>
The newest unrevoked device with the worker scope. While there is
one, a stale push is queued for it rather than merged inline.
Sourcepub fn revoke_device_audited(
&self,
id: &str,
now: &str,
build_leaf: impl FnOnce(u64, &str, &Device) -> Vec<u8>,
) -> Result<Option<Device>>
pub fn revoke_device_audited( &self, id: &str, now: &str, build_leaf: impl FnOnce(u64, &str, &Device) -> Vec<u8>, ) -> Result<Option<Device>>
Revokes a device, with a revoke leaf appended when this call is
what revoked it. Revoking one already revoked keeps the first time,
changes nothing and appends nothing — there is no new fact for a
leaf to record. None when there is no such device.
Sourcepub fn touch_device(&self, id: &str, now: &str) -> Result<()>
pub fn touch_device(&self, id: &str, now: &str) -> Result<()>
Records that a device was just seen. Not a change the audit log
records: see the module docs of store/audit.rs.
Sourcepub fn insert_authkey_audited(
&self,
k: &NewAuthkey<'_>,
build_leaf: impl FnOnce(u64, &str, &Authkey) -> Vec<u8>,
) -> Result<Authkey>
pub fn insert_authkey_audited( &self, k: &NewAuthkey<'_>, build_leaf: impl FnOnce(u64, &str, &Authkey) -> Vec<u8>, ) -> Result<Authkey>
Stores an authkey’s hash and details, with its authkey_create leaf
appended in the same transaction.
Sourcepub fn authkey_by_hash(&self, key_sha256: &str) -> Result<Option<Authkey>>
pub fn authkey_by_hash(&self, key_sha256: &str) -> Result<Option<Authkey>>
The authkey whose SHA-256 is key_sha256, in any state.
Sourcepub fn revoke_authkey_audited(
&self,
id: &str,
now: &str,
devices: bool,
build_leaf: impl FnOnce(u64, &str, &Authkey, &[String]) -> Vec<u8>,
) -> Result<Option<Authkey>>
pub fn revoke_authkey_audited( &self, id: &str, now: &str, devices: bool, build_leaf: impl FnOnce(u64, &str, &Authkey, &[String]) -> Vec<u8>, ) -> Result<Option<Authkey>>
Revokes an authkey, keeping the first time if it already was, and
with devices every device it enrolled that is not revoked yet;
without it they are untouched. None when there is no such key.
One authkey_revoke leaf is appended when this call changed
anything — revoked the key, or any device — naming the devices it
revoked (build_leaf’s last argument), rather than one leaf more per
cascaded device. A call that changes nothing, such as the same
revoke sent twice, appends nothing. Asking for the devices of a key
revoked earlier on its own does revoke them, and is recorded.
Sourcepub fn sweep_devices_audited(
&self,
idle_before: &str,
expired_before: &str,
) -> Result<(usize, usize)>
pub fn sweep_devices_audited( &self, idle_before: &str, expired_before: &str, ) -> Result<(usize, usize)>
Removes ephemeral devices last seen (or, never seen, created)
before idle_before, and enrolments that expired before
expired_before. Answers how many of each went.
One transaction, with a sweep leaf for each device it removes,
the server as their actor: the devices chosen and the devices
deleted are the same rows, read and deleted under one lock, so no
leaf claims a device the sweep did not remove, and none it removed
goes without one. Enrolments carry no leaf: an unclaimed pending
code is not a device or an authkey, the two kinds of row the log
records.
Source§impl Store
impl Store
Sourcepub fn write_and_queue_merge_audited(
&self,
project_key: &str,
file_path: &str,
incoming: &MergeSide,
job_id: &str,
now: OffsetDateTime,
build_leaf: impl FnOnce(u64, &str, &Queued) -> Vec<u8>,
) -> Result<(Queued, String)>
pub fn write_and_queue_merge_audited( &self, project_key: &str, file_path: &str, incoming: &MergeSide, job_id: &str, now: OffsetDateTime, build_leaf: impl FnOnce(u64, &str, &Queued) -> Vec<u8>, ) -> Result<(Queued, String)>
Stores incoming as the file’s content, last-write-wins, and queues
a merge of it with whatever it displaced, in one transaction with
the push’s leaf, which build_leaf makes knowing what was queued:
the job’s stored side is read under the same lock as the write, so
it is exactly the version this push replaced. Answers what was
queued and the write’s updated_at, which is its leaf’s at, as
every audited write’s is; incoming.updated_at is not used.
Sourcepub fn expire_leases_audited(
&self,
now: OffsetDateTime,
build_leaf: impl Fn(u64, &str, &JobChange<'_>) -> Vec<u8>,
) -> Result<Vec<Failure>>
pub fn expire_leases_audited( &self, now: OffsetDateTime, build_leaf: impl Fn(u64, &str, &JobChange<'_>) -> Vec<u8>, ) -> Result<Vec<Failure>>
Puts every job whose lease ran out back in the queue, or fails it when that was its last attempt. Answers each job it failed.
Each job whose lease ran out gets a job_result leaf, which
build_leaf makes, in the same transaction: the attempt ended with
no result, and the job waits for another or has failed. A call that
finds no lease run out changes nothing and appends nothing.
Sourcepub fn claim_job_audited(
&self,
kinds: &[String],
lease_id: &str,
lease: Duration,
now: OffsetDateTime,
build_leaf: impl FnOnce(u64, &str, &Job) -> Vec<u8>,
) -> Result<Option<Job>>
pub fn claim_job_audited( &self, kinds: &[String], lease_id: &str, lease: Duration, now: OffsetDateTime, build_leaf: impl FnOnce(u64, &str, &Job) -> Vec<u8>, ) -> Result<Option<Job>>
Leases the oldest queued job of one of kinds that may run now,
for lease from now, under lease_id, and appends the
job_claim leaf build_leaf makes in the same transaction. Finding
nothing to lease changes nothing and appends nothing.
Sourcepub fn settle_job_audited(
&self,
id: &str,
result: &ResultRequest,
worker: &str,
follow_up_id: &str,
now: OffsetDateTime,
build_leaf: impl FnOnce(u64, &str, &JobChange<'_>) -> Vec<u8>,
) -> Result<Settlement>
pub fn settle_job_audited( &self, id: &str, result: &ResultRequest, worker: &str, follow_up_id: &str, now: OffsetDateTime, build_leaf: impl FnOnce(u64, &str, &JobChange<'_>) -> Vec<u8>, ) -> Result<Settlement>
Records a worker’s result for job id, with the job_result leaf
build_leaf makes from what it changed, in one transaction.
A result counts only under the job’s current, unexpired lease. The same result posted again under the lease that settled it changes nothing, answers as the first did, and appends nothing.
A merge is applied as a compare-and-swap: only if the file still
has the hash of the version the job stored, and then attributed to
worker. If another push landed meanwhile, the merged content
becomes the stored side of a follow-up job, follow_up_id,
against the newer version, at most MAX_LINKS deep. An empty
merge of two versions that were not empty is never applied: it is
taken as an error, and retried.
Sourcepub fn jobs(&self, state: Option<&str>, limit: usize) -> Result<Vec<JobSummary>>
pub fn jobs(&self, state: Option<&str>, limit: usize) -> Result<Vec<JobSummary>>
Jobs, newest first, at most limit, in state when given.
Sourcepub fn retry_job_audited(
&self,
id: &str,
now: OffsetDateTime,
build_leaf: impl FnOnce(u64, &str, &JobSummary) -> Vec<u8>,
) -> Result<Retried>
pub fn retry_job_audited( &self, id: &str, now: OffsetDateTime, build_leaf: impl FnOnce(u64, &str, &JobSummary) -> Vec<u8>, ) -> Result<Retried>
Queues a failed job again, with its attempts counted afresh, and
appends the job_retry leaf build_leaf makes in the same
transaction.
A job that failed because the file kept changing kept its result: it is queued as a merge of that result with the file as it is now, which is the merge that was never finished.
Sourcepub fn release_open_jobs(&self, now: OffsetDateTime) -> Result<usize>
pub fn release_open_jobs(&self, now: OffsetDateTime) -> Result<usize>
Makes every open job claimable at once: a leased one is released, its holder being gone, and a queued one’s retry delay is dropped. Answers how many there are.
For the server draining the queue itself once no worker is left to: a revoked worker’s lease would otherwise hold its job until it ran out, and a job waiting out a delay the worker’s failure set has no worker left to wait for. Attempts still count, so a job that keeps failing here is failed all the same.
Not in the audit log: it changes no file, and what it frees is
recorded around it — the revoke of the worker that held a lease
before, and the server’s own job_claim of each job after.
Sourcepub fn fail_open_jobs_audited(
&self,
why: &str,
now: OffsetDateTime,
build_leaf: impl Fn(u64, &str, &JobChange<'_>) -> Vec<u8>,
) -> Result<Vec<String>>
pub fn fail_open_jobs_audited( &self, why: &str, now: OffsetDateTime, build_leaf: impl Fn(u64, &str, &JobChange<'_>) -> Vec<u8>, ) -> Result<Vec<String>>
Marks every open job failed, with why as its error, for a queue
nothing is left to drain. Each keeps its input, so a retry merges it
once something can. Answers their ids.
Each job gets a job_result leaf, which build_leaf makes, in the
same transaction, as a lease that ran out on a last attempt does.
Sourcepub fn queue_status(&self) -> Result<QueueStatus>
pub fn queue_status(&self) -> Result<QueueStatus>
What /health says about the queue.
Sourcepub fn prune_jobs(&self, before: &str) -> Result<usize>
pub fn prune_jobs(&self, before: &str) -> Result<usize>
Removes jobs that finished before before. Failed jobs stay until
someone retries them: each may hold a result nobody has seen.
Not in the audit log: the leaves of the jobs removed stay in it.
Source§impl Store
impl Store
Sourcepub fn has_admin_credentials(&self) -> Result<bool>
pub fn has_admin_credentials(&self) -> Result<bool>
Whether the owner has registered any passkey.
Sourcepub fn add_admin_credential_audited(
&self,
c: &NewAdminCredential<'_>,
first: Option<FirstPasskey<'_>>,
build_leaf: impl FnOnce(u64, &str) -> Vec<u8>,
) -> Result<AddedCredential>
pub fn add_admin_credential_audited( &self, c: &NewAdminCredential<'_>, first: Option<FirstPasskey<'_>>, build_leaf: impl FnOnce(u64, &str) -> Vec<u8>, ) -> Result<AddedCredential>
Stores a passkey, with the passkey_add leaf build_leaf makes, in
one transaction.
With first, only while none is stored, and only with the bootstrap
code outstanding, which it uses up. All three are one transaction,
so two bootstraps at once cannot both be the first, nor one code
register two passkeys. It takes the write lock before anything is
read, as every audited write does: reset-passkeys writes from
another process.
Sourcepub fn check_bootstrap_code(
&self,
code_sha256: &str,
now: &str,
) -> Result<BootstrapCode>
pub fn check_bootstrap_code( &self, code_sha256: &str, now: &str, ) -> Result<BootstrapCode>
Whether code_sha256 is the bootstrap code outstanding at now.
Sourcepub fn set_bootstrap_code_audited(
&self,
code_sha256: &str,
now: &str,
expires_at: &str,
build_leaf: impl FnOnce(u64, &str) -> Vec<u8>,
) -> Result<()>
pub fn set_bootstrap_code_audited( &self, code_sha256: &str, now: &str, expires_at: &str, build_leaf: impl FnOnce(u64, &str) -> Vec<u8>, ) -> Result<()>
Makes code_sha256 the one bootstrap code, until expires_at,
replacing any other, with the bootstrap_code leaf build_leaf
makes.
Sourcepub fn admin_credential(&self, id: &str) -> Result<Option<AdminCredential>>
pub fn admin_credential(&self, id: &str) -> Result<Option<AdminCredential>>
One passkey.
Sourcepub fn admin_credentials(&self) -> Result<Vec<AdminCredential>>
pub fn admin_credentials(&self) -> Result<Vec<AdminCredential>>
Every passkey, oldest first.
Sourcepub fn record_admin_sign_in(
&self,
id: &str,
sign_count: u32,
passkey: &str,
now: &str,
) -> Result<bool>
pub fn record_admin_sign_in( &self, id: &str, sign_count: u32, passkey: &str, now: &str, ) -> Result<bool>
Records a sign-in, if its signature counter moved forward.
WebAuthn §7.2 step 22: when either the stored counter or the new one
is nonzero, the new one must be greater, or two copies of the
credential’s key may exist. Both zero is what a synced passkey
reports every time, and is accepted. The check and the update are one
statement, so two sign-ins racing with the same counter cannot both
pass. Answers whether it was recorded; false is a refusal.
Sourcepub fn remove_admin_credential_audited(
&self,
id: &str,
build_leaf: impl FnOnce(u64, &str, &AdminCredential) -> Vec<u8>,
) -> Result<RemovedCredential>
pub fn remove_admin_credential_audited( &self, id: &str, build_leaf: impl FnOnce(u64, &str, &AdminCredential) -> Vec<u8>, ) -> Result<RemovedCredential>
Removes a passkey and every session it signed in, unless it is the
last one, with the passkey_remove leaf build_leaf makes from it.
Sourcepub fn reset_admin_credentials_audited(
&self,
code_sha256: &str,
now: &str,
expires_at: &str,
build_leaf: impl FnOnce(u64, &str, usize) -> Vec<u8>,
) -> Result<usize>
pub fn reset_admin_credentials_audited( &self, code_sha256: &str, now: &str, expires_at: &str, build_leaf: impl FnOnce(u64, &str, usize) -> Vec<u8>, ) -> Result<usize>
Removes every passkey and every session, and makes code_sha256
the bootstrap code until expires_at: what recall-server reset-passkeys does, for an owner who has lost them all. One
transaction with the passkey_reset leaf build_leaf makes from how
many passkeys went, which it answers, and it creates the tables
first if this database has never been opened by a server that has
them.
Run from another process than the server’s, on the same file: the
leaf takes the next seq the table has, and a running server reads
it into its tree before its own next append (see
Store::audited_each).
Sourcepub fn create_admin_session(
&self,
token_sha256: &str,
credential_id: &str,
now: &str,
expires_at: &str,
) -> Result<bool>
pub fn create_admin_session( &self, token_sha256: &str, credential_id: &str, now: &str, expires_at: &str, ) -> Result<bool>
Stores a new session, only if the passkey that signed it in is still there: a sign-in that finishes as its passkey is removed must not outlive it.
Sourcepub fn admin_session(&self, token_sha256: &str) -> Result<Option<AdminSession>>
pub fn admin_session(&self, token_sha256: &str) -> Result<Option<AdminSession>>
One session, by the hash of its token.
Sourcepub fn touch_admin_session(&self, token_sha256: &str, now: &str) -> Result<()>
pub fn touch_admin_session(&self, token_sha256: &str, now: &str) -> Result<()>
Records that a session was used, which is what keeps it from going idle.
Sourcepub fn delete_other_admin_sessions_audited(
&self,
keep: &str,
build_leaf: impl FnOnce(u64, &str, usize) -> Vec<u8>,
) -> Result<usize>
pub fn delete_other_admin_sessions_audited( &self, keep: &str, build_leaf: impl FnOnce(u64, &str, usize) -> Vec<u8>, ) -> Result<usize>
Ends every session but keep, with the sessions_end leaf
build_leaf makes from how many ended, which it answers. Ending none
changes nothing and appends nothing.
Sourcepub fn delete_admin_session(&self, token_sha256: &str) -> Result<()>
pub fn delete_admin_session(&self, token_sha256: &str) -> Result<()>
Ends a session.
Source§impl Store
impl Store
Sourcepub fn open(path: impl AsRef<Path>) -> Result<Self>
pub fn open(path: impl AsRef<Path>) -> Result<Self>
Opens (creating if needed) the database at path, switching it to
SQLite’s WAL journal with every commit synced before it returns (see
use_durable_wal in this module for why both). A file an older
server wrote with the rollback journal is converted here, once; the
mode is stored in the file. Fails, rather than serving in another
mode, if the switch cannot be made: the file is held by another
process for longer than the busy timeout, or SQLite cannot keep the
WAL’s index beside it. A network filesystem may well not fail here
and still not work; deploy/README.md says not to use one.
Sourcepub fn open_in_memory() -> Result<Self>
pub fn open_in_memory() -> Result<Self>
An in-memory database, for tests.
Sourcepub fn get(
&self,
project_key: &str,
file_path: &str,
) -> Result<Option<Existing>>
pub fn get( &self, project_key: &str, file_path: &str, ) -> Result<Option<Existing>>
Reads one row.
Sourcepub fn upsert_audited(
&self,
project_key: &str,
file_path: &str,
content: &str,
source_env: &str,
build_leaf: impl FnOnce(u64, &str) -> Vec<u8>,
) -> Result<String>
pub fn upsert_audited( &self, project_key: &str, file_path: &str, content: &str, source_env: &str, build_leaf: impl FnOnce(u64, &str) -> Vec<u8>, ) -> Result<String>
Writes content, clearing any tombstone, and appends the leaf
build_leaf makes from its seq and at in the same transaction.
Answers the time it was written, which is that at: the row’s
updated_at and its leaf’s at are one timestamp.
Sourcepub fn tombstone_audited(
&self,
project_key: &str,
file_path: &str,
source_env: &str,
build_leaf: impl FnOnce(u64, &str) -> Vec<u8>,
) -> Result<String>
pub fn tombstone_audited( &self, project_key: &str, file_path: &str, source_env: &str, build_leaf: impl FnOnce(u64, &str) -> Vec<u8>, ) -> Result<String>
Marks a file deleted while deliberately leaving its content in
place: a mistaken delete stays recoverable at the database level,
even though nothing in the app surfaces an undo yet. Store::list
withholds the content so a pull can’t resurrect it.
In the same transaction it closes the file’s open merge jobs, so
nothing merges the deleted notes back into a file pushed after it,
and appends its leaf, answering the time, as
Store::upsert_audited does.
Sourcepub fn list(&self, project_key: &str) -> Result<Vec<File>>
pub fn list(&self, project_key: &str) -> Result<Vec<File>>
Every file for a project, tombstones included so a pulling client knows what to remove locally — but with the deleted content withheld.
Sourcepub fn last_sync_at(&self) -> Result<String>
pub fn last_sync_at(&self) -> Result<String>
The most recent write across all projects, for /health. Empty when
nothing has ever been synced.
Sourcepub fn admin_stats(&self) -> Result<(Vec<ProjectStats>, AdminTotals)>
pub fn admin_stats(&self) -> Result<(Vec<ProjectStats>, AdminTotals)>
Aggregates per project for the admin page.
Sourcepub fn checkpoint(&self) -> Result<bool>
pub fn checkpoint(&self) -> Result<bool>
Copies the commits the WAL holds into the database file, as far as no reader still needs them, waiting on nothing. Answers whether that was all of them.
SQLite already does this by itself once the WAL passes 1000 pages,
which on a personal server can be days of pushes. The server also
does it every sweep, so that recall.db on its own, all a reader
that cannot see recall.db-wal has (sqlite-web’s single-file mount
in deploy/docker-compose.direct.yml), is never far behind. Nothing
relies on it for correctness: the WAL is part of the database. A
reader that keeps one transaction open holds back everything
committed after it began, and the WAL grows until it lets go; the
server says so when that lasts several sweeps.
Sourcepub fn checkpoint_all(&self) -> Result<bool>
pub fn checkpoint_all(&self) -> Result<bool>
Copies every commit the WAL holds into the database file and empties the WAL, waiting up to the busy timeout for a reader in the way. Answers whether it got all the way.
The last thing a server does as it stops. Closing the connection
does the same, but only when no other process has the file open, and
sqlite-web keeps it open. Emptied here, the WAL left beside the file
holds nothing, so a stopped server’s recall.db is the whole
database. Not something to rely on after a crash, which is why every
procedure in deploy/README.md moves or copies the WAL with the file.
Sourcepub fn backup(&self, dir: impl AsRef<Path>, keep: usize) -> Result<PathBuf>
pub fn backup(&self, dir: impl AsRef<Path>, keep: usize) -> Result<PathBuf>
Writes a consistent snapshot via VACUUM INTO, then prunes the
oldest snapshots beyond keep.
VACUUM INTO reads through this connection, so the snapshot holds
every commit, those still only in the WAL included, as of one
moment, whatever is writing meanwhile. Copying recall.db instead
would miss whatever the WAL has not yet handed back to it. And what
it writes is one self-contained file in the rollback journal’s mode,
with no -wal of its own, so a snapshot can be copied, uploaded,
opened read-only, or copied over recall.db in a restore, as it is.
Auto Trait Implementations§
impl !Freeze for Store
impl RefUnwindSafe for Store
impl Send for Store
impl Sync for Store
impl Unpin for Store
impl UnsafeUnpin for Store
impl UnwindSafe for Store
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more