@pluv/client 0.18.0 → 0.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/client",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
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.4",
21
- "@pluv/crdt": "^0.18.0",
22
- "@pluv/types": "^0.18.0"
21
+ "@pluv/crdt": "^0.20.0",
22
+ "@pluv/types": "^0.20.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "eslint": "^8.57.0",
26
- "tsup": "^8.0.2",
26
+ "tsup": "^8.1.0",
27
27
  "typescript": "^5.4.5",
28
- "@pluv/tsconfig": "^0.18.0",
29
- "eslint-config-pluv": "^0.18.0"
28
+ "@pluv/tsconfig": "^0.20.0",
29
+ "eslint-config-pluv": "^0.20.0"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup src/index.ts --format esm,cjs --dts",
@@ -1,14 +1,14 @@
1
- import type { AbstractCrdtDoc, AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
1
+ import type { AbstractCrdtDoc, CrdtType, InferCrdtJson } from "@pluv/crdt";
2
2
  import type { IOLike, Id, InferIOInput, InferIOOutput, JsonObject } from "@pluv/types";
3
3
  import type { EventNotifierSubscriptionCallback } from "./EventNotifier";
4
4
  import type { OtherNotifierSubscriptionCallback } from "./OtherNotifier";
5
5
  import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifier";
6
- import type { UserInfo, WebSocketConnection } from "./types";
6
+ import type { UpdateMyPresenceAction, UserInfo, WebSocketConnection } from "./types";
7
7
 
8
8
  export abstract class AbstractRoom<
9
9
  TIO extends IOLike,
10
10
  TPresence extends JsonObject = {},
11
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
11
+ TStorage extends Record<string, CrdtType<any, any>> = {},
12
12
  > {
13
13
  public readonly id: string;
14
14
 
@@ -44,7 +44,10 @@ export abstract class AbstractRoom<
44
44
 
45
45
  public abstract getOthers(): readonly Id<UserInfo<TIO, TPresence>>[];
46
46
 
47
- public abstract getStorage<TKey extends keyof TStorage>(key: TKey): TStorage[TKey] | null;
47
+ public abstract getStorage<TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null;
48
+
49
+ public abstract getStorageJson(): InferCrdtJson<TStorage> | null;
50
+ public abstract getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
48
51
 
49
52
  public abstract other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): () => void;
50
53
 
@@ -52,12 +55,12 @@ export abstract class AbstractRoom<
52
55
 
53
56
  public abstract storage<TKey extends keyof TStorage>(
54
57
  key: TKey,
55
- fn: (value: InferCrdtStorageJson<TStorage[TKey]>) => void,
58
+ fn: (value: InferCrdtJson<TStorage[TKey]>) => void,
56
59
  ): () => void;
57
60
 
58
61
  public abstract storageRoot(
59
62
  fn: (value: {
60
- [P in keyof TStorage]: InferCrdtStorageJson<TStorage[P]>;
63
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
61
64
  }) => void,
62
65
  ): () => void;
63
66
 
@@ -70,5 +73,5 @@ export abstract class AbstractRoom<
70
73
 
71
74
  public abstract undo(): void;
72
75
 
73
- public abstract updateMyPresence(presence: Partial<TPresence>): void;
76
+ public abstract updateMyPresence(presence: UpdateMyPresenceAction<TPresence>): void;
74
77
  }
@@ -1,7 +1,7 @@
1
- import type { AbstractCrdtDoc, AbstractCrdtDocFactory, AbstractCrdtType } from "@pluv/crdt";
1
+ import type { AbstractCrdtDoc, AbstractCrdtDocFactory, CrdtType } from "@pluv/crdt";
2
2
  import { noop } from "@pluv/crdt";
3
3
 
4
- export type CrdtManagerOptions<TStorage extends Record<string, AbstractCrdtType<any, any>> = {}> = {
4
+ export type CrdtManagerOptions<TStorage extends Record<string, CrdtType<any, any>> = {}> = {
5
5
  encodedState?: string | Uint8Array | null;
6
6
  initialStorage?: AbstractCrdtDocFactory<TStorage>;
7
7
  };
@@ -12,7 +12,7 @@ type CrdtManagerInitializeParams = {
12
12
  update: string | readonly string[];
13
13
  };
14
14
 
15
- export class CrdtManager<TStorage extends Record<string, AbstractCrdtType<any, any>>> {
15
+ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
16
16
  public doc: AbstractCrdtDoc<TStorage>;
17
17
  public initialized: boolean = false;
18
18
 
@@ -1,31 +1,31 @@
1
- import type { AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
1
+ import type { CrdtType, InferCrdtJson } from "@pluv/crdt";
2
2
  import type { Subject } from "wonka";
3
3
  import { makeSubject, subscribe } from "wonka";
4
4
 
5
- export class CrdtNotifier<TStorage extends Record<string, AbstractCrdtType<any, any>>> {
5
+ export class CrdtNotifier<TStorage extends Record<string, CrdtType<any, any>>> {
6
6
  public rootSubject = makeSubject<{
7
- [P in keyof TStorage]: InferCrdtStorageJson<TStorage[P]>;
7
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
8
8
  }>();
9
9
 
10
- public subjects = new Map<keyof TStorage, Subject<InferCrdtStorageJson<TStorage[keyof TStorage]>> | null>();
10
+ public subjects = new Map<keyof TStorage, Subject<InferCrdtJson<TStorage[keyof TStorage]>> | null>();
11
11
 
12
- public subject<TKey extends keyof TStorage>(key: TKey): Subject<InferCrdtStorageJson<TStorage[TKey]>> {
12
+ public subject<TKey extends keyof TStorage>(key: TKey): Subject<InferCrdtJson<TStorage[TKey]>> {
13
13
  const subject = this.subjects.get(key);
14
14
 
15
15
  if (subject) {
16
- return subject as Subject<InferCrdtStorageJson<TStorage[TKey]>>;
16
+ return subject as Subject<InferCrdtJson<TStorage[TKey]>>;
17
17
  }
18
18
 
19
- const newSubject = makeSubject<InferCrdtStorageJson<TStorage[TKey]>>();
19
+ const newSubject = makeSubject<InferCrdtJson<TStorage[TKey]>>();
20
20
 
21
21
  this.subjects.set(key, newSubject);
22
22
 
23
- return newSubject as Subject<InferCrdtStorageJson<TStorage[TKey]>>;
23
+ return newSubject as Subject<InferCrdtJson<TStorage[TKey]>>;
24
24
  }
25
25
 
26
26
  public subcribeRoot(
27
27
  callback: (value: {
28
- [P in keyof TStorage]: InferCrdtStorageJson<TStorage[P]>;
28
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
29
29
  }) => void,
30
30
  ): () => void {
31
31
  return subscribe(callback)(this.rootSubject.source).unsubscribe;
@@ -33,7 +33,7 @@ export class CrdtNotifier<TStorage extends Record<string, AbstractCrdtType<any,
33
33
 
34
34
  public subscribe<TKey extends keyof TStorage>(
35
35
  key: TKey,
36
- callback: (value: InferCrdtStorageJson<TStorage[TKey]>) => void,
36
+ callback: (value: InferCrdtJson<TStorage[TKey]>) => void,
37
37
  ): () => void {
38
38
  const subject = this.subject(key);
39
39
 
package/src/MockedRoom.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AbstractCrdtDoc, AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
1
+ import type { AbstractCrdtDoc, CrdtType, InferCrdtJson } from "@pluv/crdt";
2
2
  import type { IOLike, Id, InferIOInput, InferIOOutput, JsonObject } from "@pluv/types";
3
3
  import { AbstractRoom } from "./AbstractRoom";
4
4
  import type { CrdtManagerOptions } from "./CrdtManager";
@@ -12,7 +12,13 @@ import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifie
12
12
  import { StateNotifier } from "./StateNotifier";
13
13
  import type { UsersManagerConfig } from "./UsersManager";
14
14
  import { UsersManager } from "./UsersManager";
15
- import type { InternalSubscriptions, UserInfo, WebSocketConnection, WebSocketState } from "./types";
15
+ import type {
16
+ InternalSubscriptions,
17
+ UpdateMyPresenceAction,
18
+ UserInfo,
19
+ WebSocketConnection,
20
+ WebSocketState,
21
+ } from "./types";
16
22
  import { ConnectionState } from "./types";
17
23
 
18
24
  export type MockedRoomEvents<TIO extends IOLike> = Partial<{
@@ -22,14 +28,14 @@ export type MockedRoomEvents<TIO extends IOLike> = Partial<{
22
28
  export type MockedRoomConfig<
23
29
  TIO extends IOLike,
24
30
  TPresence extends JsonObject = {},
25
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
31
+ TStorage extends Record<string, CrdtType<any, any>> = {},
26
32
  > = { events?: MockedRoomEvents<TIO> } & Omit<CrdtManagerOptions<TStorage>, "encodedState"> &
27
33
  UsersManagerConfig<TPresence>;
28
34
 
29
35
  export class MockedRoom<
30
36
  TIO extends IOLike,
31
37
  TPresence extends JsonObject = {},
32
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
38
+ TStorage extends Record<string, CrdtType<any, any>> = {},
33
39
  > extends AbstractRoom<TIO, TPresence, TStorage> {
34
40
  private _crdtManager: CrdtManager<TStorage>;
35
41
  private _crdtNotifier = new CrdtNotifier<TStorage>();
@@ -111,10 +117,10 @@ export class MockedRoom<
111
117
  return this._eventNotifier.subscribe(event, callback);
112
118
  };
113
119
 
114
- public getConnection(): WebSocketConnection {
120
+ public getConnection = (): WebSocketConnection => {
115
121
  // Create a read-only clone of the connection state
116
122
  return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
117
- }
123
+ };
118
124
 
119
125
  public getDoc(): AbstractCrdtDoc<TStorage> {
120
126
  return this._crdtManager.doc;
@@ -136,14 +142,24 @@ export class MockedRoom<
136
142
  return this._usersManager.getOthers();
137
143
  };
138
144
 
139
- public getStorage = <TKey extends keyof TStorage>(key: TKey): TStorage[TKey] | null => {
140
- const sharedType = this._crdtManager.get(key);
145
+ public getStorage = <TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null => {
146
+ const sharedType = this._crdtManager.get(type);
141
147
 
142
148
  if (typeof sharedType === "undefined") return null;
143
149
 
144
- return sharedType.value;
150
+ return sharedType;
145
151
  };
146
152
 
153
+ public getStorageJson(): InferCrdtJson<TStorage> | null;
154
+ public getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
155
+ public getStorageJson<TKey extends keyof TStorage>(type?: TKey) {
156
+ if (this._state.connection.id === null) return null;
157
+
158
+ if (typeof type === "undefined") return this._crdtManager.doc.toJson();
159
+
160
+ return this._crdtManager.doc.toJson(type);
161
+ }
162
+
147
163
  public other = (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): (() => void) => {
148
164
  return this._otherNotifier.subscribe(connectionId, callback);
149
165
  };
@@ -154,14 +170,14 @@ export class MockedRoom<
154
170
 
155
171
  public storage = <TKey extends keyof TStorage>(
156
172
  key: TKey,
157
- fn: (value: InferCrdtStorageJson<TStorage[TKey]>) => void,
173
+ fn: (value: InferCrdtJson<TStorage[TKey]>) => void,
158
174
  ): (() => void) => {
159
175
  return this._crdtNotifier.subscribe(key, fn);
160
176
  };
161
177
 
162
178
  public storageRoot = (
163
179
  fn: (value: {
164
- [P in keyof TStorage]: InferCrdtStorageJson<TStorage[P]>;
180
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
165
181
  }) => void,
166
182
  ): (() => void) => {
167
183
  return this._crdtNotifier.subcribeRoot(fn);
@@ -199,8 +215,10 @@ export class MockedRoom<
199
215
  this._crdtManager.doc.undo();
200
216
  };
201
217
 
202
- public updateMyPresence = (presence: Partial<TPresence>): void => {
203
- this._usersManager.updateMyPresence(presence);
218
+ public updateMyPresence = (presence: UpdateMyPresenceAction<TPresence>): void => {
219
+ const newPresence = typeof presence === "function" ? presence(this.getMyPresence()) : presence;
220
+
221
+ this._usersManager.updateMyPresence(newPresence);
204
222
 
205
223
  const myPresence = this._usersManager.myPresence;
206
224
  const myself = this._usersManager.myself ?? null;
@@ -222,18 +240,18 @@ export class MockedRoom<
222
240
 
223
241
  const sharedTypes = this._crdtManager.doc.get();
224
242
 
225
- const storageRoot = Object.entries(sharedTypes).reduce(
226
- (acc, [prop, sharedType]) => {
243
+ const storageRoot = Object.keys(sharedTypes).reduce(
244
+ (acc, prop) => {
227
245
  if (!this._crdtManager) return acc;
228
246
 
229
- const serialized = sharedType.toJson();
247
+ const serialized = this._crdtManager.doc.toJson(prop);
230
248
 
231
249
  this._crdtNotifier.subject(prop).next(serialized);
232
250
 
233
251
  return { ...acc, [prop]: serialized };
234
252
  },
235
253
  {} as {
236
- [P in keyof TStorage]: InferCrdtStorageJson<TStorage[P]>;
254
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
237
255
  },
238
256
  );
239
257
 
@@ -3,16 +3,12 @@ import type { Subject } from "wonka";
3
3
  import { makeSubject, subscribe } from "wonka";
4
4
  import type { UserInfo } from "./types";
5
5
 
6
- export type OtherNotifierSubscriptionCallback<TIO extends IOLike> = (
7
- value: Id<UserInfo<TIO>> | null,
8
- ) => void;
6
+ export type OtherNotifierSubscriptionCallback<TIO extends IOLike> = (value: Id<UserInfo<TIO>> | null) => void;
9
7
 
10
8
  export class OtherNotifier<TIO extends IOLike> {
11
9
  public subjects = new Map<string, Subject<Id<UserInfo<TIO>> | null>>();
12
10
 
13
- public subject<TEvent extends string>(
14
- name: TEvent,
15
- ): Subject<Id<UserInfo<TIO>> | null> {
11
+ public subject<TEvent extends string>(name: TEvent): Subject<Id<UserInfo<TIO>> | null> {
16
12
  const subject = this.subjects.get(name);
17
13
 
18
14
  if (subject) return subject;
package/src/PluvClient.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { AbstractCrdtType } from "@pluv/crdt";
1
+ import type { CrdtType } from "@pluv/crdt";
2
2
  import type { IOLike, JsonObject } from "@pluv/types";
3
- import { AuthEndpoint, PluvRoom, PluvRoomOptions, RoomConfig, RoomEndpoints, WsEndpoint } from "./PluvRoom";
3
+ import type { AuthEndpoint, PluvRoomOptions, RoomConfig, RoomEndpoints, WsEndpoint } from "./PluvRoom";
4
+ import { PluvRoom } from "./PluvRoom";
4
5
 
5
6
  export type PluvClientOptions<TIO extends IOLike> = RoomEndpoints<TIO> & {
6
7
  debug?: boolean;
@@ -21,10 +22,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
21
22
  this._wsEndpoint = wsEndpoint;
22
23
  }
23
24
 
24
- public createRoom = <
25
- TPresence extends JsonObject = {},
26
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
27
- >(
25
+ public createRoom = <TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}>(
28
26
  room: string,
29
27
  options: PluvRoomOptions<TIO, TPresence, TStorage>,
30
28
  ): PluvRoom<TIO, TPresence, TStorage> => {
@@ -65,10 +63,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
65
63
  return toEnter;
66
64
  };
67
65
 
68
- public getRoom = <
69
- TPresence extends JsonObject = {},
70
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
71
- >(
66
+ public getRoom = <TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}>(
72
67
  room: string,
73
68
  ): PluvRoom<TIO, TPresence, TStorage> | null => {
74
69
  const found = this._rooms.get(room) as PluvRoom<TIO, TPresence, TStorage> | undefined;
package/src/PluvRoom.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AbstractCrdtDoc, AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
1
+ import type { AbstractCrdtDoc, CrdtType, InferCrdtJson } from "@pluv/crdt";
2
2
  import type {
3
3
  BaseIOEventRecord,
4
4
  EventMessage,
@@ -26,7 +26,14 @@ import { StateNotifier } from "./StateNotifier";
26
26
  import { StorageStore } from "./StorageStore";
27
27
  import type { UsersManagerConfig } from "./UsersManager";
28
28
  import { UsersManager } from "./UsersManager";
29
- import type { AuthorizationState, InternalSubscriptions, UserInfo, WebSocketConnection, WebSocketState } from "./types";
29
+ import type {
30
+ AuthorizationState,
31
+ InternalSubscriptions,
32
+ UpdateMyPresenceAction,
33
+ UserInfo,
34
+ WebSocketConnection,
35
+ WebSocketState,
36
+ } from "./types";
30
37
  import { ConnectionState } from "./types";
31
38
  import { debounce } from "./utils";
32
39
 
@@ -40,7 +47,7 @@ const ORIGIN_STORAGE_UPDATED = "$STORAGE_UPDATED";
40
47
  export const DEFAULT_PLUV_CLIENT_ADDON = <
41
48
  TIO extends IOLike = IOLike,
42
49
  TPresence extends JsonObject = {},
43
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
50
+ TStorage extends Record<string, CrdtType<any, any>> = {},
44
51
  >(
45
52
  input: PluvRoomAddonInput<TIO, TPresence, TStorage>,
46
53
  ): PluvRoomAddonResult => ({
@@ -94,13 +101,13 @@ interface InternalListeners {
94
101
  export type PluvRoomAddon<
95
102
  TIO extends IOLike = IOLike,
96
103
  TPresence extends JsonObject = {},
97
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
104
+ TStorage extends Record<string, CrdtType<any, any>> = {},
98
105
  > = (input: PluvRoomAddonInput<TIO, TPresence, TStorage>) => Partial<PluvRoomAddonResult>;
99
106
 
100
107
  export interface PluvRoomAddonInput<
101
108
  TIO extends IOLike = IOLike,
102
109
  TPresence extends JsonObject = {},
103
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
110
+ TStorage extends Record<string, CrdtType<any, any>> = {},
104
111
  > {
105
112
  room: PluvRoom<TIO, TPresence, TStorage>;
106
113
  }
@@ -117,7 +124,7 @@ export type PluvRoomDebug<TIO extends IOLike> = Id<{
117
124
  export type PluvRoomOptions<
118
125
  TIO extends IOLike,
119
126
  TPresence extends JsonObject = {},
120
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
127
+ TStorage extends Record<string, CrdtType<any, any>> = {},
121
128
  > = {
122
129
  addons?: readonly PluvRoomAddon<TIO, TPresence, TStorage>[];
123
130
  debug?: boolean | PluvRoomDebug<TIO>;
@@ -128,13 +135,13 @@ export type PluvRoomOptions<
128
135
  export type RoomConfig<
129
136
  TIO extends IOLike,
130
137
  TPresence extends JsonObject = {},
131
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
138
+ TStorage extends Record<string, CrdtType<any, any>> = {},
132
139
  > = RoomEndpoints<TIO> & PluvRoomOptions<TIO, TPresence, TStorage>;
133
140
 
134
141
  export class PluvRoom<
135
142
  TIO extends IOLike,
136
143
  TPresence extends JsonObject = {},
137
- TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
144
+ TStorage extends Record<string, CrdtType<any, any>> = {},
138
145
  > extends AbstractRoom<TIO, TPresence, TStorage> {
139
146
  readonly _endpoints: RoomEndpoints<TIO>;
140
147
 
@@ -347,14 +354,14 @@ export class PluvRoom<
347
354
  [event in keyof InferIOOutput<TIO>]: (callback: EventNotifierSubscriptionCallback<TIO, event>) => () => void;
348
355
  };
349
356
 
350
- public getConnection(): WebSocketConnection {
357
+ public getConnection = (): WebSocketConnection => {
351
358
  // Create a read-only clone of the connection state
352
359
  return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
353
- }
360
+ };
354
361
 
355
- public getDoc(): AbstractCrdtDoc<TStorage> {
362
+ public getDoc = (): AbstractCrdtDoc<TStorage> => {
356
363
  return this._crdtManager.doc;
357
- }
364
+ };
358
365
 
359
366
  public getMyPresence = (): TPresence => {
360
367
  return this._usersManager.myPresence;
@@ -372,14 +379,28 @@ export class PluvRoom<
372
379
  return this._usersManager.getOthers();
373
380
  };
374
381
 
375
- public getStorage = <TKey extends keyof TStorage>(key: TKey): TStorage[TKey] | null => {
376
- const sharedType = this._crdtManager.get(key);
382
+ public getStorage = <TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null => {
383
+ const sharedType = this._crdtManager.get(type);
377
384
 
378
385
  if (typeof sharedType === "undefined") return null;
379
386
 
380
387
  return sharedType;
381
388
  };
382
389
 
390
+ public getStorageJson(): InferCrdtJson<TStorage> | null;
391
+ public getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
392
+ public getStorageJson<TKey extends keyof TStorage>(type?: TKey) {
393
+ if (this._state.connection.id === null) return null;
394
+
395
+ if (typeof type === "undefined") return this._crdtManager.doc.toJson();
396
+
397
+ const sharedType = this._crdtManager.get(type);
398
+
399
+ if (typeof sharedType === "undefined") return null;
400
+
401
+ return this._crdtManager.doc.toJson(type);
402
+ }
403
+
383
404
  public other = (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): (() => void) => {
384
405
  return this._otherNotifier.subscribe(connectionId, callback);
385
406
  };
@@ -390,14 +411,14 @@ export class PluvRoom<
390
411
 
391
412
  public storage = <TKey extends keyof TStorage>(
392
413
  key: TKey,
393
- fn: (value: InferCrdtStorageJson<TStorage[TKey]>) => void,
414
+ fn: (value: InferCrdtJson<TStorage[TKey]>) => void,
394
415
  ): (() => void) => {
395
416
  return this._crdtNotifier.subscribe(key, fn);
396
417
  };
397
418
 
398
419
  public storageRoot = (
399
420
  fn: (value: {
400
- [P in keyof TStorage]: InferCrdtStorageJson<TStorage[P]>;
421
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
401
422
  }) => void,
402
423
  ): (() => void) => {
403
424
  return this._crdtNotifier.subcribeRoot(fn);
@@ -433,8 +454,10 @@ export class PluvRoom<
433
454
  this._crdtManager.doc.undo();
434
455
  };
435
456
 
436
- public updateMyPresence = (presence: Partial<TPresence>): void => {
437
- this._usersManager.updateMyPresence(presence);
457
+ public updateMyPresence = (presence: UpdateMyPresenceAction<TPresence>): void => {
458
+ const newPresence = typeof presence === "function" ? presence(this.getMyPresence()) : presence;
459
+
460
+ this._usersManager.updateMyPresence(newPresence);
438
461
 
439
462
  const myPresence = this._usersManager.myPresence;
440
463
  const myself = this._usersManager.myself ?? null;
@@ -442,7 +465,7 @@ export class PluvRoom<
442
465
  this._stateNotifier.subjects["my-presence"].next(myPresence);
443
466
  !!myself && this._stateNotifier.subjects["myself"].next(myself);
444
467
 
445
- this.broadcast("$UPDATE_PRESENCE" as keyof InferIOInput<TIO>, { presence } as any);
468
+ this.broadcast("$UPDATE_PRESENCE" as keyof InferIOInput<TIO>, { presence: newPresence } as any);
446
469
  };
447
470
 
448
471
  private async _addToStorageStore(update: string): Promise<void> {
@@ -553,17 +576,15 @@ export class PluvRoom<
553
576
  private _emitSharedTypes(): void {
554
577
  const sharedTypes = this._crdtManager.doc.get();
555
578
 
556
- const storageRoot = Object.entries(sharedTypes).reduce(
557
- (acc, [prop, sharedType]) => {
558
- const serialized = sharedType.toJson();
579
+ const storageRoot = Object.keys(sharedTypes).reduce(
580
+ (acc, prop) => {
581
+ const serialized = this._crdtManager.doc.toJson(prop);
559
582
 
560
583
  this._crdtNotifier.subject(prop).next(serialized);
561
584
 
562
585
  return { ...acc, [prop]: serialized };
563
586
  },
564
- {} as {
565
- [P in keyof TStorage]: InferCrdtStorageJson<TStorage[P]>;
566
- },
587
+ {} as { [P in keyof TStorage]: InferCrdtJson<TStorage[P]> },
567
588
  );
568
589
 
569
590
  this._crdtNotifier.rootSubject.next(storageRoot);
@@ -705,8 +726,6 @@ export class PluvRoom<
705
726
  Object.keys(data.others).forEach((connectionId) => {
706
727
  const { presence, user } = data.others[connectionId];
707
728
 
708
- if (!user) return;
709
-
710
729
  this._usersManager.setUser(connectionId, user);
711
730
 
712
731
  const other = this._usersManager.getOther(connectionId);
@@ -805,18 +824,18 @@ export class PluvRoom<
805
824
 
806
825
  const sharedTypes = this._crdtManager.doc.get();
807
826
 
808
- const storageRoot = Object.entries(sharedTypes).reduce(
809
- (acc, [prop, sharedType]) => {
827
+ const storageRoot = Object.keys(sharedTypes).reduce(
828
+ (acc, prop) => {
810
829
  if (!this._crdtManager) return acc;
811
830
 
812
- const serialized = sharedType.toJson();
831
+ const serialized = this._crdtManager.doc.toJson(prop);
813
832
 
814
833
  this._crdtNotifier.subject(prop).next(serialized);
815
834
 
816
835
  return { ...acc, [prop]: serialized };
817
836
  },
818
837
  {} as {
819
- [P in keyof TStorage]: InferCrdtStorageJson<TStorage[P]>;
838
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
820
839
  },
821
840
  );
822
841
 
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
1
+ export type { CrdtType, InferCrdtJson } from "@pluv/crdt";
2
2
  export type { BaseUser, EventMessage, EventRecord, IOEventMessage } from "@pluv/types";
3
3
  export { AbstractRoom } from "./AbstractRoom";
4
4
  export { AbstractStorageStore } from "./AbstractStorageStore";
package/src/types.ts CHANGED
@@ -17,6 +17,16 @@ export interface InternalSubscriptions {
17
17
  observeCrdt: (() => void) | null;
18
18
  }
19
19
 
20
+ export type UpdateMyPresenceAction<TPresence extends JsonObject> =
21
+ | Partial<TPresence>
22
+ | ((oldPresence: TPresence | null) => Partial<TPresence>);
23
+
24
+ export interface UserInfo<TIO extends IOLike, TPresence extends JsonObject = {}> {
25
+ connectionId: string;
26
+ presence: TPresence;
27
+ user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>;
28
+ }
29
+
20
30
  export interface WebSocketConnection {
21
31
  count: number;
22
32
  id: string | null;
@@ -28,9 +38,3 @@ export interface WebSocketState<TIO extends IOLike> {
28
38
  connection: WebSocketConnection;
29
39
  webSocket: WebSocket | null;
30
40
  }
31
-
32
- export interface UserInfo<TIO extends IOLike, TPresence extends JsonObject = {}> {
33
- connectionId: string;
34
- presence: TPresence;
35
- user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>;
36
- }