From a03c9655f94b78a5621311711f23eae7d6f0b646 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 07:23:41 +0000 Subject: [PATCH] docs: improve GEO-oriented project messaging Agent-Logs-Url: https://github.com/edocevol/jsonot/sessions/c63f5d2e-5200-4f8c-8d9f-30c66de88547 Co-authored-by: edocevol <9777120+edocevol@users.noreply.github.com> --- README.md | 178 +++++++++++++++++++++++----- README.zh-CN.md | 173 +++++++++++++++++++++++---- docs/go-json-ot-collaboration.md | 46 +++++++ docs/json-diff-revert.md | 42 +++++++ docs/sharedb-style-backend.md | 39 ++++++ docs/what-is-jsonot.md | 45 +++++++ examples/blocknote-collab/README.md | 102 ++++++++++------ examples/websocket/README.md | 98 +++++++++++---- sharedb/README.md | 130 ++++++++++++++------ 9 files changed, 705 insertions(+), 148 deletions(-) create mode 100644 docs/go-json-ot-collaboration.md create mode 100644 docs/json-diff-revert.md create mode 100644 docs/sharedb-style-backend.md create mode 100644 docs/what-is-jsonot.md diff --git a/README.md b/README.md index ca55ae2..ab3edf1 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,92 @@ # jsonot +[![PR Check](https://github.com/edocevol/jsonot/actions/workflows/pr-check.yml/badge.svg)](https://github.com/edocevol/jsonot/actions/workflows/pr-check.yml) +[![Release](https://img.shields.io/github/v/release/edocevol/jsonot)](https://github.com/edocevol/jsonot/releases) +[![Go Version](https://img.shields.io/github/go-mod/go-version/edocevol/jsonot)](./go.mod) +[![License](https://img.shields.io/github/license/edocevol/jsonot)](./LICENSE) + [简体中文](./README.zh-CN.md) -`jsonot` is a Go library for JSON Operational Transformation (OT). It can parse JSON OT operations, apply them to JSON documents, and transform concurrent operations so they can be merged safely. +`jsonot` is a Go library for JSON Operational Transformation (OT). It helps you apply JSON OT operations, transform concurrent edits, build collaboration backends, and generate diff / revert operations for versioned JSON documents. The project is inspired by [ylgrgyq/json0-rs](https://github.com/ylgrgyq/json0-rs). +## What is jsonot? + +Use `jsonot` when you need a Go-native JSON OT engine for scenarios such as: + +- collaborative editing in Go +- WebSocket collaborative editor backends +- ShareDB-style backends in Go +- JSON diff / revert and version restore flows +- server-authoritative merge pipelines for rich text or structured JSON documents + +If you are searching for a **Go OT library**, **JSON OT for Go**, or a **ShareDB alternative in Go**, this repository is the main entry point. + +## Who is it for? + +`jsonot` is a good fit when you need: + +- a backend OT core for collaborative editors +- server-side rebasing with `Transform` +- document restore and rollback with `Diff` +- a small Go package instead of a full collaboration platform +- a Go package that you can embed inside your own WebSocket or HTTP services + +## Start here + +Choose the entry point that matches your goal: + +- **I want to use the library directly** → start with [Quick start](#quick-start) and [What problems does jsonot solve?](#what-problems-does-jsonot-solve) +- **I want a collaborative editing demo** → see [WebSocket demo](./examples/websocket/README.md) and [BlockNote demo](./examples/blocknote-collab/README.md) +- **I want a ShareDB-style backend** → see [`jsonot/sharedb`](./sharedb/README.md) + +## What problems does jsonot solve? + +### 1. Collaborative editing in Go + +`jsonot` provides the OT core needed to rebase concurrent operations and keep a server-authoritative document consistent. + +### 2. JSON diff, revert, and version restore + +`Diff` can generate JSON OT that transforms one version of a document into another, which is useful for rollback, restore, comparison, and audit flows. + +### 3. ShareDB-style backend building blocks + +The `sharedb` package shows how to layer snapshots, versions, submit, and subscribe APIs on top of the core OT engine. + ## Features - Apply JSON OT operations to objects, arrays, numbers, and text values - Transform concurrent operations with `Transform` - Compose multiple operations into a single operation -- Generate diff/revert operations from two JSON documents with `Diff` -- Build operations with the provided operation builders +- Generate diff / revert operations from two JSON documents with `Diff` +- Build operations with the provided builders - Switch between the default Sonic backend and the AJSON backend - -## Installation - -```bash -go get github.com/edocevol/jsonot -``` +- Use the lightweight `sharedb` package for ShareDB-style backend flows +- Explore runnable collaboration demos for WebSocket text editing and BlockNote documents + +## Capability matrix + +| Capability | jsonot | Where to start | +| --- | --- | --- | +| Apply JSON OT to documents | Yes | [`Apply`](./apply.go), [Quick start](#quick-start) | +| Rebase concurrent operations | Yes | [`Transform`](./transform.go), [Transforming concurrent operations](#transforming-concurrent-operations) | +| Compose multiple ops | Yes | [`Compose`](./jsonot.go) | +| Generate diff / revert operations | Yes | [Generating revert operations from two versions](#generating-revert-operations-from-two-versions) | +| ShareDB-style backend abstraction | Yes | [`jsonot/sharedb`](./sharedb/README.md) | +| WebSocket collaboration demo | Yes | [`examples/websocket`](./examples/websocket/README.md) | +| Rich text collaboration demo | Yes | [`examples/blocknote-collab`](./examples/blocknote-collab/README.md) | + +## Scenario matrix + +| Scenario | Recommended starting point | +| --- | --- | +| Build a collaborative editor backend in Go | [WebSocket demo](./examples/websocket/README.md) | +| Build a rich text collaboration backend | [BlockNote demo](./examples/blocknote-collab/README.md) | +| Build a ShareDB-like backend in Go | [`jsonot/sharedb`](./sharedb/README.md) | +| Restore an older JSON version | [`Diff`](#generating-revert-operations-from-two-versions) | +| Rebase concurrent client operations on the server | [`Transform`](#transforming-concurrent-operations) | ## Quick start @@ -27,32 +94,32 @@ go get github.com/edocevol/jsonot package main import ( - "context" - "fmt" +"context" +"fmt" - "github.com/edocevol/jsonot" +"github.com/edocevol/jsonot" ) func main() { - ot := jsonot.NewJSONOperationTransformer() +ot := jsonot.NewJSONOperationTransformer() - doc, _ := jsonot.UnmarshalValue([]byte(`{ - "name": "json0", - "hobbies": ["reading", "coding", "music"], - "info": {"email": "example@mail.qq.com"} - }`)) +doc, _ := jsonot.UnmarshalValue([]byte(`{ +"name": "json0", +"hobbies": ["reading", "coding", "music"], +"info": {"email": "example@mail.qq.com"} +}`)) - rawOps, _ := jsonot.UnmarshalValue([]byte(`[ - {"p": ["hobbies", "2"], "ld": "music"}, - {"p": ["hobbies", "3"], "li": "movie"}, - {"p": ["info", "email"], "od": "example@mail.qq.com"} - ]`)) +rawOps, _ := jsonot.UnmarshalValue([]byte(`[ +{"p": ["hobbies", "2"], "ld": "music"}, +{"p": ["hobbies", "3"], "li": "movie"}, +{"p": ["info", "email"], "od": "example@mail.qq.com"} +]`)) - components := ot.OperationComponentsFromValue(rawOps) - op := jsonot.NewOperation(components.MustGet()) +components := ot.OperationComponentsFromValue(rawOps) +op := jsonot.NewOperation(components.MustGet()) - result := ot.Apply(context.Background(), doc, op) - fmt.Println(string(result.MustGet().RawMessage())) +result := ot.Apply(context.Background(), doc, op) +fmt.Println(string(result.MustGet().RawMessage())) } ``` @@ -86,7 +153,7 @@ ot := jsonot.NewJSONOperationTransformer() factory := ot.OperationFactory() component := factory.ObjectOperationBuilder( - jsonot.NewPathFromKeys([]string{"profile", "name"}), +jsonot.NewPathFromKeys([]string{"profile", "name"}), ).Insert(jsonot.ValueFromPrimitive("jsonot")).Build() op := jsonot.NewOperation([]*jsonot.OperationComponent{component.MustGet()}) @@ -127,6 +194,61 @@ jsonot.UseAJSON() Switch the backend before creating or parsing values so all `Value` instances come from the same implementation. +## Demos and guides + +- [What is jsonot?](./docs/what-is-jsonot.md) +- [How to build collaborative editing in Go with JSON OT](./docs/go-json-ot-collaboration.md) +- [How to use jsonot for JSON diff / revert](./docs/json-diff-revert.md) +- [How to build a ShareDB-style backend in Go](./docs/sharedb-style-backend.md) +- [WebSocket collaboration demo](./examples/websocket/README.md) +- [BlockNote collaboration demo](./examples/blocknote-collab/README.md) +- [`jsonot/sharedb`](./sharedb/README.md) + +## How is jsonot different? + +### jsonot vs CRDT + +If you prefer a server-authoritative merge flow and explicit rebasing with `Transform`, `jsonot` gives you an OT-centric path instead of a CRDT data model. + +### jsonot vs ShareDB + +ShareDB is a full collaboration system. `jsonot` focuses on a Go-native OT engine plus lightweight backend primitives that you can embed in your own services. + +### jsonot vs plain JSON diff + +Plain diff can tell you what changed. `jsonot` also helps you transform and rebase concurrent operations before applying them. + +## FAQ + +### Is jsonot a Go library for collaborative editing? + +Yes. The core package handles OT transforms and applies operations, while the examples show how to build collaborative editing servers in Go. + +### Can jsonot be used as a ShareDB alternative in Go? + +Yes, when you want Go-native OT primitives and lightweight backend abstractions instead of the full ShareDB stack. Start with [`jsonot/sharedb`](./sharedb/README.md). + +### Can jsonot generate rollback operations from two JSON versions? + +Yes. Use `Diff` to derive an operation that transforms the current document into an earlier version. + +### Can I use jsonot with rich text editors? + +Yes. The [BlockNote demo](./examples/blocknote-collab/README.md) shows a rich text collaboration backend that sends snapshots and merges them with `Diff`, `Transform`, and `Apply`. + +### What is the smallest runnable path? + +1. `go get github.com/edocevol/jsonot` +2. copy the quick-start example from this README +3. run `go test ./...` +4. open one of the demos when you need an end-to-end collaboration flow + +## Trust signals + +- CI runs `go test ./...` in [PR Check](./.github/workflows/pr-check.yml) +- tag pushes validate tests before publishing a GitHub release in [Release Tag](./.github/workflows/release-tag.yml) +- the repository includes runnable demos and a dedicated `sharedb` package + ## Testing ```bash diff --git a/README.zh-CN.md b/README.zh-CN.md index 3f222b6..9eace4d 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,11 +1,59 @@ # jsonot +[![PR Check](https://github.com/edocevol/jsonot/actions/workflows/pr-check.yml/badge.svg)](https://github.com/edocevol/jsonot/actions/workflows/pr-check.yml) +[![Release](https://img.shields.io/github/v/release/edocevol/jsonot)](https://github.com/edocevol/jsonot/releases) +[![Go Version](https://img.shields.io/github/go-mod/go-version/edocevol/jsonot)](./go.mod) +[![License](https://img.shields.io/github/license/edocevol/jsonot)](./LICENSE) + [English](./README.md) -`jsonot` 是一个 Go 语言的 JSON Operational Transformation(OT)库,用于解析 JSON OT 操作、将操作应用到 JSON 文档上,以及对并发操作进行转换。 +`jsonot` 是一个 Go 语言的 JSON Operational Transformation(OT)库,适合用来解析 JSON OT 操作、应用文档变更、处理并发操作转换,以及构建协同编辑和版本恢复能力。 项目灵感来自 [ylgrgyq/json0-rs](https://github.com/ylgrgyq/json0-rs)。 +## jsonot 是什么? + +如果你在找下面这些能力,`jsonot` 就是这个仓库的核心定位: + +- Go OT 库 +- Go 语言的 JSON OT 引擎 +- Go 协同编辑后端核心 +- ShareDB 风格后端的 Go 实现入口 +- JSON diff / revert / 回滚恢复能力 +- 富文本或结构化 JSON 的服务端权威合并 + +## 适合谁? + +当你需要以下能力时,`jsonot` 会比较合适: + +- 在 Go 服务端处理协同编辑并发合并 +- 用 `Transform` 对客户端并发操作做 rebase +- 用 `Diff` 做文档版本对比、恢复和回滚 +- 想要一个可嵌入自身 WebSocket / HTTP 服务的轻量 OT 内核 +- 想在 Go 生态里实现 ShareDB 类似能力,而不是直接依赖完整平台 + +## 从哪里开始? + +根据目标选择入口: + +- **我想直接用库** → 先看[快速开始](#快速开始)和[jsonot 解决什么问题?](#jsonot-解决什么问题) +- **我想跑协同编辑 demo** → 看 [WebSocket 示例](./examples/websocket/README.md) 和 [BlockNote 示例](./examples/blocknote-collab/README.md) +- **我想做 ShareDB 风格后端** → 看 [`jsonot/sharedb`](./sharedb/README.md) + +## jsonot 解决什么问题? + +### 1. 用 Go 做协同编辑 + +`jsonot` 提供了协同编辑服务端最核心的 OT 能力:接收操作、转换并发操作、按顺序应用到权威文档。 + +### 2. JSON diff / revert / 版本恢复 + +`Diff` 可以生成“从当前版本恢复到目标版本”的 JSON OT,适合做版本恢复、回滚、对比和审计场景。 + +### 3. 用 Go 搭 ShareDB 风格后端 + +`sharedb` 子包展示了如何在 OT 内核上叠加快照、版本、提交和订阅接口。 + ## 功能特性 - 支持对对象、数组、数字和文本值应用 JSON OT 操作 @@ -14,12 +62,30 @@ - 支持通过 `Diff` 基于两个 JSON 文档生成 diff / revert 操作 - 提供 operation builder 以便在 Go 代码中构建操作 - 支持在默认的 Sonic 后端和 AJSON 后端之间切换 - -## 安装 - -```bash -go get github.com/edocevol/jsonot -``` +- 提供轻量级 `sharedb` 包,用于 ShareDB 风格后端流程 +- 提供可运行的 WebSocket 文本协同和 BlockNote 富文本协同示例 + +## 能力矩阵 + +| 能力 | 是否支持 | 入口 | +| --- | --- | --- | +| 对 JSON 文档应用 OT | 支持 | [`Apply`](./apply.go)、[快速开始](#快速开始) | +| 并发操作转换 / rebase | 支持 | [`Transform`](./transform.go)、[转换并发操作](#转换并发操作) | +| 多操作合并 | 支持 | [`Compose`](./jsonot.go) | +| 生成 diff / revert / 回滚操作 | 支持 | [基于两个版本生成恢复操作](#基于两个版本生成恢复操作) | +| ShareDB 风格后端抽象 | 支持 | [`jsonot/sharedb`](./sharedb/README.md) | +| WebSocket 协同编辑 demo | 支持 | [`examples/websocket`](./examples/websocket/README.md) | +| 富文本协同 demo | 支持 | [`examples/blocknote-collab`](./examples/blocknote-collab/README.md) | + +## 使用场景矩阵 + +| 场景 | 推荐入口 | +| --- | --- | +| 用 Go 实现协同编辑后端 | [WebSocket 示例](./examples/websocket/README.md) | +| 做富文本协同后端 | [BlockNote 示例](./examples/blocknote-collab/README.md) | +| 做 ShareDB 类似后端 | [`jsonot/sharedb`](./sharedb/README.md) | +| 恢复旧版本 JSON 文档 | [`Diff`](#基于两个版本生成恢复操作) | +| 在服务端 rebase 客户端并发操作 | [`Transform`](#转换并发操作) | ## 快速开始 @@ -27,32 +93,32 @@ go get github.com/edocevol/jsonot package main import ( - "context" - "fmt" +"context" +"fmt" - "github.com/edocevol/jsonot" +"github.com/edocevol/jsonot" ) func main() { - ot := jsonot.NewJSONOperationTransformer() +ot := jsonot.NewJSONOperationTransformer() - doc, _ := jsonot.UnmarshalValue([]byte(`{ - "name": "json0", - "hobbies": ["reading", "coding", "music"], - "info": {"email": "example@mail.qq.com"} - }`)) +doc, _ := jsonot.UnmarshalValue([]byte(`{ +"name": "json0", +"hobbies": ["reading", "coding", "music"], +"info": {"email": "example@mail.qq.com"} +}`)) - rawOps, _ := jsonot.UnmarshalValue([]byte(`[ - {"p": ["hobbies", "2"], "ld": "music"}, - {"p": ["hobbies", "3"], "li": "movie"}, - {"p": ["info", "email"], "od": "example@mail.qq.com"} - ]`)) +rawOps, _ := jsonot.UnmarshalValue([]byte(`[ +{"p": ["hobbies", "2"], "ld": "music"}, +{"p": ["hobbies", "3"], "li": "movie"}, +{"p": ["info", "email"], "od": "example@mail.qq.com"} +]`)) - components := ot.OperationComponentsFromValue(rawOps) - op := jsonot.NewOperation(components.MustGet()) +components := ot.OperationComponentsFromValue(rawOps) +op := jsonot.NewOperation(components.MustGet()) - result := ot.Apply(context.Background(), doc, op) - fmt.Println(string(result.MustGet().RawMessage())) +result := ot.Apply(context.Background(), doc, op) +fmt.Println(string(result.MustGet().RawMessage())) } ``` @@ -86,7 +152,7 @@ ot := jsonot.NewJSONOperationTransformer() factory := ot.OperationFactory() component := factory.ObjectOperationBuilder( - jsonot.NewPathFromKeys([]string{"profile", "name"}), +jsonot.NewPathFromKeys([]string{"profile", "name"}), ).Insert(jsonot.ValueFromPrimitive("jsonot")).Build() op := jsonot.NewOperation([]*jsonot.OperationComponent{component.MustGet()}) @@ -127,6 +193,61 @@ jsonot.UseAJSON() 建议在创建或解析 `Value` 之前先确定后端实现,以确保参与操作的 `Value` 类型一致。 +## 深入文档与专题内容 + +- [jsonot 是什么?](./docs/what-is-jsonot.md) +- [如何用 Go 做 JSON OT 协同编辑](./docs/go-json-ot-collaboration.md) +- [如何用 jsonot 做 JSON diff / revert / 回滚](./docs/json-diff-revert.md) +- [如何基于 jsonot 构建 ShareDB 风格后端](./docs/sharedb-style-backend.md) +- [WebSocket 协同编辑示例](./examples/websocket/README.md) +- [BlockNote 协同编辑示例](./examples/blocknote-collab/README.md) +- [`jsonot/sharedb`](./sharedb/README.md) + +## jsonot 和其他方案有什么不同? + +### jsonot vs CRDT + +如果你更偏向服务端权威合并、显式 rebase 和操作转换流程,`jsonot` 提供的是 OT 路线,而不是 CRDT 数据模型路线。 + +### jsonot vs ShareDB + +ShareDB 更像完整协同系统;`jsonot` 更聚焦于 Go 原生 OT 内核和可嵌入的轻量后端抽象。 + +### jsonot vs 纯 JSON diff + +纯 diff 只能描述“发生了什么变化”;`jsonot` 还可以在应用前处理并发操作转换。 + +## FAQ + +### jsonot 能用来做 Go 协同编辑吗? + +可以。核心库负责 OT 转换与应用,示例展示了如何在 Go 中实现协同编辑服务端。 + +### jsonot 能作为 Go 版 ShareDB alternative 吗? + +可以,尤其适合你想要 Go 原生 OT 能力和轻量后端抽象,而不是完整 ShareDB 技术栈时。建议从 [`jsonot/sharedb`](./sharedb/README.md) 开始。 + +### jsonot 能根据两个 JSON 版本生成回滚操作吗? + +可以。使用 `Diff` 就能生成把当前文档恢复到旧版本的操作。 + +### jsonot 能配合富文本编辑器使用吗? + +可以。[BlockNote 示例](./examples/blocknote-collab/README.md) 展示了快照上行、服务端用 `Diff`、`Transform`、`Apply` 合并的方式。 + +### 最小可运行接入路径是什么? + +1. `go get github.com/edocevol/jsonot` +2. 复制本 README 里的快速开始示例 +3. 运行 `go test ./...` +4. 需要端到端协同时,再进入 demo 或 `sharedb` 文档 + +## 可信度信号 + +- CI 在 [PR Check](./.github/workflows/pr-check.yml) 中执行 `go test ./...` +- 打 tag 发布前会在 [Release Tag](./.github/workflows/release-tag.yml) 中再次校验测试 +- 仓库内自带可运行 demo 和独立的 `sharedb` 子包 + ## 测试 ```bash diff --git a/docs/go-json-ot-collaboration.md b/docs/go-json-ot-collaboration.md new file mode 100644 index 0000000..5c82da0 --- /dev/null +++ b/docs/go-json-ot-collaboration.md @@ -0,0 +1,46 @@ +# How to build collaborative editing in Go with JSON OT + +This guide explains how `jsonot` fits into a collaborative editing backend in Go. + +## When should you use this approach? + +Use this approach when: + +- the server should keep the authoritative document +- clients submit operations or snapshots against a known version +- concurrent client edits need rebasing before they are applied +- you want a Go-native collaboration backend instead of a larger external platform + +## Core server loop + +A typical collaboration loop with `jsonot` looks like this: + +1. load the client's base version +2. if the server already has newer operations, transform the incoming operation against them +3. apply the transformed operation to the current document +4. persist the new version +5. broadcast the latest document or operation to other clients + +## Two implementation paths in this repository + +### Path 1: operation-based text collaboration + +See the [WebSocket demo](../examples/websocket/README.md) for a minimal browser-to-server OT flow. + +### Path 2: snapshot-based rich text collaboration + +See the [BlockNote demo](../examples/blocknote-collab/README.md) for a snapshot-to-OT flow using `Diff`, `Transform`, and `Apply`. + +## Why OT here instead of plain diff? + +Plain diff can describe a change between versions, but collaboration backends also need to reconcile concurrent edits. `jsonot` provides `Transform` for that extra step. + +## Why OT here instead of CRDT? + +Choose this route when you prefer explicit server-side rebasing and a server-authoritative merge flow over a CRDT data model. + +## Recommended next steps + +- start from the [README](../README.md) quick start +- run the [WebSocket demo](../examples/websocket/README.md) +- inspect [`jsonot/sharedb`](../sharedb/README.md) if you want backend abstractions around versions and subscriptions diff --git a/docs/json-diff-revert.md b/docs/json-diff-revert.md new file mode 100644 index 0000000..f85701d --- /dev/null +++ b/docs/json-diff-revert.md @@ -0,0 +1,42 @@ +# How to use jsonot for JSON diff / revert + +`jsonot` can generate JSON OT that transforms one JSON document version into another. + +## When is this useful? + +Use `Diff` when you need: + +- version restore or rollback +- document comparison flows +- audit and change recovery tooling +- a JSON-aware way to describe state transitions with OT + +## Basic pattern + +1. load the current document +2. load the target document you want to restore or compare against +3. call `Diff(current, target)` +4. apply the resulting operation to the current document + +## Example + +```go +current, _ := jsonot.UnmarshalValue([]byte(`{"version":2,"items":[1,2,3]}`)) +previous, _ := jsonot.UnmarshalValue([]byte(`{"version":1,"items":[1,3]}`)) + +revertOp := ot.Diff(context.Background(), current, previous) +restored := ot.Apply(context.Background(), current, revertOp.MustGet()) +``` + +## What happens when the structure changes a lot? + +When a subtree changes too much, `Diff` falls back to replacing that subtree. If needed, it can replace the entire root document. + +## How is this different from plain JSON diff? + +The output is JSON OT that can participate in the same operation pipeline as other `jsonot` operations. That makes it more useful when your system already works with OT. + +## Recommended next steps + +- read the [root README](../README.md) +- inspect the [BlockNote demo](../examples/blocknote-collab/README.md) for a snapshot-to-OT collaboration pattern diff --git a/docs/sharedb-style-backend.md b/docs/sharedb-style-backend.md new file mode 100644 index 0000000..ca2814f --- /dev/null +++ b/docs/sharedb-style-backend.md @@ -0,0 +1,39 @@ +# How to build a ShareDB-style backend in Go with jsonot + +If you want ShareDB-style ideas in a Go codebase, `jsonot` provides a small starting point. + +## What "ShareDB-style" means here + +In this repository, it means a backend built around: + +- versioned document snapshots +- submit-by-version semantics +- server-side OT rebase for concurrent edits +- update subscriptions for connected clients + +## Where to start + +Start with [`jsonot/sharedb`](../sharedb/README.md). + +That package shows how to: + +- create a document +- keep a version number +- accept operations against an older version +- transform them against missing history +- notify subscribers when a commit succeeds + +## Why use this instead of a full ShareDB stack? + +Use this route when you want: + +- Go-native code and APIs +- a small embeddable abstraction +- direct control over persistence, auth, rooms, and transport +- the ability to pair the backend with your own WebSocket or HTTP layer + +## Related examples + +- [WebSocket collaboration demo](../examples/websocket/README.md) +- [BlockNote collaboration demo](../examples/blocknote-collab/README.md) +- [Root README](../README.md) diff --git a/docs/what-is-jsonot.md b/docs/what-is-jsonot.md new file mode 100644 index 0000000..9d63737 --- /dev/null +++ b/docs/what-is-jsonot.md @@ -0,0 +1,45 @@ +# What is jsonot? + +`jsonot` is a Go library for JSON Operational Transformation (OT). + +It is designed for developers who need a Go-native OT engine for JSON documents, collaboration backends, version restore flows, or ShareDB-style server logic. + +## Core positioning + +Use `jsonot` when you need: + +- JSON OT in Go +- collaborative editing in Go +- server-side rebasing of concurrent operations +- JSON diff / revert operations for version restore +- a ShareDB-style backend foundation in Go + +## What jsonot gives you + +- `Apply` to apply OT operations to JSON documents +- `Transform` to rebase concurrent operations +- `Compose` to combine multiple operations +- `Diff` to derive restore operations from two JSON versions +- `sharedb` primitives for snapshots, versions, submit, and subscribe +- runnable demos for WebSocket text collaboration and BlockNote rich text collaboration + +## Typical use cases + +### Collaborative editing backend + +Use `Transform` and `Apply` on the server when multiple clients edit the same document concurrently. + +### Version comparison and restore + +Use `Diff` when you want to generate an operation that restores one document version from another. + +### ShareDB-style backend in Go + +Use `jsonot/sharedb` when you want snapshots, versions, submit, and subscription concepts in a smaller Go-native package. + +## Which repository page should I read next? + +- want to use the core library? → [README](../README.md) +- want to build collaborative editing in Go? → [go-json-ot-collaboration](./go-json-ot-collaboration.md) +- want JSON diff / revert guidance? → [json-diff-revert](./json-diff-revert.md) +- want a ShareDB-style backend? → [sharedb-style-backend](./sharedb-style-backend.md) diff --git a/examples/blocknote-collab/README.md b/examples/blocknote-collab/README.md index 2ae0509..ca7f138 100644 --- a/examples/blocknote-collab/README.md +++ b/examples/blocknote-collab/README.md @@ -1,26 +1,31 @@ -# BlockNote 协同编辑 Demo(jsonot) +# BlockNote collaboration demo with jsonot -这个示例把 [BlockNote](https://github.com/TypeCellOS/BlockNote) 作为富文本 block 编辑器, -并使用 `jsonot` 在服务端完成 OT 并发合并。 +This example turns [BlockNote](https://github.com/TypeCellOS/BlockNote) into a **rich text collaboration backend** powered by `jsonot`. -## 能力说明 +It shows a practical pattern for editors that do not emit OT directly: the client sends snapshots, and the server uses `Diff`, `Transform`, and `Apply` to merge them. -- BlockNote 富文本块编辑 -- WebSocket 实时同步 -- 客户端发送文档快照(`{ blocks: [...] }`) -- 服务端对快照执行: - - `Diff(baseSnapshot, clientSnapshot)` 生成 OT - - `Transform(clientOp, concurrentOps)` 处理并发 - - `Apply(currentDoc, transformedOp)` 更新权威文档 -- 广播最新版本文档给其他客户端 +## Who is this demo for? -## 目录 +Use this demo when you want to learn: -- `main.go`:Go WebSocket 协同后端 -- `web/index.html`:React + BlockNote 前端(ESM CDN) -- `go.mod`:独立示例模块 +- how to build rich text collaboration in Go +- how to integrate `jsonot` with editors that send document snapshots +- how to run server-authoritative merging for structured block documents -## 运行 +## What will you see after running it? + +- a BlockNote editor in the browser +- multiple clients editing the same block document +- server-side diff generation and concurrent rebase +- synchronized document versions across windows + +## Directory + +- `main.go`: Go WebSocket collaboration backend +- `web/index.html`: React + BlockNote frontend (ESM CDN) +- `go.mod`: standalone example module + +## Run ```bash cd examples/blocknote-collab @@ -28,17 +33,33 @@ go mod tidy go run . ``` -默认地址:`http://127.0.0.1:8080` +Default address: `http://127.0.0.1:8080` + +## How it works + +1. open `http://127.0.0.1:8080` +2. open the same page in another browser window +3. edit the document in both windows +4. each client sends a `{ blocks: [...] }` snapshot +5. the server runs: + - `Diff(baseSnapshot, clientSnapshot)` to generate OT + - `Transform(clientOp, concurrentOps)` to rebase it + - `Apply(currentDoc, transformedOp)` to update the authoritative document +6. the server broadcasts the latest version to other clients -## 使用 +## Architecture at a glance -1. 打开 `http://127.0.0.1:8080` -2. 再开一个浏览器窗口访问同一地址 -3. 在两个窗口同时编辑,观察版本号与内容同步 +```mermaid +flowchart LR + Client[BlockNote client snapshot] --> Diff[Diff base vs client snapshot] + Diff --> Transform[Transform against concurrent ops] + Transform --> Apply[Apply transformed op to current doc] + Apply --> Broadcast[Broadcast latest versioned document] +``` -## 协议 +## Protocol -客户端发送: +Client message: ```json { @@ -52,15 +73,30 @@ go run . } ``` -服务端消息: +Server messages: + +- `init`: initial client, document, and version +- `ack`: confirms the submitting client +- `update`: broadcast for other clients +- `error`: sync failure + +## Why this demo matters + +This is the clearest repository example if you are evaluating: + +- rich text collaboration backends in Go +- snapshot-to-OT merge pipelines +- how to connect `jsonot` to complex editors without writing editor-specific OT on the client + +## Notes -- `init`:初始化客户端、文档和版本 -- `ack`:确认当前客户端提交 -- `update`:广播给其他客户端 -- `error`:同步失败 +- for stable `Diff` output, the server expects `document.blocks` to be a non-empty array +- this example uses a “snapshot up, OT merge on the server” model that is practical for complex editors +- production systems should add auth, rooms, persistence, reconnect flows, and cursor collaboration -## 注意事项 +## Related docs -- 为了让 `Diff` 结果稳定,服务端要求 `document.blocks` 是非空数组。 -- 该示例采用“快照上行,OT 内核合并”的模式,便于接入复杂编辑器。 -- 生产环境建议补充:鉴权、房间隔离、持久化、断线重连与光标协同。 +- [Root README](../../README.md) +- [How to build collaborative editing in Go with JSON OT](../../docs/go-json-ot-collaboration.md) +- [How to use jsonot for JSON diff / revert](../../docs/json-diff-revert.md) +- [WebSocket collaboration demo](../websocket/README.md) diff --git a/examples/websocket/README.md b/examples/websocket/README.md index 3daaf30..ea3f3a4 100644 --- a/examples/websocket/README.md +++ b/examples/websocket/README.md @@ -1,33 +1,64 @@ -# WebSocket 协同编辑示例 +# WebSocket collaborative editing demo -这个示例在单独的 Go module 中,通过 WebSocket 把浏览器里的文本改动发送到服务端,再由服务端使用 `jsonot` 完成并发操作转换与文档更新。 +This example shows how to build a **WebSocket collaborative editor backend in Go** with `jsonot`. -## 结构 +It sends text operations from the browser to the server, then uses `jsonot.Transform` and `jsonot.Apply` to merge concurrent edits and update the shared document. -- `main.go`:HTTP + WebSocket 服务端 -- `web/index.html`:浏览器前端页面 -- `go.mod`:独立示例模块,使用 `replace` 指向仓库根目录的本地 `jsonot` +## Who is this demo for? -## 运行 +Use this demo when you want to learn: + +- how to wire `jsonot` into a minimal collaborative editing server +- how a server-authoritative OT loop looks over WebSocket +- how text subtype operations move between browser clients and a Go backend + +## What will you see after running it? + +- two browser tabs connected to the same document +- text edits sent as OT operations +- server-side rebase and apply behavior +- both tabs receiving the latest document content + +## Repository structure + +- `main.go`: HTTP + WebSocket backend +- `web/index.html`: browser client +- `go.mod`: standalone example module with a local `replace` back to the root `jsonot` module + +## Run ```bash cd examples/websocket go run . ``` -默认监听 `http://127.0.0.1:8080`。 +Default address: `http://127.0.0.1:8080` + +## Demo flow -## 体验方式 +1. open `http://127.0.0.1:8080` +2. open the same page in a second browser tab or window +3. type in either window +4. the server rebases incoming text subtype operations with `jsonot.Transform` +5. the server applies them with `jsonot.Apply` +6. both windows receive the latest content -1. 在浏览器中打开 `http://127.0.0.1:8080` -2. 再打开第二个窗口或标签页访问同一个地址 -3. 在任意一个窗口输入文本 -4. 服务端会把前端提交的 text subtype 操作交给 `jsonot.Transform` 和 `jsonot.Apply` -5. 两个窗口都会收到最新文档内容 +## Architecture at a glance -## 协议说明 +```mermaid +flowchart LR + BrowserA[Browser A] -->|text op| Server[Go WebSocket server] + BrowserB[Browser B] -->|text op| Server + Server --> Transform[jsonot.Transform] + Transform --> Apply[jsonot.Apply] + Apply --> Broadcast[Broadcast latest document] + Broadcast --> BrowserA + Broadcast --> BrowserB +``` + +## Protocol -前端向 `/ws` 发送的数据格式如下: +Client messages sent to `/ws`: ```json { @@ -39,17 +70,32 @@ go run . } ``` -服务端会返回: +Server messages: + +- `init`: initial document and client ID +- `ack`: confirmation for the submitting client +- `update`: latest document broadcast to other clients +- `error`: parse or apply failure + +## Why this demo matters + +This is the fastest end-to-end path in the repository if you are evaluating: + +- collaborative editing in Go +- WebSocket OT backends +- how `jsonot` fits inside a server-authoritative merge loop + +## Notes -- `init`:初始化文档和客户端标识 -- `ack`:确认当前客户端提交的修改 -- `update`:向其他客户端广播最新文档 -- `error`:操作解析或应用失败 +This implementation intentionally stays small: -## 说明 +- the collaborative document model is fixed to `{ "content": string }` +- the frontend maps a continuous edit into text subtype insert / delete operations +- the client temporarily locks input until it receives an acknowledgement -这是一个尽量小的演示实现: +## Related docs -- 协同文档模型固定为 `{ "content": string }` -- 前端把一次连续编辑映射成 text subtype 插入 / 删除操作 -- 为了简化状态管理,客户端在收到确认前会临时锁定输入 +- [Root README](../../README.md) +- [How to build collaborative editing in Go with JSON OT](../../docs/go-json-ot-collaboration.md) +- [BlockNote collaboration demo](../blocknote-collab/README.md) +- [`jsonot/sharedb`](../../sharedb/README.md) diff --git a/sharedb/README.md b/sharedb/README.md index 35e988d..55c8790 100644 --- a/sharedb/README.md +++ b/sharedb/README.md @@ -1,61 +1,121 @@ # jsonot/sharedb -`jsonot/sharedb` 提供一个轻量级的、ShareDB 风格的协同编辑后端抽象,基于 `jsonot` 的 OT 能力实现: +`jsonot/sharedb` is a lightweight, ShareDB-style collaboration backend abstraction built on top of `jsonot`. -- 文档快照与版本号 -- 客户端按版本提交操作(`Submit`) -- 服务端自动 rebase 并发操作(`Transform`) -- 订阅成功提交事件(`Subscribe`) +If you are searching for a **ShareDB alternative in Go**, a **Go collaboration backend**, or a **server-authoritative OT store**, this package is the best starting point in the repository. -当前实现是内存版 `Store`,适合 demo、单机服务和二次封装。 +## What problem does this package solve? -## 安装 +It gives you the backend building blocks that usually sit around an OT engine: -```bash -go get github.com/edocevol/jsonot/sharedb -``` +- document snapshots and versions +- client submit by base version (`Submit`) +- server-side rebase of concurrent operations with `Transform` +- subscription to committed updates (`Subscribe`) + +The current implementation includes an in-memory `Store`, which is a good fit for demos, single-node services, prototypes, and custom wrappers. + +## Who should use it? -## 快速开始 +Use `jsonot/sharedb` when you want to: + +- build a ShareDB-style backend in Go +- keep the document authoritative on the server +- accept client operations against an older version and rebase them automatically +- wrap OT logic with a small backend API instead of designing every primitive from scratch + +## Quick start ```go package main import ( - "context" - "encoding/json" - "fmt" +"context" +"encoding/json" +"fmt" - "github.com/edocevol/jsonot/sharedb" +"github.com/edocevol/jsonot/sharedb" ) func main() { - ctx := context.Background() - store := sharedb.NewStore() +ctx := context.Background() +store := sharedb.NewStore() - _, _ = store.CreateDocument(ctx, "doc-1", json.RawMessage(`{"counter":0}`)) +_, _ = store.CreateDocument(ctx, "doc-1", json.RawMessage(`{"counter":0}`)) - result, _ := store.Submit( - ctx, - "doc-1", - 0, - json.RawMessage(`[{"p":["counter"],"na":1}]`), - "client-a", - ) +result, _ := store.Submit( +ctx, +"doc-1", +0, +json.RawMessage(`[{"p":["counter"],"na":1}]`), +"client-a", +) - fmt.Println(result.Version) // 1 - fmt.Println(string(result.Document)) // {"counter":1} +fmt.Println(result.Version) // 1 +fmt.Println(string(result.Document)) // {"counter":1} } ``` +## Request flow + +```mermaid +flowchart LR + Client[Client op with base version] --> Submit[Submit] + Submit --> Check[Load latest snapshot and history] + Check --> Rebase[Transform against missing ops when needed] + Rebase --> Apply[Apply transformed op] + Apply --> Persist[Store snapshot and version] + Persist --> Broadcast[Subscribe listeners receive commit event] +``` + ## API -- `CreateDocument(ctx, documentID, initial)`:创建文档,初始版本为 `0` -- `GetSnapshot(ctx, documentID)`:获取最新快照 -- `Submit(ctx, documentID, baseVersion, operation, source)`:提交并发操作 -- `Subscribe(ctx, documentID, buffer)`:订阅提交成功事件 +- `CreateDocument(ctx, documentID, initial)`: create a document at version `0` +- `GetSnapshot(ctx, documentID)`: get the latest snapshot +- `Submit(ctx, documentID, baseVersion, operation, source)`: submit an operation +- `Subscribe(ctx, documentID, buffer)`: subscribe to commit events + +## How this relates to ShareDB + +`jsonot/sharedb` is not a full ShareDB clone. It focuses on the backend primitives that are most useful when building your own Go collaboration service: + +- snapshot + version management +- submit by version +- OT rebase on the server +- event subscription + +That makes it a good choice when you want ShareDB-style ideas with a smaller, Go-native surface area. + +## Smallest runnable path + +1. `go get github.com/edocevol/jsonot/sharedb` +2. create a document with `CreateDocument` +3. submit an operation with `Submit` +4. read snapshots or subscribe to committed updates + +## FAQ + +### Is this a ShareDB alternative in Go? + +It can be, if your goal is to build a Go-native backend with ShareDB-style concepts rather than to adopt the full ShareDB feature set. + +### Does the server rebase old client operations? + +Yes. When `baseVersion < currentVersion`, the server transforms the submitted operation against missing history before applying it. + +### Can I use this in production? + +The in-memory store is primarily aimed at demos and small services. For production, you will usually add persistence, isolation, auth, and operational controls on top. + +## Notes + +- `Submit` requires `baseVersion` to be in `[0, currentVersion]` +- when `baseVersion < currentVersion`, the server transforms the submitted operation against the missing history range +- subscription delivery is non-blocking; slow consumers may drop events unless you add a durable queue upstream -## 说明 +## Related docs -- `Submit` 要求 `baseVersion` 在 `[0, currentVersion]` 范围内。 -- 当 `baseVersion < currentVersion` 时,服务端会把操作与缺失区间的历史操作做 OT 转换。 -- 订阅事件采用非阻塞投递,慢消费者可能丢事件;如需严格投递可在上层做持久队列。 +- [Root README](../README.md) +- [What is jsonot?](../docs/what-is-jsonot.md) +- [How to build collaborative editing in Go with JSON OT](../docs/go-json-ot-collaboration.md) +- [WebSocket collaboration demo](../examples/websocket/README.md)