Documentation
¶
Overview ¶
Package bitboxsync provides the stateful, local-first BitBoxSync client engine, including polling, persistence integration, conflict handling, and typed collection helpers for application code.
Index ¶
- Variables
- func EqualBytes(left, right []byte) bool
- func InviteURI(token protocol.NamespaceInviteToken) (string, error)
- func ParseInviteURI(value string) (protocol.NamespaceInviteToken, error)
- type Codec
- type Collection
- type CollectionConfig
- type ConditionalValueBackend
- type Config
- type Engine
- func (e *Engine) Close() error
- func (e *Engine) CreateSharedNamespace(ctx context.Context) (*Namespace, error)
- func (e *Engine) DefaultNamespace(ctx context.Context) (*Namespace, error)
- func (e *Engine) Events() <-chan Event
- func (e *Engine) Identity() IdentityState
- func (e *Engine) IdlePolling() bool
- func (e *Engine) JoinNamespace(ctx context.Context, namespaceID string) (*Namespace, error)
- func (e *Engine) KeyID() string
- func (e *Engine) ListNamespaces(ctx context.Context) ([]NamespaceState, error)
- func (e *Engine) Login(ctx context.Context) error
- func (e *Engine) Namespace(ctx context.Context, namespaceID string) (*Namespace, error)
- func (e *Engine) RevokeAllTokens(ctx context.Context) error
- func (e *Engine) Run(ctx context.Context) error
- func (e *Engine) ScheduleSync()
- func (e *Engine) SetIdlePolling(idle bool)
- func (e *Engine) SubmitJoinRequest(ctx context.Context, invite protocol.NamespaceInviteToken, ...) (*protocol.SubmitNamespaceJoinRequestResponse, error)
- func (e *Engine) SyncNow(ctx context.Context) error
- type Event
- type EventType
- type IdentityState
- type ItemState
- type MemoryValueBackend
- func (b *MemoryValueBackend[T]) Get(_ context.Context, key string) (T, error)
- func (b *MemoryValueBackend[T]) Keys(context.Context) ([]string, error)
- func (b *MemoryValueBackend[T]) Set(_ context.Context, key string, value T) error
- func (b *MemoryValueBackend[T]) SetIfCurrent(_ context.Context, key string, current T, currentFound bool, value T) (bool, error)
- func (b *MemoryValueBackend[T]) Snapshot(context.Context) (map[string]T, error)
- type MergeFunc
- type Namespace
- func (n *Namespace) ApproveJoinRequest(ctx context.Context, invite protocol.NamespaceInviteToken, ...) error
- func (n *Namespace) CreateInvite(ctx context.Context, opts NamespaceInviteOptions) (protocol.NamespaceInviteToken, error)
- func (n *Namespace) ID() string
- func (n *Namespace) Invites(ctx context.Context) ([]protocol.NamespaceInviteSummary, error)
- func (n *Namespace) JoinRequests(ctx context.Context) ([]protocol.NamespaceJoinRequestEntry, error)
- func (n *Namespace) Members(ctx context.Context) ([]protocol.NamespaceMember, error)
- func (n *Namespace) RejectJoinRequest(ctx context.Context, entry protocol.NamespaceJoinRequestEntry) error
- func (n *Namespace) RevokeInvite(ctx context.Context, inviteID string) error
- type NamespaceInviteOptions
- type NamespaceJoinRequestOptions
- type NamespaceState
- type Store
- type ValueBackend
Constants ¶
This section is empty.
Variables ¶
var ( ErrNotFound = errors.New("bitboxsync: not found") ErrRollback = errors.New("bitboxsync: rollback detected") ErrClosed = errors.New("bitboxsync: engine closed") ErrNoDefault = errors.New("bitboxsync: default namespace not provisioned") ErrNoBackend = errors.New("bitboxsync: collection value backend required") ErrCollectionRegistered = errors.New("bitboxsync: collection already registered") )
Functions ¶
func EqualBytes ¶
EqualBytes reports whether two byte slices contain the same bytes.
func InviteURI ¶
func InviteURI(token protocol.NamespaceInviteToken) (string, error)
InviteURI encodes a namespace invite token as the QR/copy URI.
func ParseInviteURI ¶
func ParseInviteURI(value string) (protocol.NamespaceInviteToken, error)
ParseInviteURI parses a QR/copy namespace invite URI.
Types ¶
type Codec ¶
type Codec[T any] interface { // Encode serializes a typed collection value into the bytes stored in the // encrypted item payload. Implementations must be deterministic for equal // inputs so conflict handling remains predictable. Encode(T) ([]byte, error) // Decode parses bytes previously produced by Encode. Implementations must // reject malformed payloads rather than silently producing partial values. Decode([]byte) (T, error) }
Codec translates typed collection values to and from encrypted payload bytes.
func BytesCodec ¶
BytesCodec returns a codec that stores raw byte slices without additional encoding.
func StringCodec ¶
StringCodec returns a codec that stores strings as UTF-8 bytes.
type Collection ¶
type Collection[T any] struct { // contains filtered or unexported fields }
Collection registers typed sync behavior for one namespace collection.
func OpenCollection ¶
func OpenCollection[T any](namespace *Namespace, name string, cfg CollectionConfig[T]) (*Collection[T], error)
OpenCollection constructs a typed collection helper bound to one namespace.
func (*Collection[T]) ResolveConflictPreferLocal ¶
func (c *Collection[T]) ResolveConflictPreferLocal(ctx context.Context, key string) error
ResolveConflictPreferLocal resolves a stored conflict by keeping the local value queued for upload.
func (*Collection[T]) ResolveConflictPreferRemote ¶
func (c *Collection[T]) ResolveConflictPreferRemote(ctx context.Context, key string) error
ResolveConflictPreferRemote resolves a stored conflict by accepting the remote value and clearing local dirty state.
func (*Collection[T]) ResolveConflictWithValue ¶
func (c *Collection[T]) ResolveConflictWithValue(ctx context.Context, key string, value T) error
ResolveConflictWithValue resolves a stored conflict by choosing an explicit replacement value to upload on the next sync.
type CollectionConfig ¶
type CollectionConfig[T any] struct { // Codec serializes values to and from encrypted item payload bytes. When nil, // OpenCollection defaults it to JSONCodec. Codec Codec[T] // Merge resolves three-way conflicts for this collection. When nil, // OpenCollection defaults it to NoMerge. Merge MergeFunc[T] // Backend stores current typed values in an app-owned data store. It is // required. Backend.Keys defines the active sync scope, and Backend.Snapshot // returns the local values reconciled during each sync pass. Backend ValueBackend[T] }
CollectionConfig defines the codec, merge policy, and storage backend for a collection.
type ConditionalValueBackend ¶
type ConditionalValueBackend[T any] interface { SetIfCurrent(ctx context.Context, key string, current T, currentFound bool, value T) (replaced bool, err error) }
ConditionalValueBackend is an optional ValueBackend extension for app-owned stores that can be written outside BitBoxSync.
SetIfCurrent must atomically compare the currently stored value for key to current/currentFound and replace it with value only when they still match. It returns replaced=false when another app write won the race. The sync engine then leaves the item dirty and retries through normal merge/upload handling instead of overwriting the app's newer value.
App-owned production backends that allow writes outside BitBoxSync should implement this interface. If they do not, remote apply falls back to Set after a best-effort re-read. That fallback can still overwrite an app write that lands after the re-read and before Set.
type Config ¶
type Config struct {
// Client is the raw BitBoxSync API client used for all network requests. It
// is required.
Client *raw.Client
// Identity provides the auth signing, attestation, and private DEK unwrap
// operations needed by the engine. It is required.
Identity raw.Identity
// Store persists identity state, namespaces, item metadata, dirty state, and
// conflicts across runs. It is required.
Store Store
// PollInterval is how often Run performs fallback background sync passes.
// Values less than or equal to zero default to 5 minutes.
PollInterval time.Duration
// IdlePolling starts Run in explicit idle polling mode. Idle mode is intended
// for app-controlled background or watch-only states where slower remote
// change detection is acceptable. It can be changed later with
// Engine.SetIdlePolling.
IdlePolling bool
// MaxPollInterval is the longest delay Run will use after explicit idle
// polling or failed polling backs off. Values less than PollInterval default
// to the larger of 60 minutes and PollInterval.
MaxPollInterval time.Duration
// DisableNamespaceWatch disables the advisory long-poll namespace watch loop.
// When enabled, Run still keeps its polling timer as a correctness fallback.
DisableNamespaceWatch bool
// RefreshSkew is how long before token expiry the engine proactively refreshes
// the bearer token. Values less than or equal to zero default to 24 hours.
RefreshSkew time.Duration
// EventBuffer is the size of the buffered Events channel. Values less than or
// equal to zero default to 64.
EventBuffer int
// InviteTTL is the default lifetime used by CreateInvite when the caller
// passes a non-positive ttl. Values less than or equal to zero default to 10
// minutes.
InviteTTL time.Duration
}
Config controls engine dependencies and background sync behavior.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine coordinates authentication, namespace reconciliation, local persistence, and conflict-aware sync.
func (*Engine) CreateSharedNamespace ¶
CreateSharedNamespace creates a new shared namespace and caches its DEK locally.
func (*Engine) DefaultNamespace ¶
DefaultNamespace returns the caller's default namespace, creating it when needed.
func (*Engine) Events ¶
Events returns important lifecycle/data/control events. Events are ordered and not dropped. If the caller does not read, the client may block.
func (*Engine) Identity ¶
func (e *Engine) Identity() IdentityState
Identity returns a snapshot of the current persisted identity state.
func (*Engine) IdlePolling ¶
IdlePolling reports whether Run is currently using explicit idle polling behavior after quiet successful polls.
func (*Engine) JoinNamespace ¶
JoinNamespace loads membership metadata and wrapped DEK for an existing namespace.
func (*Engine) ListNamespaces ¶
func (e *Engine) ListNamespaces(ctx context.Context) ([]NamespaceState, error)
ListNamespaces refreshes and returns all namespaces visible to the current identity.
func (*Engine) Login ¶
Login refreshes the identity's authenticated setup and wakes background retry loops that may have backed off while auth was unavailable.
func (*Engine) RevokeAllTokens ¶
RevokeAllTokens revokes all server-side bearer tokens for the current identity and clears the local access token.
func (*Engine) Run ¶
Run starts the background polling loop and keeps syncing until ctx is done or the engine is closed.
func (*Engine) ScheduleSync ¶
func (e *Engine) ScheduleSync()
ScheduleSync asks Run to perform a sync pass soon.
The wake-up is coalesced and non-blocking: if a sync is already queued, this call does not queue another one. ScheduleSync does not perform network work itself and is safe to call after app-owned value writes. Use SyncNow when the caller needs to wait for a foreground sync result.
func (*Engine) SetIdlePolling ¶
SetIdlePolling controls whether Run uses the slower idle polling backoff policy after successful polls with no sync activity. Apps should enable this only for explicit background, idle, or watch-only states where slower remote change detection is acceptable. Changing the mode wakes Run so the new cadence can take effect promptly.
func (*Engine) SubmitJoinRequest ¶
func (e *Engine) SubmitJoinRequest(ctx context.Context, invite protocol.NamespaceInviteToken, opts NamespaceJoinRequestOptions) (*protocol.SubmitNamespaceJoinRequestResponse, error)
SubmitJoinRequest signs and submits a request to join the namespace named by invite.
func (*Engine) SyncNow ¶
SyncNow performs one full foreground sync pass.
Conceptually, the engine maintains a local-first replica in the configured store and reconciles that replica with the server in three phases:
- Ensure authentication is usable by logging in or refreshing the bearer token as needed.
- Reconcile registered collection snapshots into dirty item metadata.
- Pull remote namespace and item state. Namespace heads act as cheap invalidation signals, backend keys map logical keys to opaque item IDs, and changed remote items are applied, merged with local dirty state, or recorded as conflicts.
- Push local dirty items with optimistic concurrency. Writes use the cached item version as If-Match, bind the target version into the item's AAD, and fall back to fetch/merge/conflict handling on precondition failures.
Run simply repeats this algorithm on a poll timer or explicit wake-up signal.
type Event ¶
type Event struct {
// Type names the event category.
Type EventType
// NamespaceID identifies the namespace involved in the event, when
// applicable.
NamespaceID string
// Collection identifies the collection involved in the event, when
// applicable.
Collection string
// Key identifies the logical key involved in the event, when applicable.
Key string
// ItemID identifies the opaque item involved in the event, when applicable.
ItemID string
// Err holds the underlying error for failure events.
Err error
// TokenExpiresAt records the bearer-token expiry for auth session events.
TokenExpiresAt time.Time
// At records when the event was emitted.
At time.Time
}
Event records a notable sync-engine state transition.
type EventType ¶
type EventType string
const ( // EventAuthLoginRequired is emitted when the engine needs a fresh login // before authenticated work can continue. EventAuthLoginRequired EventType = "auth-login-required" // EventAuthRefreshRecommended is emitted when the current bearer token is // still valid but close enough to expiry that callers should prompt the user // to reconnect. EventAuthRefreshRecommended EventType = "auth-refresh-recommended" // EventAuthSessionReady is emitted after login or refresh stores a usable // bearer token. EventAuthSessionReady EventType = "auth-session-ready" // EventSyncStarted is emitted when a sync pass begins. EventSyncStarted EventType = "sync-started" // EventSyncFinished is emitted when a sync pass completes successfully. EventSyncFinished EventType = "sync-finished" // EventSyncFailed is emitted when a sync pass terminates with an error. EventSyncFailed EventType = "sync-failed" // EventNamespaceWatchFailed is emitted when the advisory namespace watch loop // encounters a transient error. Polling continues to provide correctness. EventNamespaceWatchFailed EventType = "namespace-watch-failed" // EventNamespaceChanged is emitted when a namespace head was reconciled. EventNamespaceChanged EventType = "namespace-changed" // EventItemChanged is emitted when local state for an item changed due to // sync, merge, or conflict refresh. EventItemChanged EventType = "item-changed" // EventItemDownloaded is emitted when a remote item value was applied to // the local value backend. EventItemDownloaded EventType = "item-downloaded" // EventItemUploaded is emitted after a dirty local item was successfully // uploaded to the server. EventItemUploaded EventType = "item-uploaded" // EventItemQueued is emitted when a local write is staged for upload. EventItemQueued EventType = "item-queued" // EventConflictDetected is emitted when automatic merge cannot resolve a // local-versus-remote divergence. EventConflictDetected EventType = "conflict-detected" // EventUnknownRemoteItem is emitted when the server reports an item ID the // engine cannot yet map back to a logical key. EventUnknownRemoteItem EventType = "unknown-remote-item" )
type IdentityState ¶
type IdentityState struct {
// KeyID identifies the auth identity this state belongs to.
KeyID string
// Kind is the auth-key kind, currently expected to be "keystore".
Kind string
// AccessToken is the last bearer token issued for this identity.
AccessToken string
// TokenExpiry is when AccessToken expires server-side.
TokenExpiry time.Time
// DefaultNamespaceID is the cached default namespace identifier for this
// identity.
DefaultNamespaceID string
// UpdatedAt records when this state was last written locally.
UpdatedAt time.Time
}
IdentityState stores persisted authentication and default-namespace state.
type ItemState ¶
type ItemState struct {
// KeyID identifies the auth identity this item cache entry belongs to.
KeyID string
// NamespaceID is the hex-encoded namespace identifier.
NamespaceID string
// Collection is the logical collection name for the item.
Collection string
// Key is the logical key within Collection.
Key string
// ItemID is the opaque hex-encoded item identifier derived from Collection
// and Key.
ItemID string
// Version is the highest item version known locally.
Version uint64
// BaseVersion is the version associated with BaseValue.
BaseVersion uint64
// BaseValue is the last remote value that local edits were based on.
BaseValue []byte
// Dirty reports whether the current value still needs to be uploaded.
Dirty bool
// Conflict reports whether automatic merge failed and manual resolution is
// required.
Conflict bool
// ConflictRemoteVersion is the remote version involved in the unresolved
// conflict.
ConflictRemoteVersion uint64
// ConflictRemoteValue is the remote value involved in the unresolved
// conflict.
ConflictRemoteValue []byte
// UpdatedAt records when this state was last written locally.
UpdatedAt time.Time
}
ItemState stores the local sync metadata for one item, including merge base and conflict metadata. Current values belong to the collection's ValueBackend.
type MemoryValueBackend ¶
type MemoryValueBackend[T any] struct { // contains filtered or unexported fields }
MemoryValueBackend stores typed collection values in memory.
It is useful for tests, demos, and short-lived tools. Applications that need durable sync should provide a backend backed by their own storage.
func NewMemoryValueBackend ¶
func NewMemoryValueBackend[T any](initial map[string]T) *MemoryValueBackend[T]
NewMemoryValueBackend returns a RAM-backed ValueBackend initialized with a copy of initial.
func (*MemoryValueBackend[T]) Get ¶
func (b *MemoryValueBackend[T]) Get(_ context.Context, key string) (T, error)
Get returns the typed value for key.
func (*MemoryValueBackend[T]) Keys ¶
func (b *MemoryValueBackend[T]) Keys(context.Context) ([]string, error)
Keys returns the stored keys in stable order.
func (*MemoryValueBackend[T]) Set ¶
func (b *MemoryValueBackend[T]) Set(_ context.Context, key string, value T) error
Set stores the typed value for key.
func (*MemoryValueBackend[T]) SetIfCurrent ¶
func (b *MemoryValueBackend[T]) SetIfCurrent(_ context.Context, key string, current T, currentFound bool, value T) (bool, error)
SetIfCurrent atomically replaces key only when the stored value still matches the caller's last read.
type MergeFunc ¶
type MergeFunc[T any] func(key string, base *T, local, remote T) (merged T, resolved bool, err error)
MergeFunc resolves a conflict between the current local value and a remote value for key.
base points to the last value known to be shared by both sides. It is nil when no common base is known, for example during first enable, after local sync metadata was reset, or when two clients concurrently created the same logical key. Merge functions that can safely resolve that two-way collision may still return resolved=true. Merge functions that need a true three-way base should return resolved=false when base is nil.
Implementations should treat base, local, and remote as read-only inputs. If T contains maps, slices, or pointers, return an owned value before mutating it.
func NoMerge ¶
NoMerge returns a merge function that leaves conflicts unresolved so the app can resolve them manually.
func PreferLocal ¶
PreferLocal returns a merge function that always resolves conflicts in favor of the local value.
func PreferRemote ¶
PreferRemote returns a merge function that always resolves conflicts in favor of the remote value.
type Namespace ¶
type Namespace struct {
// contains filtered or unexported fields
}
Namespace is a lightweight handle for namespace-scoped operations.
func (*Namespace) ApproveJoinRequest ¶
func (n *Namespace) ApproveJoinRequest(ctx context.Context, invite protocol.NamespaceInviteToken, entry protocol.NamespaceJoinRequestEntry) error
ApproveJoinRequest verifies a pending join request against invite, wraps this namespace's DEK for the requester, and approves the request.
func (*Namespace) CreateInvite ¶
func (n *Namespace) CreateInvite(ctx context.Context, opts NamespaceInviteOptions) (protocol.NamespaceInviteToken, error)
CreateInvite creates a short-lived namespace invite and returns the QR material that prospective members can scan.
func (*Namespace) Invites ¶
Invites lists namespace invite management metadata visible to this member.
func (*Namespace) JoinRequests ¶
JoinRequests lists active pending join requests for this namespace.
func (*Namespace) RejectJoinRequest ¶
func (n *Namespace) RejectJoinRequest(ctx context.Context, entry protocol.NamespaceJoinRequestEntry) error
RejectJoinRequest rejects one pending request for this namespace.
type NamespaceInviteOptions ¶
type NamespaceInviteOptions struct {
// ServerOrigin is the canonical public server origin encoded into the invite
// QR. It must match one of the server's configured public origins.
ServerOrigin string
// InviteID is an optional caller-generated lowercase-hex invite ID. Leave it
// empty to generate a fresh random ID during CreateInvite.
InviteID string
// InviteSecret is an optional caller-generated unpadded base64url invite
// secret. Leave it empty to generate a fresh random secret during
// CreateInvite.
InviteSecret string
// TTL controls invite lifetime. Non-positive values use Config.InviteTTL.
TTL time.Duration
// MaxAccepted caps successful first-time approvals through this invite.
// Non-positive values use the protocol default.
MaxAccepted int
}
NamespaceInviteOptions controls creation of one shared-namespace invite.
type NamespaceJoinRequestOptions ¶
type NamespaceJoinRequestOptions struct {
// TTL controls join-request lifetime. Non-positive values use the protocol
// maximum.
TTL time.Duration
// ExpiresAt is an optional absolute Unix timestamp in seconds. Leave it zero
// to derive expiry from TTL.
ExpiresAt int64
}
NamespaceJoinRequestOptions controls submission of one namespace join request.
type NamespaceState ¶
type NamespaceState struct {
// KeyID identifies the auth identity this namespace cache entry belongs to.
KeyID string
// NamespaceID is the hex-encoded namespace identifier.
NamespaceID string
// Kind is the namespace kind, such as "default" or "shared".
Kind string
// NamespaceHead is the highest namespace head observed locally.
NamespaceHead uint64
// ActiveScopeHash identifies the set of active logical keys that was
// reconciled at NamespaceHead. It is empty when no collection is registered.
ActiveScopeHash string
// DEK is the unwrapped namespace data-encryption key cached locally.
DEK []byte
// UpdatedAt records when this state was last written locally.
UpdatedAt time.Time
}
NamespaceState stores cached namespace metadata and the unwrapped namespace DEK.
type Store ¶
type Store interface {
// Close releases any resources held by the store. It must be safe to call
// once during engine shutdown.
Close() error
// LoadIdentity loads the persisted auth/session state for keyID. It must
// return ErrNotFound when no identity state exists yet.
LoadIdentity(ctx context.Context, keyID string) (IdentityState, error)
// SaveIdentity persists the latest auth/session state for an auth identity.
// Implementations must replace the prior state atomically for the same keyID.
SaveIdentity(ctx context.Context, state IdentityState) error
// GetNamespace loads cached metadata and secrets for one namespace. It must
// return ErrNotFound when the namespace has not been cached locally.
GetNamespace(ctx context.Context, keyID, namespaceID string) (NamespaceState, error)
// ListNamespaces returns all cached namespaces for the given auth identity.
// Implementations should not filter by namespace kind.
ListNamespaces(ctx context.Context, keyID string) ([]NamespaceState, error)
// SaveNamespace persists the latest metadata for one namespace. The save must
// upsert by the tuple of keyID and namespaceID.
SaveNamespace(ctx context.Context, state NamespaceState) error
// ForgetIdentitySecrets clears locally cached secrets for one identity, such
// as bearer tokens and unwrapped namespace DEKs, while preserving namespace
// and item metadata used for later merge reconciliation.
ForgetIdentitySecrets(ctx context.Context, keyID string) error
// ResetSyncState clears locally cached namespace and item metadata for one
// identity while preserving the auth/session state. This is useful after a
// server-side rollback invalidates the local version history.
ResetSyncState(ctx context.Context, keyID string) error
// GetItemByID loads an item by its opaque item ID. It must return ErrNotFound
// when the item is unknown locally.
GetItemByID(ctx context.Context, keyID, namespaceID, itemID string) (ItemState, error)
// GetItemByLogicalKey loads an item by the human-readable collection/key
// tuple. It must return ErrNotFound when the item is unknown locally.
GetItemByLogicalKey(ctx context.Context, keyID, namespaceID, collection, key string) (ItemState, error)
// ListNamespaceItems returns all locally cached items for a namespace,
// including dirty or conflicted entries.
ListNamespaceItems(ctx context.Context, keyID, namespaceID string) ([]ItemState, error)
// ListDirtyItems returns every item with unapplied local changes for keyID.
// Implementations should include conflicted items so callers can decide how
// to handle them.
ListDirtyItems(ctx context.Context, keyID string) ([]ItemState, error)
// SaveItem persists one item snapshot, including its base value, dirty flag,
// and conflict metadata. The save must upsert by the tuple of keyID,
// namespaceID, and itemID.
SaveItem(ctx context.Context, state ItemState) error
}
Store persists engine state across runs.
type ValueBackend ¶
type ValueBackend[T any] interface { // Keys returns the full active key scope for this collection. It must include // keys with local values and keys that may exist only remotely. The engine // derives candidate item IDs from these keys, because server item IDs are // opaque and cannot be reversed into logical keys. // // Implementations may return keys that do not currently exist remotely, but // they must return stable collection-local logical keys and should be safe to // call repeatedly during sync. Each call must return the full current active // key set, not a delta from the previous call. Keys that are not returned are // outside the current sync scope and are ignored until returned again. Keys(ctx context.Context) ([]string, error) // Snapshot returns collection-local logical keys mapped to current typed // values that exist in the app store. Implementations should build the // snapshot in one efficient pass where possible and must return values safe // for the codec to read. Snapshot keys should be a subset of Keys. BitBoxSync // currently has no item-deletion protocol, so omitted snapshot keys are not // uploaded as deletions. Snapshot(ctx context.Context) (map[string]T, error) // Get returns the current typed value for key. It must return ErrNotFound // when the value is absent from the external store. Get(ctx context.Context, key string) (T, error) // Set stores the current typed value for key. Set(ctx context.Context, key string, value T) error }
ValueBackend stores collection values outside the sync store.
The sync engine still owns item IDs, versions, merge bases, dirty state, and conflicts in Store. Backends own current typed collection values.
Set is the storage primitive used by sync-applied remote values.
Implementations must be safe for concurrent calls. Set must atomically replace the value for one key, and a later Get for the same key should return the new value after Set returns nil. The sync engine never asks a backend to update multiple keys in one call.
The engine reconciles Snapshot at the start of each sync pass. For each returned key, it encodes the value with the collection codec and compares it to the last clean value stored in sync metadata. New or changed values are marked dirty before remote pull/upload. This lets apps keep their normal storage write paths and let sync observe the current app state at sync time.
The ValueBackend and Store are separate durability domains.