@pluv/client 2.1.0 → 2.2.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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { CrdtType, AbstractCrdtDocFactory, NoopCrdtDocFactory, InferStorage, InferCrdtJson } from '@pluv/crdt';
1
+ import { AbstractCrdtDocFactory, InferDocLike, NoopCrdtDocFactory, InferStorage, InferDoc, InferCrdtJson } from '@pluv/crdt';
2
2
  export { CrdtType, InferCrdtJson } from '@pluv/crdt';
3
- import { IOLike, JsonObject, EventRecord, CrdtDocLike, UserInfo, MaybePromise, Id, ConnectionState, ProcedureLike, InputZodLike, IORouterLike, InferIOAuthorizeUser, InferIOAuthorize, BaseUser, RoomLike, InferIOOutput, InferIOInput, BroadcastProxy, EventProxy, OtherSubscriptionCallback, StorageProxy, SubscribeProxy, UpdateMyPresenceAction, MergeEvents } from '@pluv/types';
4
- export { BaseUser, ConnectionState, EventMessage, EventNotifierSubscriptionCallback, EventRecord, IOEventMessage, MergeEvents, RoomLike, UserInfo, WebSocketState } from '@pluv/types';
3
+ import { IOLike, JsonObject, EventRecord, CrdtDocLike, UserInfo, MaybePromise, Id, ProcedureLike, InputZodLike, IORouterLike, InferIOAuthorizeUser, InferIOAuthorize, BaseUser, RoomLike, InferIOOutput, InferIOInput, RoomEventListenerMap, BroadcastProxy, EventProxy, WebSocketConnection, OtherSubscriptionCallback, StorageProxy, SubscribeProxy, UpdateMyPresenceAction, MergeEvents } from '@pluv/types';
4
+ export { BaseUser, ConnectionState, EventMessage, EventNotifierSubscriptionCallback, EventRecord, IOEventMessage, MergeEvents, RoomLike, UserInfo, WebSocketConnection, WebSocketState } from '@pluv/types';
5
5
 
6
6
  declare abstract class AbstractStorageStore {
7
7
  room: string;
@@ -21,9 +21,9 @@ type InferCallback<TIO extends IOLike<any, any>> = (i: typeof identity) => {
21
21
  };
22
22
  declare const infer: <TIO extends IOLike<any, any>>(callback: InferCallback<TIO>) => InferCallback<TIO>;
23
23
 
24
- type EventResolver<TIO extends IOLike = IOLike, TInput extends JsonObject = {}, TOutput extends EventRecord<string, any> = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = (data: TInput, context: EventResolverContext<TIO, TPresence, TStorage>) => MaybePromise<TOutput | void>;
25
- interface EventResolverContext<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> {
26
- doc: CrdtDocLike<TStorage>;
24
+ type EventResolver<TIO extends IOLike, TInput extends JsonObject, TOutput extends EventRecord<string, any>, TPresence extends Record<string, any>, TDocLike extends CrdtDocLike<any, any>> = (data: TInput, context: EventResolverContext<TIO, TPresence, TDocLike>) => MaybePromise<TOutput | void>;
25
+ interface EventResolverContext<TIO extends IOLike, TPresence extends Record<string, any>, TDocLike extends CrdtDocLike<any, any>> {
26
+ doc: TDocLike;
27
27
  others: readonly UserInfo<TIO, TPresence>[];
28
28
  room: string;
29
29
  user: UserInfo<TIO, TPresence>;
@@ -45,65 +45,47 @@ interface PublicKeyParams<TMetadata extends JsonObject> {
45
45
  metadata: TMetadata;
46
46
  }
47
47
  type PublicKey<TMetadata extends JsonObject> = string | ((params: PublicKeyParams<TMetadata>) => string);
48
- interface WebSocketConnection {
49
- /**
50
- * @description How many times a connection attempt was made. This will increment upon each
51
- * unsuccessful attempt, and will reset upon a successful connection.
52
- * @date April 13, 2025
53
- */
54
- attempts: number;
55
- /**
56
- * @description How many times the user has connected to the room. This can be more than 1 in
57
- * the case of reconnects. This is tracked because on the very first connection (i.e. when
58
- * count is 0) we don't want to send the storage state as an update to the room and overwrite
59
- * the upstream state.
60
- * @date April 13, 2025
61
- */
62
- count: number;
63
- id: string | null;
64
- state: ConnectionState;
65
- }
66
48
  type WithMetadata<TMetadata extends JsonObject = {}> = keyof TMetadata extends never ? {
67
49
  metadata?: undefined;
68
50
  } : {
69
51
  metadata: TMetadata;
70
52
  };
71
53
 
72
- interface PluvProcedureConfig<TIO extends IOLike = IOLike, TInput extends JsonObject = {}, TOutput extends EventRecord<string, any> = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> {
73
- broadcast?: EventResolver<TIO, TInput, TOutput, TPresence, TStorage> | null;
54
+ interface PluvProcedureConfig<TIO extends IOLike, TInput extends JsonObject, TOutput extends EventRecord<string, any>, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>> {
55
+ broadcast?: EventResolver<TIO, TInput, TOutput, TPresence, InferDocLike<TCrdt>> | null;
74
56
  input?: InputZodLike<TInput>;
75
57
  }
76
- declare class PluvProcedure<TIO extends IOLike = IOLike, TInput extends JsonObject = {}, TOutput extends EventRecord<string, any> = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TFilled extends "input" | "broadcast" | "" = ""> implements ProcedureLike<TInput, TOutput> {
58
+ declare class PluvProcedure<TIO extends IOLike, TInput extends JsonObject, TOutput extends EventRecord<string, any>, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>, TFilled extends "input" | "broadcast" | ""> implements ProcedureLike<TInput, TOutput> {
77
59
  private _broadcast;
78
60
  private _input;
79
61
  get config(): ProcedureLike<TInput, TOutput>["config"];
80
- constructor(config?: PluvProcedureConfig<TIO, TInput, TOutput, TPresence, TStorage>);
81
- broadcast<TResult extends EventRecord<string, any> = {}>(resolver: EventResolver<TIO, TInput, TResult, TPresence, TStorage>): Omit<PluvProcedure<TIO, TInput, MergeEventRecords<[TOutput, TResult]>, TPresence, TStorage, TFilled | "input" | "broadcast">, TFilled | "input" | "broadcast">;
82
- input<TData extends JsonObject>(input: InputZodLike<TData>): Omit<PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">, TFilled | "input">;
62
+ constructor(config?: PluvProcedureConfig<TIO, TInput, TOutput, TPresence, TCrdt>);
63
+ broadcast<TResult extends EventRecord<string, any> = {}>(resolver: EventResolver<TIO, TInput, TResult, TPresence, InferDocLike<TCrdt>>): Omit<PluvProcedure<TIO, TInput, MergeEventRecords<[TOutput, TResult]>, TPresence, TCrdt, TFilled | "input" | "broadcast">, TFilled | "input" | "broadcast">;
64
+ input<TData extends JsonObject>(input: InputZodLike<TData>): Omit<PluvProcedure<TIO, TData, {}, TPresence, TCrdt, TFilled | "input">, TFilled | "input">;
83
65
  private _resolver;
84
66
  }
85
67
 
86
- type CrdtManagerOptions<TCrdt extends AbstractCrdtDocFactory<any>> = {
68
+ type CrdtManagerOptions<TCrdt extends AbstractCrdtDocFactory<any, any>> = {
87
69
  initialStorage?: TCrdt;
88
70
  };
89
71
 
90
- type PluvRouterEventConfig<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = {
91
- [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TStorage>, "config">;
72
+ type PluvRouterEventConfig<TIO extends IOLike, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>> = {
73
+ [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TCrdt, "">, "config">;
92
74
  };
93
- type MergedRouter<TRouters extends PluvRouter<TIO, TPresence, TStorage, any>[] = [], TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TRoot extends TRouters[0]["_defs"]["events"] = {}> = TRouters extends [
94
- infer IHead extends PluvRouter<TIO, TPresence, TStorage, any>,
95
- ...infer ITail extends PluvRouter<TIO, TPresence, TStorage, any>[]
96
- ] ? MergedRouter<ITail, TIO, TPresence, TStorage, TRoot & IHead["_defs"]["events"]> : PluvRouter<TIO, TPresence, TStorage, TRoot>;
97
- declare class PluvRouter<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> implements IORouterLike<TEvents> {
75
+ type MergedRouter<TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[], TIO extends IOLike, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>, TRoot extends TRouters[0]["_defs"]["events"]> = TRouters extends [
76
+ infer IHead extends PluvRouter<TIO, TPresence, TCrdt, any>,
77
+ ...infer ITail extends PluvRouter<TIO, TPresence, TCrdt, any>[]
78
+ ] ? MergedRouter<ITail, TIO, TPresence, TCrdt, TRoot & IHead["_defs"]["events"]> : PluvRouter<TIO, TPresence, TCrdt, TRoot>;
79
+ declare class PluvRouter<TIO extends IOLike, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>, TEvents extends PluvRouterEventConfig<TIO, TPresence, TCrdt> = {}> implements IORouterLike<TEvents> {
98
80
  readonly _defs: {
99
81
  events: TEvents;
100
82
  };
101
83
  constructor(events: TEvents);
102
- static merge<TRouters extends PluvRouter<any, any, any, any>[]>(...routers: TRouters): MergedRouter<TRouters>;
84
+ static merge<TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[], TIO extends IOLike, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>>(...routers: TRouters): MergedRouter<TRouters, TIO, TPresence, TCrdt, {}>;
103
85
  private _isValidEventName;
104
86
  }
105
87
 
106
- type UsersManagerConfig<TPresence extends JsonObject = {}> = {
88
+ type UsersManagerConfig<TPresence extends Record<string, any> = {}> = {
107
89
  initialPresence?: TPresence;
108
90
  limits: PluvClientLimits;
109
91
  presence?: InputZodLike<TPresence>;
@@ -133,8 +115,8 @@ type RoomEndpoints<TIO extends IOLike<any, any>, TMetadata extends JsonObject> =
133
115
  } : {
134
116
  authEndpoint?: undefined;
135
117
  });
136
- type PluvRoomAddon<TIO extends IOLike, TMetadata extends JsonObject, TPresence extends JsonObject, TCrdt extends AbstractCrdtDocFactory<any>> = (input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TCrdt>) => Partial<PluvRoomAddonResult>;
137
- interface PluvRoomAddonInput<TIO extends IOLike, TMetadata extends JsonObject, TPresence extends JsonObject, TCrdt extends AbstractCrdtDocFactory<any>> {
118
+ type PluvRoomAddon<TIO extends IOLike, TMetadata extends JsonObject, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>> = (input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TCrdt>) => Partial<PluvRoomAddonResult>;
119
+ interface PluvRoomAddonInput<TIO extends IOLike, TMetadata extends JsonObject, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>> {
138
120
  room: PluvRoom<TIO, TMetadata, TPresence, TCrdt>;
139
121
  }
140
122
  interface PluvRoomAddonResult {
@@ -149,7 +131,7 @@ interface ReconnectTimeoutMsParams {
149
131
  attempts: number;
150
132
  }
151
133
  type ReconnectTimeoutMs = number | ((params: ReconnectTimeoutMsParams) => number);
152
- type RoomConfig<TIO extends IOLike, TMetadata extends JsonObject, TPresence extends JsonObject, TCrdt extends AbstractCrdtDocFactory<any>, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>> = Id<{
134
+ type RoomConfig<TIO extends IOLike, TMetadata extends JsonObject, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>> = Id<{
153
135
  addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TCrdt>[];
154
136
  debug?: boolean | PluvRoomDebug<TIO>;
155
137
  limits: PluvClientLimits;
@@ -159,7 +141,7 @@ type RoomConfig<TIO extends IOLike, TMetadata extends JsonObject, TPresence exte
159
141
  reconnectTimeoutMs?: ReconnectTimeoutMs;
160
142
  router?: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
161
143
  } & RoomEndpoints<TIO, TMetadata> & Pick<CrdtManagerOptions<TCrdt>, "initialStorage"> & UsersManagerConfig<TPresence>>;
162
- declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObject = {}, TPresence extends JsonObject = {}, TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {}> implements RoomLike<TIO, TPresence, InferStorage<TCrdt>> {
144
+ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObject = {}, TPresence extends Record<string, any> = {}, TCrdt extends AbstractCrdtDocFactory<any, any> = NoopCrdtDocFactory, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {}> implements RoomLike<TIO, InferDoc<TCrdt>, TPresence, InferStorage<TCrdt>> {
163
145
  readonly _endpoints: RoomEndpoints<TIO, TMetadata>;
164
146
  readonly id: string;
165
147
  readonly metadata?: InputZodLike<TMetadata>;
@@ -169,6 +151,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
169
151
  private readonly _eventNotifier;
170
152
  private readonly _intervals;
171
153
  private readonly _limits;
154
+ private readonly _listenerManager;
172
155
  private readonly _listeners;
173
156
  private readonly _publicKey;
174
157
  private readonly _reconnectTimeoutMs;
@@ -184,8 +167,12 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
184
167
  private _windowListeners;
185
168
  private _wsListeners;
186
169
  constructor(room: string, options: RoomConfig<TIO, TMetadata, TPresence, TCrdt, TEvents>);
170
+ /**
171
+ * @deprecated To be removed in v3. Use `getStorageLoaded` instead.
172
+ */
187
173
  get storageLoaded(): boolean;
188
174
  get webSocket(): WebSocket | null;
175
+ addEventListener<TKind extends keyof RoomEventListenerMap>(kind: TKind, handler: RoomEventListenerMap[TKind]): () => void;
189
176
  broadcast: BroadcastProxy<TIO, TEvents>;
190
177
  canRedo: () => boolean;
191
178
  canUndo: () => boolean;
@@ -196,7 +183,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
196
183
  */
197
184
  event: EventProxy<TIO, TEvents>;
198
185
  getConnection: () => WebSocketConnection;
199
- getDoc: () => CrdtDocLike<InferStorage<TCrdt>>;
186
+ getDoc: () => CrdtDocLike<InferDoc<TCrdt>, InferStorage<TCrdt>>;
200
187
  getMyPresence: () => TPresence;
201
188
  getMyself: () => Id<UserInfo<TIO, TPresence>> | null;
202
189
  getOther: (connectionId: string) => Id<UserInfo<TIO, TPresence>> | null;
@@ -204,13 +191,14 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
204
191
  getStorage: <TKey extends keyof InferStorage<TCrdt>>(type: TKey) => InferStorage<TCrdt>[TKey] | null;
205
192
  getStorageJson(): InferCrdtJson<InferStorage<TCrdt>> | null;
206
193
  getStorageJson<TKey extends keyof InferStorage<TCrdt>>(type: TKey): InferCrdtJson<InferStorage<TCrdt>[TKey]> | null;
194
+ getStorageLoaded: () => boolean;
207
195
  /**
208
196
  * @deprecated To be removed in v3. Use PluvRoom.subscribe.other instead
209
197
  */
210
198
  other: (connectionId: string, callback: OtherSubscriptionCallback<TIO, TPresence>) => (() => void);
211
199
  redo: () => void;
212
200
  /**
213
- * @deprecated To be removed in v3. To be removed in v3. Use PluvRoom.subscribe.storage instead
201
+ * @deprecated To be removed in v3. Use PluvRoom.subscribe.storage instead
214
202
  */
215
203
  storage: StorageProxy<InferStorage<TCrdt>>;
216
204
  /**
@@ -270,7 +258,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
270
258
  private _updateState;
271
259
  }
272
260
 
273
- type PluvClientOptions<TIO extends IOLike<any, any>, TPresence extends JsonObject, TCrdt extends AbstractCrdtDocFactory<any>, TMetadata extends JsonObject> = RoomEndpoints<TIO, TMetadata> & {
261
+ type PluvClientOptions<TIO extends IOLike<any, any>, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>, TMetadata extends JsonObject> = RoomEndpoints<TIO, TMetadata> & {
274
262
  debug?: boolean;
275
263
  initialStorage?: TCrdt;
276
264
  /**
@@ -283,7 +271,7 @@ type PluvClientOptions<TIO extends IOLike<any, any>, TPresence extends JsonObjec
283
271
  publicKey?: PublicKey<TMetadata>;
284
272
  types: InferCallback<TIO>;
285
273
  };
286
- type CreateRoomOptions<TIO extends IOLike<any, any>, TPresence extends JsonObject, TCrdt extends AbstractCrdtDocFactory<any>, TMetadata extends JsonObject, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {}> = {
274
+ type CreateRoomOptions<TIO extends IOLike<any, any>, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>, TMetadata extends JsonObject, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {}> = {
287
275
  addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, InferStorage<TCrdt>>[];
288
276
  debug?: boolean | PluvRoomDebug<TIO>;
289
277
  initialPresence?: TPresence;
@@ -293,7 +281,7 @@ type CreateRoomOptions<TIO extends IOLike<any, any>, TPresence extends JsonObjec
293
281
  router?: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
294
282
  };
295
283
  type EnterRoomParams<TMetadata extends JsonObject = {}> = keyof TMetadata extends never ? [] : [WithMetadata<TMetadata>];
296
- declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObject = {}, TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory, TMetadata extends JsonObject = {}> {
284
+ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends Record<string, any> = {}, TCrdt extends AbstractCrdtDocFactory<any, any> = NoopCrdtDocFactory, TMetadata extends JsonObject = {}> {
297
285
  readonly metadata?: InputZodLike<TMetadata>;
298
286
  private readonly _authEndpoint;
299
287
  private readonly _debug;
@@ -306,7 +294,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
306
294
  get _defs(): {
307
295
  initialStorage: TCrdt | undefined;
308
296
  };
309
- get procedure(): PluvProcedure<TIO, {}, {}, TPresence, InferStorage<TCrdt>>;
297
+ get procedure(): PluvProcedure<TIO, {}, {}, TPresence, TCrdt, "">;
310
298
  constructor(options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>);
311
299
  createRoom: <TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {}>(room: string, options?: CreateRoomOptions<TIO, TPresence, TCrdt, TMetadata, TEvents>) => PluvRoom<TIO, TMetadata, TPresence, TCrdt, TEvents>;
312
300
  enter: (room: string | PluvRoom<TIO, TMetadata, any, any, any>, ...args: EnterRoomParams<TMetadata>) => Promise<PluvRoom<TIO, TMetadata, TPresence, TCrdt, any>>;
@@ -317,18 +305,22 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
317
305
  private _logDebug;
318
306
  }
319
307
 
320
- declare const createClient: <TIO extends IOLike<any, any>, TPresence extends JsonObject = {}, TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory, TMetadata extends JsonObject = {}>(options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>) => PluvClient<TIO, TPresence, TCrdt, TMetadata>;
308
+ declare const createClient: <TIO extends IOLike<any, any>, TPresence extends Record<string, any> = {}, TCrdt extends AbstractCrdtDocFactory<any, any> = NoopCrdtDocFactory, TMetadata extends JsonObject = {}>(options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>) => PluvClient<TIO, TPresence, TCrdt, TMetadata>;
321
309
 
322
310
  type MockedRoomEvents<TIO extends IOLike> = Partial<{
323
311
  [P in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[P]>) => Partial<InferIOOutput<TIO>>;
324
312
  }>;
325
- type MockedRoomConfig<TIO extends IOLike, TPresence extends JsonObject, TCrdt extends AbstractCrdtDocFactory<any>, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>> = {
313
+ type MockedRoomConfig<TIO extends IOLike, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>> = {
326
314
  events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
327
315
  limits?: PluvClientLimits;
328
316
  router?: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
329
317
  } & Pick<CrdtManagerOptions<TCrdt>, "initialStorage"> & Omit<UsersManagerConfig<TPresence>, "limits">;
330
- declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject, TCrdt extends AbstractCrdtDocFactory<any>, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>> implements RoomLike<TIO, TPresence, InferStorage<TCrdt>> {
318
+ declare class MockedRoom<TIO extends IOLike, TPresence extends Record<string, any>, TCrdt extends AbstractCrdtDocFactory<any, any>, TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>> implements RoomLike<TIO, InferDoc<TCrdt>, TPresence, InferStorage<TCrdt>> {
331
319
  readonly id: string;
320
+ /**
321
+ * @deprecated To be removed in v3. Use `getStorageLoaded` instead.
322
+ */
323
+ readonly storageLoaded: boolean;
332
324
  private readonly _crdtManager;
333
325
  private readonly _crdtNotifier;
334
326
  private readonly _eventNotifier;
@@ -341,13 +333,13 @@ declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject, TCrdt
341
333
  private readonly _subscriptions;
342
334
  private readonly _usersManager;
343
335
  constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TCrdt, TEvents>);
344
- get storageLoaded(): boolean;
336
+ addEventListener<TKind extends keyof RoomEventListenerMap>(kind: TKind, handler: RoomEventListenerMap[TKind]): () => void;
345
337
  broadcast: BroadcastProxy<TIO, TEvents>;
346
338
  canRedo: () => boolean;
347
339
  canUndo: () => boolean;
348
340
  event: EventProxy<TIO, TEvents>;
349
341
  getConnection: () => WebSocketConnection;
350
- getDoc(): CrdtDocLike<InferStorage<TCrdt>>;
342
+ getDoc(): CrdtDocLike<InferDoc<TCrdt>, InferStorage<TCrdt>>;
351
343
  getMyPresence: () => TPresence;
352
344
  getMyself: () => Id<UserInfo<TIO, TPresence>> | null;
353
345
  getOther: (connectionId: string) => Id<UserInfo<TIO, TPresence>> | null;
@@ -355,6 +347,7 @@ declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject, TCrdt
355
347
  getStorage: <TKey extends keyof InferStorage<TCrdt>>(type: TKey) => InferStorage<TCrdt>[TKey] | null;
356
348
  getStorageJson(): InferCrdtJson<InferStorage<TCrdt>> | null;
357
349
  getStorageJson<TKey extends keyof InferStorage<TCrdt>>(type: TKey): InferCrdtJson<InferStorage<TCrdt>[TKey]> | null;
350
+ getStorageLoaded(): boolean;
358
351
  other: (connectionId: string, callback: OtherSubscriptionCallback<TIO, TPresence>) => (() => void);
359
352
  redo: () => void;
360
353
  storage: StorageProxy<InferStorage<TCrdt>>;
@@ -377,4 +370,4 @@ declare const register: <TMetadata extends JsonObject>(params: RegisterParams<TM
377
370
  wsEndpoint: (params: EndpointParams<TMetadata>) => string;
378
371
  };
379
372
 
380
- export { AbstractStorageStore, type CreateRoomOptions, type EnterRoomParams, type InferCallback, type InferMetadata, MockedRoom, type MockedRoomConfig, type MockedRoomEvents, PluvClient, type PluvClientOptions, type PluvProcedureConfig, PluvRoom, type PluvRoomAddon, type PluvRoomAddonInput, type PluvRoomAddonResult, type PluvRoomDebug, PluvRouter, type PluvRouterEventConfig, type PublicKey, type PublicKeyParams, type RegisterParams, type RoomConnectParams, type RoomEndpoints, type WebSocketConnection, createClient, infer, register };
373
+ export { AbstractStorageStore, type CreateRoomOptions, type EnterRoomParams, type InferCallback, type InferMetadata, MockedRoom, type MockedRoomConfig, type MockedRoomEvents, PluvClient, type PluvClientOptions, type PluvProcedureConfig, PluvRoom, type PluvRoomAddon, type PluvRoomAddonInput, type PluvRoomAddonResult, type PluvRoomDebug, PluvRouter, type PluvRouterEventConfig, type PublicKey, type PublicKeyParams, type RegisterParams, type RoomConnectParams, type RoomEndpoints, createClient, infer, register };
package/dist/index.js CHANGED
@@ -78,6 +78,7 @@ var AbstractStorageStore = class {
78
78
 
79
79
  // src/constants.ts
80
80
  var MAX_PRESENCE_SIZE_BYTES = 512;
81
+ var PLUV_PRESENCE_META_KEY = "__pluv";
81
82
 
82
83
  // src/PluvProcedure.ts
83
84
  var PluvProcedure = class _PluvProcedure {
@@ -124,7 +125,7 @@ var CrdtManager = class {
124
125
  this.initialized = false;
125
126
  const { initialStorage = import_crdt.noop.doc({}) } = options;
126
127
  this._docFactory = initialStorage;
127
- this.doc = initialStorage.getEmpty();
128
+ this.doc = this._docFactory.getEmpty();
128
129
  }
129
130
  /**
130
131
  * !HACK
@@ -164,11 +165,7 @@ var CrdtManager = class {
164
165
  if (!updates.length) return this;
165
166
  const initialized = this._docFactory.getInitialized();
166
167
  const reference = initialized.get();
167
- this.doc = this._applyDocUpdates(
168
- this._docFactory.getEmpty(),
169
- updates,
170
- origin
171
- ).rebuildStorage(reference);
168
+ this.doc = this._applyDocUpdates(this.doc, updates, origin).rebuildStorage(reference);
172
169
  this.initialized = true;
173
170
  initialized.destroy();
174
171
  return this;
@@ -228,6 +225,25 @@ var EventNotifier = class {
228
225
  }
229
226
  };
230
227
 
228
+ // src/ListenerManager.ts
229
+ var import_wonka3 = require("wonka");
230
+ var ListenerManager = class {
231
+ constructor() {
232
+ this.subjects = {
233
+ close: (0, import_wonka3.makeSubject)(),
234
+ error: (0, import_wonka3.makeSubject)(),
235
+ message: (0, import_wonka3.makeSubject)(),
236
+ open: (0, import_wonka3.makeSubject)()
237
+ };
238
+ }
239
+ subscribe(kind, callback) {
240
+ var _a;
241
+ const source = (_a = this.subjects[kind]) == null ? void 0 : _a.source;
242
+ if (!source) return () => void 0;
243
+ return (0, import_wonka3.subscribe)(callback)(source).unsubscribe;
244
+ }
245
+ };
246
+
231
247
  // src/PluvRouter.ts
232
248
  var PluvRouter = class _PluvRouter {
233
249
  constructor(events) {
@@ -256,22 +272,22 @@ var PluvRouter = class _PluvRouter {
256
272
  };
257
273
 
258
274
  // src/StateNotifier.ts
259
- var import_wonka3 = require("wonka");
275
+ var import_wonka4 = require("wonka");
260
276
  var StateNotifier = class {
261
277
  constructor() {
262
278
  this.subjects = {
263
- connection: (0, import_wonka3.makeSubject)(),
264
- "my-presence": (0, import_wonka3.makeSubject)(),
265
- myself: (0, import_wonka3.makeSubject)(),
266
- others: (0, import_wonka3.makeSubject)(),
267
- "storage-loaded": (0, import_wonka3.makeSubject)()
279
+ connection: (0, import_wonka4.makeSubject)(),
280
+ "my-presence": (0, import_wonka4.makeSubject)(),
281
+ myself: (0, import_wonka4.makeSubject)(),
282
+ others: (0, import_wonka4.makeSubject)(),
283
+ "storage-loaded": (0, import_wonka4.makeSubject)()
268
284
  };
269
285
  }
270
286
  subscribe(name, callback) {
271
287
  var _a;
272
288
  const source = (_a = this.subjects[name]) == null ? void 0 : _a.source;
273
289
  if (!source) throw new Error(`Could not subscribe to source: ${name}`);
274
- return (0, import_wonka3.subscribe)(callback)(source).unsubscribe;
290
+ return (0, import_wonka4.subscribe)(callback)(source).unsubscribe;
275
291
  }
276
292
  };
277
293
 
@@ -444,6 +460,9 @@ var UsersManager = class {
444
460
  );
445
461
  const presence = __spreadValues(__spreadValues(__spreadValues({}, this.initialPresence), cleanedPresence), cleanedPatch);
446
462
  const validated = this._presence ? this._presence.parse(presence) : presence;
463
+ if (!!presence[PLUV_PRESENCE_META_KEY]) {
464
+ validated[PLUV_PRESENCE_META_KEY] = presence[PLUV_PRESENCE_META_KEY];
465
+ }
447
466
  this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence: validated }));
448
467
  return validated;
449
468
  }
@@ -522,10 +541,10 @@ var UsersManager = class {
522
541
  };
523
542
 
524
543
  // src/UsersNotifier.ts
525
- var import_wonka4 = require("wonka");
544
+ var import_wonka5 = require("wonka");
526
545
  var UsersNotifier = class {
527
546
  constructor() {
528
- this.others = (0, import_wonka4.makeSubject)();
547
+ this.others = (0, import_wonka5.makeSubject)();
529
548
  this._otherSubjects = /* @__PURE__ */ new Map();
530
549
  }
531
550
  clear() {
@@ -541,17 +560,17 @@ var UsersNotifier = class {
541
560
  other(clientId) {
542
561
  const subject = this._otherSubjects.get(clientId);
543
562
  if (subject) return subject;
544
- const newSubject = (0, import_wonka4.makeSubject)();
563
+ const newSubject = (0, import_wonka5.makeSubject)();
545
564
  this._otherSubjects.set(clientId, newSubject);
546
565
  return newSubject;
547
566
  }
548
567
  subscribeOther(clientId, callback) {
549
568
  const source = this.other(clientId).source;
550
- const subscription = (0, import_wonka4.subscribe)(callback)(source);
569
+ const subscription = (0, import_wonka5.subscribe)(callback)(source);
551
570
  return subscription.unsubscribe;
552
571
  }
553
572
  subscribeOthers(callback) {
554
- const subscription = (0, import_wonka4.subscribe)(
573
+ const subscription = (0, import_wonka5.subscribe)(
555
574
  ({ others, event }) => callback(others, event)
556
575
  )(this.others.source);
557
576
  return subscription.unsubscribe;
@@ -577,6 +596,7 @@ var PluvRoom = class {
577
596
  this._intervals = {
578
597
  heartbeat: null
579
598
  };
599
+ this._listenerManager = new ListenerManager();
580
600
  this._publicKey = null;
581
601
  this._stateNotifier = new StateNotifier();
582
602
  this._subscriptions = {
@@ -595,10 +615,12 @@ var PluvRoom = class {
595
615
  },
596
616
  connection: {
597
617
  attempts: 0,
598
- count: 0,
599
618
  id: null,
600
619
  state: import_types.ConnectionState.Untouched
601
620
  },
621
+ storage: {
622
+ state: import_types.StorageState.Unavailable
623
+ },
602
624
  webSocket: null
603
625
  };
604
626
  this._windowListeners = null;
@@ -675,6 +697,9 @@ var PluvRoom = class {
675
697
  if (typeof sharedType === "undefined") return null;
676
698
  return sharedType;
677
699
  };
700
+ this.getStorageLoaded = () => {
701
+ return this._state.storage.state === import_types.StorageState.Offline || this._state.storage.state === import_types.StorageState.Synced;
702
+ };
678
703
  /**
679
704
  * @deprecated To be removed in v3. Use PluvRoom.subscribe.other instead
680
705
  */
@@ -687,7 +712,7 @@ var PluvRoom = class {
687
712
  this._crdtManager.doc.redo();
688
713
  };
689
714
  /**
690
- * @deprecated To be removed in v3. To be removed in v3. Use PluvRoom.subscribe.storage instead
715
+ * @deprecated To be removed in v3. Use PluvRoom.subscribe.storage instead
691
716
  */
692
717
  this.storage = new Proxy(
693
718
  (key, callback) => this._crdtNotifier.subscribe(key, callback),
@@ -825,12 +850,18 @@ var PluvRoom = class {
825
850
  });
826
851
  this._crdtManager = new CrdtManager({ initialStorage });
827
852
  }
853
+ /**
854
+ * @deprecated To be removed in v3. Use `getStorageLoaded` instead.
855
+ */
828
856
  get storageLoaded() {
829
- return this._crdtManager.initialized;
857
+ return this.getStorageLoaded();
830
858
  }
831
859
  get webSocket() {
832
860
  return this._state.webSocket;
833
861
  }
862
+ addEventListener(kind, handler) {
863
+ return this._listenerManager.subscribe(kind, handler);
864
+ }
834
865
  connect(...args) {
835
866
  return __async(this, null, function* () {
836
867
  var _a;
@@ -872,6 +903,7 @@ var PluvRoom = class {
872
903
  this._updateState((oldState) => {
873
904
  oldState.connection.attempts = 0;
874
905
  oldState.authorization.token = authToken;
906
+ oldState.storage.state = import_types.StorageState.Loading;
875
907
  oldState.webSocket = webSocket;
876
908
  return oldState;
877
909
  });
@@ -892,6 +924,7 @@ var PluvRoom = class {
892
924
  this._updateState((oldState) => {
893
925
  oldState.authorization.token = null;
894
926
  oldState.connection.state = import_types.ConnectionState.Closed;
927
+ oldState.storage.state = oldState.storage.state === import_types.StorageState.Synced ? import_types.StorageState.Offline : import_types.StorageState.Unavailable;
895
928
  oldState.webSocket = null;
896
929
  return oldState;
897
930
  });
@@ -1138,7 +1171,6 @@ var PluvRoom = class {
1138
1171
  const data = message.data;
1139
1172
  const state = data.state;
1140
1173
  this._updateState((oldState) => {
1141
- oldState.connection.count += 1;
1142
1174
  oldState.connection.id = connectionId;
1143
1175
  oldState.authorization.user = user != null ? user : null;
1144
1176
  return oldState;
@@ -1190,6 +1222,10 @@ var PluvRoom = class {
1190
1222
  const encodedState = this._crdtManager.doc.getEncodedState();
1191
1223
  this._emitSharedTypes();
1192
1224
  this._observeCrdt();
1225
+ this._updateState((oldState) => {
1226
+ oldState.storage.state = import_types.StorageState.Synced;
1227
+ return oldState;
1228
+ });
1193
1229
  this._stateNotifier.subjects["storage-loaded"].next(true);
1194
1230
  this._sendMessage({
1195
1231
  type: "$updateStorage",
@@ -1302,11 +1338,13 @@ var PluvRoom = class {
1302
1338
  this._listeners.onAuthorizationFail(authError);
1303
1339
  this._updateState((oldState) => {
1304
1340
  oldState.connection.state = import_types.ConnectionState.Unavailable;
1341
+ oldState.storage.state = import_types.StorageState.Unavailable;
1305
1342
  return oldState;
1306
1343
  });
1307
1344
  }
1308
1345
  _onClose(event) {
1309
1346
  return __async(this, null, function* () {
1347
+ this._listenerManager.subjects.close.next(event);
1310
1348
  this._logDebug("WebSocket closed");
1311
1349
  if (!!event.reason) this._logDebug(event.reason);
1312
1350
  const shouldRetry = [
@@ -1334,6 +1372,7 @@ var PluvRoom = class {
1334
1372
  */
1335
1373
  import_types.ConnectionState.Closed
1336
1374
  );
1375
+ oldState.storage.state = oldState.storage.state === import_types.StorageState.Synced ? import_types.StorageState.Offline : import_types.StorageState.Unavailable;
1337
1376
  oldState.webSocket = null;
1338
1377
  return oldState;
1339
1378
  });
@@ -1350,9 +1389,11 @@ var PluvRoom = class {
1350
1389
  * @description Handle websocket errors in a meaningful way, should they occur
1351
1390
  * @date August 19, 2022
1352
1391
  */
1353
- _onError() {
1392
+ _onError(event) {
1393
+ this._listenerManager.subjects.error.next(event);
1354
1394
  }
1355
1395
  _onMessage(event) {
1396
+ this._listenerManager.subjects.message.next(event);
1356
1397
  const message = this._parseMessage(event);
1357
1398
  if (!message) return;
1358
1399
  const shouldLog = typeof this._debug === "boolean" ? this._debug : this._debug.output.find((value) => value === message.type);
@@ -1400,7 +1441,8 @@ var PluvRoom = class {
1400
1441
  default:
1401
1442
  }
1402
1443
  }
1403
- _onOpen() {
1444
+ _onOpen(event) {
1445
+ this._listenerManager.subjects.open.next(event);
1404
1446
  if (this._state.connection.state !== import_types.ConnectionState.Connecting) return;
1405
1447
  this._logDebug("WebSocket connected");
1406
1448
  this._updateState((oldState) => {
@@ -1418,6 +1460,7 @@ var PluvRoom = class {
1418
1460
  if (this._state.connection.state === import_types.ConnectionState.Closed) return;
1419
1461
  this._updateState((oldState) => {
1420
1462
  oldState.connection.state = import_types.ConnectionState.Unavailable;
1463
+ oldState.storage.state = oldState.storage.state === import_types.StorageState.Synced ? import_types.StorageState.Offline : import_types.StorageState.Unavailable;
1421
1464
  return oldState;
1422
1465
  });
1423
1466
  });
@@ -1517,6 +1560,7 @@ var PluvRoom = class {
1517
1560
  const oldState = __spreadProps(__spreadValues({}, this._state), {
1518
1561
  authorization,
1519
1562
  connection: JSON.parse(JSON.stringify(this._state.connection)),
1563
+ storage: JSON.parse(JSON.stringify(this._state.storage)),
1520
1564
  webSocket: this._state.webSocket
1521
1565
  });
1522
1566
  const newState = updater(oldState);
@@ -1633,6 +1677,10 @@ var infer = (callback) => callback;
1633
1677
  var import_types2 = require("@pluv/types");
1634
1678
  var MockedRoom = class {
1635
1679
  constructor(room, options) {
1680
+ /**
1681
+ * @deprecated To be removed in v3. Use `getStorageLoaded` instead.
1682
+ */
1683
+ this.storageLoaded = true;
1636
1684
  this._crdtNotifier = new CrdtNotifier();
1637
1685
  this._eventNotifier = new EventNotifier();
1638
1686
  this._usersNotifier = new UsersNotifier();
@@ -1643,10 +1691,12 @@ var MockedRoom = class {
1643
1691
  },
1644
1692
  connection: {
1645
1693
  attempts: 0,
1646
- count: 1,
1647
1694
  id: null,
1648
1695
  state: import_types2.ConnectionState.Untouched
1649
1696
  },
1697
+ storage: {
1698
+ state: import_types2.StorageState.Unavailable
1699
+ },
1650
1700
  webSocket: null
1651
1701
  };
1652
1702
  this._stateNotifier = new StateNotifier();
@@ -1821,8 +1871,8 @@ var MockedRoom = class {
1821
1871
  });
1822
1872
  this._observeCrdt();
1823
1873
  }
1824
- get storageLoaded() {
1825
- return true;
1874
+ addEventListener(kind, handler) {
1875
+ return () => void 0;
1826
1876
  }
1827
1877
  getDoc() {
1828
1878
  return this._crdtManager.doc;
@@ -1832,6 +1882,9 @@ var MockedRoom = class {
1832
1882
  if (typeof type === "undefined") return this._crdtManager.doc.toJson();
1833
1883
  return this._crdtManager.doc.toJson(type);
1834
1884
  }
1885
+ getStorageLoaded() {
1886
+ return true;
1887
+ }
1835
1888
  _observeCrdt() {
1836
1889
  var _a, _b;
1837
1890
  (_b = (_a = this._subscriptions).observeCrdt) == null ? void 0 : _b.call(_a);