@pluv/crdt-yjs 2.1.0 → 2.2.1

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/dist/index.js CHANGED
@@ -45,10 +45,12 @@ module.exports = __toCommonJS(index_exports);
45
45
  var yjs_exports = {};
46
46
  __export(yjs_exports, {
47
47
  array: () => array,
48
+ awareness: () => awareness,
48
49
  doc: () => doc,
49
50
  kind: () => kind,
50
51
  map: () => map,
51
52
  object: () => object,
53
+ provider: () => provider,
52
54
  text: () => text,
53
55
  xmlElement: () => xmlElement,
54
56
  xmlFragment: () => xmlFragment,
@@ -73,6 +75,147 @@ var array = (value = []) => {
73
75
  return new YjsArray(value);
74
76
  };
75
77
 
78
+ // src/awareness/PluvYjsAwareness.ts
79
+ var import_observable = require("lib0/observable");
80
+
81
+ // src/constants.ts
82
+ var PLUV_PRESENCE_META_KEY = "__pluv";
83
+ var PLUV_PRESENCE_Y_ID_KEY = "__y_clientid";
84
+
85
+ // src/awareness/PluvYjsAwareness.ts
86
+ var PluvYjsAwareness = class extends import_observable.ObservableV2 {
87
+ constructor(params) {
88
+ super();
89
+ this._field = null;
90
+ this._idMap = /* @__PURE__ */ new Map();
91
+ /**
92
+ * !HACK
93
+ * @description We dgaf about these. We're just mirroring the fields that exist in the
94
+ * og awareness to make sure everything aligns.
95
+ * @see https://github.com/yjs/y-protocols/blob/master/awareness.js
96
+ * @date May 13, 2025
97
+ */
98
+ this.meta = /* @__PURE__ */ new Map();
99
+ this._checkInterval = 0;
100
+ const { field, room } = params;
101
+ this.doc = room.getDoc().value;
102
+ this._room = room;
103
+ if (!!field) this._field = field;
104
+ const myPresence = this._room.getMyPresence();
105
+ const myMeta = (myPresence == null ? void 0 : myPresence[PLUV_PRESENCE_META_KEY]) || {};
106
+ this._room.updateMyPresence({
107
+ [PLUV_PRESENCE_META_KEY]: __spreadProps(__spreadValues({}, myMeta), {
108
+ [PLUV_PRESENCE_Y_ID_KEY]: this.clientID
109
+ })
110
+ });
111
+ this._unsubscribe = this._room.subscribe.others((others, event) => {
112
+ const updates = this._deriveUpdates(others, event);
113
+ if (!updates) return;
114
+ this.emit("change", [updates, "presence"]);
115
+ this.emit("update", [updates, "presence"]);
116
+ });
117
+ }
118
+ get clientID() {
119
+ return this.doc.clientID;
120
+ }
121
+ get states() {
122
+ return this.getStates();
123
+ }
124
+ destroy() {
125
+ this.emit("destroy", [this]);
126
+ this._unsubscribe();
127
+ super.destroy();
128
+ }
129
+ getLocalState() {
130
+ return this._adaptPresence(this._room.getMyPresence());
131
+ }
132
+ setLocalState(state) {
133
+ const patch = !!this._field ? { [this._field]: state } : state;
134
+ this._room.updateMyPresence(patch);
135
+ this.emit("update", [{ added: [], updated: [this.clientID], removed: [] }, "local"]);
136
+ }
137
+ setLocalStateField(field, value) {
138
+ const patch = !!this._field ? { [this._field]: __spreadProps(__spreadValues({}, this.getLocalState()), { [field]: value }) } : { [field]: value };
139
+ this._room.updateMyPresence(patch);
140
+ }
141
+ /**
142
+ * !HACK
143
+ * @description We're building the awareness presences from the states stored on the room's
144
+ * presense instead of using the map defined in the awareness interface.
145
+ * @see https://github.com/yjs/y-protocols/blob/master/awareness.js
146
+ * @date May 13, 2025
147
+ */
148
+ getStates() {
149
+ const others = this._room.getOthers();
150
+ const states = others.reduce((map2, other) => {
151
+ var _a;
152
+ const presence = this._adaptPresence(other.presence);
153
+ const meta = ((_a = other.presence) == null ? void 0 : _a[PLUV_PRESENCE_META_KEY]) || {};
154
+ const clientID = meta[PLUV_PRESENCE_Y_ID_KEY];
155
+ return typeof presence !== "undefined" && typeof clientID !== "undefined" ? map2.set(clientID, presence) : map2;
156
+ }, /* @__PURE__ */ new Map());
157
+ const myPresence = this.getLocalState();
158
+ if (typeof myPresence !== "undefined") states.set(this.clientID, myPresence);
159
+ return states;
160
+ }
161
+ _adaptPresence(presence) {
162
+ return !!this._field ? presence[this._field] : presence;
163
+ }
164
+ _deriveUpdates(others, event) {
165
+ switch (event.kind) {
166
+ case "clear": {
167
+ this._idMap.clear();
168
+ return null;
169
+ }
170
+ case "enter": {
171
+ const clientID = this._resetIdMap(others).get(event.user.connectionId);
172
+ return {
173
+ added: typeof clientID !== "undefined" ? [clientID] : [],
174
+ updated: [],
175
+ removed: []
176
+ };
177
+ }
178
+ case "leave": {
179
+ const clientID = this._idMap.get(event.user.connectionId);
180
+ this._resetIdMap(others);
181
+ return {
182
+ added: [],
183
+ updated: [],
184
+ removed: typeof clientID !== "undefined" ? [clientID] : []
185
+ };
186
+ }
187
+ case "sync": {
188
+ this._resetIdMap(others);
189
+ return null;
190
+ }
191
+ case "update": {
192
+ const clientID = this._resetIdMap(others).get(event.user.connectionId);
193
+ return {
194
+ added: [],
195
+ updated: typeof clientID !== "undefined" ? [clientID] : [],
196
+ removed: []
197
+ };
198
+ }
199
+ default:
200
+ return null;
201
+ }
202
+ }
203
+ _resetIdMap(others) {
204
+ this._idMap.clear();
205
+ return others.reduce((map2, other) => {
206
+ var _a;
207
+ const clientID = (_a = other.presence[PLUV_PRESENCE_META_KEY]) == null ? void 0 : _a[PLUV_PRESENCE_Y_ID_KEY];
208
+ if (typeof clientID === "undefined") return map2;
209
+ return map2.set(other.connectionId, clientID);
210
+ }, this._idMap);
211
+ }
212
+ };
213
+
214
+ // src/awareness/awareness.ts
215
+ var awareness = (params) => {
216
+ return new PluvYjsAwareness(params);
217
+ };
218
+
76
219
  // src/doc/CrdtYjsDoc.ts
77
220
  var import_js_base64 = require("js-base64");
78
221
  var import_yjs9 = require("yjs");
@@ -459,6 +602,86 @@ var object = (value = {}) => {
459
602
  return new YjsObject(value);
460
603
  };
461
604
 
605
+ // src/provider/PluvYjsProvider.ts
606
+ var import_types = require("@pluv/types");
607
+ var import_observable2 = require("lib0/observable");
608
+ var PluvYjsProvider = class extends import_observable2.ObservableV2 {
609
+ constructor(params) {
610
+ super();
611
+ this._synced = false;
612
+ const { field, room } = params;
613
+ this.awareness = awareness({ field, room });
614
+ this.doc = room.getDoc().value;
615
+ this._room = room;
616
+ const subscriptions = [
617
+ this._room.subscribe.connection(({ storage }) => {
618
+ const status = this._toProviderStatus(storage.state);
619
+ if (!status) return;
620
+ this.emit("status", [{ status }]);
621
+ }),
622
+ this._room.addEventListener("close", (event) => {
623
+ this.emit("connection-close", [event, this]);
624
+ }),
625
+ this._room.addEventListener("error", (event) => {
626
+ this.emit("connection-error", [event, this]);
627
+ })
628
+ ];
629
+ this._unsubscribe = subscriptions.reduce(
630
+ (fn, unsubscribe) => () => {
631
+ fn();
632
+ unsubscribe();
633
+ },
634
+ () => void 0
635
+ );
636
+ }
637
+ get synced() {
638
+ return this._synced;
639
+ }
640
+ set synced(state) {
641
+ if (this._synced === state) return;
642
+ this._synced = state;
643
+ this.emit("sync", [state]);
644
+ this.emit("synced", [state]);
645
+ }
646
+ destroy() {
647
+ this.awareness.destroy();
648
+ this._unsubscribe();
649
+ super.destroy();
650
+ }
651
+ _toProviderStatus(state) {
652
+ switch (state) {
653
+ case import_types.StorageState.Loading: {
654
+ return "connecting";
655
+ }
656
+ case import_types.StorageState.Unavailable: {
657
+ return "disconnected";
658
+ }
659
+ case import_types.StorageState.Offline:
660
+ case import_types.StorageState.Synced: {
661
+ return "connected";
662
+ }
663
+ default:
664
+ return null;
665
+ }
666
+ }
667
+ /**
668
+ * !HACK
669
+ * @description We dgaf about these, since the room manages these already. We're just mirroring
670
+ * the fields that exist in the og provider to make sure everything aligns.
671
+ * @see https://github.com/yjs/y-websocket/blob/master/src/y-websocket.js
672
+ * @date May 13, 2025
673
+ */
674
+ connect() {
675
+ }
676
+ disconnect() {
677
+ }
678
+ };
679
+
680
+ // src/provider/provider.ts
681
+ var provider = (params) => {
682
+ return new PluvYjsProvider(params);
683
+ };
684
+
462
685
  // src/text/text.ts
463
686
  var text = (value = "") => {
464
687
  return new YjsText(value);
package/dist/index.mjs CHANGED
@@ -26,10 +26,12 @@ var __export = (target, all) => {
26
26
  var yjs_exports = {};
27
27
  __export(yjs_exports, {
28
28
  array: () => array,
29
+ awareness: () => awareness,
29
30
  doc: () => doc,
30
31
  kind: () => kind,
31
32
  map: () => map,
32
33
  object: () => object,
34
+ provider: () => provider,
33
35
  text: () => text,
34
36
  xmlElement: () => xmlElement,
35
37
  xmlFragment: () => xmlFragment,
@@ -54,6 +56,147 @@ var array = (value = []) => {
54
56
  return new YjsArray(value);
55
57
  };
56
58
 
59
+ // src/awareness/PluvYjsAwareness.ts
60
+ import { ObservableV2 } from "lib0/observable";
61
+
62
+ // src/constants.ts
63
+ var PLUV_PRESENCE_META_KEY = "__pluv";
64
+ var PLUV_PRESENCE_Y_ID_KEY = "__y_clientid";
65
+
66
+ // src/awareness/PluvYjsAwareness.ts
67
+ var PluvYjsAwareness = class extends ObservableV2 {
68
+ constructor(params) {
69
+ super();
70
+ this._field = null;
71
+ this._idMap = /* @__PURE__ */ new Map();
72
+ /**
73
+ * !HACK
74
+ * @description We dgaf about these. We're just mirroring the fields that exist in the
75
+ * og awareness to make sure everything aligns.
76
+ * @see https://github.com/yjs/y-protocols/blob/master/awareness.js
77
+ * @date May 13, 2025
78
+ */
79
+ this.meta = /* @__PURE__ */ new Map();
80
+ this._checkInterval = 0;
81
+ const { field, room } = params;
82
+ this.doc = room.getDoc().value;
83
+ this._room = room;
84
+ if (!!field) this._field = field;
85
+ const myPresence = this._room.getMyPresence();
86
+ const myMeta = (myPresence == null ? void 0 : myPresence[PLUV_PRESENCE_META_KEY]) || {};
87
+ this._room.updateMyPresence({
88
+ [PLUV_PRESENCE_META_KEY]: __spreadProps(__spreadValues({}, myMeta), {
89
+ [PLUV_PRESENCE_Y_ID_KEY]: this.clientID
90
+ })
91
+ });
92
+ this._unsubscribe = this._room.subscribe.others((others, event) => {
93
+ const updates = this._deriveUpdates(others, event);
94
+ if (!updates) return;
95
+ this.emit("change", [updates, "presence"]);
96
+ this.emit("update", [updates, "presence"]);
97
+ });
98
+ }
99
+ get clientID() {
100
+ return this.doc.clientID;
101
+ }
102
+ get states() {
103
+ return this.getStates();
104
+ }
105
+ destroy() {
106
+ this.emit("destroy", [this]);
107
+ this._unsubscribe();
108
+ super.destroy();
109
+ }
110
+ getLocalState() {
111
+ return this._adaptPresence(this._room.getMyPresence());
112
+ }
113
+ setLocalState(state) {
114
+ const patch = !!this._field ? { [this._field]: state } : state;
115
+ this._room.updateMyPresence(patch);
116
+ this.emit("update", [{ added: [], updated: [this.clientID], removed: [] }, "local"]);
117
+ }
118
+ setLocalStateField(field, value) {
119
+ const patch = !!this._field ? { [this._field]: __spreadProps(__spreadValues({}, this.getLocalState()), { [field]: value }) } : { [field]: value };
120
+ this._room.updateMyPresence(patch);
121
+ }
122
+ /**
123
+ * !HACK
124
+ * @description We're building the awareness presences from the states stored on the room's
125
+ * presense instead of using the map defined in the awareness interface.
126
+ * @see https://github.com/yjs/y-protocols/blob/master/awareness.js
127
+ * @date May 13, 2025
128
+ */
129
+ getStates() {
130
+ const others = this._room.getOthers();
131
+ const states = others.reduce((map2, other) => {
132
+ var _a;
133
+ const presence = this._adaptPresence(other.presence);
134
+ const meta = ((_a = other.presence) == null ? void 0 : _a[PLUV_PRESENCE_META_KEY]) || {};
135
+ const clientID = meta[PLUV_PRESENCE_Y_ID_KEY];
136
+ return typeof presence !== "undefined" && typeof clientID !== "undefined" ? map2.set(clientID, presence) : map2;
137
+ }, /* @__PURE__ */ new Map());
138
+ const myPresence = this.getLocalState();
139
+ if (typeof myPresence !== "undefined") states.set(this.clientID, myPresence);
140
+ return states;
141
+ }
142
+ _adaptPresence(presence) {
143
+ return !!this._field ? presence[this._field] : presence;
144
+ }
145
+ _deriveUpdates(others, event) {
146
+ switch (event.kind) {
147
+ case "clear": {
148
+ this._idMap.clear();
149
+ return null;
150
+ }
151
+ case "enter": {
152
+ const clientID = this._resetIdMap(others).get(event.user.connectionId);
153
+ return {
154
+ added: typeof clientID !== "undefined" ? [clientID] : [],
155
+ updated: [],
156
+ removed: []
157
+ };
158
+ }
159
+ case "leave": {
160
+ const clientID = this._idMap.get(event.user.connectionId);
161
+ this._resetIdMap(others);
162
+ return {
163
+ added: [],
164
+ updated: [],
165
+ removed: typeof clientID !== "undefined" ? [clientID] : []
166
+ };
167
+ }
168
+ case "sync": {
169
+ this._resetIdMap(others);
170
+ return null;
171
+ }
172
+ case "update": {
173
+ const clientID = this._resetIdMap(others).get(event.user.connectionId);
174
+ return {
175
+ added: [],
176
+ updated: typeof clientID !== "undefined" ? [clientID] : [],
177
+ removed: []
178
+ };
179
+ }
180
+ default:
181
+ return null;
182
+ }
183
+ }
184
+ _resetIdMap(others) {
185
+ this._idMap.clear();
186
+ return others.reduce((map2, other) => {
187
+ var _a;
188
+ const clientID = (_a = other.presence[PLUV_PRESENCE_META_KEY]) == null ? void 0 : _a[PLUV_PRESENCE_Y_ID_KEY];
189
+ if (typeof clientID === "undefined") return map2;
190
+ return map2.set(other.connectionId, clientID);
191
+ }, this._idMap);
192
+ }
193
+ };
194
+
195
+ // src/awareness/awareness.ts
196
+ var awareness = (params) => {
197
+ return new PluvYjsAwareness(params);
198
+ };
199
+
57
200
  // src/doc/CrdtYjsDoc.ts
58
201
  import { fromUint8Array, toUint8Array } from "js-base64";
59
202
  import {
@@ -454,6 +597,86 @@ var object = (value = {}) => {
454
597
  return new YjsObject(value);
455
598
  };
456
599
 
600
+ // src/provider/PluvYjsProvider.ts
601
+ import { StorageState } from "@pluv/types";
602
+ import { ObservableV2 as ObservableV22 } from "lib0/observable";
603
+ var PluvYjsProvider = class extends ObservableV22 {
604
+ constructor(params) {
605
+ super();
606
+ this._synced = false;
607
+ const { field, room } = params;
608
+ this.awareness = awareness({ field, room });
609
+ this.doc = room.getDoc().value;
610
+ this._room = room;
611
+ const subscriptions = [
612
+ this._room.subscribe.connection(({ storage }) => {
613
+ const status = this._toProviderStatus(storage.state);
614
+ if (!status) return;
615
+ this.emit("status", [{ status }]);
616
+ }),
617
+ this._room.addEventListener("close", (event) => {
618
+ this.emit("connection-close", [event, this]);
619
+ }),
620
+ this._room.addEventListener("error", (event) => {
621
+ this.emit("connection-error", [event, this]);
622
+ })
623
+ ];
624
+ this._unsubscribe = subscriptions.reduce(
625
+ (fn, unsubscribe) => () => {
626
+ fn();
627
+ unsubscribe();
628
+ },
629
+ () => void 0
630
+ );
631
+ }
632
+ get synced() {
633
+ return this._synced;
634
+ }
635
+ set synced(state) {
636
+ if (this._synced === state) return;
637
+ this._synced = state;
638
+ this.emit("sync", [state]);
639
+ this.emit("synced", [state]);
640
+ }
641
+ destroy() {
642
+ this.awareness.destroy();
643
+ this._unsubscribe();
644
+ super.destroy();
645
+ }
646
+ _toProviderStatus(state) {
647
+ switch (state) {
648
+ case StorageState.Loading: {
649
+ return "connecting";
650
+ }
651
+ case StorageState.Unavailable: {
652
+ return "disconnected";
653
+ }
654
+ case StorageState.Offline:
655
+ case StorageState.Synced: {
656
+ return "connected";
657
+ }
658
+ default:
659
+ return null;
660
+ }
661
+ }
662
+ /**
663
+ * !HACK
664
+ * @description We dgaf about these, since the room manages these already. We're just mirroring
665
+ * the fields that exist in the og provider to make sure everything aligns.
666
+ * @see https://github.com/yjs/y-websocket/blob/master/src/y-websocket.js
667
+ * @date May 13, 2025
668
+ */
669
+ connect() {
670
+ }
671
+ disconnect() {
672
+ }
673
+ };
674
+
675
+ // src/provider/provider.ts
676
+ var provider = (params) => {
677
+ return new PluvYjsProvider(params);
678
+ };
679
+
457
680
  // src/text/text.ts
458
681
  var text = (value = "") => {
459
682
  return new YjsText(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/crdt-yjs",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "yjs for @pluv/io",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -17,10 +17,11 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@types/node": "^22.15.17",
20
+ "@types/node": "^22.15.18",
21
21
  "js-base64": "^3.7.7",
22
- "@pluv/crdt": "^2.1.0",
23
- "@pluv/types": "^2.1.0"
22
+ "lib0": "^0.2.108",
23
+ "@pluv/crdt": "^2.2.1",
24
+ "@pluv/types": "^2.2.1"
24
25
  },
25
26
  "peerDependencies": {
26
27
  "yjs": "^13.0.0"
@@ -29,9 +30,9 @@
29
30
  "eslint": "^9.26.0",
30
31
  "tsup": "^8.4.0",
31
32
  "typescript": "^5.8.3",
32
- "yjs": "^13.6.26",
33
- "@pluv/tsconfig": "^2.1.0",
34
- "eslint-config-pluv": "^2.1.0"
33
+ "yjs": "^13.6.27",
34
+ "@pluv/tsconfig": "^2.2.1",
35
+ "eslint-config-pluv": "^2.2.1"
35
36
  },
36
37
  "scripts": {
37
38
  "build": "tsup src/index.ts --format esm,cjs --dts",