feat(sync): wire up sync.mode config to change sync behavior
Implements hq-ew1mbr.27: The sync.mode config now actually changes how bd sync operates: - git-portable (default): JSONL exported on push, imported on pull - realtime: JSONL exported on every change (placeholder for daemon hook) - dolt-native: Uses Dolt Push/Pull, skips JSONL workflow entirely - belt-and-suspenders: Both Dolt remotes AND JSONL for redundancy Changes: - Add sync_mode.go with mode constants, Get/Set functions, and helpers - Update bd sync --status to show actual mode from config - Add --set-mode flag to bd sync for configuring the mode - Modify doExportSync to respect mode (Dolt push for dolt-native) - Modify doPullFirstSync to use Dolt pull for dolt-native mode - Add RemoteStorage interface for Push/Pull operations - Add comprehensive tests for sync mode functionality Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
c99bd00ca7
commit
356ab92b78
129
cmd/bd/sync_mode.go
Normal file
129
cmd/bd/sync_mode.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/steveyegge/beads/internal/storage"
|
||||
)
|
||||
|
||||
// Sync mode constants define how beads synchronizes data with git.
|
||||
const (
|
||||
// SyncModeGitPortable exports to JSONL on push, imports on pull.
|
||||
// This is the default mode - works with standard git workflows.
|
||||
SyncModeGitPortable = "git-portable"
|
||||
|
||||
// SyncModeRealtime exports to JSONL on every database mutation.
|
||||
// Provides immediate persistence but more git noise.
|
||||
SyncModeRealtime = "realtime"
|
||||
|
||||
// SyncModeDoltNative uses Dolt remotes for sync, skipping JSONL.
|
||||
// Requires Dolt backend and configured Dolt remote.
|
||||
SyncModeDoltNative = "dolt-native"
|
||||
|
||||
// SyncModeBeltAndSuspenders uses both Dolt remotes AND JSONL.
|
||||
// Maximum redundancy - Dolt for versioning, JSONL for git portability.
|
||||
SyncModeBeltAndSuspenders = "belt-and-suspenders"
|
||||
|
||||
// SyncModeConfigKey is the database config key for sync mode.
|
||||
SyncModeConfigKey = "sync.mode"
|
||||
|
||||
// SyncExportOnConfigKey controls when JSONL export happens.
|
||||
SyncExportOnConfigKey = "sync.export_on"
|
||||
|
||||
// SyncImportOnConfigKey controls when JSONL import happens.
|
||||
SyncImportOnConfigKey = "sync.import_on"
|
||||
)
|
||||
|
||||
// Trigger constants for export_on and import_on settings.
|
||||
const (
|
||||
// TriggerPush triggers on git push (export) or git pull (import).
|
||||
TriggerPush = "push"
|
||||
TriggerPull = "pull"
|
||||
|
||||
// TriggerChange triggers on every database mutation (realtime mode).
|
||||
TriggerChange = "change"
|
||||
)
|
||||
|
||||
// GetSyncMode returns the configured sync mode, defaulting to git-portable.
|
||||
func GetSyncMode(ctx context.Context, s storage.Storage) string {
|
||||
mode, err := s.GetConfig(ctx, SyncModeConfigKey)
|
||||
if err != nil || mode == "" {
|
||||
return SyncModeGitPortable
|
||||
}
|
||||
|
||||
// Validate mode
|
||||
switch mode {
|
||||
case SyncModeGitPortable, SyncModeRealtime, SyncModeDoltNative, SyncModeBeltAndSuspenders:
|
||||
return mode
|
||||
default:
|
||||
// Invalid mode, return default
|
||||
return SyncModeGitPortable
|
||||
}
|
||||
}
|
||||
|
||||
// SetSyncMode sets the sync mode configuration.
|
||||
func SetSyncMode(ctx context.Context, s storage.Storage, mode string) error {
|
||||
// Validate mode
|
||||
switch mode {
|
||||
case SyncModeGitPortable, SyncModeRealtime, SyncModeDoltNative, SyncModeBeltAndSuspenders:
|
||||
// Valid
|
||||
default:
|
||||
return fmt.Errorf("invalid sync mode: %s (valid: %s, %s, %s, %s)",
|
||||
mode, SyncModeGitPortable, SyncModeRealtime, SyncModeDoltNative, SyncModeBeltAndSuspenders)
|
||||
}
|
||||
|
||||
return s.SetConfig(ctx, SyncModeConfigKey, mode)
|
||||
}
|
||||
|
||||
// GetExportTrigger returns when JSONL export should happen.
|
||||
func GetExportTrigger(ctx context.Context, s storage.Storage) string {
|
||||
trigger, err := s.GetConfig(ctx, SyncExportOnConfigKey)
|
||||
if err != nil || trigger == "" {
|
||||
// Default based on sync mode
|
||||
mode := GetSyncMode(ctx, s)
|
||||
if mode == SyncModeRealtime {
|
||||
return TriggerChange
|
||||
}
|
||||
return TriggerPush
|
||||
}
|
||||
return trigger
|
||||
}
|
||||
|
||||
// GetImportTrigger returns when JSONL import should happen.
|
||||
func GetImportTrigger(ctx context.Context, s storage.Storage) string {
|
||||
trigger, err := s.GetConfig(ctx, SyncImportOnConfigKey)
|
||||
if err != nil || trigger == "" {
|
||||
return TriggerPull
|
||||
}
|
||||
return trigger
|
||||
}
|
||||
|
||||
// ShouldExportJSONL returns true if the current sync mode uses JSONL export.
|
||||
func ShouldExportJSONL(ctx context.Context, s storage.Storage) bool {
|
||||
mode := GetSyncMode(ctx, s)
|
||||
// All modes except dolt-native use JSONL
|
||||
return mode != SyncModeDoltNative
|
||||
}
|
||||
|
||||
// ShouldUseDoltRemote returns true if the current sync mode uses Dolt remotes.
|
||||
func ShouldUseDoltRemote(ctx context.Context, s storage.Storage) bool {
|
||||
mode := GetSyncMode(ctx, s)
|
||||
return mode == SyncModeDoltNative || mode == SyncModeBeltAndSuspenders
|
||||
}
|
||||
|
||||
// SyncModeDescription returns a human-readable description of the sync mode.
|
||||
func SyncModeDescription(mode string) string {
|
||||
switch mode {
|
||||
case SyncModeGitPortable:
|
||||
return "JSONL exported on push, imported on pull"
|
||||
case SyncModeRealtime:
|
||||
return "JSONL exported on every change"
|
||||
case SyncModeDoltNative:
|
||||
return "Dolt remotes only, no JSONL"
|
||||
case SyncModeBeltAndSuspenders:
|
||||
return "Both Dolt remotes and JSONL"
|
||||
default:
|
||||
return "unknown mode"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user