# ShareDB (OT) Collaboration

ShareDB is an OT-based real-time database that enables collaborative editing with automatic conflict resolution. The `@rowsncolumns/sharedb` package provides a React hook for integrating ShareDB with your spreadsheet.

## Installation

{% tabs %}
{% tab title="yarn" %}

```sh
yarn add "@rowsncolumns/sharedb sharedb reconnecting-websocket"
```

{% endtab %}

{% tab title="npm" %}

```sh
npm install "@rowsncolumns/sharedb sharedb reconnecting-websocket"
```

{% endtab %}
{% endtabs %}

## Quick Start

```tsx
import { useShareDBSpreadsheet } from "@rowsncolumns/sharedb";
import ShareDBClient from "sharedb/lib/client";
import ReconnectingWebSocket from "reconnecting-websocket";

// Create ShareDB connection
const socket = new ReconnectingWebSocket("ws://localhost:8080");
const connection = new ShareDBClient.Connection(socket);

function SpreadsheetEditor() {
  const [sheetData, setSheetData] = useState({});
  const [sheets, setSheets] = useState([]);
  const [tables, setTables] = useState([]);
  const [sheetId, setSheetId] = useState(1);
  const [activeCell, setActiveCell] = useState({ rowIndex: 1, columnIndex: 1 });

  const { onBroadcastPatch, users, synced, isLeader } = useShareDBSpreadsheet({
    connection,
    collection: "spreadsheets",
    documentId: "my-spreadsheet",
    userId: "user-123",
    title: "John Doe",
    sheetId,
    activeCell,
    initialSheets: [],
    onChangeSheetData: setSheetData,
    onChangeSheets: setSheets,
    onChangeTables: setTables,
    onChangeActiveSheet: setSheetId,
    calculateNow,
    enqueueGraphOperation: (op) => {
      // Handle dependency graph updates for formula recalculation
    },
  });

  // Pass onBroadcastPatch to useSpreadsheetState
  return <Spreadsheet sheetData={sheetData} sheets={sheets} users={users} />;
}
```

## Integration with useSpreadsheetState

Connect the ShareDB adapter to your spreadsheet state:

```tsx
import { useSpreadsheetState } from "@rowsncolumns/spreadsheet-state";
import { useShareDBSpreadsheet } from "@rowsncolumns/sharedb";

function CollaborativeSpreadsheet() {
  const {
    sheetData,
    sheets,
    tables,
    charts,
    embeds,
    namedRanges,
    protectedRanges,
    conditionalFormats,
    dataValidations,
    pivotTables,
    cellXfs,
    sharedStrings,
    setSheetData,
    setSheets,
    setTables,
    setCharts,
    setEmbeds,
    setNamedRanges,
    setProtectedRanges,
    setConditionalFormats,
    setDataValidations,
    setPivotTables,
    setCellXfs,
    setSharedStrings,
    enqueueGraphOperation,
    sheetId,
    activeCell,
    calculateNow,
  } = useSpreadsheetState({
    // Your spreadsheet state config
  });

  const { onBroadcastPatch, users, synced } = useShareDBSpreadsheet({
    connection,
    collection: "spreadsheets",
    documentId: "doc-id",
    userId: currentUser.id,
    title: currentUser.name,
    sheetId,
    activeCell,
    initialSheets: [],
    calculateNow,
    onChangeSheetData: setSheetData,
    onChangeSheets: setSheets,
    onChangeTables: setTables,
    onChangeCharts: setCharts,
    onChangeEmbeds: setEmbeds,
    onChangeNamedRanges: setNamedRanges,
    onChangeProtectedRanges: setProtectedRanges,
    onChangeConditionalFormats: setConditionalFormats,
    onChangeDataValidations: setDataValidations,
    onChangePivotTables: setPivotTables,
    onChangeCellXfs: setCellXfs,
    onChangeSharedStrings: setSharedStrings,
    enqueueGraphOperation,
  });

  // Pass onBroadcastPatch to your spreadsheet
  // ...
}
```

## Setting Up a ShareDB Server

### Basic Server

Create a file `server.js`:

```javascript
const http = require("http");
const express = require("express");
const ShareDB = require("sharedb");
const WebSocket = require("ws");
const WebSocketJSONStream = require("@teamwork/websocket-json-stream");

// Initialize ShareDB
const backend = new ShareDB();

// Create Express app and HTTP server
const app = express();
const server = http.createServer(app);

// Create WebSocket server
const wss = new WebSocket.Server({ server });

// Handle WebSocket connections
wss.on("connection", (ws) => {
  const stream = new WebSocketJSONStream(ws);
  backend.listen(stream);
});

// Start server
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
  console.log(`ShareDB server listening on port ${PORT}`);
});
```

Install dependencies:

```bash
npm install express sharedb ws @teamwork/websocket-json-stream
```

Run the server:

```bash
node server.js
```

### Server with MongoDB Persistence

For production environments, persist data to MongoDB:

```bash
npm install sharedb-mongo mongodb
```

```javascript
const ShareDBMongo = require("sharedb-mongo");

// Connect to MongoDB
const db = new ShareDBMongo("mongodb://localhost:27017/spreadsheets");

// Initialize ShareDB with MongoDB
const backend = new ShareDB({ db });
```

### Server with PostgreSQL Persistence

```bash
npm install sharedb-postgres pg
```

```javascript
const ShareDBPostgres = require("sharedb-postgres");

const db = new ShareDBPostgres({
  connectionString: "postgresql://user:password@localhost:5432/spreadsheets",
});

const backend = new ShareDB({ db });
```

## Document Structure

The ShareDB document stores all spreadsheet data:

```typescript
type ShareDBSpreadsheetDoc = {
  // Cell data: flat map with keys like "1!A1" -> { value, sId, r, c }
  sheetData: Record<string, CellDataV3>;

  // Sheet definitions
  sheets: Sheet[];

  // Tables, charts, embeds
  tables: TableView[];
  charts: EmbeddedChart[];
  embeds: EmbeddedObject[];

  // Named ranges, protected ranges
  namedRanges: NamedRange[];
  protectedRanges: ProtectedRange[];

  // Conditional formats, data validations
  conditionalFormats: ConditionalFormatRule[];
  dataValidations: DataValidationRuleRecord[];

  // Pivot tables
  pivotTables: PivotTable[];

  // Cell formats and shared strings
  cellXfs: Record<string, CellFormat>;
  sharedStrings: Record<string, string>;
};
```

### Cell Key Format

Cell keys follow the pattern `${sheetId}!${A1Address}`:

| Key         | Description           |
| ----------- | --------------------- |
| `"1!A1"`    | Cell A1 on sheet 1    |
| `"2!B5"`    | Cell B5 on sheet 2    |
| `"1!AA100"` | Cell AA100 on sheet 1 |

### CellDataV3 Structure

Each cell value is stored with its position metadata:

```typescript
type CellDataV3<T> = {
  value: T; // The cell data (text, formula, number, etc.)
  sId: number; // Sheet ID
  r: number; // Row index (0-based)
  c: number; // Column index (0-based)
};
```

## Presence Awareness

The hook automatically manages presence, allowing you to display other users' cursor positions:

```tsx
const { users } = useShareDBSpreadsheet({ ... });

// Render collaborators in your grid
<CanvasGrid
  users={users}
  userId={currentUserId}
/>
```

Each user in the `users` array includes:

```typescript
type Collaborator = {
  userId: string;
  title: string;
  sheetId: number;
  activeCell: { rowIndex: number; columnIndex: number };
};
```

## Leader Election

The adapter automatically elects a leader among connected clients. The leader is responsible for coordinating recalculation operations.

```tsx
const { isLeader } = useShareDBSpreadsheet({ ... });

// Leader-specific logic
if (isLeader) {
  // This client is responsible for coordinating recalcs
}
```

## Error Handling

Handle connection errors with the `onError` callback:

```tsx
useShareDBSpreadsheet({
  // ...
  onError: (err) => {
    console.error("ShareDB error:", err);
    toast.error("Connection lost. Reconnecting...");
  },
});
```

## Why ShareDB for Spreadsheets?

ShareDB's [Operational Transformation (OT)](https://en.wikipedia.org/wiki/Operational_transformation) approach is particularly well-suited for spreadsheet collaboration compared to CRDT-based solutions like Yjs.

### Comparison with Y.js

| Aspect              | ShareDB (OT)                            | Yjs (CRDT)                                   |
| ------------------- | --------------------------------------- | -------------------------------------------- |
| Conflict Resolution | OT - Server determines canonical order  | CRDT - Automatic merge, eventual consistency |
| Server Required     | Yes                                     | Optional (P2P possible)                      |
| Recalculation       | Leader election makes coordination easy | Requires additional coordination layer       |
| Undo/Redo           | Manual (via Immer patches)              | Built-in                                     |
| Offline Support     | Requires server connection              | Full offline-first support                   |
| Audit Trails        | Sequential operation log                | Distributed history                          |
| Data Persistence    | MongoDB, PostgreSQL, etc.               | LevelDB, IndexedDB, etc.                     |

### Why OT Works Well for Spreadsheets

1. **Cell-level granularity** - The V3 flat map structure (`"sheetId!A1"` keys) maps perfectly to ShareDB's json0 OT type. Each cell is an independent key-value pair, so conflicts are rare.
2. **Server-centric calculation** - Spreadsheets typically need a server anyway for formula calculation, persistence, and permissions. OT's server authority aligns naturally with this architecture.
3. **Simpler undo/redo** - OT's sequential operation log makes history management straightforward. The server maintains a clear order of operations.
4. **Predictable conflict resolution** - When two users edit the same cell simultaneously (rare in practice), the server determines the winner. No surprising merged states.
5. **Batched operations** - Large operations (paste 1000 cells, delete rows) can be batched into a single atomic `submitOp` call, maintaining consistency.

### When to Choose ShareDB

* Need a traditional client-server architecture
* Want to leverage existing MongoDB/PostgreSQL infrastructure
* Prefer predictable OT semantics for conflict resolution
* Server-side formula calculation is required
* Need clean audit trails and operation history

### When to Choose Y.js

* Need offline-first capabilities
* Want peer-to-peer collaboration without a server
* Need built-in undo/redo support
* Decentralized architecture is preferred

For most spreadsheet use cases with a server backend, ShareDB provides a cleaner, more predictable collaboration model.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rowsncolumns.app/collaboration/sharedb-collaboration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
