@pluv/types 2.0.2 → 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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @pluv/types@2.0.2 build /home/runner/work/pluv/pluv/packages/types
2
+ > @pluv/types@2.2.0 build /home/runner/work/pluv/pluv/packages/types
3
3
  > tsup src/index.ts --format esm,cjs --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,11 +8,11 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- CJS dist/index.js 1.21 KB
11
+ ESM dist/index.mjs 330.00 B
12
+ ESM ⚡️ Build success in 33ms
13
+ CJS dist/index.js 1.37 KB
12
14
  CJS ⚡️ Build success in 33ms
13
- ESM dist/index.mjs 197.00 B
14
- ESM ⚡️ Build success in 34ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 1191ms
17
- DTS dist/index.d.mts 14.71 KB
18
- DTS dist/index.d.ts 14.71 KB
16
+ DTS ⚡️ Build success in 1278ms
17
+ DTS dist/index.d.mts 18.73 KB
18
+ DTS dist/index.d.ts 18.73 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @pluv/types
2
2
 
3
+ ## 2.2.0
4
+
5
+ ## 2.1.0
6
+
3
7
  ## 2.0.2
4
8
 
5
9
  ## 2.0.1
package/dist/index.d.mts CHANGED
@@ -56,13 +56,14 @@ interface DocBatchApplyEncodedStateParams {
56
56
  origin?: string;
57
57
  updates?: Maybe<readonly Maybe<string | Uint8Array>[]>;
58
58
  }
59
- interface DocSubscribeCallbackParams<TStorage extends Record<string, CrdtType<any, any>>> {
60
- doc: CrdtDocLike<TStorage>;
59
+ interface DocSubscribeCallbackParams<TDoc extends any, TStorage extends Record<string, CrdtType<any, any>>> {
60
+ doc: CrdtDocLike<TDoc, TStorage>;
61
61
  local: boolean;
62
62
  origin?: string | null;
63
63
  update: string;
64
64
  }
65
- interface CrdtDocLike<TStorage extends Record<string, CrdtType<any, any>>> {
65
+ interface CrdtDocLike<TDoc extends any, TStorage extends Record<string, CrdtType<any, any>>> {
66
+ value: TDoc;
66
67
  applyEncodedState(params: DocApplyEncodedStateParams): this;
67
68
  batchApplyEncodedState(params: DocBatchApplyEncodedStateParams): this;
68
69
  canRedo(): boolean;
@@ -74,7 +75,7 @@ interface CrdtDocLike<TStorage extends Record<string, CrdtType<any, any>>> {
74
75
  isEmpty(): boolean;
75
76
  rebuildStorage(reference: TStorage): this;
76
77
  redo(): this;
77
- subscribe(listener: (params: DocSubscribeCallbackParams<TStorage>) => void): () => void;
78
+ subscribe(listener: (params: DocSubscribeCallbackParams<TDoc, TStorage>) => void): () => void;
78
79
  toJson(): InferCrdtJson<TStorage>;
79
80
  toJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]>;
80
81
  track(): this;
@@ -83,13 +84,20 @@ interface CrdtDocLike<TStorage extends Record<string, CrdtType<any, any>>> {
83
84
  }
84
85
 
85
86
  declare const ConnectionState: {
86
- readonly Closed: "Closed";
87
- readonly Connecting: "Connecting";
88
- readonly Open: "Open";
89
- readonly Unavailable: "Unavailable";
90
- readonly Untouched: "Untouched";
87
+ readonly Closed: "closed";
88
+ readonly Connecting: "connecting";
89
+ readonly Open: "open";
90
+ readonly Unavailable: "unavailable";
91
+ readonly Untouched: "untouched";
91
92
  };
92
93
  type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState];
94
+ declare const StorageState: {
95
+ Loading: string;
96
+ Offline: string;
97
+ Synced: string;
98
+ Unavailable: string;
99
+ };
100
+ type StorageState = (typeof StorageState)[keyof typeof StorageState];
93
101
 
94
102
  type BaseUser = {
95
103
  id: string;
@@ -242,19 +250,36 @@ interface AuthorizationState<TIO extends IOLike> {
242
250
  token: string | null;
243
251
  user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>> | null;
244
252
  }
245
- type EventNotifierSubscriptionCallback<TIO extends IOLike<any, any>, TEvent extends keyof InferIOOutput<TIO>> = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
246
- type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject> = (value: Id<UserInfo<TIO, TPresence>> | null) => void;
247
- interface StateNotifierSubjects<TIO extends IOLike, TPresence extends JsonObject> {
253
+ type OtherSubscriptionCallback<TIO extends IOLike, TPresence extends Record<string, any>> = (value: Id<UserInfo<TIO, TPresence>> | null) => void;
254
+ type OtherSubscriptionFn<TIO extends IOLike, TPresence extends Record<string, any>> = (connectionId: string, callback: OtherSubscriptionCallback<TIO, TPresence>) => () => void;
255
+ type OthersSubscriptionEvent<TIO extends IOLike, TPresence extends Record<string, any>> = {
256
+ kind: "clear";
257
+ } | {
258
+ kind: "enter";
259
+ user: Id<UserInfo<TIO, TPresence>>;
260
+ } | {
261
+ kind: "leave";
262
+ user: Id<UserInfo<TIO, TPresence>>;
263
+ } | {
264
+ kind: "sync";
265
+ users: readonly Id<UserInfo<TIO, TPresence>>[];
266
+ } | {
267
+ kind: "update";
268
+ user: Id<UserInfo<TIO, TPresence>>;
269
+ };
270
+ type OthersSubscriptionCallback<TIO extends IOLike, TPresence extends Record<string, any>> = (value: readonly Id<UserInfo<TIO, TPresence>>[], event: OthersSubscriptionEvent<TIO, TPresence>) => void;
271
+ type OthersSubscriptionFn<TIO extends IOLike, TPresence extends Record<string, any>> = (callback: OthersSubscriptionCallback<TIO, TPresence>) => () => void;
272
+ interface StateNotifierSubjects<TIO extends IOLike, TPresence extends Record<string, any>> {
248
273
  connection: Subject<Id<WebSocketState<TIO>>>;
249
274
  "my-presence": Subject<TPresence | null>;
250
- myself: Subject<Readonly<Id<UserInfo<TIO>>> | null>;
251
- others: Subject<readonly Id<UserInfo<TIO>>[]>;
275
+ myself: Subject<Readonly<Id<UserInfo<TIO, TPresence>>> | null>;
276
+ others: Subject<readonly Id<UserInfo<TIO, TPresence>>[]>;
252
277
  "storage-loaded": Subject<boolean>;
253
278
  }
254
- type InferSubjectValue<TIO extends IOLike, TPresence extends JsonObject, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue> ? Id<IValue> : never;
255
- type SubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
256
- type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
257
- interface UserInfo<TIO extends IOLike, TPresence extends JsonObject = {}> {
279
+ type InferSubjectValue<TIO extends IOLike, TPresence extends Record<string, any>, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue> ? Id<IValue> : never;
280
+ type SubscriptionCallback<TIO extends IOLike, TPresence extends Record<string, any>, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
281
+ type UpdateMyPresenceAction<TPresence extends Record<string, any>> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
282
+ interface UserInfo<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
258
283
  connectionId: string;
259
284
  presence: TPresence;
260
285
  user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>;
@@ -266,30 +291,64 @@ interface WebSocketConnection {
266
291
  * @date April 13, 2025
267
292
  */
268
293
  attempts: number;
269
- /**
270
- * @description How many times the user has connected to the room. This can be more than 1 in
271
- * the case of reconnects. This is tracked because on the very first connection (i.e. when
272
- * count is 0) we don't want to send the storage state as an update to the room and overwrite
273
- * the upstream state.
274
- * @date April 13, 2025
275
- */
276
- count: number;
277
294
  id: string | null;
278
295
  state: ConnectionState;
279
296
  }
297
+ interface StorageInfo {
298
+ state: StorageState;
299
+ }
280
300
  interface WebSocketState<TIO extends IOLike> {
281
301
  authorization: AuthorizationState<TIO>;
282
302
  connection: WebSocketConnection;
303
+ storage: StorageInfo;
283
304
  webSocket: WebSocket | null;
284
305
  }
285
- interface RoomLike<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig = {}> {
306
+ type BroadcastProxy<TIO extends IOLike, TEvents extends PluvRouterEventConfig> = (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>) => Promise<void>) & {
307
+ [PEvent in keyof InferIOInput<MergeEvents<TEvents, TIO>>]: (data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[PEvent]>) => Promise<void>;
308
+ };
309
+ type EventNotifierSubscriptionCallback<TIO extends IOLike<any, any>, TEvent extends keyof InferIOOutput<TIO>> = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
310
+ type EventSubscriptionFn<TIO extends IOLike, TEvents extends PluvRouterEventConfig> = <TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(event: TEvent, callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>) => () => void;
311
+ type EventProxy<TIO extends IOLike, TEvents extends PluvRouterEventConfig> = EventSubscriptionFn<TIO, TEvents> & {
312
+ [PEvent in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, PEvent>) => () => void;
313
+ };
314
+ type StorageSubscriptionCallback<TStorage extends Record<string, CrdtType<any, any>>, TKey extends keyof TStorage> = (value: InferCrdtJson<TStorage[TKey]>) => void;
315
+ type StorageSubscriptionFn<TStorage extends Record<string, CrdtType<any, any>>> = <TKey extends keyof TStorage>(key: TKey, fn: StorageSubscriptionCallback<TStorage, TKey>) => () => void;
316
+ type StorageRootSubscriptionCallback<TStorage extends Record<string, CrdtType<any, any>>> = (value: {
317
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
318
+ }) => void;
319
+ type StorageRootSubscriptionFn<TStorage extends Record<string, CrdtType<any, any>>> = (callback: StorageRootSubscriptionCallback<TStorage>) => () => void;
320
+ type StorageProxy<TStorage extends Record<string, CrdtType<any, any>>> = StorageSubscriptionFn<TStorage> & StorageRootSubscriptionFn<TStorage> & {
321
+ [PKey in keyof TStorage]: (callback: StorageSubscriptionCallback<TStorage, PKey>) => () => void;
322
+ };
323
+ type SubscribeFn<TValue extends unknown> = (callback: (value: TValue) => void) => () => void;
324
+ type SubscribeProxy<TIO extends IOLike, TPresence extends Record<string, any>, TStorage extends Record<string, CrdtType<any, any>>, TEvents extends PluvRouterEventConfig> = (<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(name: TSubject, callback: SubscriptionCallback<TIO, TPresence, TSubject>) => () => void) & {
325
+ connection: SubscribeFn<Id<WebSocketState<TIO>>>;
326
+ event: EventProxy<TIO, TEvents>;
327
+ myPresence: SubscribeFn<TPresence | null>;
328
+ myself: SubscribeFn<Id<UserInfo<TIO>> | null>;
329
+ other: OtherSubscriptionFn<TIO, TPresence>;
330
+ others: OthersSubscriptionFn<TIO, TPresence>;
331
+ storage: StorageProxy<TStorage>;
332
+ storageLoaded: SubscribeFn<boolean>;
333
+ };
334
+ interface RoomEventListenerMap {
335
+ close: (event: CloseEvent | null) => void;
336
+ error: (event: Event) => void;
337
+ message: (event: MessageEvent<any>) => void;
338
+ open: (event: Event) => void;
339
+ }
340
+ interface RoomLike<TIO extends IOLike, TDoc extends any, TPresence extends Record<string, any> = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig = {}> {
286
341
  id: string;
342
+ broadcast: BroadcastProxy<TIO, TEvents>;
343
+ /**
344
+ * @deprecated To be removed in v3. Use getStorageLoaded instead.
345
+ */
287
346
  storageLoaded: boolean;
288
- broadcast<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>): void;
347
+ addEventListener<TKind extends keyof RoomEventListenerMap>(kind: TKind, handler: RoomEventListenerMap[TKind]): () => void;
289
348
  canRedo(): boolean;
290
349
  canUndo(): boolean;
291
- getDoc(): CrdtDocLike<TStorage>;
292
- event<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(event: TEvent, callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>): () => void;
350
+ getDoc(): CrdtDocLike<TDoc, TStorage>;
351
+ event: EventProxy<TIO, TEvents>;
293
352
  getConnection(): WebSocketConnection;
294
353
  getMyPresence(): TPresence;
295
354
  getMyself(): Id<UserInfo<TIO, TPresence>> | null;
@@ -298,16 +357,17 @@ interface RoomLike<TIO extends IOLike, TPresence extends JsonObject = {}, TStora
298
357
  getStorage<TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null;
299
358
  getStorageJson(): InferCrdtJson<TStorage> | null;
300
359
  getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
301
- other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>): () => void;
360
+ getStorageLoaded: () => boolean;
361
+ other(connectionId: string, callback: OtherSubscriptionCallback<TIO, TPresence>): () => void;
302
362
  redo(): void;
303
- storage<TKey extends keyof TStorage>(key: TKey, fn: (value: InferCrdtJson<TStorage[TKey]>) => void): () => void;
363
+ storage: StorageProxy<TStorage>;
304
364
  storageRoot(fn: (value: {
305
365
  [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
306
366
  }) => void): () => void;
307
- subscribe<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(name: TSubject, callback: SubscriptionCallback<TIO, TPresence, TSubject>): () => void;
367
+ subscribe: SubscribeProxy<TIO, TPresence, TStorage, TEvents>;
308
368
  transact(fn: (storage: TStorage) => void, origin?: string): void;
309
369
  undo(): void;
310
370
  updateMyPresence(presence: UpdateMyPresenceAction<TPresence>): void;
311
371
  }
312
372
 
313
- export { type BaseClientEventRecord, type BaseClientMessage, type BaseIOEventRecord, type BaseUser, ConnectionState, type Constructable, type CrdtDocLike, type CrdtType, type DeepPartial, type DeepWriteable, type DocApplyEncodedStateParams, type DocBatchApplyEncodedStateParams, type DocSubscribeCallbackParams, type EventMessage, type EventNotifierSubscriptionCallback, type EventRecord, type GetEventMessage, type IOAuthorize, type IOAuthorizeEventMessage, type IOEventMessage, type IOLike, type IORouterLike, type Id, type InferEventMessage, type InferEventsInput, type InferEventsOutput, type InferIOAuthorize, type InferIOAuthorizeUser, type InferIOInput, type InferIOOutput, type InputZodLike, type Json, type JsonObject, type JsonPrimitive, type Maybe, type MaybePromise, type MaybeReadonlyArray, type MergeEvents, type NonNilProps, type OptionalProps, type OtherNotifierSubscriptionCallback, type ProcedureLike, type RoomLike, type Spread, type SpreadProperties, type UndefinedProps, type UpdateMyPresenceAction, type UserInfo, type WebSocketConnection, type WebSocketState };
373
+ export { type BaseClientEventRecord, type BaseClientMessage, type BaseIOEventRecord, type BaseUser, type BroadcastProxy, ConnectionState, type Constructable, type CrdtDocLike, type CrdtType, type DeepPartial, type DeepWriteable, type DocApplyEncodedStateParams, type DocBatchApplyEncodedStateParams, type DocSubscribeCallbackParams, type EventMessage, type EventNotifierSubscriptionCallback, type EventProxy, type EventRecord, type GetEventMessage, type IOAuthorize, type IOAuthorizeEventMessage, type IOEventMessage, type IOLike, type IORouterLike, type Id, type InferCrdtJson, type InferEventMessage, type InferEventsInput, type InferEventsOutput, type InferIOAuthorize, type InferIOAuthorizeUser, type InferIOInput, type InferIOOutput, type InputZodLike, type Json, type JsonObject, type JsonPrimitive, type Maybe, type MaybePromise, type MaybeReadonlyArray, type MergeEvents, type NonNilProps, type OptionalProps, type OtherSubscriptionCallback, type OtherSubscriptionFn, type OthersSubscriptionCallback, type OthersSubscriptionEvent, type OthersSubscriptionFn, type PluvRouterEventConfig, type ProcedureLike, type RoomEventListenerMap, type RoomLike, type Spread, type SpreadProperties, type StateNotifierSubjects, type StorageInfo, type StorageProxy, type StorageRootSubscriptionCallback, type StorageRootSubscriptionFn, StorageState, type StorageSubscriptionCallback, type StorageSubscriptionFn, type SubscribeFn, type SubscribeProxy, type SubscriptionCallback, type UndefinedProps, type UpdateMyPresenceAction, type UserInfo, type WebSocketConnection, type WebSocketState };
package/dist/index.d.ts CHANGED
@@ -56,13 +56,14 @@ interface DocBatchApplyEncodedStateParams {
56
56
  origin?: string;
57
57
  updates?: Maybe<readonly Maybe<string | Uint8Array>[]>;
58
58
  }
59
- interface DocSubscribeCallbackParams<TStorage extends Record<string, CrdtType<any, any>>> {
60
- doc: CrdtDocLike<TStorage>;
59
+ interface DocSubscribeCallbackParams<TDoc extends any, TStorage extends Record<string, CrdtType<any, any>>> {
60
+ doc: CrdtDocLike<TDoc, TStorage>;
61
61
  local: boolean;
62
62
  origin?: string | null;
63
63
  update: string;
64
64
  }
65
- interface CrdtDocLike<TStorage extends Record<string, CrdtType<any, any>>> {
65
+ interface CrdtDocLike<TDoc extends any, TStorage extends Record<string, CrdtType<any, any>>> {
66
+ value: TDoc;
66
67
  applyEncodedState(params: DocApplyEncodedStateParams): this;
67
68
  batchApplyEncodedState(params: DocBatchApplyEncodedStateParams): this;
68
69
  canRedo(): boolean;
@@ -74,7 +75,7 @@ interface CrdtDocLike<TStorage extends Record<string, CrdtType<any, any>>> {
74
75
  isEmpty(): boolean;
75
76
  rebuildStorage(reference: TStorage): this;
76
77
  redo(): this;
77
- subscribe(listener: (params: DocSubscribeCallbackParams<TStorage>) => void): () => void;
78
+ subscribe(listener: (params: DocSubscribeCallbackParams<TDoc, TStorage>) => void): () => void;
78
79
  toJson(): InferCrdtJson<TStorage>;
79
80
  toJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]>;
80
81
  track(): this;
@@ -83,13 +84,20 @@ interface CrdtDocLike<TStorage extends Record<string, CrdtType<any, any>>> {
83
84
  }
84
85
 
85
86
  declare const ConnectionState: {
86
- readonly Closed: "Closed";
87
- readonly Connecting: "Connecting";
88
- readonly Open: "Open";
89
- readonly Unavailable: "Unavailable";
90
- readonly Untouched: "Untouched";
87
+ readonly Closed: "closed";
88
+ readonly Connecting: "connecting";
89
+ readonly Open: "open";
90
+ readonly Unavailable: "unavailable";
91
+ readonly Untouched: "untouched";
91
92
  };
92
93
  type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState];
94
+ declare const StorageState: {
95
+ Loading: string;
96
+ Offline: string;
97
+ Synced: string;
98
+ Unavailable: string;
99
+ };
100
+ type StorageState = (typeof StorageState)[keyof typeof StorageState];
93
101
 
94
102
  type BaseUser = {
95
103
  id: string;
@@ -242,19 +250,36 @@ interface AuthorizationState<TIO extends IOLike> {
242
250
  token: string | null;
243
251
  user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>> | null;
244
252
  }
245
- type EventNotifierSubscriptionCallback<TIO extends IOLike<any, any>, TEvent extends keyof InferIOOutput<TIO>> = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
246
- type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject> = (value: Id<UserInfo<TIO, TPresence>> | null) => void;
247
- interface StateNotifierSubjects<TIO extends IOLike, TPresence extends JsonObject> {
253
+ type OtherSubscriptionCallback<TIO extends IOLike, TPresence extends Record<string, any>> = (value: Id<UserInfo<TIO, TPresence>> | null) => void;
254
+ type OtherSubscriptionFn<TIO extends IOLike, TPresence extends Record<string, any>> = (connectionId: string, callback: OtherSubscriptionCallback<TIO, TPresence>) => () => void;
255
+ type OthersSubscriptionEvent<TIO extends IOLike, TPresence extends Record<string, any>> = {
256
+ kind: "clear";
257
+ } | {
258
+ kind: "enter";
259
+ user: Id<UserInfo<TIO, TPresence>>;
260
+ } | {
261
+ kind: "leave";
262
+ user: Id<UserInfo<TIO, TPresence>>;
263
+ } | {
264
+ kind: "sync";
265
+ users: readonly Id<UserInfo<TIO, TPresence>>[];
266
+ } | {
267
+ kind: "update";
268
+ user: Id<UserInfo<TIO, TPresence>>;
269
+ };
270
+ type OthersSubscriptionCallback<TIO extends IOLike, TPresence extends Record<string, any>> = (value: readonly Id<UserInfo<TIO, TPresence>>[], event: OthersSubscriptionEvent<TIO, TPresence>) => void;
271
+ type OthersSubscriptionFn<TIO extends IOLike, TPresence extends Record<string, any>> = (callback: OthersSubscriptionCallback<TIO, TPresence>) => () => void;
272
+ interface StateNotifierSubjects<TIO extends IOLike, TPresence extends Record<string, any>> {
248
273
  connection: Subject<Id<WebSocketState<TIO>>>;
249
274
  "my-presence": Subject<TPresence | null>;
250
- myself: Subject<Readonly<Id<UserInfo<TIO>>> | null>;
251
- others: Subject<readonly Id<UserInfo<TIO>>[]>;
275
+ myself: Subject<Readonly<Id<UserInfo<TIO, TPresence>>> | null>;
276
+ others: Subject<readonly Id<UserInfo<TIO, TPresence>>[]>;
252
277
  "storage-loaded": Subject<boolean>;
253
278
  }
254
- type InferSubjectValue<TIO extends IOLike, TPresence extends JsonObject, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue> ? Id<IValue> : never;
255
- type SubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
256
- type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
257
- interface UserInfo<TIO extends IOLike, TPresence extends JsonObject = {}> {
279
+ type InferSubjectValue<TIO extends IOLike, TPresence extends Record<string, any>, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue> ? Id<IValue> : never;
280
+ type SubscriptionCallback<TIO extends IOLike, TPresence extends Record<string, any>, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
281
+ type UpdateMyPresenceAction<TPresence extends Record<string, any>> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
282
+ interface UserInfo<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
258
283
  connectionId: string;
259
284
  presence: TPresence;
260
285
  user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>;
@@ -266,30 +291,64 @@ interface WebSocketConnection {
266
291
  * @date April 13, 2025
267
292
  */
268
293
  attempts: number;
269
- /**
270
- * @description How many times the user has connected to the room. This can be more than 1 in
271
- * the case of reconnects. This is tracked because on the very first connection (i.e. when
272
- * count is 0) we don't want to send the storage state as an update to the room and overwrite
273
- * the upstream state.
274
- * @date April 13, 2025
275
- */
276
- count: number;
277
294
  id: string | null;
278
295
  state: ConnectionState;
279
296
  }
297
+ interface StorageInfo {
298
+ state: StorageState;
299
+ }
280
300
  interface WebSocketState<TIO extends IOLike> {
281
301
  authorization: AuthorizationState<TIO>;
282
302
  connection: WebSocketConnection;
303
+ storage: StorageInfo;
283
304
  webSocket: WebSocket | null;
284
305
  }
285
- interface RoomLike<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig = {}> {
306
+ type BroadcastProxy<TIO extends IOLike, TEvents extends PluvRouterEventConfig> = (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>) => Promise<void>) & {
307
+ [PEvent in keyof InferIOInput<MergeEvents<TEvents, TIO>>]: (data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[PEvent]>) => Promise<void>;
308
+ };
309
+ type EventNotifierSubscriptionCallback<TIO extends IOLike<any, any>, TEvent extends keyof InferIOOutput<TIO>> = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
310
+ type EventSubscriptionFn<TIO extends IOLike, TEvents extends PluvRouterEventConfig> = <TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(event: TEvent, callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>) => () => void;
311
+ type EventProxy<TIO extends IOLike, TEvents extends PluvRouterEventConfig> = EventSubscriptionFn<TIO, TEvents> & {
312
+ [PEvent in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, PEvent>) => () => void;
313
+ };
314
+ type StorageSubscriptionCallback<TStorage extends Record<string, CrdtType<any, any>>, TKey extends keyof TStorage> = (value: InferCrdtJson<TStorage[TKey]>) => void;
315
+ type StorageSubscriptionFn<TStorage extends Record<string, CrdtType<any, any>>> = <TKey extends keyof TStorage>(key: TKey, fn: StorageSubscriptionCallback<TStorage, TKey>) => () => void;
316
+ type StorageRootSubscriptionCallback<TStorage extends Record<string, CrdtType<any, any>>> = (value: {
317
+ [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
318
+ }) => void;
319
+ type StorageRootSubscriptionFn<TStorage extends Record<string, CrdtType<any, any>>> = (callback: StorageRootSubscriptionCallback<TStorage>) => () => void;
320
+ type StorageProxy<TStorage extends Record<string, CrdtType<any, any>>> = StorageSubscriptionFn<TStorage> & StorageRootSubscriptionFn<TStorage> & {
321
+ [PKey in keyof TStorage]: (callback: StorageSubscriptionCallback<TStorage, PKey>) => () => void;
322
+ };
323
+ type SubscribeFn<TValue extends unknown> = (callback: (value: TValue) => void) => () => void;
324
+ type SubscribeProxy<TIO extends IOLike, TPresence extends Record<string, any>, TStorage extends Record<string, CrdtType<any, any>>, TEvents extends PluvRouterEventConfig> = (<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(name: TSubject, callback: SubscriptionCallback<TIO, TPresence, TSubject>) => () => void) & {
325
+ connection: SubscribeFn<Id<WebSocketState<TIO>>>;
326
+ event: EventProxy<TIO, TEvents>;
327
+ myPresence: SubscribeFn<TPresence | null>;
328
+ myself: SubscribeFn<Id<UserInfo<TIO>> | null>;
329
+ other: OtherSubscriptionFn<TIO, TPresence>;
330
+ others: OthersSubscriptionFn<TIO, TPresence>;
331
+ storage: StorageProxy<TStorage>;
332
+ storageLoaded: SubscribeFn<boolean>;
333
+ };
334
+ interface RoomEventListenerMap {
335
+ close: (event: CloseEvent | null) => void;
336
+ error: (event: Event) => void;
337
+ message: (event: MessageEvent<any>) => void;
338
+ open: (event: Event) => void;
339
+ }
340
+ interface RoomLike<TIO extends IOLike, TDoc extends any, TPresence extends Record<string, any> = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig = {}> {
286
341
  id: string;
342
+ broadcast: BroadcastProxy<TIO, TEvents>;
343
+ /**
344
+ * @deprecated To be removed in v3. Use getStorageLoaded instead.
345
+ */
287
346
  storageLoaded: boolean;
288
- broadcast<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>): void;
347
+ addEventListener<TKind extends keyof RoomEventListenerMap>(kind: TKind, handler: RoomEventListenerMap[TKind]): () => void;
289
348
  canRedo(): boolean;
290
349
  canUndo(): boolean;
291
- getDoc(): CrdtDocLike<TStorage>;
292
- event<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(event: TEvent, callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>): () => void;
350
+ getDoc(): CrdtDocLike<TDoc, TStorage>;
351
+ event: EventProxy<TIO, TEvents>;
293
352
  getConnection(): WebSocketConnection;
294
353
  getMyPresence(): TPresence;
295
354
  getMyself(): Id<UserInfo<TIO, TPresence>> | null;
@@ -298,16 +357,17 @@ interface RoomLike<TIO extends IOLike, TPresence extends JsonObject = {}, TStora
298
357
  getStorage<TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null;
299
358
  getStorageJson(): InferCrdtJson<TStorage> | null;
300
359
  getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
301
- other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>): () => void;
360
+ getStorageLoaded: () => boolean;
361
+ other(connectionId: string, callback: OtherSubscriptionCallback<TIO, TPresence>): () => void;
302
362
  redo(): void;
303
- storage<TKey extends keyof TStorage>(key: TKey, fn: (value: InferCrdtJson<TStorage[TKey]>) => void): () => void;
363
+ storage: StorageProxy<TStorage>;
304
364
  storageRoot(fn: (value: {
305
365
  [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
306
366
  }) => void): () => void;
307
- subscribe<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(name: TSubject, callback: SubscriptionCallback<TIO, TPresence, TSubject>): () => void;
367
+ subscribe: SubscribeProxy<TIO, TPresence, TStorage, TEvents>;
308
368
  transact(fn: (storage: TStorage) => void, origin?: string): void;
309
369
  undo(): void;
310
370
  updateMyPresence(presence: UpdateMyPresenceAction<TPresence>): void;
311
371
  }
312
372
 
313
- export { type BaseClientEventRecord, type BaseClientMessage, type BaseIOEventRecord, type BaseUser, ConnectionState, type Constructable, type CrdtDocLike, type CrdtType, type DeepPartial, type DeepWriteable, type DocApplyEncodedStateParams, type DocBatchApplyEncodedStateParams, type DocSubscribeCallbackParams, type EventMessage, type EventNotifierSubscriptionCallback, type EventRecord, type GetEventMessage, type IOAuthorize, type IOAuthorizeEventMessage, type IOEventMessage, type IOLike, type IORouterLike, type Id, type InferEventMessage, type InferEventsInput, type InferEventsOutput, type InferIOAuthorize, type InferIOAuthorizeUser, type InferIOInput, type InferIOOutput, type InputZodLike, type Json, type JsonObject, type JsonPrimitive, type Maybe, type MaybePromise, type MaybeReadonlyArray, type MergeEvents, type NonNilProps, type OptionalProps, type OtherNotifierSubscriptionCallback, type ProcedureLike, type RoomLike, type Spread, type SpreadProperties, type UndefinedProps, type UpdateMyPresenceAction, type UserInfo, type WebSocketConnection, type WebSocketState };
373
+ export { type BaseClientEventRecord, type BaseClientMessage, type BaseIOEventRecord, type BaseUser, type BroadcastProxy, ConnectionState, type Constructable, type CrdtDocLike, type CrdtType, type DeepPartial, type DeepWriteable, type DocApplyEncodedStateParams, type DocBatchApplyEncodedStateParams, type DocSubscribeCallbackParams, type EventMessage, type EventNotifierSubscriptionCallback, type EventProxy, type EventRecord, type GetEventMessage, type IOAuthorize, type IOAuthorizeEventMessage, type IOEventMessage, type IOLike, type IORouterLike, type Id, type InferCrdtJson, type InferEventMessage, type InferEventsInput, type InferEventsOutput, type InferIOAuthorize, type InferIOAuthorizeUser, type InferIOInput, type InferIOOutput, type InputZodLike, type Json, type JsonObject, type JsonPrimitive, type Maybe, type MaybePromise, type MaybeReadonlyArray, type MergeEvents, type NonNilProps, type OptionalProps, type OtherSubscriptionCallback, type OtherSubscriptionFn, type OthersSubscriptionCallback, type OthersSubscriptionEvent, type OthersSubscriptionFn, type PluvRouterEventConfig, type ProcedureLike, type RoomEventListenerMap, type RoomLike, type Spread, type SpreadProperties, type StateNotifierSubjects, type StorageInfo, type StorageProxy, type StorageRootSubscriptionCallback, type StorageRootSubscriptionFn, StorageState, type StorageSubscriptionCallback, type StorageSubscriptionFn, type SubscribeFn, type SubscribeProxy, type SubscriptionCallback, type UndefinedProps, type UpdateMyPresenceAction, type UserInfo, type WebSocketConnection, type WebSocketState };
package/dist/index.js CHANGED
@@ -20,19 +20,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- ConnectionState: () => ConnectionState
23
+ ConnectionState: () => ConnectionState,
24
+ StorageState: () => StorageState
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
26
27
 
27
28
  // src/pluv/enums.ts
28
29
  var ConnectionState = {
29
- Closed: "Closed",
30
- Connecting: "Connecting",
31
- Open: "Open",
32
- Unavailable: "Unavailable",
33
- Untouched: "Untouched"
30
+ Closed: "closed",
31
+ Connecting: "connecting",
32
+ Open: "open",
33
+ Unavailable: "unavailable",
34
+ Untouched: "untouched"
35
+ };
36
+ var StorageState = {
37
+ Loading: "loading",
38
+ Offline: "offline",
39
+ Synced: "synced",
40
+ Unavailable: "unavailable"
34
41
  };
35
42
  // Annotate the CommonJS export names for ESM import in node:
36
43
  0 && (module.exports = {
37
- ConnectionState
44
+ ConnectionState,
45
+ StorageState
38
46
  });
package/dist/index.mjs CHANGED
@@ -1,11 +1,18 @@
1
1
  // src/pluv/enums.ts
2
2
  var ConnectionState = {
3
- Closed: "Closed",
4
- Connecting: "Connecting",
5
- Open: "Open",
6
- Unavailable: "Unavailable",
7
- Untouched: "Untouched"
3
+ Closed: "closed",
4
+ Connecting: "connecting",
5
+ Open: "open",
6
+ Unavailable: "unavailable",
7
+ Untouched: "untouched"
8
+ };
9
+ var StorageState = {
10
+ Loading: "loading",
11
+ Offline: "offline",
12
+ Synced: "synced",
13
+ Unavailable: "unavailable"
8
14
  };
9
15
  export {
10
- ConnectionState
16
+ ConnectionState,
17
+ StorageState
11
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/types",
3
- "version": "2.0.2",
3
+ "version": "2.2.0",
4
4
  "description": "@pluv/io shared types",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -19,8 +19,8 @@
19
19
  "eslint": "^9.26.0",
20
20
  "tsup": "^8.4.0",
21
21
  "typescript": "^5.8.3",
22
- "@pluv/tsconfig": "^2.0.2",
23
- "eslint-config-pluv": "^2.0.2"
22
+ "@pluv/tsconfig": "^2.2.0",
23
+ "eslint-config-pluv": "^2.2.0"
24
24
  },
25
25
  "dependencies": {
26
26
  "wonka": "^6.3.5"
package/src/index.ts CHANGED
@@ -15,12 +15,13 @@ export type {
15
15
  SpreadProperties,
16
16
  UndefinedProps,
17
17
  } from "./general";
18
- export { ConnectionState } from "./pluv";
18
+ export { ConnectionState, StorageState } from "./pluv";
19
19
  export type {
20
20
  BaseClientEventRecord,
21
21
  BaseClientMessage,
22
22
  BaseIOEventRecord,
23
23
  BaseUser,
24
+ BroadcastProxy,
24
25
  CrdtDocLike,
25
26
  CrdtType,
26
27
  DocApplyEncodedStateParams,
@@ -28,8 +29,10 @@ export type {
28
29
  DocSubscribeCallbackParams,
29
30
  EventMessage,
30
31
  EventNotifierSubscriptionCallback,
32
+ EventProxy,
31
33
  EventRecord,
32
34
  GetEventMessage,
35
+ InferCrdtJson,
33
36
  InferEventMessage,
34
37
  InferEventsInput,
35
38
  InferEventsOutput,
@@ -44,9 +47,25 @@ export type {
44
47
  IOLike,
45
48
  IORouterLike,
46
49
  MergeEvents,
47
- OtherNotifierSubscriptionCallback,
50
+ OthersSubscriptionCallback,
51
+ OthersSubscriptionEvent,
52
+ OthersSubscriptionFn,
53
+ OtherSubscriptionCallback,
54
+ OtherSubscriptionFn,
55
+ PluvRouterEventConfig,
48
56
  ProcedureLike,
57
+ RoomEventListenerMap,
49
58
  RoomLike,
59
+ StateNotifierSubjects,
60
+ StorageInfo,
61
+ StorageProxy,
62
+ StorageRootSubscriptionCallback,
63
+ StorageRootSubscriptionFn,
64
+ StorageSubscriptionCallback,
65
+ StorageSubscriptionFn,
66
+ SubscribeFn,
67
+ SubscribeProxy,
68
+ SubscriptionCallback,
50
69
  UpdateMyPresenceAction,
51
70
  UserInfo,
52
71
  WebSocketConnection,
package/src/pluv/crdt.ts CHANGED
@@ -30,14 +30,22 @@ export interface DocBatchApplyEncodedStateParams {
30
30
  updates?: Maybe<readonly Maybe<string | Uint8Array>[]>;
31
31
  }
32
32
 
33
- export interface DocSubscribeCallbackParams<TStorage extends Record<string, CrdtType<any, any>>> {
34
- doc: CrdtDocLike<TStorage>;
33
+ export interface DocSubscribeCallbackParams<
34
+ TDoc extends any,
35
+ TStorage extends Record<string, CrdtType<any, any>>,
36
+ > {
37
+ doc: CrdtDocLike<TDoc, TStorage>;
35
38
  local: boolean;
36
39
  origin?: string | null;
37
40
  update: string;
38
41
  }
39
42
 
40
- export interface CrdtDocLike<TStorage extends Record<string, CrdtType<any, any>>> {
43
+ export interface CrdtDocLike<
44
+ TDoc extends any,
45
+ TStorage extends Record<string, CrdtType<any, any>>,
46
+ > {
47
+ value: TDoc;
48
+
41
49
  applyEncodedState(params: DocApplyEncodedStateParams): this;
42
50
  batchApplyEncodedState(params: DocBatchApplyEncodedStateParams): this;
43
51
  canRedo(): boolean;
@@ -49,7 +57,7 @@ export interface CrdtDocLike<TStorage extends Record<string, CrdtType<any, any>>
49
57
  isEmpty(): boolean;
50
58
  rebuildStorage(reference: TStorage): this;
51
59
  redo(): this;
52
- subscribe(listener: (params: DocSubscribeCallbackParams<TStorage>) => void): () => void;
60
+ subscribe(listener: (params: DocSubscribeCallbackParams<TDoc, TStorage>) => void): () => void;
53
61
  toJson(): InferCrdtJson<TStorage>;
54
62
  toJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]>;
55
63
  track(): this;
package/src/pluv/enums.ts CHANGED
@@ -1,9 +1,18 @@
1
1
  export const ConnectionState = {
2
- Closed: "Closed",
3
- Connecting: "Connecting",
4
- Open: "Open",
5
- Unavailable: "Unavailable",
6
- Untouched: "Untouched",
2
+ Closed: "closed",
3
+ Connecting: "connecting",
4
+ Open: "open",
5
+ Unavailable: "unavailable",
6
+ Untouched: "untouched",
7
7
  } as const;
8
8
 
9
9
  export type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState];
10
+
11
+ export const StorageState = {
12
+ Loading: "loading",
13
+ Offline: "offline",
14
+ Synced: "synced",
15
+ Unavailable: "unavailable",
16
+ };
17
+
18
+ export type StorageState = (typeof StorageState)[keyof typeof StorageState];
package/src/pluv/room.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Subject } from "wonka";
2
- import type { ConnectionState } from "./enums";
3
2
  import type { Id, JsonObject } from "../general";
4
3
  import type { CrdtDocLike, CrdtType, InferCrdtJson } from "./crdt";
4
+ import type { ConnectionState, StorageState } from "./enums";
5
5
  import type {
6
6
  InferIOAuthorize,
7
7
  InferIOAuthorizeUser,
@@ -18,26 +18,45 @@ export interface AuthorizationState<TIO extends IOLike> {
18
18
  user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>> | null;
19
19
  }
20
20
 
21
- export type EventNotifierSubscriptionCallback<
22
- TIO extends IOLike<any, any>,
23
- TEvent extends keyof InferIOOutput<TIO>,
24
- > = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
25
-
26
- export type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject> = (
21
+ export type OtherSubscriptionCallback<TIO extends IOLike, TPresence extends Record<string, any>> = (
27
22
  value: Id<UserInfo<TIO, TPresence>> | null,
28
23
  ) => void;
29
24
 
30
- export interface StateNotifierSubjects<TIO extends IOLike, TPresence extends JsonObject> {
25
+ export type OtherSubscriptionFn<TIO extends IOLike, TPresence extends Record<string, any>> = (
26
+ connectionId: string,
27
+ callback: OtherSubscriptionCallback<TIO, TPresence>,
28
+ ) => () => void;
29
+
30
+ export type OthersSubscriptionEvent<TIO extends IOLike, TPresence extends Record<string, any>> =
31
+ | { kind: "clear" }
32
+ | { kind: "enter"; user: Id<UserInfo<TIO, TPresence>> }
33
+ | { kind: "leave"; user: Id<UserInfo<TIO, TPresence>> }
34
+ | { kind: "sync"; users: readonly Id<UserInfo<TIO, TPresence>>[] }
35
+ | { kind: "update"; user: Id<UserInfo<TIO, TPresence>> };
36
+
37
+ export type OthersSubscriptionCallback<
38
+ TIO extends IOLike,
39
+ TPresence extends Record<string, any>,
40
+ > = (
41
+ value: readonly Id<UserInfo<TIO, TPresence>>[],
42
+ event: OthersSubscriptionEvent<TIO, TPresence>,
43
+ ) => void;
44
+
45
+ export type OthersSubscriptionFn<TIO extends IOLike, TPresence extends Record<string, any>> = (
46
+ callback: OthersSubscriptionCallback<TIO, TPresence>,
47
+ ) => () => void;
48
+
49
+ export interface StateNotifierSubjects<TIO extends IOLike, TPresence extends Record<string, any>> {
31
50
  connection: Subject<Id<WebSocketState<TIO>>>;
32
51
  "my-presence": Subject<TPresence | null>;
33
- myself: Subject<Readonly<Id<UserInfo<TIO>>> | null>;
34
- others: Subject<readonly Id<UserInfo<TIO>>[]>;
52
+ myself: Subject<Readonly<Id<UserInfo<TIO, TPresence>>> | null>;
53
+ others: Subject<readonly Id<UserInfo<TIO, TPresence>>[]>;
35
54
  "storage-loaded": Subject<boolean>;
36
55
  }
37
56
 
38
57
  type InferSubjectValue<
39
58
  TIO extends IOLike,
40
- TPresence extends JsonObject,
59
+ TPresence extends Record<string, any>,
41
60
  TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
42
61
  > =
43
62
  StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue>
@@ -46,15 +65,15 @@ type InferSubjectValue<
46
65
 
47
66
  export type SubscriptionCallback<
48
67
  TIO extends IOLike,
49
- TPresence extends JsonObject,
68
+ TPresence extends Record<string, any>,
50
69
  TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
51
70
  > = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
52
71
 
53
- export type UpdateMyPresenceAction<TPresence extends JsonObject> =
72
+ export type UpdateMyPresenceAction<TPresence extends Record<string, any>> =
54
73
  | Partial<TPresence>
55
74
  | ((oldPresence: TPresence | null) => Partial<TPresence>);
56
75
 
57
- export interface UserInfo<TIO extends IOLike, TPresence extends JsonObject = {}> {
76
+ export interface UserInfo<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
58
77
  connectionId: string;
59
78
  presence: TPresence;
60
79
  user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>;
@@ -67,48 +86,134 @@ export interface WebSocketConnection {
67
86
  * @date April 13, 2025
68
87
  */
69
88
  attempts: number;
70
- /**
71
- * @description How many times the user has connected to the room. This can be more than 1 in
72
- * the case of reconnects. This is tracked because on the very first connection (i.e. when
73
- * count is 0) we don't want to send the storage state as an update to the room and overwrite
74
- * the upstream state.
75
- * @date April 13, 2025
76
- */
77
- count: number;
78
89
  id: string | null;
79
90
  state: ConnectionState;
80
91
  }
81
92
 
93
+ export interface StorageInfo {
94
+ state: StorageState;
95
+ }
96
+
82
97
  export interface WebSocketState<TIO extends IOLike> {
83
98
  authorization: AuthorizationState<TIO>;
84
99
  connection: WebSocketConnection;
100
+ storage: StorageInfo;
85
101
  webSocket: WebSocket | null;
86
102
  }
87
103
 
104
+ export type BroadcastProxy<TIO extends IOLike, TEvents extends PluvRouterEventConfig> = (<
105
+ TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>,
106
+ >(
107
+ event: TEvent,
108
+ data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
109
+ ) => Promise<void>) & {
110
+ [PEvent in keyof InferIOInput<MergeEvents<TEvents, TIO>>]: (
111
+ data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[PEvent]>,
112
+ ) => Promise<void>;
113
+ };
114
+
115
+ export type EventNotifierSubscriptionCallback<
116
+ TIO extends IOLike<any, any>,
117
+ TEvent extends keyof InferIOOutput<TIO>,
118
+ > = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
119
+
120
+ export type EventSubscriptionFn<TIO extends IOLike, TEvents extends PluvRouterEventConfig> = <
121
+ TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>,
122
+ >(
123
+ event: TEvent,
124
+ callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>,
125
+ ) => () => void;
126
+
127
+ export type EventProxy<
128
+ TIO extends IOLike,
129
+ TEvents extends PluvRouterEventConfig,
130
+ > = EventSubscriptionFn<TIO, TEvents> & {
131
+ [PEvent in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (
132
+ callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, PEvent>,
133
+ ) => () => void;
134
+ };
135
+
136
+ export type StorageSubscriptionCallback<
137
+ TStorage extends Record<string, CrdtType<any, any>>,
138
+ TKey extends keyof TStorage,
139
+ > = (value: InferCrdtJson<TStorage[TKey]>) => void;
140
+
141
+ export type StorageSubscriptionFn<TStorage extends Record<string, CrdtType<any, any>>> = <
142
+ TKey extends keyof TStorage,
143
+ >(
144
+ key: TKey,
145
+ fn: StorageSubscriptionCallback<TStorage, TKey>,
146
+ ) => () => void;
147
+
148
+ export type StorageRootSubscriptionCallback<TStorage extends Record<string, CrdtType<any, any>>> =
149
+ (value: { [P in keyof TStorage]: InferCrdtJson<TStorage[P]> }) => void;
150
+
151
+ export type StorageRootSubscriptionFn<TStorage extends Record<string, CrdtType<any, any>>> = (
152
+ callback: StorageRootSubscriptionCallback<TStorage>,
153
+ ) => () => void;
154
+
155
+ export type StorageProxy<TStorage extends Record<string, CrdtType<any, any>>> =
156
+ StorageSubscriptionFn<TStorage> &
157
+ StorageRootSubscriptionFn<TStorage> & {
158
+ [PKey in keyof TStorage]: (
159
+ callback: StorageSubscriptionCallback<TStorage, PKey>,
160
+ ) => () => void;
161
+ };
162
+
163
+ export type SubscribeFn<TValue extends unknown> = (callback: (value: TValue) => void) => () => void;
164
+
165
+ export type SubscribeProxy<
166
+ TIO extends IOLike,
167
+ TPresence extends Record<string, any>,
168
+ TStorage extends Record<string, CrdtType<any, any>>,
169
+ TEvents extends PluvRouterEventConfig,
170
+ > = (<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
171
+ name: TSubject,
172
+ callback: SubscriptionCallback<TIO, TPresence, TSubject>,
173
+ ) => () => void) & {
174
+ connection: SubscribeFn<Id<WebSocketState<TIO>>>;
175
+ event: EventProxy<TIO, TEvents>;
176
+ myPresence: SubscribeFn<TPresence | null>;
177
+ myself: SubscribeFn<Id<UserInfo<TIO>> | null>;
178
+ other: OtherSubscriptionFn<TIO, TPresence>;
179
+ others: OthersSubscriptionFn<TIO, TPresence>;
180
+ storage: StorageProxy<TStorage>;
181
+ storageLoaded: SubscribeFn<boolean>;
182
+ };
183
+
184
+ export interface RoomEventListenerMap {
185
+ close: (event: CloseEvent | null) => void;
186
+ error: (event: Event) => void;
187
+ message: (event: MessageEvent<any>) => void;
188
+ open: (event: Event) => void;
189
+ }
190
+
88
191
  export interface RoomLike<
89
192
  TIO extends IOLike,
90
- TPresence extends JsonObject = {},
193
+ TDoc extends any,
194
+ TPresence extends Record<string, any> = {},
91
195
  TStorage extends Record<string, CrdtType<any, any>> = {},
92
196
  TEvents extends PluvRouterEventConfig = {},
93
197
  > {
94
198
  id: string;
199
+ broadcast: BroadcastProxy<TIO, TEvents>;
200
+ /**
201
+ * @deprecated To be removed in v3. Use getStorageLoaded instead.
202
+ */
95
203
  storageLoaded: boolean;
96
204
 
97
- broadcast<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
98
- event: TEvent,
99
- data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
100
- ): void;
205
+ addEventListener<TKind extends keyof RoomEventListenerMap>(
206
+ kind: TKind,
207
+ handler: RoomEventListenerMap[TKind],
208
+ ): () => void;
101
209
 
102
210
  canRedo(): boolean;
103
211
 
104
212
  canUndo(): boolean;
105
213
 
106
- getDoc(): CrdtDocLike<TStorage>;
214
+ getDoc(): CrdtDocLike<TDoc, TStorage>;
107
215
 
108
- event<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
109
- event: TEvent,
110
- callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>,
111
- ): () => void;
216
+ event: EventProxy<TIO, TEvents>;
112
217
 
113
218
  getConnection(): WebSocketConnection;
114
219
 
@@ -125,17 +230,13 @@ export interface RoomLike<
125
230
  getStorageJson(): InferCrdtJson<TStorage> | null;
126
231
  getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
127
232
 
128
- other(
129
- connectionId: string,
130
- callback: OtherNotifierSubscriptionCallback<TIO, TPresence>,
131
- ): () => void;
233
+ getStorageLoaded: () => boolean;
234
+
235
+ other(connectionId: string, callback: OtherSubscriptionCallback<TIO, TPresence>): () => void;
132
236
 
133
237
  redo(): void;
134
238
 
135
- storage<TKey extends keyof TStorage>(
136
- key: TKey,
137
- fn: (value: InferCrdtJson<TStorage[TKey]>) => void,
138
- ): () => void;
239
+ storage: StorageProxy<TStorage>;
139
240
 
140
241
  storageRoot(
141
242
  fn: (value: {
@@ -143,10 +244,7 @@ export interface RoomLike<
143
244
  }) => void,
144
245
  ): () => void;
145
246
 
146
- subscribe<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
147
- name: TSubject,
148
- callback: SubscriptionCallback<TIO, TPresence, TSubject>,
149
- ): () => void;
247
+ subscribe: SubscribeProxy<TIO, TPresence, TStorage, TEvents>;
150
248
 
151
249
  transact(fn: (storage: TStorage) => void, origin?: string): void;
152
250