@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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +44 -0
- package/dist/index.d.mts +49 -56
- package/dist/index.d.ts +49 -56
- package/dist/index.js +80 -27
- package/dist/index.mjs +82 -29
- package/package.json +5 -5
- package/src/CrdtManager.ts +9 -13
- package/src/ListenerManager.ts +21 -0
- package/src/MockedRoom.ts +36 -16
- package/src/PluvClient.ts +8 -8
- package/src/PluvProcedure.ts +27 -25
- package/src/PluvRoom.ts +76 -25
- package/src/PluvRouter.ts +31 -22
- package/src/StateNotifier.ts +2 -2
- package/src/UsersManager.ts +18 -4
- package/src/UsersNotifier.ts +1 -1
- package/src/constants.ts +1 -0
- package/src/createClient.ts +2 -2
- package/src/index.ts +2 -1
- package/src/types.ts +10 -31
package/src/PluvProcedure.ts
CHANGED
|
@@ -1,29 +1,34 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AbstractCrdtDocFactory, InferDocLike } from "@pluv/crdt";
|
|
2
2
|
import type { EventRecord, IOLike, InputZodLike, JsonObject, ProcedureLike } from "@pluv/types";
|
|
3
3
|
import type { EventResolver, MergeEventRecords } from "./types";
|
|
4
4
|
|
|
5
5
|
export interface PluvProcedureConfig<
|
|
6
|
-
TIO extends IOLike
|
|
7
|
-
TInput extends JsonObject
|
|
8
|
-
TOutput extends EventRecord<string, any
|
|
9
|
-
TPresence extends
|
|
10
|
-
|
|
6
|
+
TIO extends IOLike,
|
|
7
|
+
TInput extends JsonObject,
|
|
8
|
+
TOutput extends EventRecord<string, any>,
|
|
9
|
+
TPresence extends Record<string, any>,
|
|
10
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
11
11
|
> {
|
|
12
|
-
broadcast?: EventResolver<TIO, TInput, TOutput, TPresence,
|
|
12
|
+
broadcast?: EventResolver<TIO, TInput, TOutput, TPresence, InferDocLike<TCrdt>> | null;
|
|
13
13
|
input?: InputZodLike<TInput>;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class PluvProcedure<
|
|
17
|
-
TIO extends IOLike
|
|
18
|
-
TInput extends JsonObject
|
|
19
|
-
TOutput extends EventRecord<string, any
|
|
20
|
-
TPresence extends
|
|
21
|
-
|
|
22
|
-
TFilled extends "input" | "broadcast" | ""
|
|
17
|
+
TIO extends IOLike,
|
|
18
|
+
TInput extends JsonObject,
|
|
19
|
+
TOutput extends EventRecord<string, any>,
|
|
20
|
+
TPresence extends Record<string, any>,
|
|
21
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
22
|
+
TFilled extends "input" | "broadcast" | "",
|
|
23
23
|
> implements ProcedureLike<TInput, TOutput>
|
|
24
24
|
{
|
|
25
|
-
private _broadcast: EventResolver<
|
|
26
|
-
|
|
25
|
+
private _broadcast: EventResolver<
|
|
26
|
+
TIO,
|
|
27
|
+
TInput,
|
|
28
|
+
Partial<TOutput>,
|
|
29
|
+
TPresence,
|
|
30
|
+
InferDocLike<TCrdt>
|
|
31
|
+
> | null = null;
|
|
27
32
|
private _input: InputZodLike<TInput> | null = null;
|
|
28
33
|
|
|
29
34
|
public get config(): ProcedureLike<TInput, TOutput>["config"] {
|
|
@@ -34,7 +39,7 @@ export class PluvProcedure<
|
|
|
34
39
|
} as ProcedureLike<TInput, TOutput>["config"];
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
constructor(config: PluvProcedureConfig<TIO, TInput, TOutput, TPresence,
|
|
42
|
+
constructor(config: PluvProcedureConfig<TIO, TInput, TOutput, TPresence, TCrdt> = {}) {
|
|
38
43
|
const { broadcast, input } = config;
|
|
39
44
|
|
|
40
45
|
this._broadcast = broadcast ?? null;
|
|
@@ -42,14 +47,14 @@ export class PluvProcedure<
|
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
public broadcast<TResult extends EventRecord<string, any> = {}>(
|
|
45
|
-
resolver: EventResolver<TIO, TInput, TResult, TPresence,
|
|
50
|
+
resolver: EventResolver<TIO, TInput, TResult, TPresence, InferDocLike<TCrdt>>,
|
|
46
51
|
): Omit<
|
|
47
52
|
PluvProcedure<
|
|
48
53
|
TIO,
|
|
49
54
|
TInput,
|
|
50
55
|
MergeEventRecords<[TOutput, TResult]>,
|
|
51
56
|
TPresence,
|
|
52
|
-
|
|
57
|
+
TCrdt,
|
|
53
58
|
TFilled | "input" | "broadcast"
|
|
54
59
|
>,
|
|
55
60
|
TFilled | "input" | "broadcast"
|
|
@@ -63,7 +68,7 @@ export class PluvProcedure<
|
|
|
63
68
|
TInput,
|
|
64
69
|
MergeEventRecords<[TOutput, TResult]>,
|
|
65
70
|
TPresence,
|
|
66
|
-
|
|
71
|
+
TCrdt,
|
|
67
72
|
TFilled | "input" | "broadcast"
|
|
68
73
|
>({
|
|
69
74
|
...(this.config as any),
|
|
@@ -73,14 +78,11 @@ export class PluvProcedure<
|
|
|
73
78
|
|
|
74
79
|
public input<TData extends JsonObject>(
|
|
75
80
|
input: InputZodLike<TData>,
|
|
76
|
-
): Omit<
|
|
77
|
-
PluvProcedure<TIO, TData, {}, TPresence,
|
|
78
|
-
TFilled | "input"
|
|
79
|
-
> {
|
|
80
|
-
return new PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">({ input });
|
|
81
|
+
): Omit<PluvProcedure<TIO, TData, {}, TPresence, TCrdt, TFilled | "input">, TFilled | "input"> {
|
|
82
|
+
return new PluvProcedure<TIO, TData, {}, TPresence, TCrdt, TFilled | "input">({ input });
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
private _resolver(): EventResolver<TIO, TInput, TOutput, TPresence,
|
|
85
|
+
private _resolver(): EventResolver<TIO, TInput, TOutput, TPresence, InferDocLike<TCrdt>> {
|
|
84
86
|
return (data, context) => this._broadcast?.(data, context) as TOutput;
|
|
85
87
|
}
|
|
86
88
|
}
|
package/src/PluvRoom.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AbstractCrdtDocFactory,
|
|
3
3
|
InferCrdtJson,
|
|
4
|
+
InferDoc,
|
|
5
|
+
InferDocLike,
|
|
4
6
|
InferStorage,
|
|
5
7
|
NoopCrdtDocFactory,
|
|
6
8
|
} from "@pluv/crdt";
|
|
@@ -25,6 +27,7 @@ import type {
|
|
|
25
27
|
OptionalProps,
|
|
26
28
|
OtherSubscriptionCallback,
|
|
27
29
|
OthersSubscriptionCallback,
|
|
30
|
+
RoomEventListenerMap,
|
|
28
31
|
RoomLike,
|
|
29
32
|
StateNotifierSubjects,
|
|
30
33
|
StorageProxy,
|
|
@@ -34,14 +37,16 @@ import type {
|
|
|
34
37
|
SubscriptionCallback,
|
|
35
38
|
UpdateMyPresenceAction,
|
|
36
39
|
UserInfo,
|
|
40
|
+
WebSocketConnection,
|
|
37
41
|
WebSocketState,
|
|
38
42
|
} from "@pluv/types";
|
|
39
|
-
import { ConnectionState } from "@pluv/types";
|
|
43
|
+
import { ConnectionState, StorageState } from "@pluv/types";
|
|
40
44
|
import type { AbstractStorageStore } from "./AbstractStorageStore";
|
|
41
45
|
import type { CrdtManagerOptions } from "./CrdtManager";
|
|
42
46
|
import { CrdtManager } from "./CrdtManager";
|
|
43
47
|
import { CrdtNotifier } from "./CrdtNotifier";
|
|
44
48
|
import { EventNotifier } from "./EventNotifier";
|
|
49
|
+
import { ListenerManager } from "./ListenerManager";
|
|
45
50
|
import { PluvProcedure } from "./PluvProcedure";
|
|
46
51
|
import type { PluvRouterEventConfig } from "./PluvRouter";
|
|
47
52
|
import { PluvRouter } from "./PluvRouter";
|
|
@@ -54,7 +59,6 @@ import type {
|
|
|
54
59
|
InternalSubscriptions,
|
|
55
60
|
PluvClientLimits,
|
|
56
61
|
PublicKey,
|
|
57
|
-
WebSocketConnection,
|
|
58
62
|
WithMetadata,
|
|
59
63
|
} from "./types";
|
|
60
64
|
import type { UsersManagerConfig } from "./UsersManager";
|
|
@@ -85,8 +89,8 @@ declare global {
|
|
|
85
89
|
export const DEFAULT_PLUV_CLIENT_ADDON = <
|
|
86
90
|
TIO extends IOLike,
|
|
87
91
|
TMetadata extends JsonObject,
|
|
88
|
-
TPresence extends
|
|
89
|
-
TCrdt extends AbstractCrdtDocFactory<any>,
|
|
92
|
+
TPresence extends Record<string, any>,
|
|
93
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
90
94
|
>(
|
|
91
95
|
input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TCrdt>,
|
|
92
96
|
): PluvRoomAddonResult => ({
|
|
@@ -102,9 +106,9 @@ interface WindowListeners {
|
|
|
102
106
|
|
|
103
107
|
interface WebSocketListeners {
|
|
104
108
|
onClose: (event: CloseEvent) => void;
|
|
105
|
-
onError: () => void;
|
|
109
|
+
onError: (event: Event) => void;
|
|
106
110
|
onMessage: (message: MessageEvent<string>) => void;
|
|
107
|
-
onOpen: () => void;
|
|
111
|
+
onOpen: (event: Event) => void;
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
interface IntervalIds {
|
|
@@ -143,15 +147,15 @@ interface InternalListeners {
|
|
|
143
147
|
export type PluvRoomAddon<
|
|
144
148
|
TIO extends IOLike,
|
|
145
149
|
TMetadata extends JsonObject,
|
|
146
|
-
TPresence extends
|
|
147
|
-
TCrdt extends AbstractCrdtDocFactory<any>,
|
|
150
|
+
TPresence extends Record<string, any>,
|
|
151
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
148
152
|
> = (input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TCrdt>) => Partial<PluvRoomAddonResult>;
|
|
149
153
|
|
|
150
154
|
export interface PluvRoomAddonInput<
|
|
151
155
|
TIO extends IOLike,
|
|
152
156
|
TMetadata extends JsonObject,
|
|
153
|
-
TPresence extends
|
|
154
|
-
TCrdt extends AbstractCrdtDocFactory<any>,
|
|
157
|
+
TPresence extends Record<string, any>,
|
|
158
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
155
159
|
> {
|
|
156
160
|
room: PluvRoom<TIO, TMetadata, TPresence, TCrdt>;
|
|
157
161
|
}
|
|
@@ -181,8 +185,8 @@ export type ReconnectTimeoutMs = number | ((params: ReconnectTimeoutMsParams) =>
|
|
|
181
185
|
export type RoomConfig<
|
|
182
186
|
TIO extends IOLike,
|
|
183
187
|
TMetadata extends JsonObject,
|
|
184
|
-
TPresence extends
|
|
185
|
-
TCrdt extends AbstractCrdtDocFactory<any>,
|
|
188
|
+
TPresence extends Record<string, any>,
|
|
189
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
186
190
|
TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>,
|
|
187
191
|
> = Id<
|
|
188
192
|
{
|
|
@@ -202,10 +206,10 @@ export type RoomConfig<
|
|
|
202
206
|
export class PluvRoom<
|
|
203
207
|
TIO extends IOLike<any, any>,
|
|
204
208
|
TMetadata extends JsonObject = {},
|
|
205
|
-
TPresence extends
|
|
206
|
-
TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory,
|
|
209
|
+
TPresence extends Record<string, any> = {},
|
|
210
|
+
TCrdt extends AbstractCrdtDocFactory<any, any> = NoopCrdtDocFactory,
|
|
207
211
|
TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {},
|
|
208
|
-
> implements RoomLike<TIO, TPresence, InferStorage<TCrdt>>
|
|
212
|
+
> implements RoomLike<TIO, InferDoc<TCrdt>, TPresence, InferStorage<TCrdt>>
|
|
209
213
|
{
|
|
210
214
|
readonly _endpoints: RoomEndpoints<TIO, TMetadata>;
|
|
211
215
|
|
|
@@ -220,6 +224,7 @@ export class PluvRoom<
|
|
|
220
224
|
heartbeat: null,
|
|
221
225
|
};
|
|
222
226
|
private readonly _limits: PluvClientLimits;
|
|
227
|
+
private readonly _listenerManager = new ListenerManager();
|
|
223
228
|
private readonly _listeners: InternalListeners;
|
|
224
229
|
private readonly _publicKey: PublicKey<TMetadata> | null = null;
|
|
225
230
|
private readonly _reconnectTimeoutMs: ReconnectTimeoutMs;
|
|
@@ -244,10 +249,12 @@ export class PluvRoom<
|
|
|
244
249
|
},
|
|
245
250
|
connection: {
|
|
246
251
|
attempts: 0,
|
|
247
|
-
count: 0,
|
|
248
252
|
id: null,
|
|
249
253
|
state: ConnectionState.Untouched,
|
|
250
254
|
},
|
|
255
|
+
storage: {
|
|
256
|
+
state: StorageState.Unavailable,
|
|
257
|
+
},
|
|
251
258
|
webSocket: null,
|
|
252
259
|
};
|
|
253
260
|
private _windowListeners: WindowListeners | null = null;
|
|
@@ -304,14 +311,24 @@ export class PluvRoom<
|
|
|
304
311
|
this._crdtManager = new CrdtManager<TCrdt>({ initialStorage });
|
|
305
312
|
}
|
|
306
313
|
|
|
314
|
+
/**
|
|
315
|
+
* @deprecated To be removed in v3. Use `getStorageLoaded` instead.
|
|
316
|
+
*/
|
|
307
317
|
public get storageLoaded(): boolean {
|
|
308
|
-
return this.
|
|
318
|
+
return this.getStorageLoaded();
|
|
309
319
|
}
|
|
310
320
|
|
|
311
321
|
public get webSocket(): WebSocket | null {
|
|
312
322
|
return this._state.webSocket;
|
|
313
323
|
}
|
|
314
324
|
|
|
325
|
+
public addEventListener<TKind extends keyof RoomEventListenerMap>(
|
|
326
|
+
kind: TKind,
|
|
327
|
+
handler: RoomEventListenerMap[TKind],
|
|
328
|
+
): () => void {
|
|
329
|
+
return this._listenerManager.subscribe(kind, handler as any);
|
|
330
|
+
}
|
|
331
|
+
|
|
315
332
|
public broadcast = new Proxy(
|
|
316
333
|
async <TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
317
334
|
event: TEvent,
|
|
@@ -327,7 +344,8 @@ export class PluvRoom<
|
|
|
327
344
|
any,
|
|
328
345
|
any,
|
|
329
346
|
TPresence,
|
|
330
|
-
|
|
347
|
+
TCrdt,
|
|
348
|
+
""
|
|
331
349
|
> | null;
|
|
332
350
|
|
|
333
351
|
if (!procedure?.config.broadcast) {
|
|
@@ -341,7 +359,7 @@ export class PluvRoom<
|
|
|
341
359
|
if (!myself) return;
|
|
342
360
|
|
|
343
361
|
const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
|
|
344
|
-
const context: EventResolverContext<TIO, TPresence,
|
|
362
|
+
const context: EventResolverContext<TIO, TPresence, InferDocLike<TCrdt>> = {
|
|
345
363
|
doc: this._crdtManager.doc,
|
|
346
364
|
others: this._usersManager.getOthers(),
|
|
347
365
|
room: this.id,
|
|
@@ -436,6 +454,7 @@ export class PluvRoom<
|
|
|
436
454
|
this._updateState((oldState) => {
|
|
437
455
|
oldState.connection.attempts = 0;
|
|
438
456
|
oldState.authorization.token = authToken;
|
|
457
|
+
oldState.storage.state = StorageState.Loading;
|
|
439
458
|
oldState.webSocket = webSocket;
|
|
440
459
|
|
|
441
460
|
return oldState;
|
|
@@ -460,6 +479,10 @@ export class PluvRoom<
|
|
|
460
479
|
this._updateState((oldState) => {
|
|
461
480
|
oldState.authorization.token = null;
|
|
462
481
|
oldState.connection.state = ConnectionState.Closed;
|
|
482
|
+
oldState.storage.state =
|
|
483
|
+
oldState.storage.state === StorageState.Synced
|
|
484
|
+
? StorageState.Offline
|
|
485
|
+
: StorageState.Unavailable;
|
|
463
486
|
oldState.webSocket = null;
|
|
464
487
|
|
|
465
488
|
return oldState;
|
|
@@ -492,7 +515,7 @@ export class PluvRoom<
|
|
|
492
515
|
return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
|
|
493
516
|
};
|
|
494
517
|
|
|
495
|
-
public getDoc = (): CrdtDocLike<InferStorage<TCrdt>> => {
|
|
518
|
+
public getDoc = (): CrdtDocLike<InferDoc<TCrdt>, InferStorage<TCrdt>> => {
|
|
496
519
|
return this._crdtManager.doc;
|
|
497
520
|
};
|
|
498
521
|
|
|
@@ -528,7 +551,6 @@ export class PluvRoom<
|
|
|
528
551
|
): InferCrdtJson<InferStorage<TCrdt>[TKey]> | null;
|
|
529
552
|
public getStorageJson<TKey extends keyof InferStorage<TCrdt>>(type?: TKey) {
|
|
530
553
|
if (this._state.connection.id === null) return null;
|
|
531
|
-
|
|
532
554
|
if (typeof type === "undefined") return this._crdtManager.doc.toJson();
|
|
533
555
|
|
|
534
556
|
const sharedType = this._crdtManager.get(type);
|
|
@@ -538,6 +560,13 @@ export class PluvRoom<
|
|
|
538
560
|
return this._crdtManager.doc.toJson(type);
|
|
539
561
|
}
|
|
540
562
|
|
|
563
|
+
public getStorageLoaded = (): boolean => {
|
|
564
|
+
return (
|
|
565
|
+
this._state.storage.state === StorageState.Offline ||
|
|
566
|
+
this._state.storage.state === StorageState.Synced
|
|
567
|
+
);
|
|
568
|
+
};
|
|
569
|
+
|
|
541
570
|
/**
|
|
542
571
|
* @deprecated To be removed in v3. Use PluvRoom.subscribe.other instead
|
|
543
572
|
*/
|
|
@@ -557,7 +586,7 @@ export class PluvRoom<
|
|
|
557
586
|
};
|
|
558
587
|
|
|
559
588
|
/**
|
|
560
|
-
* @deprecated To be removed in v3.
|
|
589
|
+
* @deprecated To be removed in v3. Use PluvRoom.subscribe.storage instead
|
|
561
590
|
*/
|
|
562
591
|
public storage = new Proxy(
|
|
563
592
|
<TKey extends keyof InferStorage<TCrdt>>(
|
|
@@ -1041,7 +1070,6 @@ export class PluvRoom<
|
|
|
1041
1070
|
const state = data.state;
|
|
1042
1071
|
|
|
1043
1072
|
this._updateState((oldState) => {
|
|
1044
|
-
oldState.connection.count += 1;
|
|
1045
1073
|
oldState.connection.id = connectionId;
|
|
1046
1074
|
oldState.authorization.user = user ?? null;
|
|
1047
1075
|
|
|
@@ -1114,6 +1142,11 @@ export class PluvRoom<
|
|
|
1114
1142
|
|
|
1115
1143
|
this._emitSharedTypes();
|
|
1116
1144
|
this._observeCrdt();
|
|
1145
|
+
this._updateState((oldState) => {
|
|
1146
|
+
oldState.storage.state = StorageState.Synced;
|
|
1147
|
+
|
|
1148
|
+
return oldState;
|
|
1149
|
+
});
|
|
1117
1150
|
this._stateNotifier.subjects["storage-loaded"].next(true);
|
|
1118
1151
|
|
|
1119
1152
|
this._sendMessage({
|
|
@@ -1277,12 +1310,15 @@ export class PluvRoom<
|
|
|
1277
1310
|
|
|
1278
1311
|
this._updateState((oldState) => {
|
|
1279
1312
|
oldState.connection.state = ConnectionState.Unavailable;
|
|
1313
|
+
oldState.storage.state = StorageState.Unavailable;
|
|
1280
1314
|
|
|
1281
1315
|
return oldState;
|
|
1282
1316
|
});
|
|
1283
1317
|
}
|
|
1284
1318
|
|
|
1285
1319
|
private async _onClose(event: CloseEvent): Promise<void> {
|
|
1320
|
+
this._listenerManager.subjects.close.next(event);
|
|
1321
|
+
|
|
1286
1322
|
this._logDebug("WebSocket closed");
|
|
1287
1323
|
if (!!event.reason) this._logDebug(event.reason);
|
|
1288
1324
|
|
|
@@ -1312,6 +1348,10 @@ export class PluvRoom<
|
|
|
1312
1348
|
* @date April 28, 2025
|
|
1313
1349
|
*/
|
|
1314
1350
|
ConnectionState.Closed;
|
|
1351
|
+
oldState.storage.state =
|
|
1352
|
+
oldState.storage.state === StorageState.Synced
|
|
1353
|
+
? StorageState.Offline
|
|
1354
|
+
: StorageState.Unavailable;
|
|
1315
1355
|
oldState.webSocket = null;
|
|
1316
1356
|
|
|
1317
1357
|
return oldState;
|
|
@@ -1331,9 +1371,13 @@ export class PluvRoom<
|
|
|
1331
1371
|
* @description Handle websocket errors in a meaningful way, should they occur
|
|
1332
1372
|
* @date August 19, 2022
|
|
1333
1373
|
*/
|
|
1334
|
-
private _onError(): void {
|
|
1374
|
+
private _onError(event: Event): void {
|
|
1375
|
+
this._listenerManager.subjects.error.next(event);
|
|
1376
|
+
}
|
|
1335
1377
|
|
|
1336
1378
|
private _onMessage(event: MessageEvent<string>): void {
|
|
1379
|
+
this._listenerManager.subjects.message.next(event);
|
|
1380
|
+
|
|
1337
1381
|
const message = this._parseMessage(event);
|
|
1338
1382
|
|
|
1339
1383
|
if (!message) return;
|
|
@@ -1390,7 +1434,9 @@ export class PluvRoom<
|
|
|
1390
1434
|
}
|
|
1391
1435
|
}
|
|
1392
1436
|
|
|
1393
|
-
private _onOpen(): void {
|
|
1437
|
+
private _onOpen(event: Event): void {
|
|
1438
|
+
this._listenerManager.subjects.open.next(event);
|
|
1439
|
+
|
|
1394
1440
|
if (this._state.connection.state !== ConnectionState.Connecting) return;
|
|
1395
1441
|
|
|
1396
1442
|
this._logDebug("WebSocket connected");
|
|
@@ -1412,6 +1458,10 @@ export class PluvRoom<
|
|
|
1412
1458
|
|
|
1413
1459
|
this._updateState((oldState) => {
|
|
1414
1460
|
oldState.connection.state = ConnectionState.Unavailable;
|
|
1461
|
+
oldState.storage.state =
|
|
1462
|
+
oldState.storage.state === StorageState.Synced
|
|
1463
|
+
? StorageState.Offline
|
|
1464
|
+
: StorageState.Unavailable;
|
|
1415
1465
|
|
|
1416
1466
|
return oldState;
|
|
1417
1467
|
});
|
|
@@ -1562,6 +1612,7 @@ export class PluvRoom<
|
|
|
1562
1612
|
...this._state,
|
|
1563
1613
|
authorization,
|
|
1564
1614
|
connection: JSON.parse(JSON.stringify(this._state.connection)),
|
|
1615
|
+
storage: JSON.parse(JSON.stringify(this._state.storage)),
|
|
1565
1616
|
webSocket: this._state.webSocket,
|
|
1566
1617
|
};
|
|
1567
1618
|
|
package/src/PluvRouter.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AbstractCrdtDocFactory } from "@pluv/crdt";
|
|
2
2
|
import type { IOLike, IORouterLike, JsonObject } from "@pluv/types";
|
|
3
3
|
import type { PluvProcedure } from "./PluvProcedure";
|
|
4
4
|
|
|
5
5
|
export type PluvRouterEventConfig<
|
|
6
|
-
TIO extends IOLike
|
|
7
|
-
TPresence extends
|
|
8
|
-
|
|
9
|
-
> = { [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence,
|
|
6
|
+
TIO extends IOLike,
|
|
7
|
+
TPresence extends Record<string, any>,
|
|
8
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
9
|
+
> = { [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TCrdt, "">, "config"> };
|
|
10
10
|
|
|
11
11
|
export type MergedRouter<
|
|
12
|
-
TRouters extends PluvRouter<TIO, TPresence,
|
|
13
|
-
TIO extends IOLike
|
|
14
|
-
TPresence extends
|
|
15
|
-
|
|
16
|
-
TRoot extends TRouters[0]["_defs"]["events"]
|
|
12
|
+
TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
13
|
+
TIO extends IOLike,
|
|
14
|
+
TPresence extends Record<string, any>,
|
|
15
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
16
|
+
TRoot extends TRouters[0]["_defs"]["events"],
|
|
17
17
|
> = TRouters extends [
|
|
18
|
-
infer IHead extends PluvRouter<TIO, TPresence,
|
|
19
|
-
...infer ITail extends PluvRouter<TIO, TPresence,
|
|
18
|
+
infer IHead extends PluvRouter<TIO, TPresence, TCrdt, any>,
|
|
19
|
+
...infer ITail extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
20
20
|
]
|
|
21
|
-
? MergedRouter<ITail, TIO, TPresence,
|
|
22
|
-
: PluvRouter<TIO, TPresence,
|
|
21
|
+
? MergedRouter<ITail, TIO, TPresence, TCrdt, TRoot & IHead["_defs"]["events"]>
|
|
22
|
+
: PluvRouter<TIO, TPresence, TCrdt, TRoot>;
|
|
23
23
|
|
|
24
24
|
export class PluvRouter<
|
|
25
|
-
TIO extends IOLike
|
|
26
|
-
TPresence extends
|
|
27
|
-
|
|
28
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence,
|
|
25
|
+
TIO extends IOLike,
|
|
26
|
+
TPresence extends Record<string, any>,
|
|
27
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
28
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TCrdt> = {},
|
|
29
29
|
> implements IORouterLike<TEvents>
|
|
30
30
|
{
|
|
31
31
|
readonly _defs: { events: TEvents } = { events: {} as TEvents };
|
|
@@ -42,9 +42,12 @@ export class PluvRouter<
|
|
|
42
42
|
this._defs = { events };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
public static merge<
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
public static merge<
|
|
46
|
+
TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
47
|
+
TIO extends IOLike,
|
|
48
|
+
TPresence extends Record<string, any>,
|
|
49
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
50
|
+
>(...routers: TRouters): MergedRouter<TRouters, TIO, TPresence, TCrdt, {}> {
|
|
48
51
|
const events = Object.assign(
|
|
49
52
|
Object.create(null),
|
|
50
53
|
...routers.map((router) => router._defs.events),
|
|
@@ -54,7 +57,13 @@ export class PluvRouter<
|
|
|
54
57
|
throw new Error('Procedures may not start with "$"');
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
return new PluvRouter<any, any, any, any>(events) as MergedRouter<
|
|
60
|
+
return new PluvRouter<any, any, any, any>(events) as MergedRouter<
|
|
61
|
+
TRouters,
|
|
62
|
+
TIO,
|
|
63
|
+
TPresence,
|
|
64
|
+
TCrdt,
|
|
65
|
+
{}
|
|
66
|
+
>;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
private _isValidEventName(name: string): boolean {
|
package/src/StateNotifier.ts
CHANGED
|
@@ -12,14 +12,14 @@ import { makeSubject, subscribe } from "wonka";
|
|
|
12
12
|
|
|
13
13
|
type InferSubjectValue<
|
|
14
14
|
TIO extends IOLike,
|
|
15
|
-
TPresence extends
|
|
15
|
+
TPresence extends Record<string, any>,
|
|
16
16
|
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
17
17
|
> =
|
|
18
18
|
StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue>
|
|
19
19
|
? Id<IValue>
|
|
20
20
|
: never;
|
|
21
21
|
|
|
22
|
-
export class StateNotifier<TIO extends IOLike, TPresence extends
|
|
22
|
+
export class StateNotifier<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
23
23
|
public subjects: StateNotifierSubjects<TIO, TPresence> = {
|
|
24
24
|
connection: makeSubject<Id<WebSocketState<TIO>>>(),
|
|
25
25
|
"my-presence": makeSubject<TPresence>(),
|
package/src/UsersManager.ts
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import type { InputZodLike, IOLike, JsonObject, OptionalProps, UserInfo } from "@pluv/types";
|
|
2
|
+
import { PLUV_PRESENCE_META_KEY } from "./constants";
|
|
2
3
|
import type { PluvClientLimits } from "./types";
|
|
3
4
|
import { pickBy } from "./utils";
|
|
4
5
|
|
|
5
6
|
export type Presence = Record<string, unknown>;
|
|
6
7
|
|
|
7
|
-
export type UsersManagerConfig<TPresence extends
|
|
8
|
+
export type UsersManagerConfig<TPresence extends Record<string, any> = {}> = {
|
|
8
9
|
initialPresence?: TPresence;
|
|
9
10
|
limits: PluvClientLimits;
|
|
10
11
|
presence?: InputZodLike<TPresence>;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export type AddConnectionResult<TIO extends IOLike, TPresence extends
|
|
14
|
+
export type AddConnectionResult<TIO extends IOLike, TPresence extends Record<string, any> = {}> = {
|
|
14
15
|
clientId: string;
|
|
15
16
|
data: UserInfo<TIO, TPresence>;
|
|
16
17
|
isMyself: boolean;
|
|
17
18
|
remaining: number;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
export type DeleteConnectionResult<
|
|
21
|
+
export type DeleteConnectionResult<
|
|
22
|
+
TIO extends IOLike,
|
|
23
|
+
TPresence extends Record<string, any> = {},
|
|
24
|
+
> = {
|
|
21
25
|
clientId: string;
|
|
22
26
|
data: UserInfo<TIO, TPresence>;
|
|
23
27
|
remaining: number;
|
|
24
28
|
};
|
|
25
29
|
|
|
26
|
-
export class UsersManager<TIO extends IOLike, TPresence extends
|
|
30
|
+
export class UsersManager<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
27
31
|
public readonly initialPresence: TPresence;
|
|
28
32
|
|
|
29
33
|
private readonly _idMap = {
|
|
@@ -210,6 +214,16 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
210
214
|
} as TPresence;
|
|
211
215
|
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
212
216
|
|
|
217
|
+
/**
|
|
218
|
+
* !HACK
|
|
219
|
+
* @description We're patching internal metadata back into the presence field so it doesn't
|
|
220
|
+
* get stripped from validation
|
|
221
|
+
* @date May 13, 2025
|
|
222
|
+
*/
|
|
223
|
+
if (!!presence[PLUV_PRESENCE_META_KEY]) {
|
|
224
|
+
(validated as any)[PLUV_PRESENCE_META_KEY] = presence[PLUV_PRESENCE_META_KEY];
|
|
225
|
+
}
|
|
226
|
+
|
|
213
227
|
this._others.set(clientId, { ...other, presence: validated });
|
|
214
228
|
|
|
215
229
|
return validated;
|
package/src/UsersNotifier.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
import type { Subject } from "wonka";
|
|
11
11
|
import { makeSubject, subscribe, TypeOfSource } from "wonka";
|
|
12
12
|
|
|
13
|
-
export class UsersNotifier<TIO extends IOLike, TPresence extends
|
|
13
|
+
export class UsersNotifier<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
14
14
|
public readonly others = makeSubject<{
|
|
15
15
|
others: readonly Id<UserInfo<TIO, TPresence>>[];
|
|
16
16
|
event: OthersSubscriptionEvent<TIO, TPresence>;
|
package/src/constants.ts
CHANGED
package/src/createClient.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { PluvClient } from "./PluvClient";
|
|
|
5
5
|
|
|
6
6
|
export const createClient = <
|
|
7
7
|
TIO extends IOLike<any, any>,
|
|
8
|
-
TPresence extends
|
|
9
|
-
TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory,
|
|
8
|
+
TPresence extends Record<string, any> = {},
|
|
9
|
+
TCrdt extends AbstractCrdtDocFactory<any, any> = NoopCrdtDocFactory,
|
|
10
10
|
TMetadata extends JsonObject = {},
|
|
11
11
|
>(
|
|
12
12
|
options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>,
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type {
|
|
|
9
9
|
MergeEvents,
|
|
10
10
|
RoomLike,
|
|
11
11
|
UserInfo,
|
|
12
|
+
WebSocketConnection,
|
|
12
13
|
WebSocketState,
|
|
13
14
|
} from "@pluv/types";
|
|
14
15
|
export { AbstractStorageStore } from "./AbstractStorageStore";
|
|
@@ -32,4 +33,4 @@ export type {
|
|
|
32
33
|
export type { PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
33
34
|
export { register } from "./register";
|
|
34
35
|
export type { RegisterParams } from "./register";
|
|
35
|
-
export type { InferMetadata, PublicKey, PublicKeyParams
|
|
36
|
+
export type { InferMetadata, PublicKey, PublicKeyParams } from "./types";
|