## ----------------------------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ## ----------------------------------------------------------------------------- # # From R-universe # install.packages("automerge", repos = "https://posit-dev.r-universe.dev") # # # From GitHub # pak::pak("posit-dev/automerge-r") ## ----------------------------------------------------------------------------- # library(automerge) # # # Create # doc <- am_create() # New document # doc <- am_create("fd3ca50687f13477f1f9ea2b216b958a") # With custom actor ID # # # Load/Save # bytes <- am_save(doc) # Save to bytes # doc <- am_load(bytes) # Load from bytes # # # Actor ID # actor <- am_get_actor(doc) # Get actor ID (raw bytes) # actor_hex <- am_get_actor_hex(doc) # Get actor ID as hex string # am_set_actor(doc, actor_hex) # Set actor ID (raw, hex, or NULL) # # # Fork/Merge # doc2 <- am_fork(doc) # Create independent copy # am_merge(doc, doc2) # Merge doc2 into doc1 # # # Transactions # am_commit(doc, "message") # Commit changes # am_rollback(doc) # Cancel pending changes ## ----------------------------------------------------------------------------- # # S3 operators # doc[["key"]] <- "value" # Set value # value <- doc[["key"]] # Get value # doc$key <- value # Alternative syntax # value <- doc$key # Alternative syntax # # # Functional API # am_delete(doc, AM_ROOT, "key") # Delete key # am_put(doc, AM_ROOT, "key", value) # Set value # value <- am_get(doc, AM_ROOT, "key") # Get value # # # Introspection # keys <- names(doc) # Get all keys # keys <- am_keys(doc, AM_ROOT) # Functional version # values <- am_values(doc, AM_ROOT) # Get all values # n <- length(doc) # Number of keys # n <- am_length(doc, AM_ROOT) # Functional version ## ----------------------------------------------------------------------------- # # Automatic recursive conversion (recommended) # am_put( # doc, # AM_ROOT, # "user", # list( # name = "Alice", # age = 30L, # address = list(city = "NYC", zip = 10001L) # ) # ) # # # Path-based access (simple for deep structures) # am_put_path(doc, c("user", "address", "city"), "Boston") # city <- am_get_path(doc, c("user", "address", "city")) # am_delete_path(doc, c("user", "address")) # # # Manual access # user_obj <- am_get(doc, AM_ROOT, "user") # am_put(doc, user_obj, "name", "Bob") # name <- am_get(doc, user_obj, "name") ## ----------------------------------------------------------------------------- # # Create list # am_put(doc, AM_ROOT, "items", AM_OBJ_TYPE_LIST) # items <- am_get(doc, AM_ROOT, "items") # # # Operations (1-based indexing) # am_insert(doc, items, 1, "first") # Insert at index 1 # am_put(doc, items, 1, "FIRST") # Replace at index 1 # am_put(doc, items, "end", "append") # Append to end # value <- am_get(doc, items, 1) # Get index 1 # am_delete(doc, items, 1) # Delete index 1 # # # Introspection # n <- am_length(doc, items) # List length ## ----------------------------------------------------------------------------- # # Text objects use 0-based inter-character positions # # For the text "Hello": # # H e l l o # # 0 1 2 3 4 5 <- positions (0-based, between characters) # # # Create text object # am_put(doc, AM_ROOT, "content", am_text("Hello")) # text_obj <- am_get(doc, AM_ROOT, "content") # # # Operations # am_text_splice(text_obj, 5, 0, " World") # Insert at position 5 # content <- am_text_content(text_obj) # Get full text # # # Update text (ideal for collaborative editing) # old_text <- am_text_content(text_obj) # am_text_update(text_obj, old_text, "Hello Universe") # Computes and applies diff # # # Cursors (stable positions) # cursor <- am_cursor(text_obj, 5) # Create cursor at position 5 # pos <- am_cursor_position(cursor) # Get current position # # # Marks (formatting) # am_mark(text_obj, 0, 5, "bold", TRUE, expand = "none") # marks <- am_marks(text_obj) # Get all marks # marks_at <- am_marks_at(text_obj, 2) # Marks at position 2 ## ----------------------------------------------------------------------------- # # NULL # am_put(doc, AM_ROOT, "null", NULL) # # # Boolean # am_put(doc, AM_ROOT, "bool", TRUE) # # # Integer # am_put(doc, AM_ROOT, "int", 42L) # # # Double # am_put(doc, AM_ROOT, "float", 3.14) # # # String # am_put(doc, AM_ROOT, "str", "text") # # # Raw bytes # am_put(doc, AM_ROOT, "bytes", raw(10)) # # # Timestamp # am_put(doc, AM_ROOT, "time", Sys.time()) # # # Counter # am_put(doc, AM_ROOT, "score", am_counter(0)) # am_counter_increment(doc, AM_ROOT, "score", 10) # value <- am_get(doc, AM_ROOT, "score") # # # Unsigned 64-bit integer (for cross-platform interop) # am_put(doc, AM_ROOT, "id", am_uint64(12345)) # # # Explicit type constructors # am_put(doc, AM_ROOT, "items", am_list()) # Empty list # am_put(doc, AM_ROOT, "config", am_map()) # Empty map # am_put(doc, AM_ROOT, "notes", am_text()) # Text object ## ----------------------------------------------------------------------------- # # Bidirectional sync (auto-converge) # # Documents are modified in place # rounds <- am_sync(doc1, doc2) # cat("Converged in", rounds, "rounds\n") # # # One-way merge # am_merge(doc1, doc2) # Merge doc2 into doc1 ## ----------------------------------------------------------------------------- # # Create sync state # sync_state <- am_sync_state_new() # # # Encode/decode messages # msg <- am_sync_encode(doc, sync_state) # am_sync_decode(doc, sync_state, msg) # # # Manual sync loop # repeat { # msg1 <- am_sync_encode(doc1, sync1) # if (is.null(msg1)) { # break # } # am_sync_decode(doc2, sync2, msg1) # # msg2 <- am_sync_encode(doc2, sync2) # if (is.null(msg2)) { # break # } # am_sync_decode(doc1, sync1, msg2) # } ## ----------------------------------------------------------------------------- # # Get heads # heads <- am_get_heads(doc) # # # Get changes # changes <- am_get_changes(doc, NULL) # All changes # changes <- am_get_changes(doc, heads) # Since specific heads # # # Apply changes # am_apply_changes(doc, changes) # # # Full history # history <- am_get_history(doc) ## ----------------------------------------------------------------------------- # # R → Automerge # doc <- as_automerge(list(name = "Alice", age = 30L)) # # # Automerge → R # r_list <- from_automerge(doc) # # r_list <- as.list(doc) # S3 method for documents # text_str <- as.character(text_obj) # S3 method for text objects ## ----------------------------------------------------------------------------- # doc <- am_create() |> # am_put(AM_ROOT, "name", "Alice") |> # am_put(AM_ROOT, "age", 30L) |> # am_commit("Initial data") ## ----------------------------------------------------------------------------- # # Save to file # writeBin(am_save(doc), "document.automerge") # # # Load from file # doc <- am_load(readBin("document.automerge", "raw", 1e6)) ## ----------------------------------------------------------------------------- # AM_ROOT # Root object (NULL) # AM_OBJ_TYPE_LIST # "list" # AM_OBJ_TYPE_MAP # "map" # AM_OBJ_TYPE_TEXT # "text" # # AM_MARK_EXPAND_NONE # "none" (mark doesn't expand at boundaries) # AM_MARK_EXPAND_BEFORE # "before" (expands when text inserted before start) # AM_MARK_EXPAND_AFTER # "after" (expands when text inserted after end) # AM_MARK_EXPAND_BOTH # "both" (expands at both boundaries) ## ----------------------------------------------------------------------------- # # Function help # ?am_create # ?am_put # ?am_sync # # # Package help # ?automerge # help(package = "automerge") # # # Vignettes # vignette("automerge", "automerge") # vignette("crdt-concepts", "automerge") # vignette("sync-protocol", "automerge") # vignette(package = "automerge") # List all