@epicenter/sync 0.1.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/LICENSE +666 -0
- package/README.md +107 -0
- package/package.json +43 -0
- package/src/index.ts +39 -0
- package/src/protocol.test.ts +776 -0
- package/src/protocol.ts +612 -0
- package/src/rpc-errors.ts +89 -0
- package/tsconfig.json +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @epicenter/sync
|
|
2
|
+
|
|
3
|
+
`@epicenter/sync` is the wire-format package for Epicenter sync. It owns the binary framing for Yjs sync, awareness, sync-status, and peer-to-peer RPC messages so the transport layer can stay dumb. `@epicenter/workspace` and `apps/api` use it when they need to turn a `Y.Doc` change into bytes—or turn bytes back into something the app can reason about.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Inside this monorepo:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@epicenter/sync": "workspace:*"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This package has a peer dependency on `yjs`.
|
|
18
|
+
|
|
19
|
+
## Quick usage
|
|
20
|
+
|
|
21
|
+
The core flow is small on purpose: encode a sync message, send it over whatever transport you want, then decode or handle it on the other side.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import * as Y from 'yjs';
|
|
25
|
+
import {
|
|
26
|
+
MESSAGE_TYPE,
|
|
27
|
+
SYNC_MESSAGE_TYPE,
|
|
28
|
+
decodeMessageType,
|
|
29
|
+
decodeSyncMessage,
|
|
30
|
+
encodeSyncStep1,
|
|
31
|
+
handleSyncPayload,
|
|
32
|
+
} from '@epicenter/sync';
|
|
33
|
+
|
|
34
|
+
const doc = new Y.Doc();
|
|
35
|
+
doc.getMap('users').set('alice', { name: 'Alice', age: 30 });
|
|
36
|
+
|
|
37
|
+
const step1 = encodeSyncStep1({ doc });
|
|
38
|
+
const messageType = decodeMessageType(step1);
|
|
39
|
+
|
|
40
|
+
if (messageType === MESSAGE_TYPE.SYNC) {
|
|
41
|
+
const decoded = decodeSyncMessage(step1);
|
|
42
|
+
|
|
43
|
+
if (decoded.type === 'step1') {
|
|
44
|
+
const response = handleSyncPayload({
|
|
45
|
+
syncType: SYNC_MESSAGE_TYPE.STEP1,
|
|
46
|
+
payload: decoded.stateVector,
|
|
47
|
+
doc,
|
|
48
|
+
origin: null,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// send response over WebSocket, HTTP, BroadcastChannel, or anything else
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
That example is the same shape used in the package tests and in the API room bootstrap, where the server starts a connection with `encodeSyncStep1({ doc })`.
|
|
57
|
+
|
|
58
|
+
## Dumb server, separate transport
|
|
59
|
+
|
|
60
|
+
This package is strict about one boundary: it handles protocol framing, not connection management. That split is why the same message helpers work over WebSockets, one-shot HTTP sync, or any custom relay you want to write.
|
|
61
|
+
|
|
62
|
+
The design shows up in a few places:
|
|
63
|
+
|
|
64
|
+
- `encodeSyncStep1`, `encodeSyncStep2`, and `encodeSyncUpdate` only deal with Yjs payloads.
|
|
65
|
+
- `encodeSyncRequest` and `decodeSyncRequest` collapse the WebSocket handshake into a binary HTTP request/response format.
|
|
66
|
+
- `encodeSyncStatus` uses an echoed version counter for save-state UX; the server can relay the payload unchanged.
|
|
67
|
+
- RPC framing is separate from RPC behavior. The package defines request/response bytes and shared error variants, not the transport policy around retries or timeouts.
|
|
68
|
+
|
|
69
|
+
If you want lifecycle helpers for a WebSocket server, this package is the protocol layer under them—not the server itself.
|
|
70
|
+
|
|
71
|
+
## API overview
|
|
72
|
+
|
|
73
|
+
Main exports from `src/index.ts`:
|
|
74
|
+
|
|
75
|
+
- Message constants: `MESSAGE_TYPE`, `SYNC_MESSAGE_TYPE`, `RPC_TYPE`
|
|
76
|
+
- Sync encode/decode: `encodeSyncStep1`, `encodeSyncStep2`, `encodeSyncUpdate`, `decodeSyncMessage`, `handleSyncPayload`
|
|
77
|
+
- Awareness helpers: `encodeAwareness`, `encodeAwarenessStates`, `encodeQueryAwareness`
|
|
78
|
+
- HTTP sync helpers: `encodeSyncRequest`, `decodeSyncRequest`
|
|
79
|
+
- Save-status helpers: `encodeSyncStatus`, `decodeSyncStatus`, `stateVectorsEqual`
|
|
80
|
+
- RPC helpers: `encodeRpcRequest`, `encodeRpcResponse`, `decodeRpcMessage`, `decodeRpcPayload`
|
|
81
|
+
- RPC types and guards: `DecodedRpcMessage`, `RpcError`, `isRpcError`
|
|
82
|
+
|
|
83
|
+
The package exports pure functions. Feed them bytes and docs; they give you bytes or decoded shapes back.
|
|
84
|
+
|
|
85
|
+
## Relationship to other packages
|
|
86
|
+
|
|
87
|
+
`@epicenter/sync` sits below the rest of the sync stack.
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
apps/api durable-object rooms, websocket handling
|
|
91
|
+
│
|
|
92
|
+
@epicenter/workspace client sync extension, rpc helpers
|
|
93
|
+
│
|
|
94
|
+
@epicenter/sync protocol framing and shared rpc error types
|
|
95
|
+
│
|
|
96
|
+
yjs + y-protocols crdt state, awareness, update encoding
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
In practice:
|
|
100
|
+
|
|
101
|
+
- `apps/api` uses it to compute initial room messages and decode incoming sync traffic.
|
|
102
|
+
- `@epicenter/workspace` uses it in the client sync extension.
|
|
103
|
+
- Other packages do not need to know about the wire format unless they are implementing a transport.
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
AGPL-3.0. That matches the package manifest and the repository's split-license model for sync infrastructure.
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@epicenter/sync",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Sync protocol for Epicenter workspaces. Yjs-based encoding, WebSocket transport, and broadcast channel sync.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"local-first",
|
|
7
|
+
"Yjs",
|
|
8
|
+
"CRDT",
|
|
9
|
+
"sync",
|
|
10
|
+
"epicenter",
|
|
11
|
+
"WebSocket",
|
|
12
|
+
"offline-first",
|
|
13
|
+
"real-time"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/EpicenterHQ/epicenter.git",
|
|
18
|
+
"directory": "packages/sync"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://epicenter.so",
|
|
21
|
+
"main": "./src/index.ts",
|
|
22
|
+
"types": "./src/index.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": "./src/index.ts"
|
|
25
|
+
},
|
|
26
|
+
"license": "AGPL-3.0",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"lib0": "catalog:",
|
|
32
|
+
"wellcrafted": "catalog:",
|
|
33
|
+
"y-protocols": "catalog:"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"yjs": "catalog:"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/bun": "catalog:",
|
|
40
|
+
"typescript": "catalog:",
|
|
41
|
+
"yjs": "catalog:"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @epicenter/sync — Yjs Sync Protocol Primitives
|
|
3
|
+
*
|
|
4
|
+
* Encode/decode functions for the y-websocket wire protocol, plus
|
|
5
|
+
* RPC error variants shared by both server and client.
|
|
6
|
+
*
|
|
7
|
+
* For server-side WebSocket lifecycle handlers, import from
|
|
8
|
+
* `@epicenter/sync/server` instead.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Protocol (encode/decode for WS messages and HTTP sync requests)
|
|
12
|
+
export {
|
|
13
|
+
decodeMessageType,
|
|
14
|
+
decodeRpcMessage,
|
|
15
|
+
decodeRpcPayload,
|
|
16
|
+
type DecodedRpcMessage,
|
|
17
|
+
decodeSyncMessage,
|
|
18
|
+
decodeSyncRequest,
|
|
19
|
+
decodeSyncStatus,
|
|
20
|
+
encodeAwareness,
|
|
21
|
+
encodeAwarenessStates,
|
|
22
|
+
encodeQueryAwareness,
|
|
23
|
+
encodeRpcRequest,
|
|
24
|
+
encodeRpcResponse,
|
|
25
|
+
encodeSyncRequest,
|
|
26
|
+
encodeSyncStatus,
|
|
27
|
+
encodeSyncStep1,
|
|
28
|
+
encodeSyncStep2,
|
|
29
|
+
encodeSyncUpdate,
|
|
30
|
+
handleSyncPayload,
|
|
31
|
+
MESSAGE_TYPE,
|
|
32
|
+
RPC_TYPE,
|
|
33
|
+
SYNC_MESSAGE_TYPE,
|
|
34
|
+
type SyncMessageType,
|
|
35
|
+
stateVectorsEqual,
|
|
36
|
+
} from './protocol';
|
|
37
|
+
|
|
38
|
+
// RPC error variants and type guard (used by both server and client)
|
|
39
|
+
export { isRpcError, RpcError } from './rpc-errors';
|