Skip to main content
The sync package provides a client-side runtime for building offline-first applications. It combines reactive state primitives (Signals, Computed values, Effects) with synchronized collections that stay in sync with a server.

What is Offline-First?

In a traditional web application, the server is the source of truth. When you’re offline, the app doesn’t work.
Traditional App:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”    Request    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Client  β”‚ ────────────> β”‚ Server  β”‚
β”‚         β”‚ <──────────── β”‚         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    Response   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

     If server is unreachable = App broken
In an offline-first application, the client keeps a local copy of the data. Changes are queued locally and synced when connectivity is available.
Offline-First App:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Client                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Local State β”‚  β”‚ Mutation     β”‚  β”‚
β”‚  β”‚             β”‚  β”‚ Queue        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚         β”‚                β”‚          β”‚
β”‚         β”‚                β”‚          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚   Background   β”‚
          β–Ό    Sync        β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ Server  β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

     If server is unreachable = App still works!
Benefits:
  • App works without internet
  • Instant UI updates (no loading spinners)
  • Better user experience on slow connections
  • Resilience to network failures

What the Sync Package Provides

Reactive Primitives

TypeDescription
Signal[T]A reactive value container
Computed[T]A derived value that updates automatically
EffectA side effect that runs when dependencies change
These provide fine-grained reactivity similar to SolidJS or Vue’s Composition API.

Synchronized State

TypeDescription
ClientManages server sync, mutation queue, and connection state
Collection[T]A reactive set of entities that syncs with the server
Entity[T]A single synchronized record

The Sync Protocol

The client and server communicate using a simple protocol:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Client   β”‚                     β”‚   Server   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚                                   β”‚
      │──── Push(mutations) ─────────────>β”‚
      β”‚                                   β”‚
      β”‚<─── Pull(cursor) ─────────────────│
      β”‚     Returns changes since cursor  β”‚
      β”‚                                   β”‚
      β”‚<─── Snapshot(scope) ──────────────│
      β”‚     Returns full state            β”‚
      β”‚                                   β”‚
  • Push: Client sends queued mutations to server
  • Pull: Client requests changes since last sync point (cursor)
  • Snapshot: Client requests full state (initial load or resync)

Reactive Primitives

Signal

A Signal holds a value and notifies subscribers when it changes:
import "github.com/go-mizu/mizu/view/sync"

// Create a signal with initial value
count := sync.NewSignal(0)

// Read the value
fmt.Println(count.Get())  // 0

// Update the value
count.Set(5)
fmt.Println(count.Get())  // 5

// Update with a function
count.Update(func(v int) int {
    return v + 1
})
fmt.Println(count.Get())  // 6

Computed

A Computed value derives from other signals and updates automatically:
count := sync.NewSignal(10)
price := sync.NewSignal(5.0)

// Automatically tracks dependencies
total := sync.NewComputed(func() float64 {
    return float64(count.Get()) * price.Get()
})

fmt.Println(total.Get())  // 50.0

count.Set(20)
fmt.Println(total.Get())  // 100.0 (auto-updated!)

Effect

An Effect runs a function whenever its dependencies change:
count := sync.NewSignal(0)

effect := sync.NewEffect(func() {
    fmt.Printf("Count is now: %d\n", count.Get())
})
// Prints: "Count is now: 0"

count.Set(1)
// Prints: "Count is now: 1"

count.Set(2)
// Prints: "Count is now: 2"

// Stop the effect
effect.Stop()

Collections and Entities

Collections manage groups of entities that sync with a server:
// Define your data type
type Todo struct {
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

// Create a client
client := sync.New(sync.Options{
    BaseURL: "http://localhost:8080/_sync",
    Scope:   "user:123",
})

// Create a collection
todos := sync.NewCollection[Todo](client, "todo")

// Create an entity
todo := todos.Create("todo-1", Todo{
    Title:     "Buy groceries",
    Completed: false,
})

// Read (reactive - tracks dependencies)
fmt.Println(todo.Get().Title)

// Update
todo.Set(Todo{
    Title:     "Buy groceries",
    Completed: true,
})

// Delete
todo.Delete()

// Query the collection
all := todos.All()          // All entities (reactive)
count := todos.Count()      // Entity count (reactive)
found := todos.Find(func(t Todo) bool {
    return !t.Completed
})

Quick Example

Here’s a complete example showing the sync system:
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-mizu/mizu/view/sync"
)

type Todo struct {
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

func main() {
    // Create client
    client := sync.New(sync.Options{
        BaseURL: "http://localhost:8080/_sync",
        Scope:   "user:demo",
        OnSync: func(cursor uint64) {
            fmt.Printf("Synced to cursor %d\n", cursor)
        },
        OnOnline: func() {
            fmt.Println("Connected!")
        },
        OnOffline: func() {
            fmt.Println("Offline - changes will sync later")
        },
    })

    // Start sync
    ctx := context.Background()
    client.Start(ctx)
    defer client.Stop()

    // Create a collection
    todos := sync.NewCollection[Todo](client, "todo")

    // Create a todo (works offline!)
    todo := todos.Create("todo-1", Todo{
        Title: "Learn sync",
    })

    // Create an effect to react to changes
    sync.NewEffect(func() {
        t := todo.Get()
        status := "pending"
        if t.Completed {
            status = "done"
        }
        fmt.Printf("Todo: %s [%s]\n", t.Title, status)
    })

    // Update the todo
    todo.Set(Todo{
        Title:     "Learn sync",
        Completed: true,
    })

    // Wait a bit to see sync happen
    time.Sleep(2 * time.Second)
}

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Client                          β”‚
β”‚                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Reactive    β”‚    β”‚      Sync Engine         β”‚  β”‚
β”‚  β”‚   Layer       β”‚    β”‚                          β”‚  β”‚
β”‚  β”‚               β”‚    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚
β”‚  β”‚  Signal[T]    β”‚    β”‚  β”‚   Mutation Queue   β”‚  β”‚  β”‚
β”‚  β”‚  Computed[T]  β”‚    β”‚  β”‚   (offline-safe)   β”‚  β”‚  β”‚
β”‚  β”‚  Effect       β”‚    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚
β”‚  β”‚               β”‚    β”‚                          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚
β”‚         β”‚             β”‚  β”‚   Store            β”‚  β”‚  β”‚
β”‚         β–Ό             β”‚  β”‚   (local state)    β”‚  β”‚  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚
β”‚  β”‚  Collections  β”‚    β”‚                          β”‚  β”‚
β”‚  β”‚               │◄────  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚
β”‚  β”‚  Entity[T]    β”‚    β”‚  β”‚   Cursor           β”‚  β”‚  β”‚
β”‚  β”‚               β”‚    β”‚  β”‚   (sync position)  β”‚  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚
β”‚                       β”‚                          β”‚  β”‚
β”‚                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                  β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚                             β”‚
                    β–Ό                             β–Ό
              Push/Pull                      Live Updates
              (HTTP)                        (WebSocket)
                    β”‚                             β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   β”‚
                                   β–Ό
                            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                            β”‚   Server    β”‚
                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

When to Use Sync

Use sync when:
  • Building offline-capable applications
  • You need instant UI updates without loading states
  • Multiple clients need to see synchronized data
  • You want fine-grained reactivity in Go
Consider alternatives when:
  • Building server-side only applications
  • You don’t need offline support
  • Simple request/response patterns are sufficient

What’s Next?

  1. Quick Start - Build your first synced app
  2. Client - Client configuration and lifecycle
  3. Reactive - Deep dive into Signal, Computed, Effect
  4. Collections - Working with synchronized data
  5. Integration - Combining sync with live for real-time updates