@pluv/client 1.0.1 → 2.0.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/src/MockedRoom.ts CHANGED
@@ -1,32 +1,40 @@
1
- import type { AbstractCrdtDoc, CrdtType, InferCrdtJson } from "@pluv/crdt";
2
- import type { IOLike, Id, InferIOInput, InferIOOutput, JsonObject } from "@pluv/types";
3
- import { AbstractRoom } from "./AbstractRoom";
1
+ import type { AbstractCrdtDocFactory, CrdtType, InferCrdtJson, InferStorage } from "@pluv/crdt";
2
+ import type {
3
+ CrdtDocLike,
4
+ EventNotifierSubscriptionCallback,
5
+ IOLike,
6
+ Id,
7
+ InferIOInput,
8
+ InferIOOutput,
9
+ JsonObject,
10
+ MergeEvents,
11
+ OtherNotifierSubscriptionCallback,
12
+ RoomLike,
13
+ UpdateMyPresenceAction,
14
+ UserInfo,
15
+ WebSocketState,
16
+ } from "@pluv/types";
17
+ import { ConnectionState } from "@pluv/types";
4
18
  import type { CrdtManagerOptions } from "./CrdtManager";
5
19
  import { CrdtManager } from "./CrdtManager";
6
20
  import { CrdtNotifier } from "./CrdtNotifier";
7
- import type { EventNotifierSubscriptionCallback } from "./EventNotifier";
8
21
  import { EventNotifier } from "./EventNotifier";
9
- import type { OtherNotifierSubscriptionCallback } from "./OtherNotifier";
10
22
  import { OtherNotifier } from "./OtherNotifier";
11
23
  import { PluvProcedure } from "./PluvProcedure";
12
- import type { MergeEvents, PluvRouterEventConfig } from "./PluvRouter";
24
+ import type { PluvRouterEventConfig } from "./PluvRouter";
13
25
  import { PluvRouter } from "./PluvRouter";
14
26
  import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifier";
15
27
  import { StateNotifier } from "./StateNotifier";
16
28
  import type { UsersManagerConfig } from "./UsersManager";
17
29
  import { UsersManager } from "./UsersManager";
18
- import { ConnectionState } from "./enums";
30
+ import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
19
31
  import type {
20
32
  EventResolver,
21
33
  EventResolverContext,
22
34
  InternalSubscriptions,
23
35
  PluvClientLimits,
24
- UpdateMyPresenceAction,
25
- UserInfo,
26
36
  WebSocketConnection,
27
- WebSocketState,
28
37
  } from "./types";
29
- import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
30
38
 
31
39
  export type MockedRoomEvents<TIO extends IOLike> = Partial<{
32
40
  [P in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[P]>) => Partial<InferIOOutput<TIO>>;
@@ -34,29 +42,32 @@ export type MockedRoomEvents<TIO extends IOLike> = Partial<{
34
42
 
35
43
  export type MockedRoomConfig<
36
44
  TIO extends IOLike,
37
- TPresence extends JsonObject = {},
38
- TStorage extends Record<string, CrdtType<any, any>> = {},
39
- TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
45
+ TPresence extends JsonObject,
46
+ TCrdt extends AbstractCrdtDocFactory<any>,
47
+ TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>,
40
48
  > = {
41
49
  events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
42
50
  limits?: PluvClientLimits;
43
- router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
44
- } & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> &
51
+ router?: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
52
+ } & Pick<CrdtManagerOptions<TCrdt>, "initialStorage"> &
45
53
  Omit<UsersManagerConfig<TPresence>, "limits">;
46
54
 
47
55
  export class MockedRoom<
48
56
  TIO extends IOLike,
49
- TPresence extends JsonObject = {},
50
- TStorage extends Record<string, CrdtType<any, any>> = {},
51
- TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
52
- > extends AbstractRoom<TIO, TPresence, TStorage> {
53
- private readonly _crdtManager: CrdtManager<TStorage>;
54
- private readonly _crdtNotifier = new CrdtNotifier<TStorage>();
57
+ TPresence extends JsonObject,
58
+ TCrdt extends AbstractCrdtDocFactory<any>,
59
+ TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>,
60
+ > implements RoomLike<TIO, TPresence, InferStorage<TCrdt>>
61
+ {
62
+ public readonly id: string;
63
+
64
+ private readonly _crdtManager: CrdtManager<TCrdt>;
65
+ private readonly _crdtNotifier = new CrdtNotifier<InferStorage<TCrdt>>();
55
66
  private readonly _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
56
67
  private readonly _events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
57
68
  private readonly _limits: PluvClientLimits;
58
69
  private readonly _otherNotifier = new OtherNotifier<TIO, TPresence>();
59
- private readonly _router: PluvRouter<TIO, TPresence, TStorage, TEvents>;
70
+ private readonly _router: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
60
71
  private _state: WebSocketState<TIO> = {
61
72
  authorization: {
62
73
  token: null,
@@ -76,10 +87,10 @@ export class MockedRoom<
76
87
  };
77
88
  private readonly _usersManager: UsersManager<TIO, TPresence>;
78
89
 
79
- constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage, TEvents>) {
90
+ constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TCrdt, TEvents>) {
80
91
  const { events, initialPresence, initialStorage, limits, presence, router } = options;
81
92
 
82
- super(room);
93
+ this.id = room;
83
94
 
84
95
  this._events = events;
85
96
  this._limits = {
@@ -87,14 +98,15 @@ export class MockedRoom<
87
98
  ...limits,
88
99
  };
89
100
  this._router =
90
- router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
101
+ router ??
102
+ (new PluvRouter({}) as PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>);
91
103
  this._usersManager = new UsersManager<TIO, TPresence>({
92
104
  initialPresence,
93
105
  limits: this._limits,
94
106
  presence,
95
107
  });
96
108
 
97
- this._crdtManager = new CrdtManager<TStorage>({
109
+ this._crdtManager = new CrdtManager<TCrdt>({
98
110
  initialStorage,
99
111
  });
100
112
 
@@ -120,7 +132,7 @@ export class MockedRoom<
120
132
  any,
121
133
  any,
122
134
  TPresence,
123
- TStorage
135
+ InferStorage<TCrdt>
124
136
  > | null;
125
137
 
126
138
  if (!procedure?.config.broadcast) {
@@ -134,7 +146,7 @@ export class MockedRoom<
134
146
  if (!myself) return;
135
147
 
136
148
  const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
137
- const context: EventResolverContext<TIO, TPresence, TStorage> = {
149
+ const context: EventResolverContext<TIO, TPresence, InferStorage<TCrdt>> = {
138
150
  doc: this._crdtManager.doc,
139
151
  others: this._usersManager.getOthers(),
140
152
  room: this.id,
@@ -142,7 +154,13 @@ export class MockedRoom<
142
154
  };
143
155
 
144
156
  const output = await (
145
- procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>
157
+ procedure.config.broadcast as EventResolver<
158
+ TIO,
159
+ any,
160
+ any,
161
+ TPresence,
162
+ InferStorage<TCrdt>
163
+ >
146
164
  )(parsed, context);
147
165
 
148
166
  Object.entries(output).forEach(([_type, _data]) => {
@@ -199,7 +217,7 @@ export class MockedRoom<
199
217
  return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
200
218
  };
201
219
 
202
- public getDoc(): AbstractCrdtDoc<TStorage> {
220
+ public getDoc(): CrdtDocLike<InferStorage<TCrdt>> {
203
221
  return this._crdtManager.doc;
204
222
  }
205
223
 
@@ -219,7 +237,9 @@ export class MockedRoom<
219
237
  return this._usersManager.getOthers();
220
238
  };
221
239
 
222
- public getStorage = <TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null => {
240
+ public getStorage = <TKey extends keyof InferStorage<TCrdt>>(
241
+ type: TKey,
242
+ ): InferStorage<TCrdt>[TKey] | null => {
223
243
  const sharedType = this._crdtManager.get(type);
224
244
 
225
245
  if (typeof sharedType === "undefined") return null;
@@ -227,11 +247,11 @@ export class MockedRoom<
227
247
  return sharedType;
228
248
  };
229
249
 
230
- public getStorageJson(): InferCrdtJson<TStorage> | null;
231
- public getStorageJson<TKey extends keyof TStorage>(
250
+ public getStorageJson(): InferCrdtJson<InferStorage<TCrdt>> | null;
251
+ public getStorageJson<TKey extends keyof InferStorage<TCrdt>>(
232
252
  type: TKey,
233
- ): InferCrdtJson<TStorage[TKey]> | null;
234
- public getStorageJson<TKey extends keyof TStorage>(type?: TKey) {
253
+ ): InferCrdtJson<InferStorage<TCrdt>[TKey]> | null;
254
+ public getStorageJson<TKey extends keyof InferStorage<TCrdt>>(type?: TKey) {
235
255
  if (this._state.connection.id === null) return null;
236
256
 
237
257
  if (typeof type === "undefined") return this._crdtManager.doc.toJson();
@@ -254,16 +274,16 @@ export class MockedRoom<
254
274
  this._crdtManager.doc.redo();
255
275
  };
256
276
 
257
- public storage = <TKey extends keyof TStorage>(
277
+ public storage = <TKey extends keyof InferStorage<TCrdt>>(
258
278
  key: TKey,
259
- fn: (value: InferCrdtJson<TStorage[TKey]>) => void,
279
+ fn: (value: InferCrdtJson<InferStorage<TCrdt>[TKey]>) => void,
260
280
  ): (() => void) => {
261
281
  return this._crdtNotifier.subscribe(key, fn);
262
282
  };
263
283
 
264
284
  public storageRoot = (
265
285
  fn: (value: {
266
- [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
286
+ [P in keyof InferStorage<TCrdt>]: InferCrdtJson<InferStorage<TCrdt>[P]>;
267
287
  }) => void,
268
288
  ): (() => void) => {
269
289
  return this._crdtNotifier.subcribeRoot(fn);
@@ -276,7 +296,7 @@ export class MockedRoom<
276
296
  return this._stateNotifier.subscribe(name, callback);
277
297
  };
278
298
 
279
- public transact = (fn: (storage: TStorage) => void, origin?: string): void => {
299
+ public transact = (fn: (storage: InferStorage<TCrdt>) => void, origin?: string): void => {
280
300
  const _origin = origin ?? this._state.connection.id;
281
301
  const crdtManager = this._crdtManager;
282
302
 
@@ -338,7 +358,7 @@ export class MockedRoom<
338
358
  return { ...acc, [prop]: serialized };
339
359
  },
340
360
  {} as {
341
- [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
361
+ [P in keyof InferStorage<TCrdt>]: InferCrdtJson<InferStorage<TCrdt>[P]>;
342
362
  },
343
363
  );
344
364
 
@@ -1,11 +1,12 @@
1
- import type { Id, IOLike, JsonObject } from "@pluv/types";
1
+ import type {
2
+ Id,
3
+ IOLike,
4
+ JsonObject,
5
+ OtherNotifierSubscriptionCallback,
6
+ UserInfo,
7
+ } from "@pluv/types";
2
8
  import type { Subject } from "wonka";
3
9
  import { makeSubject, subscribe } from "wonka";
4
- import type { UserInfo } from "./types";
5
-
6
- export type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject> = (
7
- value: Id<UserInfo<TIO, TPresence>> | null,
8
- ) => void;
9
10
 
10
11
  export class OtherNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
11
12
  private _subjects = new Map<
package/src/PluvClient.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { AbstractCrdtDocFactory, CrdtType } from "@pluv/crdt";
1
+ import type {
2
+ AbstractCrdtDocFactory,
3
+ CrdtType,
4
+ InferStorage,
5
+ NoopCrdtDocFactory,
6
+ } from "@pluv/crdt";
2
7
  import type { InputZodLike, IOLike, JsonObject } from "@pluv/types";
3
8
  import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
4
9
  import type { InferCallback } from "./infer";
@@ -20,11 +25,11 @@ import type { PluvClientLimits, PublicKey, WithMetadata } from "./types";
20
25
  export type PluvClientOptions<
21
26
  TIO extends IOLike<any, any>,
22
27
  TPresence extends JsonObject,
23
- TStorage extends Record<string, CrdtType<any, any>>,
28
+ TCrdt extends AbstractCrdtDocFactory<any>,
24
29
  TMetadata extends JsonObject,
25
30
  > = RoomEndpoints<TIO, TMetadata> & {
26
31
  debug?: boolean;
27
- initialStorage?: AbstractCrdtDocFactory<TStorage>;
32
+ initialStorage?: TCrdt;
28
33
  /**
29
34
  * @description Configurable limits defined for client-side validation. You should only set
30
35
  * this if you control the server and have changed the limits there.
@@ -39,17 +44,17 @@ export type PluvClientOptions<
39
44
  export type CreateRoomOptions<
40
45
  TIO extends IOLike<any, any>,
41
46
  TPresence extends JsonObject,
42
- TStorage extends Record<string, CrdtType<any, any>>,
47
+ TCrdt extends AbstractCrdtDocFactory<any>,
43
48
  TMetadata extends JsonObject,
44
- TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
49
+ TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {},
45
50
  > = {
46
- addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
51
+ addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, InferStorage<TCrdt>>[];
47
52
  debug?: boolean | PluvRoomDebug<TIO>;
48
53
  initialPresence?: TPresence;
49
- initialStorage?: AbstractCrdtDocFactory<TStorage>;
54
+ initialStorage?: TCrdt;
50
55
  onAuthorizationFail?: (error: Error) => void;
51
56
  reconnectTimeoutMs?: ReconnectTimeoutMs;
52
- router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
57
+ router?: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
53
58
  };
54
59
 
55
60
  export type EnterRoomParams<TMetadata extends JsonObject = {}> = keyof TMetadata extends never
@@ -59,18 +64,18 @@ export type EnterRoomParams<TMetadata extends JsonObject = {}> = keyof TMetadata
59
64
  export class PluvClient<
60
65
  TIO extends IOLike<any, any>,
61
66
  TPresence extends JsonObject = {},
62
- TStorage extends Record<string, CrdtType<any, any>> = {},
67
+ TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory,
63
68
  TMetadata extends JsonObject = {},
64
69
  > {
65
70
  public readonly metadata?: InputZodLike<TMetadata>;
66
71
 
67
72
  private readonly _authEndpoint: AuthEndpoint<TMetadata> | undefined;
68
73
  private readonly _debug: boolean;
69
- private readonly _initialStorage?: AbstractCrdtDocFactory<TStorage>;
74
+ private readonly _initialStorage?: TCrdt;
70
75
  private readonly _limits: PluvClientLimits;
71
76
  private readonly _presence?: InputZodLike<TPresence>;
72
77
  private readonly _publicKey: PublicKey<TMetadata> | null = null;
73
- private readonly _rooms = new Map<string, PluvRoom<TIO, TMetadata, TPresence, TStorage, any>>();
78
+ private readonly _rooms = new Map<string, PluvRoom<TIO, TMetadata, TPresence, TCrdt, any>>();
74
79
  private readonly _wsEndpoint: WsEndpoint<TMetadata> | undefined;
75
80
 
76
81
  public get _defs() {
@@ -79,11 +84,11 @@ export class PluvClient<
79
84
  };
80
85
  }
81
86
 
82
- public get procedure(): PluvProcedure<TIO, {}, {}, TPresence, TStorage> {
83
- return new PluvProcedure<TIO, {}, {}, TPresence, TStorage>();
87
+ public get procedure(): PluvProcedure<TIO, {}, {}, TPresence, InferStorage<TCrdt>> {
88
+ return new PluvProcedure<TIO, {}, {}, TPresence, InferStorage<TCrdt>>();
84
89
  }
85
90
 
86
- constructor(options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>) {
91
+ constructor(options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>) {
87
92
  const {
88
93
  authEndpoint,
89
94
  debug = false,
@@ -110,20 +115,22 @@ export class PluvClient<
110
115
  if (!!publicKey) this._publicKey = publicKey;
111
116
  }
112
117
 
113
- public createRoom = <TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(
118
+ public createRoom = <
119
+ TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {},
120
+ >(
114
121
  room: string,
115
- options: CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents> = {},
116
- ): PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents> => {
122
+ options: CreateRoomOptions<TIO, TPresence, TCrdt, TMetadata, TEvents> = {},
123
+ ): PluvRoom<TIO, TMetadata, TPresence, TCrdt, TEvents> => {
117
124
  const oldRoom = this.getRoom(room);
118
125
 
119
126
  if (oldRoom) return oldRoom;
120
127
 
121
- const newRoom = new PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>(room, {
128
+ const newRoom = new PluvRoom<TIO, TMetadata, TPresence, TCrdt, TEvents>(room, {
122
129
  addons: options.addons,
123
130
  authEndpoint: this._authEndpoint,
124
131
  debug: options.debug,
125
132
  initialPresence: options.initialPresence,
126
- initialStorage: this._initialStorage,
133
+ initialStorage: options.initialStorage ?? this._initialStorage,
127
134
  limits: this._limits,
128
135
  metadata: this.metadata,
129
136
  onAuthorizationFail: options.onAuthorizationFail,
@@ -132,7 +139,7 @@ export class PluvClient<
132
139
  reconnectTimeoutMs: options.reconnectTimeoutMs,
133
140
  router: options.router,
134
141
  wsEndpoint: this._wsEndpoint,
135
- } as RoomConfig<TIO, TMetadata, TPresence, TStorage, TEvents>);
142
+ } as RoomConfig<TIO, TMetadata, TPresence, TCrdt, TEvents>);
136
143
 
137
144
  this._rooms.set(room, newRoom);
138
145
 
@@ -144,7 +151,7 @@ export class PluvClient<
144
151
  public enter = async (
145
152
  room: string | PluvRoom<TIO, TMetadata, any, any, any>,
146
153
  ...args: EnterRoomParams<TMetadata>
147
- ): Promise<PluvRoom<TIO, TMetadata, TPresence, TStorage, any>> => {
154
+ ): Promise<PluvRoom<TIO, TMetadata, TPresence, TCrdt, any>> => {
148
155
  const toEnter = typeof room === "string" ? this.getRoom(room) : room;
149
156
 
150
157
  if (!toEnter) throw new Error(`Could not find room: ${room}.`);
@@ -157,15 +164,15 @@ export class PluvClient<
157
164
  return toEnter;
158
165
  };
159
166
 
160
- public getRoom = (room: string): PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | null => {
167
+ public getRoom = (room: string): PluvRoom<TIO, TMetadata, TPresence, TCrdt, any> | null => {
161
168
  const found = this._rooms.get(room) as
162
- | PluvRoom<TIO, TMetadata, TPresence, TStorage, any>
169
+ | PluvRoom<TIO, TMetadata, TPresence, TCrdt, any>
163
170
  | undefined;
164
171
 
165
172
  return found ?? null;
166
173
  };
167
174
 
168
- public getRooms = (): readonly PluvRoom<TIO, TMetadata, TPresence, TStorage, any>[] => {
175
+ public getRooms = (): readonly PluvRoom<TIO, TMetadata, TPresence, TCrdt, any>[] => {
169
176
  return Array.from(this._rooms.values());
170
177
  };
171
178
 
@@ -181,20 +188,21 @@ export class PluvClient<
181
188
  this._logDebug(`Left and deleted room: ${room}`);
182
189
  };
183
190
 
184
- public router<TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(
191
+ public router<TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {}>(
185
192
  events: TEvents,
186
- ): PluvRouter<TIO, TPresence, TStorage, TEvents> {
193
+ ): PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents> {
187
194
  const invalidName = Object.keys(events).find((name) => name.includes("$"));
188
195
 
189
196
  if (typeof invalidName === "string") {
190
197
  throw new Error(`Invalid event name. Event names must not contain $: "${invalidName}"`);
191
198
  }
192
199
 
193
- return new PluvRouter<TIO, TPresence, TStorage, TEvents>(events);
200
+ return new PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>(events);
194
201
  }
195
202
 
196
203
  private _logDebug(...data: any[]): void {
197
- if (process?.env?.NODE_ENV === "production") return;
204
+ if (typeof process === "undefined") return;
205
+ if (process.env?.NODE_ENV === "production") return;
198
206
 
199
207
  if (this._debug) console.log(...data);
200
208
  }
@@ -20,7 +20,8 @@ export class PluvProcedure<
20
20
  TPresence extends JsonObject = {},
21
21
  TStorage extends Record<string, CrdtType<any, any>> = {},
22
22
  TFilled extends "input" | "broadcast" | "" = "",
23
- > {
23
+ > implements ProcedureLike<TInput, TOutput>
24
+ {
24
25
  private _broadcast: EventResolver<TIO, TInput, Partial<TOutput>, TPresence, TStorage> | null =
25
26
  null;
26
27
  private _input: InputZodLike<TInput> | null = null;