mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 19:07:17 +01:00
Extracts the three-method remote surface GitHub was reached through into a `RemoteStore` interface, then implements it against Firebase Realtime Database over REST, so apps can move backend with a constructor change. Why REST and not the SDKs: official FlutterFire has no Linux desktop support, and `wake_alarm`'s PC side is a dependency-free systemd job. The RTDB REST endpoints are plain HTTPS and behave identically on Linux, Android and headless. Firebase is a transport, never the datastore -- each app keeps its local log as the source of truth, so offline-first survives. Why RTDB and not Firestore: on the Spark plan RTDB bills only storage and bandwidth with no per-operation quota, so a runaway loop cannot exhaust a daily budget and silently stop syncing mid-day. Two savings make the no-cost tier a guarantee rather than a hope. Measured against the GitHub-backed history this replaces: 4894 of 5542 commits (88.3%) were byte-identical no-ops, and every tick re-downloaded every peer's whole log. Both are now driven by one sha256 revision -- an unchanged log is not re-pushed, and an unchanged peer is not re-fetched. That turns egress from "ticks x log size" (~700 MB/month, and growing with the log) into "edits" (~40 MB/month against a 10 GiB cap). Revisions are one node per device, written only by its owner, rather than one shared map -- a whole-map write would erase every peer's entry, after which those peers would look permanently unchanged and never be fetched again. Per-device keys make that impossible rather than merely avoided. `.` is illegal in an RTDB key and the REST `.json` is a format suffix, so paths get a reversible `~XX` escape (`log.json` -> `log~2Ejson`). Lossy extension-stripping would break `todo`, whose listings return filenames. `MirrorStore` / `MirrorSyncClient` dual-writes during the cutover. Reads consult *both* backends: with primary-only reads a migrated device would never see an un-migrated peer's writes while still mirroring its own back -- silent one-directional convergence with no error raised. Writes are asymmetric on purpose: a primary failure fails the tick, a mirror failure is logged and survived. Auth is Firebase email/password over identitytoolkit REST, with mandatory refresh-token rotation. No service-account key anywhere: it would bypass the security rules and is trivially extractable from an APK. Verified live against a real database, not only mocks: sign-in and refresh, key escaping round-tripping byte-for-byte, an unchanged tick writing nothing, two devices converging, a revoked session throwing rather than returning null, and a log written by the Python client being read by the Dart one. No app is migrated by this commit; every app still runs on GitHub. Dart: 194 tests. Python: 203 tests at 100% branch coverage. No lint suppressions added.
71 lines
1.7 KiB
Python
71 lines
1.7 KiB
Python
"""Shared CRDT merge scheme + pluggable remote sync transports."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from crdt_sync._firebase import (
|
|
DatabaseNotFoundError,
|
|
FirebaseSyncClient,
|
|
FirebaseSyncError,
|
|
)
|
|
from crdt_sync._firebase_auth import (
|
|
CredentialStore,
|
|
FileCredentialStore,
|
|
FirebaseAuthError,
|
|
FirebaseCredentials,
|
|
FirebaseTokenProvider,
|
|
MemoryCredentialStore,
|
|
)
|
|
from crdt_sync._github import GitHubSyncClient, GitHubSyncError, RepoNotFoundError
|
|
from crdt_sync._hlc import Hlc
|
|
from crdt_sync._log import Log, merge_logs
|
|
from crdt_sync._mirror import MirrorSyncClient
|
|
from crdt_sync._record import Field, Record, merge_field, merge_record
|
|
from crdt_sync._remote import RemoteNotFoundError, RemoteStore, RemoteSyncError
|
|
from crdt_sync._store import dump_log, load_log, read_log, write_log
|
|
from crdt_sync._sync import (
|
|
FileSyncStateStore,
|
|
MemorySyncStateStore,
|
|
SyncState,
|
|
SyncStateStore,
|
|
default_revs_path,
|
|
revision_of,
|
|
sync_log,
|
|
)
|
|
|
|
__all__ = [
|
|
"CredentialStore",
|
|
"DatabaseNotFoundError",
|
|
"Field",
|
|
"FileCredentialStore",
|
|
"FileSyncStateStore",
|
|
"FirebaseAuthError",
|
|
"FirebaseCredentials",
|
|
"FirebaseSyncClient",
|
|
"FirebaseSyncError",
|
|
"FirebaseTokenProvider",
|
|
"GitHubSyncClient",
|
|
"GitHubSyncError",
|
|
"Hlc",
|
|
"Log",
|
|
"MemoryCredentialStore",
|
|
"MemorySyncStateStore",
|
|
"MirrorSyncClient",
|
|
"Record",
|
|
"RemoteNotFoundError",
|
|
"RemoteStore",
|
|
"RemoteSyncError",
|
|
"RepoNotFoundError",
|
|
"SyncState",
|
|
"SyncStateStore",
|
|
"default_revs_path",
|
|
"dump_log",
|
|
"load_log",
|
|
"merge_field",
|
|
"merge_logs",
|
|
"merge_record",
|
|
"read_log",
|
|
"revision_of",
|
|
"sync_log",
|
|
"write_log",
|
|
]
|