---
title: "Cross-Platform Synchronization: JavaScript ↔ R"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Cross-Platform Synchronization: JavaScript ↔ R}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE # Set to FALSE since examples require Node.js and filesystem operations
)
```

## Running the Examples

This vignette includes executable JavaScript and R code examples. To run them:

### Option 1: Automated Test Script

The easiest way to verify JavaScript ↔ R interoperability:

```bash
# From package root directory
Rscript inst/js/run-examples.R

# Or from inst/js/ directory
cd inst/js
Rscript run-examples.R
```

This script will:

- Check for Node.js availability
- Install required npm packages (`@automerge/automerge`)
- Execute all examples and verify results
- Display output showing success/failure

### Option 2: Manual Execution

**Prerequisites:**
```bash
# 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 install
```

**Run individual examples:**
```bash
# Create a document in JavaScript
node inst/js/create-shared-doc.js shared_doc.automerge

# Then load in R
Rscript -e 'doc <- automerge::am_load(readBin("shared_doc.automerge", "raw", 1e7)); print(doc)'
```

### Option 3: Interactive Verification

```r
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)
}
```

## Overview

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.

## Binary Format Compatibility

Automerge uses a standardized binary format (see [automerge.org/automerge-binary-format-spec](https://automerge.org/automerge-binary-format-spec/)) that is identical across all implementations. This means:

- Documents saved in JavaScript can be loaded in R
- Changes made in R can be merged back into JavaScript
- The sync protocol works seamlessly between platforms
- All CRDT types (maps, lists, text, counters) are fully compatible

## Prerequisites

You'll need:

- **JavaScript**: `@automerge/automerge` package (npm install)
- **R**: `automerge` package (this package)
- A way to exchange binary data between environments (files, HTTP, WebSockets, etc.)

For these examples, we'll use file-based exchange with Node.js on the JavaScript side.

## Example 1: Creating a Document in JavaScript, Loading in R

### JavaScript Side

```javascript
// 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))
```

### R Side

```{r}
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"
)
```

## Example 2: Modifying in R, Syncing Back to JavaScript

### R Side - Make Changes

```{r}
# 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")
```

### JavaScript Side - Load Updated Document

```javascript
// 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))
```

## Example 3: Real-Time Sync Protocol

This example shows how to use the sync protocol for real-time synchronization between JavaScript and R.

### R Side - Set Up Sync

```{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")
```

### JavaScript Side - Receive and Respond

```javascript
// 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')
}
```

### R Side - Complete Sync

```{r}
# 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")
}
```

## Example 4: Concurrent Edits and Automatic Merge

This demonstrates Automerge's CRDT capabilities with concurrent edits in both platforms.

### Scenario Setup

```{r}
# Create a shared document
shared <- am_create() |>
  am_put(AM_ROOT, "document", "Shared Document") |>
  am_put(AM_ROOT, "sections", am_list()) |>
  am_commit("Initialize document")

# Save for both platforms
shared_bytes <- am_save(shared)
writeBin(shared_bytes, "concurrent_doc.automerge")
```

### JavaScript - Concurrent Edit 1

```javascript
// 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:
```bash
# From installed package
JS_DIR=$(Rscript -e "cat(system.file('js', package='automerge'))")
node $JS_DIR/concurrent-edit.js concurrent_doc.automerge js_concurrent.automerge

# Or from source
node inst/js/concurrent-edit.js concurrent_doc.automerge js_concurrent.automerge
```

### R - Concurrent Edit 2

```{r}
# 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")
```

### Merge Concurrent Changes (R Side)

```{r}
# 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")
```

### Merge Concurrent Changes (JavaScript Side)

The same merge can be done on the JavaScript side:

```javascript
// 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:
```bash
# From installed package
JS_DIR=$(Rscript -e "cat(system.file('js', package='automerge'))")
node $JS_DIR/verify-merge.js r_concurrent.automerge

# Or from source
node inst/js/verify-merge.js r_concurrent.automerge
```

## Example 5: Text CRDT Synchronization

Text objects are particularly interesting as they demonstrate character-level CRDT merge.

### JavaScript - Create Text Document

```javascript
let textDoc = Automerge.change(Automerge.init(), doc => {
  doc.notes = new Automerge.Text('Hello from JavaScript')
})

fs.writeFileSync('text_doc.automerge', Automerge.save(textDoc))
```

### R - Load and Edit Text

```{r}
# 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")
```

### JavaScript - Verify Text Edits

```javascript
// Load updated text document
const updatedTextDoc = Automerge.load(fs.readFileSync('text_doc.automerge'))

console.log('Text content:', updatedTextDoc.notes.toString())
// Output: "Hello from JavaScript and R!"
```

## Type Compatibility Matrix

| 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.

## Binary Format Details

The saved document format includes:

- **Change history**: All operations since document creation
- **Actor IDs**: Unique identifiers for each editing session
- **Dependencies**: Causal relationships between changes
- **Compressed storage**: Columnar format with RLE compression

The binary format is deterministic and identical across platforms, enabling:

- File-based collaboration (Dropbox, Git LFS, etc.)
- Network synchronization (HTTP, WebSockets)
- Conflict-free merging regardless of edit order

## Troubleshooting

### Character Encoding Issues

Both JavaScript and R use UTF-8 for strings. If you encounter encoding issues:

```{r}
# Ensure UTF-8 encoding when reading from files
doc <- am_load(readBin("doc.automerge", "raw", 1e7))

# Check string encoding
str_value <- doc[["string_field"]]
Encoding(str_value) # Should be "UTF-8"
```

### Binary File Transfer

When transferring files between systems, always use binary mode:

```bash
# Correct: binary transfer
scp -B doc.automerge server:/path/

# Incorrect: text mode (can corrupt)
# Don't use text mode transfer for .automerge files
```

### Actor ID Collisions

Each platform generates random actor IDs. To use custom IDs:

```{r}
# R - specify actor ID as raw bytes or hex string
doc <- am_create(actor_id = "r-session-123")
```

```javascript
// JavaScript - specify actor ID
let doc = Automerge.init({ actorId: "js-session-456" })
```

### Indexing Differences

Remember the indexing conventions:

```{r}
# Lists: R uses 1-based indexing
list_obj <- doc[["items"]]
first_item <- am_get(doc, list_obj, 1) # First element

# Text operations: 0-based positions (same as JavaScript)
text_obj <- doc[["content"]]
am_text_splice(text_obj, 0, 0, "Start") # Position 0 = before first char
```

```javascript
// JavaScript - lists use 0-based indexing
const firstItem = doc.items[0]  // First element

// Text operations - also 0-based
doc.content.insertAt(0, "Start")
```

## Testing Cross-Platform Interoperability

All examples in this vignette can be tested using the executable scripts provided in `inst/js/`:

### Automated Testing

Run all examples automatically:
```bash
Rscript inst/js/run-examples.R
```

This will execute all JavaScript scripts, verify results in R, and demonstrate complete round-trip interoperability.

### Manual Testing

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.

### Available Scripts

The following scripts are available in `inst/js/`:

- `create-shared-doc.js` - Create documents in JavaScript
- `verify-r-changes.js` - Verify R modifications from JavaScript
- `concurrent-edit.js` - Make concurrent edits in JavaScript
- `verify-merge.js` - Verify CRDT merge from JavaScript

To find these scripts after installation:
```r
system.file("js", package = "automerge")
```

## Further Reading

- [Automerge Website](https://automerge.org)
- [Binary Format Specification](https://automerge.org/automerge-binary-format-spec/)
- [CRDT Research Papers](https://crdt.tech)
- [Sync Protocol Vignette](sync-protocol.html) - Details on the sync protocol
- [CRDT Concepts Vignette](crdt-concepts.html) - Understanding CRDTs
- JavaScript Interoperability Scripts - See `system.file("js", package = "automerge")` for executable examples
