@oh-just-another/collab 0.57.0 → 0.58.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/CHANGELOG.md +38 -0
- package/README.md +88 -21
- package/dist/.tsbuildinfo +1 -1
- package/dist/awareness.d.ts.map +1 -1
- package/dist/awareness.js +4 -6
- package/dist/awareness.js.map +1 -1
- package/dist/bind-awareness.js.map +1 -1
- package/dist/branch-doc.d.ts.map +1 -1
- package/dist/branch-doc.js +1 -1
- package/dist/branch-doc.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/diff-map.d.ts +9 -0
- package/dist/diff-map.d.ts.map +1 -0
- package/dist/diff-map.js +17 -0
- package/dist/diff-map.js.map +1 -0
- package/dist/encryption.d.ts +5 -5
- package/dist/encryption.d.ts.map +1 -1
- package/dist/encryption.js +1 -1
- package/dist/encryption.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mentions.d.ts.map +1 -1
- package/dist/mentions.js.map +1 -1
- package/dist/scene-doc.d.ts.map +1 -1
- package/dist/scene-doc.js +5 -14
- package/dist/scene-doc.js.map +1 -1
- package/dist/yjs-history.d.ts +6 -7
- package/dist/yjs-history.d.ts.map +1 -1
- package/dist/yjs-history.js +3 -17
- package/dist/yjs-history.js.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @oh-just-another/collab
|
|
2
2
|
|
|
3
|
+
## 0.58.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d1627f2: Renamed `YjsHistory` / `YjsHistoryOptions` to `CollabHistory` / `CollabHistoryOptions`, so the public name describes the role (collaborative undo scoped to the local client) rather than the backing implementation.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [b4b252b]
|
|
12
|
+
- Updated dependencies [d20d50a]
|
|
13
|
+
- Updated dependencies [0152ed6]
|
|
14
|
+
- Updated dependencies [938e7c8]
|
|
15
|
+
- Updated dependencies [9673846]
|
|
16
|
+
- Updated dependencies [f370dba]
|
|
17
|
+
- Updated dependencies [8f00738]
|
|
18
|
+
- Updated dependencies [da91d59]
|
|
19
|
+
- Updated dependencies [3152317]
|
|
20
|
+
- Updated dependencies [fc47ecc]
|
|
21
|
+
- Updated dependencies [8fc6b69]
|
|
22
|
+
- Updated dependencies [f98730f]
|
|
23
|
+
- Updated dependencies [904cc09]
|
|
24
|
+
- Updated dependencies [edde5d0]
|
|
25
|
+
- Updated dependencies [1c7cc6c]
|
|
26
|
+
- Updated dependencies [c5be6e5]
|
|
27
|
+
- @oh-just-another/state@0.59.0
|
|
28
|
+
- @oh-just-another/scene@0.59.0
|
|
29
|
+
- @oh-just-another/history@0.57.2
|
|
30
|
+
|
|
31
|
+
## 0.57.1
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- Updated dependencies [d1b96d9]
|
|
36
|
+
- @oh-just-another/scene@0.58.0
|
|
37
|
+
- @oh-just-another/state@0.58.0
|
|
38
|
+
- @oh-just-another/history@0.57.1
|
|
39
|
+
- @oh-just-another/serialization@0.57.1
|
|
40
|
+
|
|
3
41
|
## 0.57.0
|
|
4
42
|
|
|
5
43
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @oh-just-another/collab
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CRDT collaboration layer for `@oh-just-another/scene` documents.
|
|
4
|
+
|
|
5
|
+
Real-time multi-peer editing built on Yjs (L4): a CRDT-backed scene mirror, presence/awareness for cursors and selections, mentions, branch-and-merge, CRDT-aware undo, and client-side encryption. Pair with a `@oh-just-another/network` transport (BroadcastChannel for tabs, WebSocket for cross-machine).
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -8,29 +10,37 @@ Real-time collaboration on `@oh-just-another/scene` documents — Yjs CRDT bindi
|
|
|
8
10
|
pnpm add @oh-just-another/collab
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Peer deps: `yjs` and `y-protocols`.
|
|
12
14
|
|
|
13
15
|
## Quick start
|
|
14
16
|
|
|
15
|
-
Same-tab demo (e.g. for testing):
|
|
16
|
-
|
|
17
17
|
```ts
|
|
18
18
|
import * as Y from "yjs";
|
|
19
19
|
import { Editor } from "@oh-just-another/state";
|
|
20
20
|
import { BroadcastChannelTransport } from "@oh-just-another/network";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
SceneDoc,
|
|
23
|
+
CollabAwareness,
|
|
24
|
+
TransportProvider,
|
|
25
|
+
bindEditor,
|
|
26
|
+
bindAwareness,
|
|
27
|
+
} from "@oh-just-another/collab";
|
|
22
28
|
|
|
23
29
|
const doc = new Y.Doc();
|
|
24
30
|
const sceneDoc = new SceneDoc(doc);
|
|
25
31
|
const awareness = new CollabAwareness(doc);
|
|
26
|
-
awareness.updateLocal({ user: { id: "me", name: "Alice", color: "#1a73e8" } });
|
|
27
32
|
|
|
28
33
|
const transport = new BroadcastChannelTransport("room-foo");
|
|
29
34
|
const provider = new TransportProvider({ doc, transport, awareness: awareness.awareness });
|
|
30
35
|
|
|
31
|
-
const
|
|
36
|
+
const unbindScene = bindEditor(editor, sceneDoc);
|
|
37
|
+
const unbindPresence = bindAwareness(editor, awareness, {
|
|
38
|
+
user: { id: "me", name: "Alice", color: "#1a73e8" },
|
|
39
|
+
});
|
|
40
|
+
|
|
32
41
|
// …later
|
|
33
|
-
|
|
42
|
+
unbindPresence();
|
|
43
|
+
unbindScene();
|
|
34
44
|
provider.destroy();
|
|
35
45
|
awareness.destroy();
|
|
36
46
|
transport.close();
|
|
@@ -39,19 +49,76 @@ doc.destroy();
|
|
|
39
49
|
|
|
40
50
|
## API
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `TransportProvider({ doc, transport, awareness? })` | Bridge `Y.Doc` (+ awareness) onto a `Transport`. Multiplexes doc / awareness / sync-request over one channel.
|
|
52
|
+
### Document & transport
|
|
53
|
+
|
|
54
|
+
| Name | Purpose |
|
|
55
|
+
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| `SceneDoc(doc?)` | CRDT-backed scene mirror over a `Y.Doc`: `elements` / `links` / `layers` / `annotations` / `viewport` maps. `snapshot()`, `replace(scene)`, `applyDelta(...)`. |
|
|
57
|
+
| `bindEditor(editor, sceneDoc)` | Wire an `Editor` to a `SceneDoc`. Self-origin filtered. Returns an unbind function. |
|
|
58
|
+
| `TransportProvider({ doc, transport, awareness? })` | Bridge `Y.Doc` (+ optional awareness) onto a `Transport`. Multiplexes doc / awareness / sync-request over one channel via a 1-byte tag. |
|
|
59
|
+
| `TransportProviderOptions` | Options type for the provider constructor. |
|
|
60
|
+
|
|
61
|
+
### Awareness (presence)
|
|
62
|
+
|
|
63
|
+
| Name | Purpose |
|
|
64
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| `CollabAwareness(doc)` | Typed wrapper over `y-protocols/awareness`: `updateLocal`, `getPeers` / `getOthers` / `onPeers`, `clientId`. |
|
|
66
|
+
| `bindAwareness(editor, awareness, opts)` | Publish local user / cursor / selection into awareness (throttled) and paint peer cursors + selection halos back into the editor. Returns an unbind function. |
|
|
67
|
+
| `BindAwarenessOptions` | `{ user: PeerUser; cursorThrottleMs?: number }`. |
|
|
68
|
+
| `Peer`, `PeerUser` | Presence payload types (clientId, user identity, cursor, selection, free-form `extra`). |
|
|
69
|
+
|
|
70
|
+
### Mentions
|
|
71
|
+
|
|
72
|
+
| Name | Purpose |
|
|
73
|
+
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `extractMentions(body)` | Pull `@handle` tokens out of a comment body (lowercased, e-mail-safe). |
|
|
75
|
+
| `resolveMentions(mentions, peers)` | Match tokens against the peer list by display name; deduped `Peer[]`. |
|
|
76
|
+
| `notifyMention(Notification, opts)` | Fire a browser `Notification` when the local user is mentioned. No-op outside the browser / unless granted. |
|
|
77
|
+
|
|
78
|
+
### Branch & merge
|
|
79
|
+
|
|
80
|
+
| Name | Purpose |
|
|
81
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `BranchDoc(doc?)` | Named branches as Yjs subdocs of one parent doc. `ensureRoot`, `createBranch`, `sceneDocFor`, `mergeBranch`, `applyConflictResolution`, `commitMerge`. Implements `BranchMergeAPI`. |
|
|
83
|
+
| `BranchMergeAPI` | Editor-facing branch/merge contract (`branchToDoc`, `mergeBranch`, `applyConflictResolution`). |
|
|
84
|
+
| `BranchId` | `{ id, name, parentVersionId }`. |
|
|
85
|
+
| `MergeReport` | Three-way merge result: `applied`, `conflicts`, `autoMerged` scene. |
|
|
86
|
+
| `MergeConflict` | Per-element conflict: `base` / `source` / `target` values. |
|
|
87
|
+
| `ConflictChoice` | `"ours" \| "theirs" \| "both"`. |
|
|
88
|
+
| `ConflictResolution` | `{ elementId, choice }` — a user's resolution from the merge UI. |
|
|
89
|
+
|
|
90
|
+
### History
|
|
91
|
+
|
|
92
|
+
| Name | Purpose |
|
|
93
|
+
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| `CollabHistory(sceneDoc, opts?)` | CRDT-aware `HistoryProvider` backed by `Y.UndoManager`. Scopes undo/redo to the local client and emits coalesced patches to peers. Pass as `Editor`'s `history`. |
|
|
95
|
+
| `CollabHistoryOptions` | `{ captureLimit?: number; captureTimeout?: number }` (transaction coalescing window). |
|
|
96
|
+
|
|
97
|
+
### Encryption
|
|
98
|
+
|
|
99
|
+
| Name | Purpose |
|
|
100
|
+
| --------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
101
|
+
| `generateRoomKey()` | Mint `{ roomId, keyBase64, key }` for a new session via WebCrypto. |
|
|
102
|
+
| `importRoomKey(keyBase64)` | Re-import an AES key parsed from an invite URL fragment. Throws on bad length / base64. |
|
|
103
|
+
| `EncryptedTransport(inner, key, opts?)` | Wrap any `Transport` so payloads are AES-GCM encrypted/decrypted; a blind relay sees only ciphertext. |
|
|
104
|
+
| `RoomCredentials` | Result type of `generateRoomKey`. |
|
|
105
|
+
|
|
106
|
+
### Constants
|
|
107
|
+
|
|
108
|
+
| Name | Value | Meaning |
|
|
109
|
+
| --------------------- | ----- | ----------------------------------------------- |
|
|
110
|
+
| `ROOM_ID_BYTES` | `10` | Public room id length in bytes (20-hex string). |
|
|
111
|
+
| `ENCRYPTION_KEY_BITS` | `128` | AES-GCM session-key length in bits. |
|
|
112
|
+
| `ENCRYPTION_IV_BYTES` | `12` | AES-GCM initialisation-vector length in bytes. |
|
|
49
113
|
|
|
50
114
|
## Design notes
|
|
51
115
|
|
|
52
|
-
- **CRDT as the source of truth.** `SceneDoc.snapshot()` rebuilds a typed `Scene` from `Y.Map`s on demand.
|
|
53
|
-
- **`Y.Map<id,
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
116
|
+
- **CRDT as the source of truth.** `SceneDoc.snapshot()` rebuilds a typed `Scene` from the `Y.Map`s on demand. Rather than incrementally patching the local scene from individual Yjs events, `editor.loadScene(snapshot)` runs after each remote update — simpler and within budget for typical scene sizes.
|
|
117
|
+
- **`Y.Map<id, value>` per kind.** Concurrent edits to different ids merge under last-writer-wins. Same-id conflicts are also LWW today — fine for editor UX where one peer's drag stops the moment the other commits.
|
|
118
|
+
- **Self-origin filter.** `bindEditor` and `CollabHistory` tag their own writes so the `update` listener doesn't ricochet through `loadScene`.
|
|
119
|
+
- **Local-scoped undo.** `CollabHistory` tracks only the local origin in `Y.UndoManager`, so undo affects this client's changes — not a peer's. The rewind diff is emitted to peers as one coalesced patch through the same `applyDelta` path `bindEditor` uses.
|
|
120
|
+
- **Branches as subdocs.** Each branch is an independent Yjs subdoc that loads/unloads on demand and replicates over the same provider. Merges are three-way against the source branch's stored ancestor; conflicts wait for host resolution.
|
|
121
|
+
- **Blind-relay encryption.** The AES key lives in the URL fragment, which the browser never transmits — including on the WebSocket upgrade — so the relay routes by `roomId` without ever seeing plaintext. Random IV per frame; tampered frames fail the auth tag and are dropped.
|
|
122
|
+
- **Single-tag wire format.** `TransportProvider` prepends one byte per message: `0x00` doc / `0x01` awareness / `0x02` sync-request — multiplexing over a single binary channel without a separate signalling layer.
|
|
123
|
+
|
|
124
|
+
See [ohjustanother.site](https://ohjustanother.site) for the full docs.
|