@pluv/client 5.0.1 → 5.0.3
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.mts +355 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1765 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +8 -8
- package/CHANGELOG.md +0 -2415
- package/eslint.config.mjs +0 -4
- package/src/AbstractStorageStore.ts +0 -19
- package/src/CrdtManager.ts +0 -119
- package/src/CrdtNotifier.ts +0 -46
- package/src/EventNotifier.ts +0 -39
- package/src/ListenerManager.ts +0 -21
- package/src/MockedRoom.ts +0 -457
- package/src/PluvClient.ts +0 -204
- package/src/PluvProcedure.ts +0 -87
- package/src/PluvRoom.ts +0 -1610
- package/src/PluvRouter.ts +0 -71
- package/src/StateNotifier.ts +0 -41
- package/src/StorageStore.ts +0 -31
- package/src/UsersManager.ts +0 -341
- package/src/UsersNotifier.ts +0 -66
- package/src/constants.ts +0 -2
- package/src/createClient.ts +0 -14
- package/src/index.ts +0 -36
- package/src/infer.ts +0 -12
- package/src/register.ts +0 -20
- package/src/types.ts +0 -78
- package/src/utils/debounce.ts +0 -27
- package/src/utils/identity.ts +0 -1
- package/src/utils/index.ts +0 -3
- package/src/utils/pickBy.ts +0 -16
- package/tsconfig.json +0 -5
- package/tsdown.config.ts +0 -14
package/src/MockedRoom.ts
DELETED
|
@@ -1,457 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AbstractCrdtDocFactory,
|
|
3
|
-
InferCrdtJson,
|
|
4
|
-
InferDoc,
|
|
5
|
-
InferDocLike,
|
|
6
|
-
InferStorage,
|
|
7
|
-
} from "@pluv/crdt";
|
|
8
|
-
import type {
|
|
9
|
-
BroadcastProxy,
|
|
10
|
-
CrdtDocLike,
|
|
11
|
-
EventNotifierSubscriptionCallback,
|
|
12
|
-
EventProxy,
|
|
13
|
-
IOLike,
|
|
14
|
-
Id,
|
|
15
|
-
InferIOInput,
|
|
16
|
-
InferIOOutput,
|
|
17
|
-
MergeEvents,
|
|
18
|
-
OtherSubscriptionCallback,
|
|
19
|
-
OthersSubscriptionCallback,
|
|
20
|
-
RoomEventListenerMap,
|
|
21
|
-
RoomLike,
|
|
22
|
-
StateNotifierSubjects,
|
|
23
|
-
StorageProxy,
|
|
24
|
-
StorageRootSubscriptionCallback,
|
|
25
|
-
StorageSubscriptionCallback,
|
|
26
|
-
SubscribeProxy,
|
|
27
|
-
SubscriptionCallback,
|
|
28
|
-
UpdateMyPresenceAction,
|
|
29
|
-
UserInfo,
|
|
30
|
-
WebSocketConnection,
|
|
31
|
-
WebSocketState,
|
|
32
|
-
} from "@pluv/types";
|
|
33
|
-
import { ConnectionState, StorageState } from "@pluv/types";
|
|
34
|
-
import type { CrdtManagerOptions } from "./CrdtManager";
|
|
35
|
-
import { CrdtManager } from "./CrdtManager";
|
|
36
|
-
import { CrdtNotifier } from "./CrdtNotifier";
|
|
37
|
-
import { EventNotifier } from "./EventNotifier";
|
|
38
|
-
import { PluvProcedure } from "./PluvProcedure";
|
|
39
|
-
import type { PluvRouterEventConfig } from "./PluvRouter";
|
|
40
|
-
import { PluvRouter } from "./PluvRouter";
|
|
41
|
-
import { StateNotifier } from "./StateNotifier";
|
|
42
|
-
import type { UsersManagerConfig } from "./UsersManager";
|
|
43
|
-
import { UsersManager } from "./UsersManager";
|
|
44
|
-
import { UsersNotifier } from "./UsersNotifier";
|
|
45
|
-
import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
|
|
46
|
-
import type {
|
|
47
|
-
EventResolver,
|
|
48
|
-
EventResolverContext,
|
|
49
|
-
InternalSubscriptions,
|
|
50
|
-
PluvClientLimits,
|
|
51
|
-
} from "./types";
|
|
52
|
-
|
|
53
|
-
export type MockedRoomEvents<TIO extends IOLike> = Partial<{
|
|
54
|
-
[P in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[P]>) => Partial<InferIOOutput<TIO>>;
|
|
55
|
-
}>;
|
|
56
|
-
|
|
57
|
-
export type MockedRoomConfig<
|
|
58
|
-
TIO extends IOLike,
|
|
59
|
-
TPresence extends Record<string, any>,
|
|
60
|
-
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
61
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>,
|
|
62
|
-
> = {
|
|
63
|
-
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
64
|
-
limits?: PluvClientLimits;
|
|
65
|
-
router?: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
|
|
66
|
-
} & Pick<CrdtManagerOptions<TCrdt>, "initialStorage"> &
|
|
67
|
-
Omit<UsersManagerConfig<TPresence>, "limits">;
|
|
68
|
-
|
|
69
|
-
export class MockedRoom<
|
|
70
|
-
TIO extends IOLike,
|
|
71
|
-
TPresence extends Record<string, any>,
|
|
72
|
-
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
73
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>,
|
|
74
|
-
> implements RoomLike<TIO, InferDoc<TCrdt>, TPresence, InferStorage<TCrdt>> {
|
|
75
|
-
public readonly id: string;
|
|
76
|
-
|
|
77
|
-
private readonly _crdtManager: CrdtManager<TCrdt>;
|
|
78
|
-
private readonly _crdtNotifier = new CrdtNotifier<InferStorage<TCrdt>>();
|
|
79
|
-
private readonly _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
|
|
80
|
-
private readonly _events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
81
|
-
private readonly _limits: PluvClientLimits;
|
|
82
|
-
private readonly _usersNotifier = new UsersNotifier<TIO, TPresence>();
|
|
83
|
-
private readonly _router: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
|
|
84
|
-
private _state: WebSocketState<TIO> = {
|
|
85
|
-
authorization: {
|
|
86
|
-
token: null,
|
|
87
|
-
user: null,
|
|
88
|
-
},
|
|
89
|
-
connection: {
|
|
90
|
-
attempts: 0,
|
|
91
|
-
id: null,
|
|
92
|
-
state: ConnectionState.Untouched,
|
|
93
|
-
},
|
|
94
|
-
storage: {
|
|
95
|
-
state: StorageState.Unavailable,
|
|
96
|
-
},
|
|
97
|
-
webSocket: null,
|
|
98
|
-
};
|
|
99
|
-
private readonly _stateNotifier = new StateNotifier<TIO, TPresence>();
|
|
100
|
-
private readonly _subscriptions: InternalSubscriptions = {
|
|
101
|
-
observeCrdt: null,
|
|
102
|
-
};
|
|
103
|
-
private readonly _usersManager: UsersManager<TIO, TPresence>;
|
|
104
|
-
|
|
105
|
-
constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TCrdt, TEvents>) {
|
|
106
|
-
const { events, initialPresence, initialStorage, limits, presence, router } = options;
|
|
107
|
-
|
|
108
|
-
this.id = room;
|
|
109
|
-
|
|
110
|
-
this._events = events;
|
|
111
|
-
this._limits = {
|
|
112
|
-
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
|
|
113
|
-
...limits,
|
|
114
|
-
};
|
|
115
|
-
this._router =
|
|
116
|
-
router ??
|
|
117
|
-
(new PluvRouter({}) as PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>);
|
|
118
|
-
this._usersManager = new UsersManager<TIO, TPresence>({
|
|
119
|
-
initialPresence,
|
|
120
|
-
limits: this._limits,
|
|
121
|
-
presence,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
this._crdtManager = new CrdtManager<TCrdt>({
|
|
125
|
-
initialStorage,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
this._observeCrdt();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
public addEventListener<TKind extends keyof RoomEventListenerMap>(
|
|
132
|
-
kind: TKind,
|
|
133
|
-
handler: RoomEventListenerMap[TKind],
|
|
134
|
-
): () => void {
|
|
135
|
-
return () => undefined;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public broadcast = new Proxy(
|
|
139
|
-
async <TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
140
|
-
event: TEvent,
|
|
141
|
-
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
142
|
-
): Promise<void> => {
|
|
143
|
-
if (!this._state.webSocket) return;
|
|
144
|
-
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
145
|
-
|
|
146
|
-
const type = event.toString();
|
|
147
|
-
|
|
148
|
-
const procedure = this._router._defs.events[type] as PluvProcedure<
|
|
149
|
-
TIO,
|
|
150
|
-
any,
|
|
151
|
-
any,
|
|
152
|
-
TPresence,
|
|
153
|
-
TCrdt,
|
|
154
|
-
""
|
|
155
|
-
> | null;
|
|
156
|
-
|
|
157
|
-
if (!procedure?.config.broadcast) {
|
|
158
|
-
this._simulateEvent(type as TEvent, data);
|
|
159
|
-
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const myself = this._usersManager.myself;
|
|
164
|
-
|
|
165
|
-
if (!myself) return;
|
|
166
|
-
|
|
167
|
-
const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
|
|
168
|
-
const context: EventResolverContext<TIO, TPresence, InferDocLike<TCrdt>> = {
|
|
169
|
-
doc: this._crdtManager.doc,
|
|
170
|
-
others: this._usersManager.getOthers(),
|
|
171
|
-
room: this.id,
|
|
172
|
-
user: myself,
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const output = await (
|
|
176
|
-
procedure.config.broadcast as EventResolver<
|
|
177
|
-
TIO,
|
|
178
|
-
any,
|
|
179
|
-
any,
|
|
180
|
-
TPresence,
|
|
181
|
-
InferStorage<TCrdt>
|
|
182
|
-
>
|
|
183
|
-
)(parsed, context);
|
|
184
|
-
|
|
185
|
-
Object.entries(output).forEach(([_type, _data]) => {
|
|
186
|
-
this._simulateEvent(_type as TEvent, _data as any);
|
|
187
|
-
});
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
get(fn, prop) {
|
|
191
|
-
return async (
|
|
192
|
-
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>,
|
|
193
|
-
): Promise<void> => {
|
|
194
|
-
return await fn(prop, data);
|
|
195
|
-
};
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
) as BroadcastProxy<TIO, TEvents>;
|
|
199
|
-
|
|
200
|
-
public canRedo = (): boolean => {
|
|
201
|
-
return !!this._crdtManager.doc.canRedo();
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
public canUndo = (): boolean => {
|
|
205
|
-
return !!this._crdtManager.doc.canUndo();
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
public getConnection = (): WebSocketConnection => {
|
|
209
|
-
// Create a read-only clone of the connection state
|
|
210
|
-
return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
public getDoc(): CrdtDocLike<InferDoc<TCrdt>, InferStorage<TCrdt>> {
|
|
214
|
-
return this._crdtManager.doc;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
public getMyPresence = (): TPresence => {
|
|
218
|
-
return this._usersManager.myPresence;
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
public getMyself = (): Id<UserInfo<TIO, TPresence>> | null => {
|
|
222
|
-
return this._usersManager.myself;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
public getOther = (connectionId: string): Id<UserInfo<TIO, TPresence>> | null => {
|
|
226
|
-
return this._usersManager.getOther(connectionId);
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
public getOthers = (): readonly Id<UserInfo<TIO, TPresence>>[] => {
|
|
230
|
-
return this._usersManager.getOthers();
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
public getStorage = <TKey extends keyof InferStorage<TCrdt>>(
|
|
234
|
-
type: TKey,
|
|
235
|
-
): InferStorage<TCrdt>[TKey] | null => {
|
|
236
|
-
const sharedType = this._crdtManager.get(type);
|
|
237
|
-
|
|
238
|
-
if (typeof sharedType === "undefined") return null;
|
|
239
|
-
|
|
240
|
-
return sharedType;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
public getStorageJson(): InferCrdtJson<InferStorage<TCrdt>> | null;
|
|
244
|
-
public getStorageJson<TKey extends keyof InferStorage<TCrdt>>(
|
|
245
|
-
type: TKey,
|
|
246
|
-
): InferCrdtJson<InferStorage<TCrdt>[TKey]> | null;
|
|
247
|
-
public getStorageJson<TKey extends keyof InferStorage<TCrdt>>(type?: TKey) {
|
|
248
|
-
if (this._state.connection.id === null) return null;
|
|
249
|
-
if (typeof type === "undefined") return this._crdtManager.doc.toJson();
|
|
250
|
-
|
|
251
|
-
return this._crdtManager.doc.toJson(type);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
public getStorageLoaded(): boolean {
|
|
255
|
-
return true;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
public redo = (): void => {
|
|
259
|
-
this._crdtManager.doc.redo();
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
public storageRoot = (
|
|
263
|
-
fn: (value: {
|
|
264
|
-
[P in keyof InferStorage<TCrdt>]: InferCrdtJson<InferStorage<TCrdt>[P]>;
|
|
265
|
-
}) => void,
|
|
266
|
-
): (() => void) => {
|
|
267
|
-
return this._crdtNotifier.subcribeRoot(fn);
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
public subscribe = new Proxy(
|
|
271
|
-
<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
|
|
272
|
-
name: TSubject,
|
|
273
|
-
callback: SubscriptionCallback<TIO, TPresence, TSubject>,
|
|
274
|
-
): (() => void) => {
|
|
275
|
-
return this._stateNotifier.subscribe(name, callback);
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
get: (fn, prop) => {
|
|
279
|
-
if (prop === "connection") {
|
|
280
|
-
return (callback: SubscriptionCallback<TIO, TPresence, "connection">) => {
|
|
281
|
-
return fn("connection", callback);
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (prop === "myPresence") {
|
|
286
|
-
return (callback: SubscriptionCallback<TIO, TPresence, "my-presence">) => {
|
|
287
|
-
return fn("my-presence", callback);
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (prop === "myself") {
|
|
292
|
-
return (callback: SubscriptionCallback<TIO, TPresence, "myself">) => {
|
|
293
|
-
return fn("myself", callback);
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (prop === "others") {
|
|
298
|
-
return (callback: OthersSubscriptionCallback<TIO, TPresence>) => {
|
|
299
|
-
return this._usersNotifier.subscribeOthers(callback);
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (prop === "storageLoaded") {
|
|
304
|
-
return (callback: SubscriptionCallback<TIO, TPresence, "storage-loaded">) => {
|
|
305
|
-
return fn("storage-loaded", callback);
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (prop === "event") return this._event;
|
|
310
|
-
if (prop === "other") return this._other;
|
|
311
|
-
if (prop === "storage") return this.#_storage;
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
) as SubscribeProxy<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
|
|
315
|
-
|
|
316
|
-
public transact = (fn: (storage: InferStorage<TCrdt>) => void, origin?: string): void => {
|
|
317
|
-
const _origin = origin ?? this._state.connection.id;
|
|
318
|
-
const crdtManager = this._crdtManager;
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* !HACK
|
|
322
|
-
* @description Don't transact anything if there is no origin, because
|
|
323
|
-
* that means the user isn't connected yet. This will mean events are
|
|
324
|
-
* lost unfortunately.
|
|
325
|
-
* @date September 23, 2023
|
|
326
|
-
*/
|
|
327
|
-
if (typeof _origin !== "string") return;
|
|
328
|
-
if (!crdtManager) return;
|
|
329
|
-
|
|
330
|
-
crdtManager.doc.transact(() => {
|
|
331
|
-
const storage = crdtManager.doc.get();
|
|
332
|
-
|
|
333
|
-
fn(storage);
|
|
334
|
-
}, _origin);
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
public undo = (): void => {
|
|
338
|
-
this._crdtManager.doc.undo();
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
public updateMyPresence = (presence: UpdateMyPresenceAction<TPresence>): void => {
|
|
342
|
-
const newPresence =
|
|
343
|
-
typeof presence === "function" ? presence(this.getMyPresence()) : presence;
|
|
344
|
-
|
|
345
|
-
this._usersManager.updateMyPresence(newPresence);
|
|
346
|
-
|
|
347
|
-
const myPresence = this._usersManager.myPresence;
|
|
348
|
-
const myself = this._usersManager.myself ?? null;
|
|
349
|
-
|
|
350
|
-
this._stateNotifier.subjects["my-presence"].next(myPresence);
|
|
351
|
-
if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
private _event = new Proxy(
|
|
355
|
-
<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
356
|
-
event: TEvent,
|
|
357
|
-
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
|
|
358
|
-
): (() => void) => this._eventNotifier.subscribe(event, callback),
|
|
359
|
-
{
|
|
360
|
-
get(fn, prop) {
|
|
361
|
-
return (
|
|
362
|
-
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
|
|
363
|
-
): (() => void) => fn(prop as any, callback);
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
) as EventProxy<TIO, TEvents>;
|
|
367
|
-
|
|
368
|
-
private _observeCrdt(): void {
|
|
369
|
-
this._subscriptions.observeCrdt?.();
|
|
370
|
-
|
|
371
|
-
if (!this._crdtManager) return;
|
|
372
|
-
|
|
373
|
-
const unsubscribe = this._crdtManager.doc.subscribe((event) => {
|
|
374
|
-
const origin = event.origin ?? null;
|
|
375
|
-
|
|
376
|
-
if (!this._crdtManager) return;
|
|
377
|
-
if (origin === "$storageUpdated") return;
|
|
378
|
-
|
|
379
|
-
const sharedTypes = this._crdtManager.doc.get();
|
|
380
|
-
|
|
381
|
-
const storageRoot = Object.keys(sharedTypes).reduce(
|
|
382
|
-
(acc, prop) => {
|
|
383
|
-
if (!this._crdtManager) return acc;
|
|
384
|
-
|
|
385
|
-
const serialized = this._crdtManager.doc.toJson(prop);
|
|
386
|
-
|
|
387
|
-
this._crdtNotifier.subject(prop).next(serialized);
|
|
388
|
-
|
|
389
|
-
return { ...acc, [prop]: serialized };
|
|
390
|
-
},
|
|
391
|
-
{} as {
|
|
392
|
-
[P in keyof InferStorage<TCrdt>]: InferCrdtJson<InferStorage<TCrdt>[P]>;
|
|
393
|
-
},
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
this._subscriptions.observeCrdt = unsubscribe;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
private _other = (
|
|
403
|
-
connectionId: string,
|
|
404
|
-
callback: OtherSubscriptionCallback<TIO, TPresence>,
|
|
405
|
-
): (() => void) => {
|
|
406
|
-
const clientId = this._usersManager.getClientId(connectionId);
|
|
407
|
-
|
|
408
|
-
if (!clientId) return () => undefined;
|
|
409
|
-
|
|
410
|
-
return this._usersNotifier.subscribeOther(clientId, callback);
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
private _simulateEvent<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
414
|
-
event: TEvent,
|
|
415
|
-
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
416
|
-
) {
|
|
417
|
-
if (!this._events) return;
|
|
418
|
-
|
|
419
|
-
const type = event.toString();
|
|
420
|
-
const resolver = this._events[type as TEvent];
|
|
421
|
-
|
|
422
|
-
if (!resolver) return;
|
|
423
|
-
|
|
424
|
-
const result = resolver(data);
|
|
425
|
-
|
|
426
|
-
Object.keys(result).forEach((type) => {
|
|
427
|
-
const _type = type.toString() as keyof Partial<InferIOOutput<TIO>>;
|
|
428
|
-
const data = result[_type] as any;
|
|
429
|
-
|
|
430
|
-
if (!data) return;
|
|
431
|
-
|
|
432
|
-
this._eventNotifier.subject(_type).next(data);
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
#_storage = new Proxy(
|
|
437
|
-
<TKey extends keyof InferStorage<TCrdt>>(
|
|
438
|
-
key: TKey,
|
|
439
|
-
callback: StorageSubscriptionCallback<InferStorage<TCrdt>, TKey>,
|
|
440
|
-
): (() => void) => this._crdtNotifier.subscribe(key, callback),
|
|
441
|
-
{
|
|
442
|
-
get: (fn, prop) => {
|
|
443
|
-
type _Storage = InferStorage<TCrdt>;
|
|
444
|
-
|
|
445
|
-
if (!!prop) {
|
|
446
|
-
return (callback: StorageRootSubscriptionCallback<_Storage>) => {
|
|
447
|
-
return this._crdtNotifier.subcribeRoot(callback);
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return (callback: StorageSubscriptionCallback<_Storage, keyof _Storage>) => {
|
|
452
|
-
return fn(prop as keyof InferStorage<TCrdt>, callback);
|
|
453
|
-
};
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
) as StorageProxy<InferStorage<TCrdt>>;
|
|
457
|
-
}
|
package/src/PluvClient.ts
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import type { AbstractCrdtDocFactory, InferStorage, NoopCrdtDocFactory } from "@pluv/crdt";
|
|
2
|
-
import type { InferIOCrdtKind, InputZodLike, IOLike, JsonObject } from "@pluv/types";
|
|
3
|
-
import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
|
|
4
|
-
import type { InferCallback } from "./infer";
|
|
5
|
-
import { PluvProcedure } from "./PluvProcedure";
|
|
6
|
-
import type {
|
|
7
|
-
AuthEndpoint,
|
|
8
|
-
PluvRoomAddon,
|
|
9
|
-
PluvRoomDebug,
|
|
10
|
-
ReconnectTimeoutMs,
|
|
11
|
-
RoomConfig,
|
|
12
|
-
RoomEndpoints,
|
|
13
|
-
WsEndpoint,
|
|
14
|
-
} from "./PluvRoom";
|
|
15
|
-
import { PluvRoom } from "./PluvRoom";
|
|
16
|
-
import type { PluvRouterEventConfig } from "./PluvRouter";
|
|
17
|
-
import { PluvRouter } from "./PluvRouter";
|
|
18
|
-
import type { PluvClientLimits, PublicKey, WithMetadata } from "./types";
|
|
19
|
-
|
|
20
|
-
export type PluvClientOptions<
|
|
21
|
-
TIO extends IOLike<any, any, any>,
|
|
22
|
-
TPresence extends Record<string, any>,
|
|
23
|
-
TCrdt extends InferIOCrdtKind<TIO>,
|
|
24
|
-
TMetadata extends JsonObject,
|
|
25
|
-
> = RoomEndpoints<TIO, TMetadata> & {
|
|
26
|
-
debug?: boolean;
|
|
27
|
-
/**
|
|
28
|
-
* @description Configurable limits defined for client-side validation. You should only set
|
|
29
|
-
* this if you control the server and have changed the limits there.
|
|
30
|
-
*/
|
|
31
|
-
limits?: PluvClientLimits;
|
|
32
|
-
metadata?: InputZodLike<TMetadata>;
|
|
33
|
-
presence?: InputZodLike<TPresence>;
|
|
34
|
-
publicKey?: PublicKey<TMetadata>;
|
|
35
|
-
types: InferCallback<TIO>;
|
|
36
|
-
} & (InferIOCrdtKind<TIO> extends NoopCrdtDocFactory
|
|
37
|
-
? { initialStorage?: "[ERROR]: Must provide crdt to createIO to use storage" }
|
|
38
|
-
: { initialStorage?: TCrdt });
|
|
39
|
-
|
|
40
|
-
export type CreateRoomOptions<
|
|
41
|
-
TIO extends IOLike<any, any, any>,
|
|
42
|
-
TPresence extends Record<string, any>,
|
|
43
|
-
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
44
|
-
TMetadata extends JsonObject,
|
|
45
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {},
|
|
46
|
-
> = {
|
|
47
|
-
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, InferStorage<TCrdt>>[];
|
|
48
|
-
debug?: boolean | PluvRoomDebug<TIO>;
|
|
49
|
-
initialPresence?: TPresence;
|
|
50
|
-
initialStorage?: TCrdt;
|
|
51
|
-
onAuthorizationFail?: (error: Error) => void;
|
|
52
|
-
reconnectTimeoutMs?: ReconnectTimeoutMs;
|
|
53
|
-
router?: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export type EnterRoomParams<TMetadata extends JsonObject = {}> = keyof TMetadata extends never
|
|
57
|
-
? []
|
|
58
|
-
: [WithMetadata<TMetadata>];
|
|
59
|
-
|
|
60
|
-
export class PluvClient<
|
|
61
|
-
TIO extends IOLike<any, any, any>,
|
|
62
|
-
TPresence extends Record<string, any> = {},
|
|
63
|
-
TCrdt extends InferIOCrdtKind<TIO> = InferIOCrdtKind<TIO>,
|
|
64
|
-
TMetadata extends JsonObject = {},
|
|
65
|
-
> {
|
|
66
|
-
public readonly metadata?: InputZodLike<TMetadata>;
|
|
67
|
-
|
|
68
|
-
private readonly _authEndpoint: AuthEndpoint<TMetadata> | undefined;
|
|
69
|
-
private readonly _debug: boolean;
|
|
70
|
-
private readonly _initialStorage?: TCrdt;
|
|
71
|
-
private readonly _limits: PluvClientLimits;
|
|
72
|
-
private readonly _presence?: InputZodLike<TPresence>;
|
|
73
|
-
private readonly _publicKey: PublicKey<TMetadata> | null = null;
|
|
74
|
-
private readonly _rooms = new Map<string, PluvRoom<TIO, TMetadata, TPresence, TCrdt, any>>();
|
|
75
|
-
private readonly _wsEndpoint: WsEndpoint<TMetadata> | undefined;
|
|
76
|
-
|
|
77
|
-
public get _defs() {
|
|
78
|
-
return {
|
|
79
|
-
initialStorage: this._initialStorage,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
public get procedure(): PluvProcedure<TIO, {}, {}, TPresence, TCrdt, ""> {
|
|
84
|
-
return new PluvProcedure<TIO, {}, {}, TPresence, TCrdt, "">();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
constructor(options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>) {
|
|
88
|
-
const {
|
|
89
|
-
authEndpoint,
|
|
90
|
-
debug = false,
|
|
91
|
-
initialStorage,
|
|
92
|
-
limits,
|
|
93
|
-
metadata,
|
|
94
|
-
presence,
|
|
95
|
-
publicKey,
|
|
96
|
-
wsEndpoint,
|
|
97
|
-
} = options;
|
|
98
|
-
|
|
99
|
-
this.metadata = metadata;
|
|
100
|
-
|
|
101
|
-
this._authEndpoint = authEndpoint as AuthEndpoint<TMetadata>;
|
|
102
|
-
this._debug = debug;
|
|
103
|
-
this._initialStorage = initialStorage as TCrdt;
|
|
104
|
-
this._limits = {
|
|
105
|
-
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
|
|
106
|
-
...limits,
|
|
107
|
-
};
|
|
108
|
-
this._presence = presence;
|
|
109
|
-
this._wsEndpoint = wsEndpoint;
|
|
110
|
-
|
|
111
|
-
if (!!publicKey) this._publicKey = publicKey;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
public createRoom = <
|
|
115
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {},
|
|
116
|
-
>(
|
|
117
|
-
room: string,
|
|
118
|
-
options: CreateRoomOptions<TIO, TPresence, TCrdt, TMetadata, TEvents> = {},
|
|
119
|
-
): PluvRoom<TIO, TMetadata, TPresence, TCrdt, TEvents> => {
|
|
120
|
-
const oldRoom = this.getRoom(room);
|
|
121
|
-
|
|
122
|
-
if (oldRoom) return oldRoom;
|
|
123
|
-
|
|
124
|
-
const newRoom = new PluvRoom<TIO, TMetadata, TPresence, TCrdt, TEvents>(room, {
|
|
125
|
-
addons: options.addons,
|
|
126
|
-
authEndpoint: this._authEndpoint,
|
|
127
|
-
debug: options.debug,
|
|
128
|
-
initialPresence: options.initialPresence,
|
|
129
|
-
initialStorage: options.initialStorage ?? this._initialStorage,
|
|
130
|
-
limits: this._limits,
|
|
131
|
-
metadata: this.metadata,
|
|
132
|
-
onAuthorizationFail: options.onAuthorizationFail,
|
|
133
|
-
presence: this._presence,
|
|
134
|
-
publicKey: this._publicKey ?? undefined,
|
|
135
|
-
reconnectTimeoutMs: options.reconnectTimeoutMs,
|
|
136
|
-
router: options.router,
|
|
137
|
-
wsEndpoint: this._wsEndpoint,
|
|
138
|
-
} as RoomConfig<TIO, TMetadata, TPresence, TCrdt, TEvents>);
|
|
139
|
-
|
|
140
|
-
this._rooms.set(room, newRoom);
|
|
141
|
-
|
|
142
|
-
this._logDebug(`New room was created: ${room}`);
|
|
143
|
-
|
|
144
|
-
return newRoom;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
public enter = async (
|
|
148
|
-
room: string | PluvRoom<TIO, TMetadata, any, any, any>,
|
|
149
|
-
...args: EnterRoomParams<TMetadata>
|
|
150
|
-
): Promise<PluvRoom<TIO, TMetadata, TPresence, TCrdt, any>> => {
|
|
151
|
-
const toEnter = typeof room === "string" ? this.getRoom(room) : room;
|
|
152
|
-
|
|
153
|
-
if (!toEnter) throw new Error(`Could not find room: ${room}.`);
|
|
154
|
-
|
|
155
|
-
this._rooms.set(toEnter.id, toEnter);
|
|
156
|
-
await toEnter.connect(...args);
|
|
157
|
-
|
|
158
|
-
this._logDebug(`Entered room: ${room}`);
|
|
159
|
-
|
|
160
|
-
return toEnter;
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
public getRoom = (room: string): PluvRoom<TIO, TMetadata, TPresence, TCrdt, any> | null => {
|
|
164
|
-
const found = this._rooms.get(room) as
|
|
165
|
-
PluvRoom<TIO, TMetadata, TPresence, TCrdt, any> | undefined;
|
|
166
|
-
|
|
167
|
-
return found ?? null;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
public getRooms = (): readonly PluvRoom<TIO, TMetadata, TPresence, TCrdt, any>[] => {
|
|
171
|
-
return Array.from(this._rooms.values());
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
public leave = async (room: string | PluvRoom<TIO, any, any, any>): Promise<void> => {
|
|
175
|
-
const toLeave = typeof room === "string" ? this.getRoom(room) : room;
|
|
176
|
-
|
|
177
|
-
if (!toLeave) return;
|
|
178
|
-
|
|
179
|
-
await toLeave.disconnect();
|
|
180
|
-
|
|
181
|
-
this._rooms.delete(toLeave.id);
|
|
182
|
-
|
|
183
|
-
this._logDebug(`Left and deleted room: ${room}`);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
public router<TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {}>(
|
|
187
|
-
events: TEvents,
|
|
188
|
-
): PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents> {
|
|
189
|
-
const invalidName = Object.keys(events).find((name) => name.includes("$"));
|
|
190
|
-
|
|
191
|
-
if (typeof invalidName === "string") {
|
|
192
|
-
throw new Error(`Invalid event name. Event names must not contain $: "${invalidName}"`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return new PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>(events);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
private _logDebug(...data: any[]): void {
|
|
199
|
-
if (typeof process === "undefined") return;
|
|
200
|
-
if (process.env?.NODE_ENV === "production") return;
|
|
201
|
-
|
|
202
|
-
if (this._debug) console.log(...data);
|
|
203
|
-
}
|
|
204
|
-
}
|