## ----------------------------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ----------------------------------------------------------------------------- library(automerge) ## ----------------------------------------------------------------------------- doc1 <- am_create() doc1[["name"]] <- "Alice" doc1[["score"]] <- 100 am_commit(doc1) doc2 <- am_fork(doc1) # Concurrent edits doc1[["name"]] <- "Alice Smith" doc2[["name"]] <- "Alice Johnson" # Merge am_merge(doc1, doc2) # One value wins (deterministic, all replicas agree) doc1[["name"]] ## ----------------------------------------------------------------------------- doc3 <- am_create() doc3[["user"]] <- list(name = "Alice", age = 30L, city = "Boston") am_commit(doc3) doc4 <- am_fork(doc3) # Concurrent edits to different keys user3 <- am_get(doc3, AM_ROOT, "user") am_put(doc3, user3, "age", 31L) user4 <- am_get(doc4, AM_ROOT, "user") am_put(doc4, user4, "city", "New York") # Merge - both changes preserved am_merge(doc3, doc4) # Both edits are present user_final <- am_get(doc3, AM_ROOT, "user") am_get(doc3, user_final, "age") am_get(doc3, user_final, "city") ## ----------------------------------------------------------------------------- doc5 <- am_create() am_put(doc5, AM_ROOT, "items", AM_OBJ_TYPE_LIST) items5 <- am_get(doc5, AM_ROOT, "items") am_put(doc5, items5, "end", "A") am_put(doc5, items5, "end", "C") am_commit(doc5) doc6 <- am_fork(doc5) # Concurrent insertions at same position items6 <- am_get(doc6, AM_ROOT, "items") am_insert(doc5, items5, 2, "B1") # Insert between A and C am_insert(doc6, items6, 2, "B2") # Insert between A and C # Merge am_merge(doc5, doc6) # Both insertions preserved with deterministic ordering for (i in seq_len(am_length(doc5, items5))) { print(am_get(doc5, items5, i)) } ## ----------------------------------------------------------------------------- doc7 <- am_create() am_put(doc7, AM_ROOT, "document", am_text("The quick fox jumps")) am_commit(doc7) doc8 <- am_fork(doc7) text7 <- am_get(doc7, AM_ROOT, "document") text8 <- am_get(doc8, AM_ROOT, "document") # Concurrent edits (0-based inter-character positions) am_text_splice(text7, 10, 0, "brown ") # Insert "brown " at position 10 am_text_splice(text8, 19, 0, " high") # Insert " high" at position 19 (end) # Merge am_merge(doc7, doc8) # Both edits preserved am_text_content(text7) ## ----------------------------------------------------------------------------- # String (deterministic conflict resolution) doc9 <- am_create() doc9[["title"]] <- "Document" doc10 <- am_fork(doc9) doc9[["title"]] <- "My Document" doc10[["title"]] <- "Our Document" am_merge(doc9, doc10) doc9[["title"]] # One value wins deterministically # Text object (CRDT) doc11 <- am_create() am_put(doc11, AM_ROOT, "content", am_text("Hello")) doc12 <- am_fork(doc11) text11 <- am_get(doc11, AM_ROOT, "content") text12 <- am_get(doc12, AM_ROOT, "content") am_text_splice(text11, 5, 0, " World") am_text_splice(text12, 5, 0, " Everyone") am_merge(doc11, doc12) am_text_content(text11) ## ----------------------------------------------------------------------------- doc13 <- am_create() am_put(doc13, AM_ROOT, "likes", am_counter(0)) am_commit(doc13) doc14 <- am_fork(doc13) # Concurrent increments am_counter_increment(doc13, AM_ROOT, "likes", 3) am_counter_increment(doc14, AM_ROOT, "likes", 5) am_counter_increment(doc14, AM_ROOT, "likes", -1) # Merge am_merge(doc13, doc14) # Sum of all increments doc13[["likes"]] ## ----------------------------------------------------------------------------- doc15 <- am_create() doc15[["created_at"]] <- Sys.time() am_commit(doc15) Sys.sleep(0.1) doc16 <- am_fork(doc15) doc15[["updated_at"]] <- Sys.time() doc16[["updated_at"]] <- Sys.time() am_merge(doc15, doc16) doc15[["created_at"]] doc15[["updated_at"]] ## ----------------------------------------------------------------------------- doc17 <- am_create() am_put(doc17, AM_ROOT, "text", am_text("Hello World")) text17 <- am_get(doc17, AM_ROOT, "text") # Create cursor at position 6 (0-based: after "Hello ") cursor <- am_cursor(text17, 6) # Insert text before cursor am_text_splice(text17, 0, 0, "Hi ") # Cursor automatically adjusts new_pos <- am_cursor_position(cursor) new_pos # Cursor moved with text from original position 6 ## ----------------------------------------------------------------------------- doc18 <- am_create() am_put(doc18, AM_ROOT, "text", am_text("Hello World")) text18 <- am_get(doc18, AM_ROOT, "text") # Mark "Hello" as bold (positions 0-4, 0-based) am_mark(text18, 0, 5, "bold", TRUE, expand = "none") # Mark "World" as italic (positions 6-10) am_mark(text18, 6, 11, "italic", TRUE, expand = "none") # Query marks marks <- am_marks(text18) str(marks) # Marks at specific position marks_at_pos <- am_marks_at(text18, 2) # Position 2 (in "Hello") str(marks_at_pos) ## ----------------------------------------------------------------------------- doc19 <- am_create() am_put(doc19, AM_ROOT, "text", am_text("Hello")) text19 <- am_get(doc19, AM_ROOT, "text") # Mark with expansion am_mark(text19, 0, 5, "bold", TRUE, expand = "after") # Insert at end of mark am_text_splice(text19, 5, 0, " World") # Mark expands to include "World" marks <- am_marks(text19) str(marks) ## ----------------------------------------------------------------------------- doc20 <- am_create() # Make many edits for (i in 1:100) { doc20[[paste0("key", i)]] <- i } am_commit(doc20) # Size includes all history length(am_save(doc20)) ## ----------------------------------------------------------------------------- # Map: Deletion vs concurrent update - update wins doc21 <- am_create() doc21[["temp"]] <- "value" am_commit(doc21) doc22 <- am_fork(doc21) am_delete(doc21, AM_ROOT, "temp") am_commit(doc21) doc22[["temp"]] <- "updated" am_commit(doc22) am_merge(doc21, doc22) doc21[["temp"]] # Update takes precedence over delete ## ----------------------------------------------------------------------------- # List: Delete and insert at same position - both operations apply doc23 <- am_create() am_put(doc23, AM_ROOT, "items", AM_OBJ_TYPE_LIST) items23 <- am_get(doc23, AM_ROOT, "items") am_put(doc23, items23, "end", "A") am_put(doc23, items23, "end", "B") am_put(doc23, items23, "end", "C") am_commit(doc23) doc24 <- am_fork(doc23) items24 <- am_get(doc24, AM_ROOT, "items") am_delete(doc23, items23, 2) am_insert(doc24, items24, 2, "X") am_merge(doc23, doc24) for (i in seq_len(am_length(doc23, items23))) { print(am_get(doc23, items23, i)) } ## ----------------------------------------------------------------------------- # Good: Independent counters per user doc_good <- am_create() doc_good[["votes"]] <- list( alice = am_counter(0), bob = am_counter(0) ) # Better than: Single counter for all votes doc_bad <- am_create() doc_bad[["total_votes"]] <- am_counter(0) # Loses attribution ## ----------------------------------------------------------------------------- doc25 <- am_create() # Good: Atomic transaction doc25[["user"]] <- list(name = "Alice", age = 30L, city = "Boston") am_commit(doc25, "Add user Alice") # Bad: Many micro-commits (increases storage) doc25[["status"]] <- "active" am_commit(doc25, "Set status") doc25[["role"]] <- "admin" am_commit(doc25, "Set role") ## ----------------------------------------------------------------------------- doc26 <- am_create() doc26[["status"]] <- "draft" am_commit(doc26) doc27 <- am_fork(doc26) doc26[["status"]] <- "published" doc27[["status"]] <- "archived" am_merge(doc26, doc27) # One will win - application should handle both states sensibly doc26[["status"]] # Should be prepared for either 'published' or 'archived'