@cocreate/crdt 1.32.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +1 -1
- package/.github/workflows/automated.yml +18 -7
- package/CHANGELOG.md +661 -634
- package/CONTRIBUTING.md +6 -6
- package/README.md +180 -92
- package/demo/index.html +2 -2
- package/docs/index.html +4 -4
- package/docs/operations.md +159 -0
- package/package.json +11 -10
- package/src/index.js +765 -716
- package/src/runs.js +46 -0
- package/src/selection.js +53 -0
- package/src/sequence.js +160 -0
- package/test/crdt.test.cjs +584 -0
- package/test/selection-adapters.test.cjs +77 -0
package/CONTRIBUTING.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# Contributing to CoCreate-crdt
|
|
2
2
|
|
|
3
|
-
This project is work of [many contributors](https://github.com/
|
|
4
|
-
You're encouraged to submit [pull requests](https://github.com/
|
|
5
|
-
[propose features and discuss issues](https://github.com/
|
|
3
|
+
This project is work of [many contributors](https://github.com/CoCreateJS/crdt/graphs/contributors).
|
|
4
|
+
You're encouraged to submit [pull requests](https://github.com/CoCreateJS/crdt/pulls),
|
|
5
|
+
[propose features and discuss issues](https://github.com/CoCreateJS/crdt/issues).
|
|
6
6
|
|
|
7
7
|
In the examples below, substitute your Github username for `contributor` in URLs.
|
|
8
8
|
|
|
9
9
|
## Fork the Project
|
|
10
10
|
|
|
11
|
-
Fork the [project on Github](https://github.com/
|
|
11
|
+
Fork the [project on Github](https://github.com/CoCreateJS/crdt) and check out your copy.
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
git crdt https://github.com/contributor/CoCreate-crdt.git
|
|
15
15
|
cd CoCreate-crdt
|
|
16
|
-
git remote add upstream https://github.com/
|
|
16
|
+
git remote add upstream https://github.com/CoCreateJS/crdt.git
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Create a Topic Branch
|
|
@@ -75,7 +75,7 @@ git push origin my-feature-branch
|
|
|
75
75
|
|
|
76
76
|
## Make a Pull Request
|
|
77
77
|
|
|
78
|
-
Go to [https://github.com/
|
|
78
|
+
Go to [https://github.com/CoCreateJS/crdt](https://github.com/CoCreateJS/crdt) and select your feature branch.
|
|
79
79
|
Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days.
|
|
80
80
|
|
|
81
81
|
## Rebase
|
package/README.md
CHANGED
|
@@ -1,92 +1,180 @@
|
|
|
1
|
-
# CoCreate-crdt
|
|
2
|
-
|
|
3
|
-
CoCreate crdt Take it for a spin in our [playground!](https://
|
|
4
|
-
|
|
5
|
-

|
|
11
|
-

|
|
12
|
-
|
|
13
|
-

|
|
44
|
-
- [Announcements](#announcements)
|
|
45
|
-
- [Roadmap](#roadmap)
|
|
46
|
-
- [How to Contribute](#how-to-contribute)
|
|
47
|
-
- [About](#about)
|
|
48
|
-
- [License](#license)
|
|
49
|
-
|
|
50
|
-
<a name="announcements"></a>
|
|
51
|
-
|
|
52
|
-
# Announcements
|
|
53
|
-
|
|
54
|
-
All updates to this library are documented in our [CHANGELOG](https://github.com/
|
|
55
|
-
|
|
56
|
-
<a name="roadmap"></a>
|
|
57
|
-
|
|
58
|
-
# Roadmap
|
|
59
|
-
|
|
60
|
-
If you are interested in the future direction of this project, please take a look at our open [issues](https://github.com/
|
|
61
|
-
|
|
62
|
-
<a name="about"></a>
|
|
63
|
-
|
|
64
|
-
# About
|
|
65
|
-
|
|
66
|
-
CoCreate-crdt is guided and supported by the CoCreate Developer Experience Team.
|
|
67
|
-
|
|
68
|
-
Please Email the Developer Experience Team [here](mailto:develop@
|
|
69
|
-
|
|
70
|
-
CoCreate-crdt is maintained and funded by CoCreate. The names and logos for CoCreate are trademarks of CoCreate, LLC.
|
|
71
|
-
|
|
72
|
-
<a name="contribute"></a>
|
|
73
|
-
|
|
74
|
-
# How to Contribute
|
|
75
|
-
|
|
76
|
-
We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](https://github.com/
|
|
77
|
-
|
|
78
|
-
We want this library to be community-driven, and CoCreate led. We need your help to realize this goal. To help make sure we are building the right things in the right order, we ask that you create [issues](https://github.com/
|
|
79
|
-
|
|
80
|
-
We appreciate your continued support, thank you!
|
|
81
|
-
|
|
82
|
-
<a name="license"></a>
|
|
83
|
-
|
|
84
|
-
# License
|
|
85
|
-
|
|
86
|
-
This software is dual-licensed under the GNU Affero General Public License version 3 (AGPLv3) and a commercial license.
|
|
87
|
-
|
|
88
|
-
- **Open Source Use**: For open-source projects and non-commercial use, this software is available under the AGPLv3. The AGPLv3 allows you to freely use, modify, and distribute this software, provided that all modifications and derivative works are also licensed under the AGPLv3. For the full license text, see the [LICENSE file](https://github.com/
|
|
89
|
-
|
|
90
|
-
- **Commercial Use**: For-profit companies and individuals intending to use this software for commercial purposes must obtain a commercial license. The commercial license is available when you sign up for an API key on our [website](https://
|
|
91
|
-
|
|
92
|
-
If you have not purchased a commercial license and intend to use this software for commercial purposes, you are required to sign up for an API key on our website.
|
|
1
|
+
# CoCreate-crdt
|
|
2
|
+
|
|
3
|
+
CoCreate crdt Take it for a spin in our [playground!](https://CoCreateJS.com/docs/crdt)
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
## [Docs & Demo](https://CoCreateJS.com/docs/crdt)
|
|
16
|
+
|
|
17
|
+
For a complete guide and working demo refer to the [doumentation](https://CoCreateJS.com/docs/crdt)
|
|
18
|
+
|
|
19
|
+
## CDN
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<script src="https://cdn.CoCreateJS.com/crdt/latest/CoCreate-crdt.min.js"></script>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script src="https://cdn.CoCreateJS.com/crdt/latest/CoCreate-crdt.min.css"></script>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## NPM
|
|
30
|
+
|
|
31
|
+
```shell
|
|
32
|
+
$ npm i @cocreate/crdt
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## yarn
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
$ yarn install @cocreate/crdt
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
# Table of Contents
|
|
42
|
+
|
|
43
|
+
- [Table of Contents](#table-of-contents)
|
|
44
|
+
- [Announcements](#announcements)
|
|
45
|
+
- [Roadmap](#roadmap)
|
|
46
|
+
- [How to Contribute](#how-to-contribute)
|
|
47
|
+
- [About](#about)
|
|
48
|
+
- [License](#license)
|
|
49
|
+
|
|
50
|
+
<a name="announcements"></a>
|
|
51
|
+
|
|
52
|
+
# Announcements
|
|
53
|
+
|
|
54
|
+
All updates to this library are documented in our [CHANGELOG](https://github.com/CoCreateJS/crdt/blob/master/CHANGELOG.md) and [releases](https://github.com/CoCreateJS/crdt/releases). You may also subscribe to email for releases and breaking changes.
|
|
55
|
+
|
|
56
|
+
<a name="roadmap"></a>
|
|
57
|
+
|
|
58
|
+
# Roadmap
|
|
59
|
+
|
|
60
|
+
If you are interested in the future direction of this project, please take a look at our open [issues](https://github.com/CoCreateJS/crdt/issues) and [pull requests](https://github.com/CoCreateJS/crdt/pulls). We would love to hear your feedback.
|
|
61
|
+
|
|
62
|
+
<a name="about"></a>
|
|
63
|
+
|
|
64
|
+
# About
|
|
65
|
+
|
|
66
|
+
CoCreate-crdt is guided and supported by the CoCreate Developer Experience Team.
|
|
67
|
+
|
|
68
|
+
Please Email the Developer Experience Team [here](mailto:develop@CoCreateJS.com) in case of any queries.
|
|
69
|
+
|
|
70
|
+
CoCreate-crdt is maintained and funded by CoCreate. The names and logos for CoCreate are trademarks of CoCreate, LLC.
|
|
71
|
+
|
|
72
|
+
<a name="contribute"></a>
|
|
73
|
+
|
|
74
|
+
# How to Contribute
|
|
75
|
+
|
|
76
|
+
We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](https://github.com/CoCreateJS/crdt/blob/master/CONTRIBUTING.md) guide for details.
|
|
77
|
+
|
|
78
|
+
We want this library to be community-driven, and CoCreate led. We need your help to realize this goal. To help make sure we are building the right things in the right order, we ask that you create [issues](https://github.com/CoCreateJS/crdt/issues) and [pull requests](https://github.com/CoCreateJS/crdt/pulls) or merely upvote or comment on existing issues or pull requests.
|
|
79
|
+
|
|
80
|
+
We appreciate your continued support, thank you!
|
|
81
|
+
|
|
82
|
+
<a name="license"></a>
|
|
83
|
+
|
|
84
|
+
# License
|
|
85
|
+
|
|
86
|
+
This software is dual-licensed under the GNU Affero General Public License version 3 (AGPLv3) and a commercial license.
|
|
87
|
+
|
|
88
|
+
- **Open Source Use**: For open-source projects and non-commercial use, this software is available under the AGPLv3. The AGPLv3 allows you to freely use, modify, and distribute this software, provided that all modifications and derivative works are also licensed under the AGPLv3. For the full license text, see the [LICENSE file](https://github.com/CoCreateJS/crdt/blob/master/LICENSE).
|
|
89
|
+
|
|
90
|
+
- **Commercial Use**: For-profit companies and individuals intending to use this software for commercial purposes must obtain a commercial license. The commercial license is available when you sign up for an API key on our [website](https://CoCreateJS.com). This license permits proprietary use and modification of the software without the copyleft requirements of the AGPLv3. It is ideal for integrating this software into proprietary commercial products and applications.
|
|
91
|
+
|
|
92
|
+
If you have not purchased a commercial license and intend to use this software for commercial purposes, you are required to sign up for an API key on our website.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Public API
|
|
96
|
+
|
|
97
|
+
Everyday text operations are `getText`, `updateText`, `replaceText`, `undoText`,
|
|
98
|
+
and `redoText`. Each receives an object containing `array`, `object`, and `key`.
|
|
99
|
+
`updateText` additionally accepts `start`, `length`, and `value`; `replaceText`
|
|
100
|
+
accepts the complete replacement `value`. `getText` initializes field state when
|
|
101
|
+
needed, so a separate initialization method is unnecessary.
|
|
102
|
+
|
|
103
|
+
Editor integration also uses `updateSelection`, `getSelection`, `getSelections`,
|
|
104
|
+
and `endRun` for presence, selection restoration, and typing boundaries.
|
|
105
|
+
|
|
106
|
+
The mutable registry, identifier construction, version reconstruction, and
|
|
107
|
+
snapshot maintenance stay internal. They are not part of the default public API.
|
|
108
|
+
|
|
109
|
+
## Identity-based operation tracking
|
|
110
|
+
|
|
111
|
+
Run `npm test` for isolated operation-engine tests with mocked transport and
|
|
112
|
+
storage. New edits use operation format 2. The public `start`, `length`, and
|
|
113
|
+
`value` interface still accepts JavaScript UTF-16 offsets, but each outgoing edit
|
|
114
|
+
also captures:
|
|
115
|
+
|
|
116
|
+
- Stable identities for the exact characters being deleted (`targets`).
|
|
117
|
+
- The deleted text (`removedValue`).
|
|
118
|
+
- Left/right neighbor identities and neighboring character values.
|
|
119
|
+
- Stable identities for inserted characters, derived from operation ID and offset.
|
|
120
|
+
|
|
121
|
+
Replay uses an anchored sequence with retained deletion markers. Two clients
|
|
122
|
+
that both delete the same character therefore delete that identity twice, rather
|
|
123
|
+
than deleting the next surviving character. Character values and neighboring
|
|
124
|
+
text provide context, but are not used to guess which repeated character an
|
|
125
|
+
operation meant. Concurrent insertion runs have deterministic ordering and remain
|
|
126
|
+
contiguous. Operations whose anchor has not arrived remain pending in the sequence
|
|
127
|
+
until that anchor arrives.
|
|
128
|
+
|
|
129
|
+
Undo deletes only the characters created by its edit and removes only that edit's
|
|
130
|
+
deletion markers. Another user's deletions and insertions remain intact. Redo
|
|
131
|
+
inverts the undo operation. Text update events include the canonical `string`, a
|
|
132
|
+
splice against the current visible text, and the original identity `operation`.
|
|
133
|
+
Existing input/textarea consumers can therefore continue applying visible splices.
|
|
134
|
+
|
|
135
|
+
Snapshots retain character identities, deleted anchors, deletion markers and
|
|
136
|
+
processed operation IDs. Late identity edits can still target retained anchors.
|
|
137
|
+
Snapshot persistence appends a checkpoint rather than replacing the stored log,
|
|
138
|
+
so concurrent appends are not overwritten. Physical history/tombstone garbage
|
|
139
|
+
collection is not implemented; the server history remains append-only.
|
|
140
|
+
|
|
141
|
+
Simultaneous readers of the same uninitialized database field derive identical
|
|
142
|
+
baseline IDs from the field scope and text, using SHA-256 through Web Crypto.
|
|
143
|
+
Different initial field values still require application/server reconciliation.
|
|
144
|
+
|
|
145
|
+
### Compatibility and limits
|
|
146
|
+
|
|
147
|
+
All actively collaborating clients must be updated together to use format 2.
|
|
148
|
+
Historical positional logs remain readable and are assigned deterministic
|
|
149
|
+
identities during replay. The original author intent of already recorded,
|
|
150
|
+
concurrent positional operations cannot be recovered after the fact. Old clients
|
|
151
|
+
will not correctly interpret identity-based deletes, restores, or snapshots.
|
|
152
|
+
|
|
153
|
+
Existing storage keys concatenate array/object/key; changing that format still
|
|
154
|
+
needs a migration to remove potential key collisions. Identity metadata is
|
|
155
|
+
retained for correctness and will grow with editing history. Large-document
|
|
156
|
+
performance and safe physical garbage collection need further work.
|
|
157
|
+
|
|
158
|
+
The tests cover concurrent deletes, repeated characters, concurrent insert/delete
|
|
159
|
+
and replacement, selective undo, out-of-order and duplicate delivery, bootstrap,
|
|
160
|
+
snapshot/reload, Unicode offsets and multi-writer convergence. They do not prove
|
|
161
|
+
every possible schedule or test production transport, browser rendering, cursor
|
|
162
|
+
mapping, authorization or persistence failure recovery. These changes do not
|
|
163
|
+
modify CoCreate-text's DOM rendering or wire Markdown to CRDT automatically.
|
|
164
|
+
|
|
165
|
+
## Typing runs
|
|
166
|
+
|
|
167
|
+
See [the typing run contract](docs/operations.md). Concurrent runs at the same
|
|
168
|
+
anchor are ordered by persisted `clientId`, then by their first operation's ID
|
|
169
|
+
(`runId`) for runs from the same browser. No user ID, server election or new random
|
|
170
|
+
run identifier participates. Insertion anchors preserve deliberate placement in
|
|
171
|
+
already-observed content.
|
|
172
|
+
|
|
173
|
+
Consecutive insertion-only operations continue a run when the next position and
|
|
174
|
+
last character anchor both match. CRDT automatically ends it on a position break
|
|
175
|
+
or an incoming edit that shifts or interrupts the run. `endRun({ array, object,
|
|
176
|
+
key })` is optional for semantic boundaries without text operations, such as blur
|
|
177
|
+
or submit. The optional boolean flags `runBreak` and
|
|
178
|
+
`runEnd` establish boundaries on a particular edit. Run metadata persists with
|
|
179
|
+
operations and snapshots. Local editing requires Socket Client to have initialized
|
|
180
|
+
its `clientId` and `frameId`.
|
package/demo/index.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
'602c4dadb7fb6c9da35ca126', name: 'html', position: '0', length: 2 })
|
|
30
30
|
|
|
31
31
|
<!--<script src="../dist/CoCreate-crdt.js"></script>-->
|
|
32
|
-
<!-- <script src="https://
|
|
33
|
-
<script src="https://
|
|
32
|
+
<!-- <script src="https://CoCreateJS.com/dist/CoCreate.js" ></script> -->
|
|
33
|
+
<script src="https://CoCreateJS.com/dist/CoCreate.js"></script>
|
|
34
34
|
</body>
|
|
35
35
|
</html>
|
package/docs/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
rel="icon"
|
|
10
10
|
type="image/png"
|
|
11
11
|
sizes="32x32"
|
|
12
|
-
href="https://
|
|
12
|
+
href="https://CoCreateJS.com/images/favicon.ico" />
|
|
13
13
|
<meta
|
|
14
14
|
name="description"
|
|
15
15
|
content="A simple HTML5 and pure javascript component. Easy configuration using HTML5 attributes or Javscript api and highly styleable." />
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
share-width="500"
|
|
60
60
|
share-media="https://via.placeholder.com/300/09f/fff.png">
|
|
61
61
|
<a
|
|
62
|
-
href="https://github.com/
|
|
62
|
+
href="https://github.com/CoCreateJS/CoCreate-crdt"
|
|
63
63
|
target="_blank"
|
|
64
64
|
class="margin-right:15px"
|
|
65
65
|
><i src="/assets/svg/github.svg"></i
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
<pre
|
|
104
104
|
class="margin-top:15px"><code class="language-javascript">npm install cocreate-crdt</code></pre>
|
|
105
105
|
<p class="padding:10px_0px">Or you can use cdn link:</p>
|
|
106
|
-
<pre><code class="language-javascript">https://cdn.
|
|
106
|
+
<pre><code class="language-javascript">https://cdn.CoCreateJS.com/js/CoCreate-crdt.min.js</code></pre>
|
|
107
107
|
|
|
108
108
|
<h2
|
|
109
109
|
class="border-bottom:1px_solid_lightgrey margin-top:20px padding:5px_0px">
|
|
@@ -247,6 +247,6 @@
|
|
|
247
247
|
</div>
|
|
248
248
|
</main>
|
|
249
249
|
|
|
250
|
-
<script src="https://
|
|
250
|
+
<script src="https://CoCreateJS.com/dist/CoCreate.js"></script>
|
|
251
251
|
</body>
|
|
252
252
|
</html>
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Typing run contract (version 1)
|
|
2
|
+
|
|
3
|
+
This contract extends identity operation format 2. It requires no server-assigned
|
|
4
|
+
rank, extra browser identity, new database collection, or randomly generated run
|
|
5
|
+
identifier. All collaborating clients must use the same ordering implementation.
|
|
6
|
+
|
|
7
|
+
## Identity and scope
|
|
8
|
+
|
|
9
|
+
The operation's `clientId` is the persisted browser identity supplied by the
|
|
10
|
+
initialized Socket Client. `frameId` identifies its existing page/frame context.
|
|
11
|
+
`user_id` remains useful author metadata but does not participate in ordering.
|
|
12
|
+
The local API rejects ordinary edits before client/frame identity is available;
|
|
13
|
+
it does not invent a fallback browser ID. Scope remains `array/object/key`.
|
|
14
|
+
|
|
15
|
+
## Operation fields
|
|
16
|
+
|
|
17
|
+
| Field | Meaning |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `id` | Unique operation ID; unchanged existing operation contract |
|
|
20
|
+
| `clientId` | Persisted Socket Client browser ID |
|
|
21
|
+
| `frameId` | Existing Socket Client frame ID |
|
|
22
|
+
| `runVersion` | `1` for this grouping contract |
|
|
23
|
+
| `runId` | ID of the first operation in this run |
|
|
24
|
+
| `runIndex` | Zero-based operation number within the run, not a character offset |
|
|
25
|
+
| `runLeft`, `runRight` | Character identities surrounding the initial run insertion |
|
|
26
|
+
| `left`, `right` | Character identities surrounding this individual operation |
|
|
27
|
+
|
|
28
|
+
Each inserted character still has its existing operation-ID/UTF-16-offset identity.
|
|
29
|
+
Runs are grouping metadata; they never replace character identities or deletion
|
|
30
|
+
markers. The server broadcasts and persists the fields with the operation. No
|
|
31
|
+
arrival-order lookup is needed to infer membership during replay.
|
|
32
|
+
|
|
33
|
+
## Run boundaries
|
|
34
|
+
|
|
35
|
+
CRDT manages run boundaries from the edits it receives. Local tracking is scoped
|
|
36
|
+
to the field, client and frame. An insertion-only operation continues the previous
|
|
37
|
+
run when both its numeric `start` equals the previous insertion's end and its
|
|
38
|
+
left anchor equals the last character inserted by that run. Otherwise it starts
|
|
39
|
+
a new run using its own operation ID. Replacements start a new run; deletes, undo
|
|
40
|
+
and redo close the active run. A whole-value `replaceText` is a standalone run.
|
|
41
|
+
|
|
42
|
+
On a foreign edit, CRDT checks whether the expected next insertion position has
|
|
43
|
+
shifted, whether the last run character was removed, or whether the edit directly
|
|
44
|
+
inserts into or modifies the run. Any of these closes the active run, so the next
|
|
45
|
+
local insertion starts anew. Concurrent insertion elsewhere that leaves the run
|
|
46
|
+
and expected position intact does not end it.
|
|
47
|
+
|
|
48
|
+
No editor call is required to detect a changed insertion position or an incoming
|
|
49
|
+
edit that breaks sequential positioning. `endRun({ array, object, key })` remains
|
|
50
|
+
an optional semantic boundary for events with no text operation, such as blur,
|
|
51
|
+
submit or an explicit move away and back to the identical position. CRDT cannot
|
|
52
|
+
observe a cursor move with no accompanying edit. Boolean `runBreak: true` and
|
|
53
|
+
`runEnd: true` can also establish boundaries on an edit when the integration knows
|
|
54
|
+
its intent (for example a standalone paste). There is no idle timeout or heuristic
|
|
55
|
+
that assumes every multi-character input is a paste.
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
await crdt.updateText({ array, object, key, realtime: true, start: 0, value: 'H' });
|
|
59
|
+
await crdt.updateText({ array, object, key, realtime: true, start: 1, value: 'i' });
|
|
60
|
+
// These operations share the first operation's ID as runId, with indexes 0 and 1.
|
|
61
|
+
crdt.endRun({ array, object, key });
|
|
62
|
+
await crdt.updateText({ array, object, key, realtime: true, start: 2, value: '!' });
|
|
63
|
+
// This operation starts a new run with its own ID.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Only `realtime: true` publishes an operation to other elements, the socket, or
|
|
67
|
+
storage. A private editor can provide a stable, editor-specific `scope` and
|
|
68
|
+
`initialValue` to keep its history in memory. It can also provide the originating
|
|
69
|
+
`element`; CRDT returns the update event with that element as its sole target.
|
|
70
|
+
The element reference is excluded from the history log and socket packet. Undo
|
|
71
|
+
and redo use the same scope. A Save action can publish the completed string with
|
|
72
|
+
a separate realtime `replaceText` call against the shared array/object/key.
|
|
73
|
+
|
|
74
|
+
## Ordering
|
|
75
|
+
|
|
76
|
+
Character anchors determine document placement. For concurrent insertion branches
|
|
77
|
+
at the same anchor, compare `clientId` as case-sensitive code-unit strings,
|
|
78
|
+
ascending. For separate runs from the same browser, compare `runId` ascending.
|
|
79
|
+
Existing operation IDs resolve remaining ties. The rank is a comparison rule,
|
|
80
|
+
not an integer stored or recalculated from the connected participant list.
|
|
81
|
+
|
|
82
|
+
A continuation stays inside its run's branch. Explicit placement before content
|
|
83
|
+
an author already observed takes precedence over concurrent priority: a deliberate
|
|
84
|
+
insertion inside an existing run is allowed to split that run. Right-anchor
|
|
85
|
+
constraints establish that placement before concurrent branch ordering is applied.
|
|
86
|
+
|
|
87
|
+
Historical operations without run metadata retain their operation-ID ordering.
|
|
88
|
+
Snapshots retain the node client/run metadata and insertion boundaries, so replay
|
|
89
|
+
uses the same rule after compaction. Clients reconnecting do not rewrite previous
|
|
90
|
+
run membership; a new frame starts new local run tracking.
|
|
91
|
+
|
|
92
|
+
## Validation coverage
|
|
93
|
+
|
|
94
|
+
Tests cover four concurrent character-by-character runs, reversed delivery,
|
|
95
|
+
multiple frames sharing one client, explicit/inferred boundaries, deliberate
|
|
96
|
+
insertions inside existing content and the existing identity/snapshot regressions.
|
|
97
|
+
Changed write positions and foreign interruptions are detected inside CRDT.
|
|
98
|
+
Optional non-writing editor actions can explicitly end a run; network and browser
|
|
99
|
+
integration testing remain separate work.
|
|
100
|
+
|
|
101
|
+
## Selections and shared presence
|
|
102
|
+
|
|
103
|
+
Text and selection use the same `crdt` socket method and field scope. An optional
|
|
104
|
+
`selection` on `updateText` describes the **resulting** source selection after
|
|
105
|
+
that edit, with directional UTF-16 offsets:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
await crdt.updateText({
|
|
109
|
+
array, object, key, realtime: true, start: 0, value: 'hello',
|
|
110
|
+
selection: { anchor: 5, head: 2 }
|
|
111
|
+
});
|
|
112
|
+
await crdt.updateSelection({ array, object, key, realtime: true, selection: { anchor: 2, head: 2 } });
|
|
113
|
+
await crdt.updateSelection({ array, object, key, realtime: true, selection: null }); // blur/hide
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Each endpoint becomes `{ left, right, affinity }`, where left and right are
|
|
117
|
+
stable neighboring character IDs (or null at a boundary). Default affinity is
|
|
118
|
+
`left`: a cursor stays immediately after its left neighbor when another writer
|
|
119
|
+
inserts into the same gap. Callers may also supply these endpoint references
|
|
120
|
+
with `affinity: 'right'` to stay before the right neighbor. Deleted neighbors
|
|
121
|
+
remain resolvable through tombstones. Unknown references wait until their text
|
|
122
|
+
arrives. Anchor/head preserve backward selections; they are not normalized away.
|
|
123
|
+
|
|
124
|
+
Editor adapters can supply a synchronous local `captureSelection()` callback
|
|
125
|
+
instead of numeric `selection`. CRDT calls it after dispatching the text update
|
|
126
|
+
so it reads the actual resulting editor selection. The callback never travels
|
|
127
|
+
on the socket or enters history. A callback failure does not stop broadcasting
|
|
128
|
+
or saving an already-applied text edit.
|
|
129
|
+
|
|
130
|
+
A text operation and its selection share one packet and history entry.
|
|
131
|
+
`updateSelection` sends `type: 'selection'` without a text operation ID, document
|
|
132
|
+
save, or undo entry. A changed position ends the local typing run; duplicate
|
|
133
|
+
positions are suppressed and metadata-only updates preserve the run. A per-frame
|
|
134
|
+
`selectionClock` prevents older packets from replacing a newer selection.
|
|
135
|
+
Selection-only updates are transient and are not restored from document history.
|
|
136
|
+
Optional `presence` carries display metadata such as userName and cursor colors.
|
|
137
|
+
|
|
138
|
+
`cocreate-crdt-selection` exposes `selection` references and a `resolved`
|
|
139
|
+
`{ anchor, head, start, end }` for the current source text (null for blur).
|
|
140
|
+
Known selections are resolved again after subsequent edits. Editor owns browser
|
|
141
|
+
selection capture and source-to-DOM mapping; its `cursors.js` renders remote
|
|
142
|
+
presence. CRDT owns transport for both text and presence. Editor surfaces do not
|
|
143
|
+
need the legacy Cursors package or Selection's former HTML-string position APIs.
|
|
144
|
+
|
|
145
|
+
For loaded fields, `updateSelection` anchors and caches the selection synchronously
|
|
146
|
+
before returning its Promise. `crdt.getSelection({array, object, key})` synchronously
|
|
147
|
+
returns the current local `{anchor, head, start, end}`, `undefined` before capture
|
|
148
|
+
or while references are unresolved, and `null` after clearing. Optional `clientId`
|
|
149
|
+
and `frameId` select another participant. Editor can restore the cached local
|
|
150
|
+
selection after a remote edit without rereading its source position from the DOM.
|
|
151
|
+
|
|
152
|
+
`crdt.getSelections({array, object, key})` returns current resolved presence entries
|
|
153
|
+
in the same shape as the selection event, including cleared entries. This lets a
|
|
154
|
+
newly bound editor display presence already known to CRDT. Returned anchor and
|
|
155
|
+
metadata objects are copied. One offset index per replayed sequence serves all
|
|
156
|
+
participants, including tombstone anchors.
|
|
157
|
+
|
|
158
|
+
Incoming presence requires a valid participant/frame and a positive safe-integer
|
|
159
|
+
clock. Invalid presence is ignored without dropping an accompanying text operation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/crdt",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CoCreate crdt",
|
|
5
5
|
"author": "CoCreate LLC",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -10,28 +10,29 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "webpack --config webpack.config.js",
|
|
12
12
|
"build": "webpack --mode=production --config webpack.config.js",
|
|
13
|
-
"dev": "webpack --config webpack.config.js --watch"
|
|
13
|
+
"dev": "webpack --config webpack.config.js --watch",
|
|
14
|
+
"test": "node --test test/*.test.cjs"
|
|
14
15
|
},
|
|
15
16
|
"repository": {
|
|
16
17
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/
|
|
18
|
+
"url": "git+https://github.com/CoCreateJS/crdt.git"
|
|
18
19
|
},
|
|
19
20
|
"bugs": {
|
|
20
|
-
"url": "https://github.com/
|
|
21
|
+
"url": "https://github.com/CoCreateJS/crdt/issues"
|
|
21
22
|
},
|
|
22
|
-
"homepage": "https://
|
|
23
|
+
"homepage": "https://CoCreateJS.com/docs/crdt",
|
|
23
24
|
"funding": {
|
|
24
25
|
"type": "GitHub Sponsors ❤",
|
|
25
|
-
"url": "https://github.com/sponsors/
|
|
26
|
+
"url": "https://github.com/sponsors/CoCreateJS"
|
|
26
27
|
},
|
|
27
28
|
"main": "./src/index.js",
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@cocreate/crud-client": "^1.
|
|
30
|
-
"@cocreate/local-storage": "^1.
|
|
31
|
-
"@cocreate/utils": "^1.
|
|
30
|
+
"@cocreate/crud-client": "^1.37.0",
|
|
31
|
+
"@cocreate/local-storage": "^1.20.0",
|
|
32
|
+
"@cocreate/utils": "^1.54.0"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@cocreate/webpack": "^1.
|
|
35
|
+
"@cocreate/webpack": "^1.9.0"
|
|
35
36
|
},
|
|
36
37
|
"allowScripts": {
|
|
37
38
|
"@cocreate/actions@1.21.4": true,
|