@liveblocks/yjs 1.1.0-yjs4 → 1.1.1-internal2

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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  # `@liveblocks/yjs`
11
11
 
12
- Provides YJS integration to effortlessly back your YJS apps with Liveblocks
12
+ Provides Yjs integration to effortlessly back your Yjs apps with Liveblocks
13
13
 
14
14
  ## Installation
15
15
 
@@ -0,0 +1,46 @@
1
+ import { Room, JsonObject, LsonObject, BaseUserMeta, Json } from '@liveblocks/client';
2
+ import { Observable } from 'lib0/observable';
3
+ import * as Y from 'yjs';
4
+
5
+ declare type MetaClientState = {
6
+ clock: number;
7
+ lastUpdated: number;
8
+ };
9
+ /**
10
+ * This class will store Yjs awareness in Liveblock's presence under the __yjs key
11
+ * IMPORTANT: The Yjs awareness protocol uses ydoc.clientId to reference users
12
+ * to their respective documents. To avoid mapping Yjs clientIds to liveblock's connectionId,
13
+ * we simply set the clientId of the doc to the connectionId. Then no further mapping is required
14
+ */
15
+ declare class Awareness extends Observable<unknown> {
16
+ private room;
17
+ doc: Y.Doc;
18
+ clientID: number;
19
+ states: Map<number, unknown>;
20
+ meta: Map<number, MetaClientState>;
21
+ _checkInterval: number;
22
+ private othersUnsub;
23
+ constructor(doc: Y.Doc, room: Room<JsonObject, LsonObject, BaseUserMeta, Json>);
24
+ destroy(): void;
25
+ getLocalState(): JsonObject | null;
26
+ setLocalState(state: Partial<JsonObject> | null): void;
27
+ setLocalStateField(field: string, value: JsonObject | null): void;
28
+ getStates(): Map<number, unknown>;
29
+ }
30
+ declare class LiveblocksProvider<P extends JsonObject, S extends LsonObject, U extends BaseUserMeta, E extends Json> extends Observable<unknown> {
31
+ private room;
32
+ private doc;
33
+ private unsubscribers;
34
+ awareness: Awareness;
35
+ private _synced;
36
+ constructor(room: Room<P, S, U, E>, doc: Y.Doc);
37
+ private syncDoc;
38
+ get synced(): boolean;
39
+ set synced(state: boolean);
40
+ private updateHandler;
41
+ destroy(): void;
42
+ disconnect(): void;
43
+ connect(): void;
44
+ }
45
+
46
+ export { Awareness, LiveblocksProvider as default };
package/dist/index.d.ts CHANGED
@@ -2,18 +2,21 @@ import { Room, JsonObject, LsonObject, BaseUserMeta, Json } from '@liveblocks/cl
2
2
  import { Observable } from 'lib0/observable';
3
3
  import * as Y from 'yjs';
4
4
 
5
- declare type LiveblocksYjsOptions = {
6
- httpEndpoint?: string;
7
- };
8
5
  declare type MetaClientState = {
9
6
  clock: number;
10
7
  lastUpdated: number;
11
8
  };
12
- declare class Awareness extends Observable<any> {
9
+ /**
10
+ * This class will store Yjs awareness in Liveblock's presence under the __yjs key
11
+ * IMPORTANT: The Yjs awareness protocol uses ydoc.clientId to reference users
12
+ * to their respective documents. To avoid mapping Yjs clientIds to liveblock's connectionId,
13
+ * we simply set the clientId of the doc to the connectionId. Then no further mapping is required
14
+ */
15
+ declare class Awareness extends Observable<unknown> {
13
16
  private room;
14
17
  doc: Y.Doc;
15
18
  clientID: number;
16
- states: Map<number, any>;
19
+ states: Map<number, unknown>;
17
20
  meta: Map<number, MetaClientState>;
18
21
  _checkInterval: number;
19
22
  private othersUnsub;
@@ -22,19 +25,22 @@ declare class Awareness extends Observable<any> {
22
25
  getLocalState(): JsonObject | null;
23
26
  setLocalState(state: Partial<JsonObject> | null): void;
24
27
  setLocalStateField(field: string, value: JsonObject | null): void;
25
- getStates(): Map<number, any>;
28
+ getStates(): Map<number, unknown>;
26
29
  }
27
- declare class LiveblocksProvider<P extends JsonObject, S extends LsonObject, U extends BaseUserMeta, E extends Json> {
30
+ declare class LiveblocksProvider<P extends JsonObject, S extends LsonObject, U extends BaseUserMeta, E extends Json> extends Observable<unknown> {
28
31
  private room;
29
- private httpEndpoint?;
30
- private lastUpdateDate;
31
32
  private doc;
32
33
  private unsubscribers;
33
34
  awareness: Awareness;
34
- constructor(room: Room<P, S, U, E>, doc: Y.Doc, config?: LiveblocksYjsOptions);
35
+ private _synced;
36
+ constructor(room: Room<P, S, U, E>, doc: Y.Doc);
37
+ private syncDoc;
38
+ get synced(): boolean;
39
+ set synced(state: boolean);
35
40
  private updateHandler;
36
- private resyncHttp;
37
41
  destroy(): void;
42
+ disconnect(): void;
43
+ connect(): void;
38
44
  }
39
45
 
40
46
  export { Awareness, LiveblocksProvider as default };
package/dist/index.js CHANGED
@@ -14,26 +14,6 @@ var __spreadValues = (a, b) => {
14
14
  }
15
15
  return a;
16
16
  };
17
- var __async = (__this, __arguments, generator) => {
18
- return new Promise((resolve, reject) => {
19
- var fulfilled = (value) => {
20
- try {
21
- step(generator.next(value));
22
- } catch (e) {
23
- reject(e);
24
- }
25
- };
26
- var rejected = (value) => {
27
- try {
28
- step(generator.throw(value));
29
- } catch (e) {
30
- reject(e);
31
- }
32
- };
33
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
34
- step((generator = generator.apply(__this, __arguments)).next());
35
- });
36
- };
37
17
 
38
18
  // src/index.ts
39
19
  var _jsbase64 = require('js-base64');
@@ -110,6 +90,7 @@ var Observable = class {
110
90
 
111
91
  // src/index.ts
112
92
  var _yjs = require('yjs'); var Y = _interopRequireWildcard(_yjs);
93
+ var Y_PRESENCE_KEY = "__yjs";
113
94
  var Awareness = class extends Observable {
114
95
  constructor(doc, room) {
115
96
  super();
@@ -118,7 +99,7 @@ var Awareness = class extends Observable {
118
99
  // manage it here. Unfortunately, it's expected to exist by various integrations, so it's an empty map.
119
100
  this.meta = /* @__PURE__ */ new Map();
120
101
  // _checkInterval this would hold a timer to remove users, but Liveblock's presence already handles this
121
- // unfortunately it's expected to exist by various integrations.
102
+ // unfortunately it's typed by various integrations
122
103
  this._checkInterval = 0;
123
104
  this.doc = doc;
124
105
  this.room = room;
@@ -152,21 +133,21 @@ var Awareness = class extends Observable {
152
133
  }
153
134
  getLocalState() {
154
135
  const presence = this.room.getPresence();
155
- if (Object.keys(this.room.getPresence()).length === 0) {
136
+ if (Object.keys(presence).length === 0 || typeof presence[Y_PRESENCE_KEY] === "undefined") {
156
137
  return null;
157
138
  }
158
- return presence["__yjs"];
139
+ return presence[Y_PRESENCE_KEY];
159
140
  }
160
141
  setLocalState(state) {
161
142
  var _a;
162
- const presence = (_a = this.room.getSelf()) == null ? void 0 : _a.presence["__yjs"];
143
+ const presence = (_a = this.room.getSelf()) == null ? void 0 : _a.presence[Y_PRESENCE_KEY];
163
144
  this.room.updatePresence({
164
145
  __yjs: __spreadValues(__spreadValues({}, presence || {}), state || {})
165
146
  });
166
147
  }
167
148
  setLocalStateField(field, value) {
168
149
  var _a;
169
- const presence = (_a = this.room.getSelf()) == null ? void 0 : _a.presence["__yjs"];
150
+ const presence = (_a = this.room.getSelf()) == null ? void 0 : _a.presence[Y_PRESENCE_KEY];
170
151
  const update = { [field]: value };
171
152
  this.room.updatePresence({
172
153
  __yjs: __spreadValues(__spreadValues({}, presence || {}), update)
@@ -179,7 +160,7 @@ var Awareness = class extends Observable {
179
160
  if (currentValue.connectionId) {
180
161
  acc.set(
181
162
  currentValue.connectionId,
182
- currentValue.presence["__yjs"] || {}
163
+ currentValue.presence[Y_PRESENCE_KEY] || {}
183
164
  );
184
165
  }
185
166
  return acc;
@@ -187,23 +168,26 @@ var Awareness = class extends Observable {
187
168
  return states;
188
169
  }
189
170
  };
190
- var LiveblocksProvider = class {
191
- constructor(room, doc, config) {
192
- this.lastUpdateDate = null;
171
+ var LiveblocksProvider = class extends Observable {
172
+ constructor(room, doc) {
173
+ var _a;
174
+ super();
193
175
  this.unsubscribers = [];
194
- this.updateHandler = (update, origin) => __async(this, null, function* () {
176
+ this._synced = false;
177
+ this.syncDoc = () => {
178
+ var _a;
179
+ this.synced = false;
180
+ this.doc.clientID = ((_a = this.room.getSelf()) == null ? void 0 : _a.connectionId) || this.doc.clientID;
181
+ this.awareness.clientID = this.doc.clientID;
182
+ const encodedVector = _jsbase64.Base64.fromUint8Array(Y.encodeStateVector(this.doc));
183
+ this.room.fetchYDoc(encodedVector);
184
+ };
185
+ this.updateHandler = (update, origin) => {
195
186
  if (origin !== "backend") {
196
187
  const encodedUpdate = _jsbase64.Base64.fromUint8Array(update);
197
- this.room.updateDoc(encodedUpdate);
198
- if (this.httpEndpoint) {
199
- yield fetch(this.httpEndpoint, {
200
- method: "POST",
201
- body: encodedUpdate
202
- });
203
- }
188
+ this.room.updateYDoc(encodedUpdate);
204
189
  }
205
- });
206
- var _a;
190
+ };
207
191
  this.doc = doc;
208
192
  this.room = room;
209
193
  const connectionId = (_a = this.room.getSelf()) == null ? void 0 : _a.connectionId;
@@ -213,60 +197,41 @@ var LiveblocksProvider = class {
213
197
  this.awareness = new Awareness(this.doc, this.room);
214
198
  this.doc.on("update", this.updateHandler);
215
199
  this.unsubscribers.push(
216
- this.room.events.connection.subscribe((e) => {
217
- var _a2;
218
- if (e === "open") {
219
- this.doc.clientID = ((_a2 = this.room.getSelf()) == null ? void 0 : _a2.connectionId) || this.doc.clientID;
220
- this.awareness.clientID = this.doc.clientID;
221
- const encodedVector = _jsbase64.Base64.fromUint8Array(
222
- Y.encodeStateVector(this.doc)
223
- );
224
- this.room.getDoc(encodedVector);
200
+ this.room.events.status.subscribe((status) => {
201
+ if (status === "connected") {
202
+ this.syncDoc();
225
203
  }
226
204
  })
227
205
  );
228
206
  this.unsubscribers.push(
229
- this.room.events.docUpdated.subscribe((updates) => {
230
- const decodedUpdates = updates.map(_jsbase64.Base64.toUint8Array);
231
- const update = Y.mergeUpdates(decodedUpdates);
232
- Y.applyUpdate(this.doc, update, "backend");
207
+ this.room.events.ydoc.subscribe((update) => {
208
+ Y.applyUpdate(this.doc, _jsbase64.Base64.toUint8Array(update), "backend");
209
+ this.synced = true;
233
210
  })
234
211
  );
235
- if (config == null ? void 0 : config.httpEndpoint) {
236
- this.httpEndpoint = config.httpEndpoint + "?room=" + this.room.id;
237
- this.unsubscribers.push(
238
- this.room.events.customEvent.subscribe(({ event }) => {
239
- if ((event == null ? void 0 : event.type) === "REFRESH") {
240
- void this.resyncHttp();
241
- }
242
- })
243
- );
244
- void this.resyncHttp();
245
- }
246
- this.room.getDoc();
212
+ this.syncDoc();
247
213
  }
248
- resyncHttp() {
249
- return __async(this, null, function* () {
250
- if (!this.httpEndpoint) {
251
- return;
252
- }
253
- const response = yield fetch(
254
- `${this.httpEndpoint}${this.lastUpdateDate !== null ? `&after=${this.lastUpdateDate.toISOString()}` : ""}`
255
- );
256
- const { updates, lastUpdate } = yield response.json();
257
- if (updates.length === 0) {
258
- return;
259
- }
260
- this.lastUpdateDate = new Date(lastUpdate);
261
- const update = Y.mergeUpdates(updates.map(_jsbase64.Base64.toUint8Array));
262
- Y.applyUpdate(this.doc, update, "backend");
263
- });
214
+ // The sync'd property is required by some provider implementations
215
+ get synced() {
216
+ return this._synced;
217
+ }
218
+ set synced(state) {
219
+ if (this._synced !== state) {
220
+ this._synced = state;
221
+ this.emit("synced", [state]);
222
+ this.emit("sync", [state]);
223
+ }
264
224
  }
265
225
  destroy() {
266
226
  this.doc.off("update", this.updateHandler);
267
227
  this.unsubscribers.forEach((unsub) => unsub());
268
228
  this.awareness.destroy();
269
229
  }
230
+ // Some provider implementations expect to be able to call connect/disconnect, implement as noop
231
+ disconnect() {
232
+ }
233
+ connect() {
234
+ }
270
235
  };
271
236
 
272
237
 
package/dist/index.mjs CHANGED
@@ -14,26 +14,6 @@ var __spreadValues = (a, b) => {
14
14
  }
15
15
  return a;
16
16
  };
17
- var __async = (__this, __arguments, generator) => {
18
- return new Promise((resolve, reject) => {
19
- var fulfilled = (value) => {
20
- try {
21
- step(generator.next(value));
22
- } catch (e) {
23
- reject(e);
24
- }
25
- };
26
- var rejected = (value) => {
27
- try {
28
- step(generator.throw(value));
29
- } catch (e) {
30
- reject(e);
31
- }
32
- };
33
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
34
- step((generator = generator.apply(__this, __arguments)).next());
35
- });
36
- };
37
17
 
38
18
  // src/index.ts
39
19
  import { Base64 } from "js-base64";
@@ -110,6 +90,7 @@ var Observable = class {
110
90
 
111
91
  // src/index.ts
112
92
  import * as Y from "yjs";
93
+ var Y_PRESENCE_KEY = "__yjs";
113
94
  var Awareness = class extends Observable {
114
95
  constructor(doc, room) {
115
96
  super();
@@ -118,7 +99,7 @@ var Awareness = class extends Observable {
118
99
  // manage it here. Unfortunately, it's expected to exist by various integrations, so it's an empty map.
119
100
  this.meta = /* @__PURE__ */ new Map();
120
101
  // _checkInterval this would hold a timer to remove users, but Liveblock's presence already handles this
121
- // unfortunately it's expected to exist by various integrations.
102
+ // unfortunately it's typed by various integrations
122
103
  this._checkInterval = 0;
123
104
  this.doc = doc;
124
105
  this.room = room;
@@ -152,21 +133,21 @@ var Awareness = class extends Observable {
152
133
  }
153
134
  getLocalState() {
154
135
  const presence = this.room.getPresence();
155
- if (Object.keys(this.room.getPresence()).length === 0) {
136
+ if (Object.keys(presence).length === 0 || typeof presence[Y_PRESENCE_KEY] === "undefined") {
156
137
  return null;
157
138
  }
158
- return presence["__yjs"];
139
+ return presence[Y_PRESENCE_KEY];
159
140
  }
160
141
  setLocalState(state) {
161
142
  var _a;
162
- const presence = (_a = this.room.getSelf()) == null ? void 0 : _a.presence["__yjs"];
143
+ const presence = (_a = this.room.getSelf()) == null ? void 0 : _a.presence[Y_PRESENCE_KEY];
163
144
  this.room.updatePresence({
164
145
  __yjs: __spreadValues(__spreadValues({}, presence || {}), state || {})
165
146
  });
166
147
  }
167
148
  setLocalStateField(field, value) {
168
149
  var _a;
169
- const presence = (_a = this.room.getSelf()) == null ? void 0 : _a.presence["__yjs"];
150
+ const presence = (_a = this.room.getSelf()) == null ? void 0 : _a.presence[Y_PRESENCE_KEY];
170
151
  const update = { [field]: value };
171
152
  this.room.updatePresence({
172
153
  __yjs: __spreadValues(__spreadValues({}, presence || {}), update)
@@ -179,7 +160,7 @@ var Awareness = class extends Observable {
179
160
  if (currentValue.connectionId) {
180
161
  acc.set(
181
162
  currentValue.connectionId,
182
- currentValue.presence["__yjs"] || {}
163
+ currentValue.presence[Y_PRESENCE_KEY] || {}
183
164
  );
184
165
  }
185
166
  return acc;
@@ -187,23 +168,26 @@ var Awareness = class extends Observable {
187
168
  return states;
188
169
  }
189
170
  };
190
- var LiveblocksProvider = class {
191
- constructor(room, doc, config) {
192
- this.lastUpdateDate = null;
171
+ var LiveblocksProvider = class extends Observable {
172
+ constructor(room, doc) {
173
+ var _a;
174
+ super();
193
175
  this.unsubscribers = [];
194
- this.updateHandler = (update, origin) => __async(this, null, function* () {
176
+ this._synced = false;
177
+ this.syncDoc = () => {
178
+ var _a;
179
+ this.synced = false;
180
+ this.doc.clientID = ((_a = this.room.getSelf()) == null ? void 0 : _a.connectionId) || this.doc.clientID;
181
+ this.awareness.clientID = this.doc.clientID;
182
+ const encodedVector = Base64.fromUint8Array(Y.encodeStateVector(this.doc));
183
+ this.room.fetchYDoc(encodedVector);
184
+ };
185
+ this.updateHandler = (update, origin) => {
195
186
  if (origin !== "backend") {
196
187
  const encodedUpdate = Base64.fromUint8Array(update);
197
- this.room.updateDoc(encodedUpdate);
198
- if (this.httpEndpoint) {
199
- yield fetch(this.httpEndpoint, {
200
- method: "POST",
201
- body: encodedUpdate
202
- });
203
- }
188
+ this.room.updateYDoc(encodedUpdate);
204
189
  }
205
- });
206
- var _a;
190
+ };
207
191
  this.doc = doc;
208
192
  this.room = room;
209
193
  const connectionId = (_a = this.room.getSelf()) == null ? void 0 : _a.connectionId;
@@ -213,60 +197,41 @@ var LiveblocksProvider = class {
213
197
  this.awareness = new Awareness(this.doc, this.room);
214
198
  this.doc.on("update", this.updateHandler);
215
199
  this.unsubscribers.push(
216
- this.room.events.connection.subscribe((e) => {
217
- var _a2;
218
- if (e === "open") {
219
- this.doc.clientID = ((_a2 = this.room.getSelf()) == null ? void 0 : _a2.connectionId) || this.doc.clientID;
220
- this.awareness.clientID = this.doc.clientID;
221
- const encodedVector = Base64.fromUint8Array(
222
- Y.encodeStateVector(this.doc)
223
- );
224
- this.room.getDoc(encodedVector);
200
+ this.room.events.status.subscribe((status) => {
201
+ if (status === "connected") {
202
+ this.syncDoc();
225
203
  }
226
204
  })
227
205
  );
228
206
  this.unsubscribers.push(
229
- this.room.events.docUpdated.subscribe((updates) => {
230
- const decodedUpdates = updates.map(Base64.toUint8Array);
231
- const update = Y.mergeUpdates(decodedUpdates);
232
- Y.applyUpdate(this.doc, update, "backend");
207
+ this.room.events.ydoc.subscribe((update) => {
208
+ Y.applyUpdate(this.doc, Base64.toUint8Array(update), "backend");
209
+ this.synced = true;
233
210
  })
234
211
  );
235
- if (config == null ? void 0 : config.httpEndpoint) {
236
- this.httpEndpoint = config.httpEndpoint + "?room=" + this.room.id;
237
- this.unsubscribers.push(
238
- this.room.events.customEvent.subscribe(({ event }) => {
239
- if ((event == null ? void 0 : event.type) === "REFRESH") {
240
- void this.resyncHttp();
241
- }
242
- })
243
- );
244
- void this.resyncHttp();
245
- }
246
- this.room.getDoc();
212
+ this.syncDoc();
247
213
  }
248
- resyncHttp() {
249
- return __async(this, null, function* () {
250
- if (!this.httpEndpoint) {
251
- return;
252
- }
253
- const response = yield fetch(
254
- `${this.httpEndpoint}${this.lastUpdateDate !== null ? `&after=${this.lastUpdateDate.toISOString()}` : ""}`
255
- );
256
- const { updates, lastUpdate } = yield response.json();
257
- if (updates.length === 0) {
258
- return;
259
- }
260
- this.lastUpdateDate = new Date(lastUpdate);
261
- const update = Y.mergeUpdates(updates.map(Base64.toUint8Array));
262
- Y.applyUpdate(this.doc, update, "backend");
263
- });
214
+ // The sync'd property is required by some provider implementations
215
+ get synced() {
216
+ return this._synced;
217
+ }
218
+ set synced(state) {
219
+ if (this._synced !== state) {
220
+ this._synced = state;
221
+ this.emit("synced", [state]);
222
+ this.emit("sync", [state]);
223
+ }
264
224
  }
265
225
  destroy() {
266
226
  this.doc.off("update", this.updateHandler);
267
227
  this.unsubscribers.forEach((unsub) => unsub());
268
228
  this.awareness.destroy();
269
229
  }
230
+ // Some provider implementations expect to be able to call connect/disconnect, implement as noop
231
+ disconnect() {
232
+ }
233
+ connect() {
234
+ }
270
235
  };
271
236
  export {
272
237
  Awareness,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/yjs",
3
- "version": "1.1.0-yjs4",
3
+ "version": "1.1.1-internal2",
4
4
  "description": "An integration with . Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.js",
@@ -11,23 +11,29 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "dev": "tsup --watch",
14
- "build": "tsup --format cjs,esm --dts --clean",
14
+ "build": "tsup && cp dist/index.d.ts dist/index.d.mts",
15
15
  "format": "eslint --fix src/; prettier --write src/",
16
16
  "lint": "eslint src/",
17
+ "lint:package": "publint --strict && attw --pack",
17
18
  "test": "jest --silent --verbose --color=always",
18
19
  "test:types": "tsd",
19
20
  "test:watch": "jest --silent --verbose --color=always --watch"
20
21
  },
21
22
  "exports": {
22
23
  ".": {
23
- "require": "./dist/index.js",
24
- "import": "./dist/index.mjs",
25
- "types": "./dist/index.d.ts"
24
+ "import": {
25
+ "types": "./dist/index.d.mts",
26
+ "default": "./dist/index.mjs"
27
+ },
28
+ "require": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ }
26
32
  }
27
33
  },
28
34
  "dependencies": {
29
- "@liveblocks/client": "1.1.0-yjs4",
30
- "@liveblocks/core": "1.1.0-yjs4",
35
+ "@liveblocks/client": "1.1.1-internal2",
36
+ "@liveblocks/core": "1.1.1-internal2",
31
37
  "js-base64": "^3.7.5"
32
38
  },
33
39
  "peerDependencies": {