The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
This vignette includes executable JavaScript and R code examples. To run them:
The easiest way to verify JavaScript ↔︎ R interoperability:
# From package root directory
Rscript inst/js/run-examples.R
# Or from inst/js/ directory
cd inst/js
Rscript run-examples.RThis script will:
@automerge/automerge)Prerequisites:
# Install Node.js from https://nodejs.org/
# Get JavaScript directory
# From installed package:
R -e "cat(system.file('js', package = 'automerge'))"
# From source: inst/js/
cd inst/js
npm installRun individual examples:
library(automerge)
# Check if Node.js is available
if (system2("node", "--version", stdout = FALSE, stderr = FALSE) == 0) {
# Get JavaScript directory
js_dir <- system.file("js", package = "automerge")
# Run JavaScript example
temp_file <- tempfile(fileext = ".automerge")
system2("node", c(file.path(js_dir, "create-shared-doc.js"), temp_file))
# Load in R
doc <- am_load(readBin(temp_file, "raw", 1e7))
print(doc)
}One of Automerge’s key strengths is seamless synchronization across different platforms and programming languages. This vignette demonstrates how documents created in JavaScript can be synced with R and vice versa, enabling collaborative workflows across different technology stacks.
Automerge uses a standardized binary format (see automerge.org/automerge-binary-format-spec) that is identical across all implementations. This means:
You’ll need:
@automerge/automerge
package (npm install)automerge package (this
package)For these examples, we’ll use file-based exchange with Node.js on the JavaScript side.
// Node.js or browser
import * as Automerge from '@automerge/automerge'
const fs = require('fs')
// Create a document
let doc = Automerge.init()
// Add some data
doc = Automerge.change(doc, 'Initial data', doc => {
doc.title = 'Collaborative Analysis'
doc.datasets = []
doc.datasets.push({ name: 'sales_2024', rows: 1000 })
doc.datasets.push({ name: 'customers', rows: 5000 })
doc.metadata = {
created_by: 'javascript',
created_at: new Date().toISOString(),
version: '1.0'
}
})
// Save to binary format
const bytes = Automerge.save(doc)
// Write to file (Node.js)
fs.writeFileSync('shared_doc.automerge', bytes)
console.log('Document created and saved')
console.log('Actor ID:', Automerge.getActorId(doc))library(automerge)
# Load the document created in JavaScript
doc_bytes <- readBin("shared_doc.automerge", "raw", 1e7)
doc <- am_load(doc_bytes)
# Examine the document
print(doc)
# Access data created in JavaScript
cat("Title:", doc[["title"]], "\n")
cat("Created by:", doc[["metadata"]][["created_by"]], "\n")
# Show datasets
datasets <- doc[["datasets"]]
cat("Number of datasets:", am_length(doc, datasets), "\n")
# Examine first dataset (R uses 1-based indexing)
dataset1 <- am_get(doc, datasets, 1)
cat(
"First dataset:",
am_get(doc, dataset1, "name"),
"with",
am_get(doc, dataset1, "rows"),
"rows\n"
)# Continue from previous example
# Add analysis results from R
am_put(
doc,
AM_ROOT,
"r_analysis",
list(
performed_by = "R",
timestamp = Sys.time(),
R_version = paste(R.version$major, R.version$minor, sep = "."),
summary_stats = list(
mean_sales = 45231.5,
median_sales = 38900.0,
total_customers = 5000L
)
)
)
# Commit changes
am_commit(doc, "Added R analysis results")
# Save back to file
writeBin(am_save(doc), "shared_doc.automerge")
cat("Document updated by R and saved\n")
cat("R Actor ID:", am_get_actor_hex(doc), "\n")// Load the updated document
const updatedBytes = fs.readFileSync('shared_doc.automerge')
let updatedDoc = Automerge.load(updatedBytes)
console.log('Document loaded with R changes')
console.log('Title:', updatedDoc.title)
console.log('R Analysis:', updatedDoc.r_analysis)
console.log('Mean sales:', updatedDoc.r_analysis.summary_stats.mean_sales)
console.log('Analysis performed by:', updatedDoc.r_analysis.performed_by)
// View change history
const changes = Automerge.getAllChanges(updatedDoc)
console.log(`Total changes: ${changes.length}`)
// Make additional changes in JavaScript
updatedDoc = Automerge.change(updatedDoc, 'Add JS visualization', doc => {
doc.visualizations = []
doc.visualizations.push({
type: 'bar_chart',
data_source: 'r_analysis.summary_stats',
created_in: 'javascript'
})
})
// Save for next R session
fs.writeFileSync('shared_doc.automerge', Automerge.save(updatedDoc))This example shows how to use the sync protocol for real-time synchronization between JavaScript and R.
# Initial R document
r_doc <- am_create() |>
am_put(AM_ROOT, "source", "R") |>
am_put(
AM_ROOT,
"data",
list(
r_value = 123,
timestamp = Sys.time()
)
) |>
am_commit("Initial R doc")
# Create sync state
r_sync <- am_sync_state_new()
# Generate sync message to send to JavaScript
sync_msg_to_js <- am_sync_encode(r_doc, r_sync)
# Save sync message to file (in practice, send over network)
writeBin(sync_msg_to_js, "r_to_js_sync.bin")
cat("R sync message ready:", length(sync_msg_to_js), "bytes\n")// Initial JavaScript document
let jsDoc = Automerge.change(Automerge.init(), 'Initial', doc => {
doc.source = 'JavaScript'
doc.data = {
js_value: 456,
timestamp: Date.now()
}
})
// Create sync state
let jsSyncState = Automerge.initSyncState()
// Load sync message from R
const syncMsgFromR = fs.readFileSync('r_to_js_sync.bin')
// Receive sync message and update document
;[jsDoc, jsSyncState] = Automerge.receiveSyncMessage(
jsDoc,
jsSyncState,
syncMsgFromR
)
console.log('Received sync from R')
console.log('Document now has:', Object.keys(jsDoc))
// Generate response sync message
const syncMsgToR = Automerge.generateSyncMessage(jsDoc, jsSyncState)
if (syncMsgToR) {
fs.writeFileSync('js_to_r_sync.bin', syncMsgToR)
console.log('JS sync message ready:', syncMsgToR.length, 'bytes')
}# Load sync message from JavaScript
sync_msg_from_js <- readBin("js_to_r_sync.bin", "raw", 1e7)
# Apply sync message
am_sync_decode(r_doc, r_sync, sync_msg_from_js)
# Documents are now synchronized
cat("Sync complete!\n")
cat("R document now contains:\n")
print(names(r_doc))
# Verify we have data from JavaScript
if (!is.null(r_doc[["data"]][["js_value"]])) {
cat("JavaScript value:", r_doc[["data"]][["js_value"]], "\n")
}This demonstrates Automerge’s CRDT capabilities with concurrent edits in both platforms.
// Load shared document
let jsDoc = Automerge.load(fs.readFileSync('concurrent_doc.automerge'))
// JavaScript makes changes
jsDoc = Automerge.change(jsDoc, 'Add JS section', doc => {
doc.sections.push({
title: 'JavaScript Analysis',
content: 'Web visualization results',
author: 'JS Team'
})
doc.js_edit_time = Date.now()
})
// Save changes
fs.writeFileSync('js_concurrent.automerge', Automerge.save(jsDoc))Or run the provided script:
# Load the same original document
r_doc <- am_load(shared_bytes)
# R makes different changes to the same document
sections <- r_doc[["sections"]]
am_insert(
r_doc,
sections,
1,
list(
title = "R Statistical Analysis",
content = "Regression model results",
author = "R Team"
)
)
am_put(r_doc, AM_ROOT, "r_edit_time", Sys.time())
am_commit(r_doc, "Add R section")
# Save R changes
writeBin(am_save(r_doc), "r_concurrent.automerge")# Load JavaScript version
js_doc_bytes <- readBin("js_concurrent.automerge", "raw", 1e7)
js_doc <- am_load(js_doc_bytes)
# Merge JavaScript changes into R document
am_merge(r_doc, js_doc)
# Verify merge - should have both sections
sections_merged <- r_doc[["sections"]]
cat(
"After merge, document has",
am_length(r_doc, sections_merged),
"sections\n"
)
# Section 1 (from R)
section1 <- am_get(r_doc, sections_merged, 1)
cat("Section 1:", am_get(r_doc, section1, "title"), "\n")
# Section 2 (from JavaScript)
section2 <- am_get(r_doc, sections_merged, 2)
cat("Section 2:", am_get(r_doc, section2, "title"), "\n")
# Both timestamps preserved
cat("R edit time:", r_doc[["r_edit_time"]], "\n")
cat("JS edit time:", r_doc[["js_edit_time"]], "\n")The same merge can be done on the JavaScript side:
// JavaScript loads R version and merges
const rDocBytes = fs.readFileSync('r_concurrent.automerge')
const rDoc = Automerge.load(rDocBytes)
// Merge R changes into JS document
jsDoc = Automerge.merge(jsDoc, rDoc)
// Verify - both sections present
console.log('After merge, sections:', jsDoc.sections.length)
console.log('Section 0:', jsDoc.sections[0].title, '(from R)')
console.log('Section 1:', jsDoc.sections[1].title, '(from JS)')
// Both timestamps preserved
console.log('R edit time:', jsDoc.r_edit_time)
console.log('JS edit time:', jsDoc.js_edit_time)Or verify using the provided script:
Text objects are particularly interesting as they demonstrate character-level CRDT merge.
# Load text document
text_doc <- am_load(readBin("text_doc.automerge", "raw", 1e7))
# Get text object
notes <- am_get(text_doc, AM_ROOT, "notes")
# Append text in R (0-based position indexing)
current_length <- am_length(text_doc, notes)
am_text_splice(notes, current_length, 0, " and R!")
am_commit(text_doc, "R appended text")
# Get full text
full_text <- am_text_content(notes)
cat("Text after R edit:", full_text, "\n")
# Output: "Hello from JavaScript and R!"
# Save back
writeBin(am_save(text_doc), "text_doc.automerge")| Automerge | JavaScript | R | Notes |
|---|---|---|---|
| Map | Object {} |
Named list | Root is always a map |
| List | Array [] |
Unnamed list | R uses 1-based indexing |
| Text | Automerge.Text |
Text object (am_text) | Character-level CRDT |
| String | string |
character(1) |
UTF-8 encoding |
| Number (int) | number |
integer / double |
32-bit int if in range, else double |
| Number (uint64) | BigInt |
am_uint64 |
Unsigned 64-bit integer |
| Number (float) | number |
double |
Double precision (64-bit) |
| Boolean | boolean |
logical |
TRUE/FALSE |
| Null | null |
NULL |
Absence of value |
| Bytes | Uint8Array |
raw |
Binary data |
| Timestamp | Date / number |
POSIXct |
Milliseconds since epoch |
| Counter | CRDT counter | am_counter |
Conflict-free counter |
Important Notes:
Integer Sizes: Automerge stores 64-bit signed
integers internally. R integers are 32-bit, so values outside the range
±2,147,483,647 are automatically converted to numeric
(double). JavaScript uses 64-bit floats for all numbers (safe integers
up to ±9,007,199,254,740,991).
List Indexing: JavaScript uses 0-based indexing
(array[0]), R uses 1-based indexing
(am_get(doc, list_obj, 1))
Text Operations: Both use 0-based positions for text operations (splice, cursors, marks)
UTF-32 vs UTF-16: R bindings use UTF-32 character indexing by default, JavaScript uses UTF-16. Positions may differ for emoji and some Unicode characters.
The saved document format includes:
The binary format is deterministic and identical across platforms, enabling:
Both JavaScript and R use UTF-8 for strings. If you encounter encoding issues:
When transferring files between systems, always use binary mode:
Each platform generates random actor IDs. To use custom IDs:
Remember the indexing conventions:
All examples in this vignette can be tested using the executable
scripts provided in inst/js/:
Run all examples automatically:
This will execute all JavaScript scripts, verify results in R, and demonstrate complete round-trip interoperability.
See the documentation in inst/js/README.md (or after
installation, use
system.file("js/README.md", package = "automerge")) for
detailed instructions on running individual examples and integrating
with your own tests.
The following scripts are available in inst/js/:
create-shared-doc.js - Create documents in
JavaScriptverify-r-changes.js - Verify R modifications from
JavaScriptconcurrent-edit.js - Make concurrent edits in
JavaScriptverify-merge.js - Verify CRDT merge from
JavaScriptTo find these scripts after installation:
system.file("js", package = "automerge") for executable
examplesThese binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.