@pluv/client 1.0.0 → 1.0.2
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +16 -0
- package/dist/index.d.mts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +127 -95
- package/dist/index.mjs +127 -95
- package/package.json +5 -5
- package/src/AbstractRoom.ts +3 -1
- package/src/CrdtManager.ts +53 -31
- package/src/CrdtNotifier.ts +4 -1
- package/src/EventNotifier.ts +11 -5
- package/src/MockedRoom.ts +21 -11
- package/src/OtherNotifier.ts +8 -2
- package/src/PluvClient.ts +3 -1
- package/src/PluvProcedure.ts +6 -2
- package/src/PluvRoom.ts +116 -72
- package/src/PluvRouter.ts +4 -1
- package/src/StateNotifier.ts +4 -1
- package/src/UsersManager.ts +23 -5
- package/src/index.ts +8 -1
- package/src/infer.ts +3 -1
- package/src/register.ts +2 -1
- package/src/types.ts +7 -2
- package/src/utils/pickBy.ts +4 -1
package/dist/index.mjs
CHANGED
|
@@ -95,9 +95,9 @@ import { noop } from "@pluv/crdt";
|
|
|
95
95
|
var CrdtManager = class {
|
|
96
96
|
constructor(options) {
|
|
97
97
|
this.initialized = false;
|
|
98
|
-
const {
|
|
98
|
+
const { initialStorage = noop.doc({}) } = options;
|
|
99
99
|
this._docFactory = initialStorage;
|
|
100
|
-
this.doc =
|
|
100
|
+
this.doc = initialStorage.getEmpty();
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* !HACK
|
|
@@ -105,40 +105,50 @@ var CrdtManager = class {
|
|
|
105
105
|
* update. This unfortunately means updates can be dropped entirely
|
|
106
106
|
* @date June 13, 2023
|
|
107
107
|
*/
|
|
108
|
-
applyUpdate(
|
|
108
|
+
applyUpdate(params) {
|
|
109
|
+
const { origin, update } = params;
|
|
109
110
|
if (!this.initialized) return this;
|
|
110
111
|
const updates = typeof update === "string" ? [update] : update;
|
|
111
112
|
this._applyDocUpdates(this.doc, updates, origin);
|
|
112
113
|
return this;
|
|
113
114
|
}
|
|
114
115
|
destroy() {
|
|
116
|
+
this.initialized = false;
|
|
115
117
|
this.doc.destroy();
|
|
118
|
+
this.doc = this._docFactory.getEmpty();
|
|
116
119
|
}
|
|
117
120
|
get(key) {
|
|
118
121
|
return this.doc.get(key);
|
|
119
122
|
}
|
|
123
|
+
getInitialState() {
|
|
124
|
+
return this._docFactory.getInitialized().getEncodedState();
|
|
125
|
+
}
|
|
120
126
|
initialize(params) {
|
|
121
|
-
const {
|
|
122
|
-
const updates = typeof update === "string" ? [update] : update;
|
|
123
|
-
if (!updates.length) return this;
|
|
127
|
+
const { origin, update } = params;
|
|
124
128
|
if (this.initialized) {
|
|
125
|
-
|
|
129
|
+
console.warn("Attempted to initialize storage multiple times");
|
|
126
130
|
return this;
|
|
127
131
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
onEmpty.destroy();
|
|
131
|
-
this.doc = this._applyDocUpdates(this._docFactory.getFresh(), updates, origin).track();
|
|
132
|
-
this.initialized = true;
|
|
133
|
-
onInitialized == null ? void 0 : onInitialized(this.doc.getEncodedState());
|
|
132
|
+
if (!this.doc.isEmpty()) {
|
|
133
|
+
console.warn("Attempted to initialize over a non-empty storage");
|
|
134
134
|
return this;
|
|
135
135
|
}
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
const updates = typeof update === "string" ? [update] : update;
|
|
137
|
+
if (!updates.length) return this;
|
|
138
|
+
const initialized = this._docFactory.getInitialized();
|
|
139
|
+
const reference = initialized.get();
|
|
140
|
+
this.doc = this._applyDocUpdates(
|
|
141
|
+
this._docFactory.getEmpty(),
|
|
142
|
+
updates,
|
|
143
|
+
origin
|
|
144
|
+
).rebuildStorage(reference);
|
|
138
145
|
this.initialized = true;
|
|
139
|
-
|
|
146
|
+
initialized.destroy();
|
|
140
147
|
return this;
|
|
141
148
|
}
|
|
149
|
+
resolveEncodedState(updates) {
|
|
150
|
+
return this._docFactory.resolveEncodedState(updates);
|
|
151
|
+
}
|
|
142
152
|
_applyDocUpdates(doc, updates, origin) {
|
|
143
153
|
doc.transact(() => {
|
|
144
154
|
doc.batchApplyEncodedState({ origin, updates });
|
|
@@ -233,7 +243,10 @@ var PluvRouter = class _PluvRouter {
|
|
|
233
243
|
this._defs = { events };
|
|
234
244
|
}
|
|
235
245
|
static merge(...routers) {
|
|
236
|
-
const events = Object.assign(
|
|
246
|
+
const events = Object.assign(
|
|
247
|
+
/* @__PURE__ */ Object.create(null),
|
|
248
|
+
...routers.map((router) => router._defs.events)
|
|
249
|
+
);
|
|
237
250
|
if (Object.keys(events).some((key) => key.startsWith("$"))) {
|
|
238
251
|
throw new Error('Procedures may not start with "$"');
|
|
239
252
|
}
|
|
@@ -386,7 +399,11 @@ var UsersManager = class {
|
|
|
386
399
|
const userInfo = (_a = this._others.get(clientId)) != null ? _a : null;
|
|
387
400
|
if (!userInfo) return null;
|
|
388
401
|
const remaining = (_c = (_b = this._getConnectionIds(clientId)) == null ? void 0 : _b.size) != null ? _c : 0;
|
|
389
|
-
const result = {
|
|
402
|
+
const result = {
|
|
403
|
+
clientId,
|
|
404
|
+
remaining,
|
|
405
|
+
data: userInfo
|
|
406
|
+
};
|
|
390
407
|
if (!remaining) this._others.delete(clientId);
|
|
391
408
|
return result;
|
|
392
409
|
}
|
|
@@ -423,7 +440,10 @@ var UsersManager = class {
|
|
|
423
440
|
const other = this._others.get(clientId);
|
|
424
441
|
if (!other) return null;
|
|
425
442
|
const cleanedPatch = pickBy(patch != null ? patch : {}, (value) => typeof value !== "undefined");
|
|
426
|
-
const cleanedPresence = pickBy(
|
|
443
|
+
const cleanedPresence = pickBy(
|
|
444
|
+
(_a = other.presence) != null ? _a : {},
|
|
445
|
+
(value) => typeof value !== "undefined"
|
|
446
|
+
);
|
|
427
447
|
const presence = __spreadValues(__spreadValues(__spreadValues({}, this.initialPresence), cleanedPresence), cleanedPatch);
|
|
428
448
|
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
429
449
|
this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence: validated }));
|
|
@@ -592,10 +612,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
592
612
|
room: this.id,
|
|
593
613
|
user: myself
|
|
594
614
|
};
|
|
595
|
-
const output = yield procedure.config.broadcast(
|
|
596
|
-
parsed,
|
|
597
|
-
context
|
|
598
|
-
);
|
|
615
|
+
const output = yield procedure.config.broadcast(parsed, context);
|
|
599
616
|
Object.entries(output).forEach(([_type, _data]) => {
|
|
600
617
|
this._sendMessage({ data: _data, type: _type });
|
|
601
618
|
});
|
|
@@ -691,7 +708,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
691
708
|
const shouldFlatten = yield this._storageStore.getShouldFlatten();
|
|
692
709
|
if (!shouldFlatten) return;
|
|
693
710
|
const updates = yield this._storageStore.getUpdates();
|
|
694
|
-
this._crdtManager.applyUpdate(updates, this);
|
|
711
|
+
this._crdtManager.applyUpdate({ update: updates, origin: this });
|
|
695
712
|
const encodedState = this._crdtManager.doc.getEncodedState();
|
|
696
713
|
yield this._storageStore.flatten(encodedState);
|
|
697
714
|
}),
|
|
@@ -718,7 +735,11 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
718
735
|
}
|
|
719
736
|
};
|
|
720
737
|
this._router = router != null ? router : new PluvRouter({});
|
|
721
|
-
this._usersManager = new UsersManager({
|
|
738
|
+
this._usersManager = new UsersManager({
|
|
739
|
+
initialPresence,
|
|
740
|
+
limits: this._limits,
|
|
741
|
+
presence
|
|
742
|
+
});
|
|
722
743
|
this._crdtManager = new CrdtManager({ initialStorage });
|
|
723
744
|
}
|
|
724
745
|
get storageLoaded() {
|
|
@@ -733,9 +754,11 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
733
754
|
const params = (_a = args[0]) != null ? _a : {};
|
|
734
755
|
const metadata = params.metadata;
|
|
735
756
|
this._setMetadata(metadata);
|
|
736
|
-
const canConnect = [
|
|
737
|
-
|
|
738
|
-
|
|
757
|
+
const canConnect = [
|
|
758
|
+
ConnectionState.Closed,
|
|
759
|
+
ConnectionState.Unavailable,
|
|
760
|
+
ConnectionState.Untouched
|
|
761
|
+
].some((state) => this._state.connection.state === state);
|
|
739
762
|
if (!canConnect) return;
|
|
740
763
|
this._clearTimeout(this._timeouts.reconnect);
|
|
741
764
|
this._updateState((oldState) => {
|
|
@@ -743,7 +766,6 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
743
766
|
return oldState;
|
|
744
767
|
});
|
|
745
768
|
yield this._storageStore.initialize();
|
|
746
|
-
yield this._applyStorageStore();
|
|
747
769
|
const wsEndpoint = this._getWsEndpoint(this.id, params);
|
|
748
770
|
const url = new URL(wsEndpoint);
|
|
749
771
|
let authToken = null;
|
|
@@ -808,20 +830,6 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
808
830
|
this._flattenStorageStore();
|
|
809
831
|
});
|
|
810
832
|
}
|
|
811
|
-
_applyStorageStore() {
|
|
812
|
-
return __async(this, null, function* () {
|
|
813
|
-
const updates = yield this._storageStore.getUpdates();
|
|
814
|
-
this._crdtManager.initialize({
|
|
815
|
-
onInitialized: () => {
|
|
816
|
-
this._stateNotifier.subjects["storage-loaded"].next(true);
|
|
817
|
-
this._observeCrdt();
|
|
818
|
-
},
|
|
819
|
-
origin: ORIGIN_INITIALIZED,
|
|
820
|
-
update: updates
|
|
821
|
-
});
|
|
822
|
-
this._emitSharedTypes();
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
833
|
_attachWindowListeners() {
|
|
826
834
|
if (typeof window === "undefined") return;
|
|
827
835
|
if (typeof document === "undefined") return;
|
|
@@ -1012,32 +1020,35 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1012
1020
|
this._stateNotifier.subjects.others.next(others);
|
|
1013
1021
|
}
|
|
1014
1022
|
_handleRegisteredMessage(message) {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
oldState
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1023
|
+
return __async(this, null, function* () {
|
|
1024
|
+
var _a, _b, _c;
|
|
1025
|
+
const { connectionId } = message;
|
|
1026
|
+
const user = message.user;
|
|
1027
|
+
if (!connectionId) return;
|
|
1028
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1029
|
+
const data = message.data;
|
|
1030
|
+
const state = data.state;
|
|
1031
|
+
this._updateState((oldState) => {
|
|
1032
|
+
oldState.connection.count += 1;
|
|
1033
|
+
oldState.connection.id = connectionId;
|
|
1034
|
+
oldState.authorization.user = user != null ? user : null;
|
|
1035
|
+
return oldState;
|
|
1036
|
+
});
|
|
1037
|
+
const userInfo = {
|
|
1038
|
+
connectionId,
|
|
1039
|
+
presence: (_a = data.presence) != null ? _a : void 0,
|
|
1040
|
+
user
|
|
1041
|
+
};
|
|
1042
|
+
this._usersManager.setMyself(userInfo);
|
|
1043
|
+
const presence = this._usersManager.myPresence;
|
|
1044
|
+
const myself = (_b = this._usersManager.myself) != null ? _b : null;
|
|
1045
|
+
this._stateNotifier.subjects["my-presence"].next(presence);
|
|
1046
|
+
this._stateNotifier.subjects.myself.next(myself);
|
|
1047
|
+
const update = !state ? (_c = this._crdtManager.resolveEncodedState(yield this._storageStore.getUpdates())) != null ? _c : this._crdtManager.getInitialState() : this._crdtManager.getInitialState();
|
|
1048
|
+
this._sendMessage({
|
|
1049
|
+
type: "$initializeSession",
|
|
1050
|
+
data: { presence, update }
|
|
1051
|
+
});
|
|
1041
1052
|
});
|
|
1042
1053
|
}
|
|
1043
1054
|
_handleStorageReceivedMessage(message) {
|
|
@@ -1046,23 +1057,34 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1046
1057
|
if (!connectionId) return;
|
|
1047
1058
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1048
1059
|
const data = message.data;
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1060
|
+
const changeKind = data.changeKind;
|
|
1061
|
+
const state = data.state;
|
|
1062
|
+
const updates = yield this._storageStore.getUpdates();
|
|
1063
|
+
const origin = ORIGIN_INITIALIZED;
|
|
1064
|
+
const updateStorage = (update) => {
|
|
1065
|
+
return this._crdtManager.initialized ? this._crdtManager.applyUpdate({ update, origin }) : this._crdtManager.initialize({ update, origin });
|
|
1066
|
+
};
|
|
1067
|
+
if (changeKind === "empty") {
|
|
1068
|
+
this._crdtManager.destroy();
|
|
1069
|
+
this._crdtManager.initialize({ origin, update: updates });
|
|
1070
|
+
} else if (changeKind === "initialized") {
|
|
1071
|
+
if (!!updates.length) updateStorage(updates);
|
|
1072
|
+
else {
|
|
1073
|
+
const encodedState2 = updateStorage(state).doc.getEncodedState();
|
|
1074
|
+
this._addToStorageStore(encodedState2);
|
|
1075
|
+
}
|
|
1076
|
+
} else {
|
|
1077
|
+
const encodedState2 = updateStorage(state).doc.getEncodedState();
|
|
1078
|
+
if (!!updates.length) this._crdtManager.applyUpdate({ update: updates, origin });
|
|
1079
|
+
yield this._addToStorageStore(encodedState2);
|
|
1080
|
+
}
|
|
1059
1081
|
const encodedState = this._crdtManager.doc.getEncodedState();
|
|
1060
|
-
yield this._storageStore.addUpdate(encodedState);
|
|
1061
1082
|
this._emitSharedTypes();
|
|
1062
|
-
|
|
1083
|
+
this._observeCrdt();
|
|
1084
|
+
this._stateNotifier.subjects["storage-loaded"].next(true);
|
|
1063
1085
|
this._sendMessage({
|
|
1064
1086
|
type: "$updateStorage",
|
|
1065
|
-
data: { origin
|
|
1087
|
+
data: { origin, update: encodedState }
|
|
1066
1088
|
});
|
|
1067
1089
|
});
|
|
1068
1090
|
}
|
|
@@ -1071,7 +1093,6 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1071
1093
|
if (!connectionId) return;
|
|
1072
1094
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1073
1095
|
const data = message.data;
|
|
1074
|
-
if (!this._crdtManager) return;
|
|
1075
1096
|
this._crdtManager.doc.applyEncodedState({
|
|
1076
1097
|
origin: ORIGIN_STORAGE_UPDATED,
|
|
1077
1098
|
update: data.state
|
|
@@ -1079,7 +1100,6 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1079
1100
|
const sharedTypes = this._crdtManager.doc.get();
|
|
1080
1101
|
const storageRoot = Object.keys(sharedTypes).reduce(
|
|
1081
1102
|
(acc, prop) => {
|
|
1082
|
-
if (!this._crdtManager) return acc;
|
|
1083
1103
|
const serialized = this._crdtManager.doc.toJson(prop);
|
|
1084
1104
|
this._crdtNotifier.subject(prop).next(serialized);
|
|
1085
1105
|
return __spreadProps(__spreadValues({}, acc), { [prop]: serialized });
|
|
@@ -1126,7 +1146,10 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1126
1146
|
}
|
|
1127
1147
|
_heartbeat() {
|
|
1128
1148
|
this._clearTimeout(this._timeouts.pong);
|
|
1129
|
-
this._timeouts.pong = setTimeout(
|
|
1149
|
+
this._timeouts.pong = setTimeout(
|
|
1150
|
+
this._reconnect.bind(this),
|
|
1151
|
+
PONG_TIMEOUT_MS
|
|
1152
|
+
);
|
|
1130
1153
|
this._sendMessage('{"type":"$ping","data":{}}');
|
|
1131
1154
|
}
|
|
1132
1155
|
_logDebug(...data) {
|
|
@@ -1137,15 +1160,14 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1137
1160
|
_observeCrdt() {
|
|
1138
1161
|
var _a, _b;
|
|
1139
1162
|
(_b = (_a = this._subscriptions).observeCrdt) == null ? void 0 : _b.call(_a);
|
|
1140
|
-
if (!this._crdtManager) return;
|
|
1141
1163
|
const unsubscribe = this._crdtManager.doc.subscribe((event) => {
|
|
1142
1164
|
var _a2;
|
|
1143
1165
|
const origin = (_a2 = event.origin) != null ? _a2 : null;
|
|
1144
|
-
this._addToStorageStore(event.update);
|
|
1145
1166
|
this._emitSharedTypes();
|
|
1146
1167
|
if (origin === ORIGIN_INITIALIZED || origin === ORIGIN_STORAGE_UPDATED) {
|
|
1147
1168
|
return;
|
|
1148
1169
|
}
|
|
1170
|
+
this._addToStorageStore(event.update);
|
|
1149
1171
|
if (!this._state.webSocket) return;
|
|
1150
1172
|
if (!this._state.connection.id) return;
|
|
1151
1173
|
if (this._state.webSocket.readyState !== WebSocket.OPEN) return;
|
|
@@ -1267,7 +1289,10 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1267
1289
|
return oldState;
|
|
1268
1290
|
});
|
|
1269
1291
|
this._clearInterval(this._intervals.heartbeat);
|
|
1270
|
-
this._intervals.heartbeat = setInterval(
|
|
1292
|
+
this._intervals.heartbeat = setInterval(
|
|
1293
|
+
this._heartbeat.bind(this),
|
|
1294
|
+
HEARTBEAT_INTERVAL_MS
|
|
1295
|
+
);
|
|
1271
1296
|
}
|
|
1272
1297
|
_onNavigatorOffline() {
|
|
1273
1298
|
return __async(this, null, function* () {
|
|
@@ -1322,8 +1347,14 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1322
1347
|
_pollReconnect() {
|
|
1323
1348
|
this._clearTimeout(this._timeouts.reconnect);
|
|
1324
1349
|
const timeoutMs = typeof this._reconnectTimeoutMs === "number" ? this._reconnectTimeoutMs : this._reconnectTimeoutMs({ attempts: this._state.connection.attempts });
|
|
1325
|
-
const clampedMs = Math.max(
|
|
1326
|
-
|
|
1350
|
+
const clampedMs = Math.max(
|
|
1351
|
+
Math.min(MIN_RECONNECT_TIMEOUT_MS, timeoutMs),
|
|
1352
|
+
MAX_RECONNECT_TIMEOUT_MS
|
|
1353
|
+
);
|
|
1354
|
+
this._timeouts.reconnect = setTimeout(
|
|
1355
|
+
this._reconnect.bind(this),
|
|
1356
|
+
clampedMs
|
|
1357
|
+
);
|
|
1327
1358
|
}
|
|
1328
1359
|
_reconnect() {
|
|
1329
1360
|
return __async(this, null, function* () {
|
|
@@ -1522,10 +1553,7 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1522
1553
|
room: this.id,
|
|
1523
1554
|
user: myself
|
|
1524
1555
|
};
|
|
1525
|
-
const output = yield procedure.config.broadcast(
|
|
1526
|
-
parsed,
|
|
1527
|
-
context
|
|
1528
|
-
);
|
|
1556
|
+
const output = yield procedure.config.broadcast(parsed, context);
|
|
1529
1557
|
Object.entries(output).forEach(([_type, _data]) => {
|
|
1530
1558
|
this._simulateEvent(_type, _data);
|
|
1531
1559
|
});
|
|
@@ -1616,7 +1644,11 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1616
1644
|
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1617
1645
|
}, limits);
|
|
1618
1646
|
this._router = router != null ? router : new PluvRouter({});
|
|
1619
|
-
this._usersManager = new UsersManager({
|
|
1647
|
+
this._usersManager = new UsersManager({
|
|
1648
|
+
initialPresence,
|
|
1649
|
+
limits: this._limits,
|
|
1650
|
+
presence
|
|
1651
|
+
});
|
|
1620
1652
|
this._crdtManager = new CrdtManager({
|
|
1621
1653
|
initialStorage
|
|
1622
1654
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "@pluv/io frontend client",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"wonka": "^6.3.5",
|
|
21
|
-
"@pluv/crdt": "^1.0.
|
|
22
|
-
"@pluv/types": "^1.0.
|
|
21
|
+
"@pluv/crdt": "^1.0.2",
|
|
22
|
+
"@pluv/types": "^1.0.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"eslint": "^9.25.1",
|
|
26
26
|
"tsup": "^8.4.0",
|
|
27
27
|
"typescript": "^5.8.3",
|
|
28
|
-
"@pluv/tsconfig": "^1.0.
|
|
29
|
-
"eslint-config-pluv": "^1.0.
|
|
28
|
+
"@pluv/tsconfig": "^1.0.2",
|
|
29
|
+
"eslint-config-pluv": "^1.0.2"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
package/src/AbstractRoom.ts
CHANGED
|
@@ -49,7 +49,9 @@ export abstract class AbstractRoom<
|
|
|
49
49
|
public abstract getStorage<TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null;
|
|
50
50
|
|
|
51
51
|
public abstract getStorageJson(): InferCrdtJson<TStorage> | null;
|
|
52
|
-
public abstract getStorageJson<TKey extends keyof TStorage>(
|
|
52
|
+
public abstract getStorageJson<TKey extends keyof TStorage>(
|
|
53
|
+
type: TKey,
|
|
54
|
+
): InferCrdtJson<TStorage[TKey]> | null;
|
|
53
55
|
|
|
54
56
|
public abstract other(
|
|
55
57
|
connectionId: string,
|
package/src/CrdtManager.ts
CHANGED
|
@@ -2,15 +2,18 @@ import type { AbstractCrdtDoc, AbstractCrdtDocFactory, CrdtType } from "@pluv/cr
|
|
|
2
2
|
import { noop } from "@pluv/crdt";
|
|
3
3
|
|
|
4
4
|
export type CrdtManagerOptions<TStorage extends Record<string, CrdtType<any, any>> = {}> = {
|
|
5
|
-
encodedState?: string | Uint8Array | null;
|
|
6
5
|
initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
7
6
|
};
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
onInitialized?: (state: string) => void;
|
|
8
|
+
interface CrdtManagerInitializeParams {
|
|
11
9
|
origin?: any;
|
|
12
10
|
update: string | readonly string[];
|
|
13
|
-
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ApplyUpdateParams {
|
|
14
|
+
origin?: any;
|
|
15
|
+
update: string | readonly string[];
|
|
16
|
+
}
|
|
14
17
|
|
|
15
18
|
export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
|
|
16
19
|
public doc: AbstractCrdtDoc<TStorage>;
|
|
@@ -19,13 +22,10 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
|
|
|
19
22
|
private readonly _docFactory: AbstractCrdtDocFactory<TStorage>;
|
|
20
23
|
|
|
21
24
|
constructor(options: CrdtManagerOptions<TStorage>) {
|
|
22
|
-
const {
|
|
25
|
+
const { initialStorage = noop.doc({}) as AbstractCrdtDocFactory<TStorage> } = options;
|
|
23
26
|
|
|
24
27
|
this._docFactory = initialStorage;
|
|
25
|
-
|
|
26
|
-
this.doc = encodedState
|
|
27
|
-
? initialStorage.getFresh().applyEncodedState({ update: encodedState })
|
|
28
|
-
: initialStorage.getFresh();
|
|
28
|
+
this.doc = initialStorage.getEmpty();
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -34,7 +34,9 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
|
|
|
34
34
|
* update. This unfortunately means updates can be dropped entirely
|
|
35
35
|
* @date June 13, 2023
|
|
36
36
|
*/
|
|
37
|
-
public applyUpdate(
|
|
37
|
+
public applyUpdate(params: ApplyUpdateParams): this {
|
|
38
|
+
const { origin, update } = params;
|
|
39
|
+
|
|
38
40
|
if (!this.initialized) return this;
|
|
39
41
|
|
|
40
42
|
const updates: readonly string[] = typeof update === "string" ? [update] : update;
|
|
@@ -45,48 +47,68 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
public destroy(): void {
|
|
50
|
+
this.initialized = false;
|
|
48
51
|
this.doc.destroy();
|
|
52
|
+
this.doc = this._docFactory.getEmpty();
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
public get<TKey extends keyof TStorage>(key: TKey): TStorage[TKey] {
|
|
52
56
|
return this.doc.get(key);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
public
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const updates = typeof update === "string" ? [update] : update;
|
|
59
|
-
|
|
60
|
-
if (!updates.length) return this;
|
|
59
|
+
public getInitialState(): string {
|
|
60
|
+
return this._docFactory.getInitialized().getEncodedState();
|
|
61
|
+
}
|
|
61
62
|
|
|
63
|
+
public initialize(params: CrdtManagerInitializeParams): this {
|
|
64
|
+
const { origin, update } = params;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* !HACK
|
|
68
|
+
* @description If the doc was already initialized, we don't want to re-initialize
|
|
69
|
+
* the doc and overwrite what's already there. Skip the initialization and log a
|
|
70
|
+
* warning.
|
|
71
|
+
* @date May 7, 2025
|
|
72
|
+
*/
|
|
62
73
|
if (this.initialized) {
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
console.warn("Attempted to initialize storage multiple times");
|
|
65
75
|
return this;
|
|
66
76
|
}
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
/**
|
|
79
|
+
* !HACK
|
|
80
|
+
* @description If the doc is already populated, somehow we're attempting to overwrite
|
|
81
|
+
* a doc that was presumably initialized but not identified as initialized. Skip the
|
|
82
|
+
* initialization and log a warning.
|
|
83
|
+
* @date May 7, 2025
|
|
84
|
+
*/
|
|
85
|
+
if (!this.doc.isEmpty()) {
|
|
86
|
+
console.warn("Attempted to initialize over a non-empty storage");
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
72
89
|
|
|
73
|
-
|
|
74
|
-
this.initialized = true;
|
|
90
|
+
const updates = typeof update === "string" ? [update] : update;
|
|
75
91
|
|
|
76
|
-
|
|
92
|
+
if (!updates.length) return this;
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
const initialized = this._docFactory.getInitialized();
|
|
95
|
+
const reference = initialized.get();
|
|
80
96
|
|
|
81
|
-
|
|
82
|
-
|
|
97
|
+
this.doc = this._applyDocUpdates(
|
|
98
|
+
this._docFactory.getEmpty(),
|
|
99
|
+
updates,
|
|
100
|
+
origin,
|
|
101
|
+
).rebuildStorage(reference);
|
|
83
102
|
this.initialized = true;
|
|
84
|
-
|
|
85
|
-
onInitialized?.(this.doc.getEncodedState());
|
|
103
|
+
initialized.destroy();
|
|
86
104
|
|
|
87
105
|
return this;
|
|
88
106
|
}
|
|
89
107
|
|
|
108
|
+
public resolveEncodedState(updates: string | string[] | readonly string[]): string | null {
|
|
109
|
+
return this._docFactory.resolveEncodedState(updates);
|
|
110
|
+
}
|
|
111
|
+
|
|
90
112
|
private _applyDocUpdates(
|
|
91
113
|
doc: AbstractCrdtDoc<TStorage>,
|
|
92
114
|
updates: readonly string[],
|
package/src/CrdtNotifier.ts
CHANGED
|
@@ -7,7 +7,10 @@ export class CrdtNotifier<TStorage extends Record<string, CrdtType<any, any>>> {
|
|
|
7
7
|
[P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
|
|
8
8
|
}>();
|
|
9
9
|
|
|
10
|
-
public subjects = new Map<
|
|
10
|
+
public subjects = new Map<
|
|
11
|
+
keyof TStorage,
|
|
12
|
+
Subject<InferCrdtJson<TStorage[keyof TStorage]>> | null
|
|
13
|
+
>();
|
|
11
14
|
|
|
12
15
|
public subject<TKey extends keyof TStorage>(key: TKey): Subject<InferCrdtJson<TStorage[TKey]>> {
|
|
13
16
|
const subject = this.subjects.get(key);
|
package/src/EventNotifier.ts
CHANGED
|
@@ -2,14 +2,20 @@ import type { Id, InferIOOutput, IOEventMessage, IOLike } from "@pluv/types";
|
|
|
2
2
|
import type { Subject } from "wonka";
|
|
3
3
|
import { makeSubject, subscribe } from "wonka";
|
|
4
4
|
|
|
5
|
-
export type EventNotifierSubscriptionCallback<
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export type EventNotifierSubscriptionCallback<
|
|
6
|
+
TIO extends IOLike<any, any>,
|
|
7
|
+
TEvent extends keyof InferIOOutput<TIO>,
|
|
8
|
+
> = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
|
|
8
9
|
|
|
9
10
|
export class EventNotifier<TIO extends IOLike> {
|
|
10
|
-
public subjects = new Map<
|
|
11
|
+
public subjects = new Map<
|
|
12
|
+
keyof InferIOOutput<TIO>,
|
|
13
|
+
Subject<Id<IOEventMessage<TIO, keyof InferIOOutput<TIO>>>>
|
|
14
|
+
>();
|
|
11
15
|
|
|
12
|
-
public subject<TEvent extends keyof InferIOOutput<TIO>>(
|
|
16
|
+
public subject<TEvent extends keyof InferIOOutput<TIO>>(
|
|
17
|
+
name: TEvent,
|
|
18
|
+
): Subject<Id<IOEventMessage<TIO, TEvent>>> {
|
|
13
19
|
const subject = this.subjects.get(name);
|
|
14
20
|
|
|
15
21
|
if (subject) return subject;
|