@pluv/io 0.17.3 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/PluvIO.ts CHANGED
@@ -2,314 +2,182 @@ import type { AbstractCrdtDocFactory } from "@pluv/crdt";
2
2
  import { noop } from "@pluv/crdt";
3
3
  import type {
4
4
  BaseIOAuthorize,
5
- EventRecord,
6
5
  IOAuthorize,
7
6
  IOLike,
8
7
  InferIOAuthorize,
9
8
  InferIOAuthorizeUser,
10
- InferIOInput,
11
- InferIOOutputBroadcast,
12
- InferIOOutputSelf,
13
- InferIOOutputSync,
14
9
  JsonObject,
15
10
  Maybe,
16
- MaybePromise,
17
- Spread,
18
11
  } from "@pluv/types";
19
- import colors from "kleur";
20
- import type {
21
- AbstractPlatform,
22
- InferPlatformEventContextType,
23
- InferPlatformRoomContextType,
24
- } from "./AbstractPlatform";
25
- import type { IORoomListenerEvent } from "./IORoom";
26
- import { IORoom } from "./IORoom";
12
+ import type { AbstractPlatform, InferPlatformRoomContextType } from "./AbstractPlatform";
13
+ import { PluvProcedure } from "./PluvProcedure";
14
+ import type { MergedRouter, PluvRouterEventConfig } from "./PluvRouter";
15
+ import { PluvRouter } from "./PluvRouter";
16
+ import { PluvServer } from "./PluvServer";
27
17
  import type { JWTEncodeParams } from "./authorize";
28
18
  import { authorize } from "./authorize";
29
- import type { EventConfig, InferEventConfig } from "./types";
19
+ import type { GetInitialStorageFn, PluvIOListeners } from "./types";
30
20
  import { __PLUV_VERSION } from "./version";
31
21
 
32
- type InferIOContext<TIO extends PluvIO<any, any, any, any, any, any, any>> =
33
- TIO extends PluvIO<any, any, infer IContext, any, any, any, any>
34
- ? IContext
35
- : never;
36
-
37
- type InferIOPlatform<TIO extends PluvIO<any, any, any, any, any, any, any>> =
38
- TIO extends PluvIO<infer IPlatform, any, any, any, any, any, any>
39
- ? IPlatform
40
- : never;
41
-
42
- export type InferIORoom<TIO extends PluvIO<any, any, any, any, any, any, any>> =
43
- TIO extends PluvIO<
44
- infer IPlatform,
45
- infer IAuthorize,
46
- infer IContext,
47
- infer IInput,
48
- infer IOutputBroadcast,
49
- infer IOutputSelf,
50
- infer IOutputSync
51
- >
52
- ? IORoom<
53
- IPlatform,
54
- IAuthorize,
55
- IContext,
56
- IInput,
57
- IOutputBroadcast,
58
- IOutputSelf,
59
- IOutputSync
60
- >
61
- : never;
62
-
63
- type GetInitialStorageEvent<TPlatform extends AbstractPlatform> = {
64
- room: string;
65
- } & InferPlatformRoomContextType<TPlatform>;
66
-
67
- export type GetInitialStorageFn<TPlatform extends AbstractPlatform> = (
68
- event: GetInitialStorageEvent<TPlatform>,
69
- ) => MaybePromise<Maybe<string>>;
70
-
71
- export interface PluvIOListeners<TPlatform extends AbstractPlatform> {
72
- onRoomDeleted: (event: IORoomListenerEvent<TPlatform>) => void;
73
- onStorageUpdated: (event: IORoomListenerEvent<TPlatform>) => void;
74
- }
75
-
76
22
  export type PluvIOConfig<
77
- TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
78
- TAuthorize extends IOAuthorize<
79
- any,
80
- any,
81
- InferPlatformRoomContextType<TPlatform>
82
- > | null = null,
83
- TContext extends JsonObject = {},
84
- TInput extends EventRecord<string, any> = {},
85
- TOutputBroadcast extends EventRecord<string, any> = {},
86
- TOutputSelf extends EventRecord<string, any> = {},
87
- TOutputSync extends EventRecord<string, any> = {},
23
+ TPlatform extends AbstractPlatform<any>,
24
+ TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>>,
25
+ TContext extends JsonObject,
88
26
  > = Partial<PluvIOListeners<TPlatform>> & {
89
27
  authorize?: TAuthorize;
90
28
  context?: TContext;
91
29
  crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
92
30
  debug?: boolean;
93
- events?: InferEventConfig<
94
- TPlatform,
95
- TContext,
96
- TInput,
97
- TOutputBroadcast,
98
- TOutputSelf,
99
- TOutputSync
100
- >;
101
31
  getInitialStorage?: GetInitialStorageFn<TPlatform>;
102
32
  platform: TPlatform;
103
33
  };
104
34
 
105
- export type GetRoomOptions<TPlatform extends AbstractPlatform> = {
106
- debug?: boolean;
107
- } & InferPlatformRoomContextType<TPlatform>;
35
+ export interface ServerConfig<
36
+ TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
37
+ TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>> = BaseIOAuthorize,
38
+ TContext extends JsonObject = {},
39
+ TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
40
+ > {
41
+ router?: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
42
+ }
108
43
 
109
44
  export class PluvIO<
110
45
  TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
111
- TAuthorize extends IOAuthorize<
112
- any,
113
- any,
114
- InferPlatformRoomContextType<TPlatform>
115
- > = BaseIOAuthorize,
46
+ TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>> = BaseIOAuthorize,
116
47
  TContext extends JsonObject = {},
117
- TInput extends EventRecord<string, any> = {},
118
- TOutputBroadcast extends EventRecord<string, any> = {},
119
- TOutputSelf extends EventRecord<string, any> = {},
120
- TOutputSync extends EventRecord<string, any> = {},
121
- > implements
122
- IOLike<TAuthorize, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>
48
+ > implements IOLike<TAuthorize, {}>
123
49
  {
124
- private _rooms = new Map<
125
- string,
126
- IORoom<
127
- TPlatform,
128
- TAuthorize,
129
- TContext,
130
- TInput,
131
- TOutputBroadcast,
132
- TOutputSelf,
133
- TOutputSync
134
- >
135
- >();
136
-
137
- readonly _authorize: TAuthorize | null = null;
138
- readonly _context: TContext = {} as TContext;
139
- readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
140
- readonly _debug: boolean;
141
- readonly _events: InferEventConfig<
142
- TPlatform,
143
- TContext,
144
- TInput,
145
- TOutputBroadcast,
146
- TOutputSelf,
147
- TOutputSync
148
- > = {
149
- $GET_OTHERS: {
150
- resolver: {
151
- sync: (data, { room, session, sessions }) => {
152
- const others = Array.from(sessions.entries())
153
- .filter(([, wsSession]) => wsSession.id !== session?.id)
154
- .reduce<{
155
- [connectionId: string]: {
156
- connectionId: string;
157
- room: string | null;
158
- user: InferIOAuthorizeUser<TAuthorize>;
159
- };
160
- }>(
161
- (acc, [connectionId, { presence, user }]) => ({
162
- ...acc,
163
- [connectionId]: {
164
- connectionId,
165
- presence,
166
- room,
167
- user: user as InferIOAuthorizeUser<TAuthorize>,
168
- },
169
- }),
170
- {},
171
- );
172
-
173
- return { $OTHERS_RECEIVED: { others } };
174
- },
175
- },
176
- },
177
- $INITIALIZE_SESSION: {
178
- resolver: {
179
- broadcast: ({ presence }, { session }) => {
180
- if (!session) return {};
181
-
182
- session.presence = presence;
183
-
184
- return {
185
- $USER_JOINED: {
186
- connectionId: session.id,
187
- user: session.user,
188
- presence,
189
- },
50
+ private _router: PluvRouter<TPlatform, TAuthorize, TContext, {}> = new PluvRouter({
51
+ $GET_OTHERS: this.procedure.sync((data, { room, session, sessions }) => {
52
+ const others = Array.from(sessions.entries())
53
+ .filter(([, wsSession]) => wsSession.id !== session?.id)
54
+ .reduce<{
55
+ [connectionId: string]: {
56
+ connectionId: string;
57
+ room: string | null;
58
+ user: JsonObject;
190
59
  };
191
- },
192
- self: async ({ update }, { context, doc, room }) => {
193
- const oldState =
194
- await this._platform.persistance.getStorageState(room);
195
-
196
- const updates: readonly Maybe<string>[] = [
197
- oldState
198
- ? null
199
- : await this._getInitialStorage?.({
200
- ...context,
201
- room,
202
- }),
203
- update,
204
- ];
205
-
206
- if (updates.some((_update) => !!_update)) {
207
- const encodedState = doc
208
- .batchApplyEncodedState(updates)
209
- .getEncodedState();
210
-
211
- await this._platform.persistance.setStorageState(
60
+ }>(
61
+ (acc, [connectionId, { presence, user }]) => ({
62
+ ...acc,
63
+ [connectionId]: {
64
+ connectionId,
65
+ presence,
212
66
  room,
213
- encodedState,
214
- );
67
+ user,
68
+ },
69
+ }),
70
+ {},
71
+ );
215
72
 
216
- this._listeners.onStorageUpdated({
217
- ...context,
218
- encodedState,
219
- room,
220
- });
221
- }
73
+ return { $OTHERS_RECEIVED: { others } };
74
+ }),
75
+ $INITIALIZE_SESSION: this.procedure
76
+ .broadcast((data, { session }) => {
77
+ const presence = (data as any)?.presence;
222
78
 
223
- const state =
224
- (await this._platform.persistance.getStorageState(
225
- room,
226
- )) ?? doc.getEncodedState();
227
-
228
- return { $STORAGE_RECEIVED: { state } };
229
- },
230
- },
231
- },
232
- $PING: {
233
- resolver: {
234
- self: (data, { session }) => {
235
- if (!session) return {};
236
-
237
- const currentTime = new Date().getTime();
238
-
239
- session.timers.ping = currentTime;
240
-
241
- return { $PONG: {} };
242
- },
243
- },
244
- },
245
- $UPDATE_PRESENCE: {
246
- resolver: ({ presence }, { session }) => {
247
79
  if (!session) return {};
248
80
 
249
- session.presence = Object.assign(
250
- Object.create(null),
251
- session.presence,
252
- presence,
253
- );
254
-
255
- return { $PRESENCE_UPDATED: { presence } };
256
- },
257
- },
258
- $UPDATE_STORAGE: {
259
- resolver: ({ origin, update }, { context, doc, room }) => {
260
- if (
261
- origin === "$INITIALIZED" &&
262
- Object.keys(doc.toJson()).length
263
- ) {
264
- return;
81
+ session.presence = presence;
82
+
83
+ return {
84
+ $USER_JOINED: {
85
+ connectionId: session.id,
86
+ user: session.user,
87
+ presence,
88
+ },
89
+ };
90
+ })
91
+ .self(async (data, { context, doc, room }) => {
92
+ const oldState = await this._platform.persistance.getStorageState(room);
93
+ const update = (data as any)?.update as Maybe<string>;
94
+
95
+ const updates: readonly Maybe<string>[] = [
96
+ oldState
97
+ ? null
98
+ : await this._getInitialStorage?.({
99
+ ...context,
100
+ room,
101
+ }),
102
+ update,
103
+ ];
104
+
105
+ if (updates.some((_update) => !!_update)) {
106
+ const encodedState = doc.batchApplyEncodedState(updates).getEncodedState();
107
+
108
+ await this._platform.persistance.setStorageState(room, encodedState);
109
+
110
+ this._listeners.onStorageUpdated({ ...context, encodedState, room });
265
111
  }
266
112
 
267
- const updated = doc.applyEncodedState({ update });
268
- const encodedState = updated.getEncodedState();
113
+ const state = (await this._platform.persistance.getStorageState(room)) ?? doc.getEncodedState();
269
114
 
270
- this._platform.persistance
271
- .setStorageState(room, encodedState)
272
- .then(() => {
273
- this._listeners.onStorageUpdated({
274
- ...context,
275
- encodedState,
276
- room,
277
- });
278
- });
279
-
280
- return { $STORAGE_UPDATED: { state: encodedState } };
281
- },
282
- },
283
- } as InferEventConfig<
284
- TPlatform,
285
- TContext,
286
- TInput,
287
- TOutputBroadcast,
288
- TOutputSelf,
289
- TOutputSync
290
- >;
115
+ return { $STORAGE_RECEIVED: { state } };
116
+ }),
117
+ $PING: this.procedure.self((data, { session }) => {
118
+ if (!session) return {};
119
+
120
+ const currentTime = new Date().getTime();
121
+
122
+ session.timers.ping = currentTime;
123
+
124
+ return { $PONG: {} };
125
+ }),
126
+ $UPDATE_PRESENCE: this.procedure.broadcast((data, { session }) => {
127
+ const presence = (data as any)?.presence;
128
+
129
+ if (!session) return {};
130
+
131
+ session.presence = Object.assign(Object.create(null), session.presence, presence);
132
+
133
+ return { $PRESENCE_UPDATED: { presence } };
134
+ }),
135
+ $UPDATE_STORAGE: this.procedure.broadcast((data, { context, doc, room }) => {
136
+ const origin = (data as any)?.origin as Maybe<string>;
137
+ const update: string | null = (data as any)?.update ?? null;
138
+
139
+ if (origin === "$INITIALIZED" && Object.keys(doc.toJson()).length) {
140
+ return {};
141
+ }
142
+
143
+ const updated = update === null ? doc : doc.applyEncodedState({ update });
144
+ const encodedState = updated.getEncodedState();
145
+
146
+ this._platform.persistance.setStorageState(room, encodedState).then(() => {
147
+ this._listeners.onStorageUpdated({
148
+ ...context,
149
+ encodedState,
150
+ room,
151
+ });
152
+ });
153
+
154
+ return { $STORAGE_UPDATED: { state: encodedState } };
155
+ }),
156
+ });
157
+
158
+ readonly _authorize: TAuthorize | null = null;
159
+ readonly _context: TContext = {} as TContext;
160
+ readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
161
+ readonly _debug: boolean;
291
162
  readonly _getInitialStorage: GetInitialStorageFn<TPlatform> | null = null;
292
163
  readonly _listeners: PluvIOListeners<TPlatform>;
293
164
  readonly _platform: TPlatform;
294
165
  readonly _version: string = __PLUV_VERSION as any;
295
166
 
296
- constructor(
297
- options: PluvIOConfig<
298
- TPlatform,
299
- TAuthorize,
300
- TContext,
301
- TInput,
302
- TOutputBroadcast,
303
- TOutputSelf,
304
- TOutputSync
305
- >,
306
- ) {
167
+ public get _events() {
168
+ return this._router._events;
169
+ }
170
+
171
+ public get procedure(): PluvProcedure<TPlatform, TAuthorize, TContext, {}, {}> {
172
+ return new PluvProcedure();
173
+ }
174
+
175
+ constructor(options: PluvIOConfig<TPlatform, TAuthorize, TContext>) {
307
176
  const {
308
177
  authorize,
309
178
  context,
310
179
  crdt = noop,
311
180
  debug = false,
312
- events,
313
181
  getInitialStorage,
314
182
  onRoomDeleted,
315
183
  onStorageUpdated,
@@ -322,7 +190,6 @@ export class PluvIO<
322
190
 
323
191
  if (authorize) this._authorize = authorize;
324
192
  if (context) this._context = context;
325
- if (events) this._events = events;
326
193
  if (getInitialStorage) this._getInitialStorage = getInitialStorage;
327
194
 
328
195
  this._listeners = {
@@ -332,21 +199,14 @@ export class PluvIO<
332
199
  }
333
200
 
334
201
  public async createToken(
335
- params: JWTEncodeParams<
336
- InferIOAuthorizeUser<InferIOAuthorize<this>>,
337
- TPlatform
338
- >,
202
+ params: JWTEncodeParams<InferIOAuthorizeUser<InferIOAuthorize<this>>, TPlatform>,
339
203
  ): Promise<string> {
340
204
  if (!this._authorize) {
341
- throw new Error(
342
- "IO does not specify authorize during initialization.",
343
- );
205
+ throw new Error("IO does not specify authorize during initialization.");
344
206
  }
345
207
 
346
208
  const ioAuthorize =
347
- typeof this._authorize === "function"
348
- ? this._authorize(params)
349
- : (this._authorize as { secret: string });
209
+ typeof this._authorize === "function" ? this._authorize(params) : (this._authorize as { secret: string });
350
210
 
351
211
  const secret = ioAuthorize.secret;
352
212
 
@@ -356,213 +216,36 @@ export class PluvIO<
356
216
  }).encode(params);
357
217
  }
358
218
 
359
- public event<
360
- TEvent extends string,
361
- TData extends JsonObject = {},
362
- TResultBroadcast extends EventRecord<string, any> = {},
363
- TResultSelf extends EventRecord<string, any> = {},
364
- TResultSync extends EventRecord<string, any> = {},
365
- >(
366
- event: TEvent,
367
- config: EventConfig<
368
- TPlatform,
369
- TContext,
370
- TData,
371
- TResultBroadcast,
372
- TResultSelf,
373
- TResultSync
374
- >,
375
- ): PluvIO<
376
- TPlatform,
377
- TAuthorize,
378
- TContext,
379
- Spread<[TInput, EventRecord<TEvent, TData>]>,
380
- Spread<[TOutputBroadcast, TResultBroadcast]>,
381
- Spread<[TOutputSelf, TResultSelf]>,
382
- Spread<[TOutputSync, TResultSync]>
383
- > {
384
- if (event.startsWith("$")) {
385
- throw new Error('Event name must not start with "$".');
386
- }
387
-
388
- const newEvent = {
389
- [event]: config,
390
- } as InferEventConfig<
391
- TPlatform,
392
- TContext,
393
- EventRecord<TEvent, TData>,
394
- TResultBroadcast extends EventRecord<string, any>
395
- ? TResultBroadcast
396
- : {},
397
- TResultSelf extends EventRecord<string, any> ? TResultSelf : {},
398
- TResultSync extends EventRecord<string, any> ? TResultSync : {}
399
- >;
400
-
401
- /**
402
- * !HACK
403
- * @description Typing this is becoming way too unmanagable, due to resolver
404
- * being able to return void and needing to handle the conditional typing.
405
- * Assume the types will work out, and cast as any here.
406
- *
407
- * TODO
408
- * @description Revisit EventResolver and related types to resolve types
409
- * without casting to any.
410
- *
411
- * @author leedavidcs
412
- * @date September 25, 2022
413
- */
414
- return PluvIO.merge(
415
- this as any,
416
- new PluvIO({
417
- crdt: this._crdt,
418
- events: newEvent as any,
419
- platform: this._platform,
420
- }),
421
- ) as any;
219
+ public mergeRouters<TRouters extends PluvRouter<TPlatform, TAuthorize, TContext, any>[]>(
220
+ ...routers: TRouters
221
+ ): MergedRouter<TRouters> {
222
+ return PluvRouter.merge(...routers);
422
223
  }
423
224
 
424
- public getRoom(
425
- room: string,
426
- options: GetRoomOptions<TPlatform>,
427
- ): IORoom<
428
- TPlatform,
429
- TAuthorize,
430
- TContext,
431
- TInput,
432
- TOutputBroadcast,
433
- TOutputSelf,
434
- TOutputSync
435
- > {
436
- const { debug, ...platformRoomContext } = options;
437
-
438
- this._purgeEmptyRooms();
439
-
440
- if (!/^[a-z0-9]+[a-z0-9\-_]+[a-z0-9]+$/i.test(room)) {
441
- throw new Error("Unsupported room name");
442
- }
443
-
444
- const platform = this._platform;
445
- const oldRoom = this._rooms.get(room);
446
-
447
- if (oldRoom) return oldRoom;
448
-
449
- const roomContext = {
450
- ...this._context,
451
- ...platformRoomContext,
452
- } as TContext & InferPlatformRoomContextType<TPlatform>;
225
+ public router<TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>>(
226
+ events: TEvents,
227
+ ): PluvRouter<TPlatform, TAuthorize, TContext, TEvents> {
228
+ return new PluvRouter<TPlatform, TAuthorize, TContext, TEvents>(events);
229
+ }
453
230
 
454
- const newRoom = new IORoom<
231
+ public server<TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>>(
232
+ config: ServerConfig<TPlatform, TAuthorize, TContext, TEvents> = {},
233
+ ): PluvServer<TPlatform, TAuthorize, TContext, TEvents> {
234
+ const router = (config.router ? this.mergeRouters(this._router, config.router) : this._router) as PluvRouter<
455
235
  TPlatform,
456
236
  TAuthorize,
457
237
  TContext,
458
- TInput,
459
- TOutputBroadcast,
460
- TOutputSelf,
461
- TOutputSync
462
- >(room, {
238
+ TEvents
239
+ >;
240
+
241
+ return new PluvServer<TPlatform, TAuthorize, TContext, TEvents>({
242
+ ...this._listeners,
463
243
  authorize: this._authorize ?? undefined,
464
- context: roomContext,
244
+ context: this._context,
465
245
  crdt: this._crdt,
466
- debug: debug ?? this._debug,
467
- events: this._events,
468
- onDestroy: ({ encodedState, room }) => {
469
- this._logDebug(
470
- `${colors.blue("Deleting empty room:")} ${room}`,
471
- );
472
-
473
- const roomContext = {
474
- room,
475
- encodedState,
476
- ...platformRoomContext,
477
- } as IORoomListenerEvent<TPlatform> &
478
- InferPlatformRoomContextType<TPlatform>;
479
-
480
- this._rooms.delete(room);
481
- this._listeners.onRoomDeleted(roomContext);
482
-
483
- if (this._debug) {
484
- const rooms = Array.from(this._rooms.keys());
485
-
486
- this._logDebug(`${colors.blue("Deleted room:")} ${room}`);
487
- this._logDebug(
488
- `${colors.blue("Rooms available:")} ${rooms.join(
489
- ", ",
490
- )}`,
491
- );
492
- }
493
- },
494
- platform,
495
- });
496
-
497
- this._rooms.set(room, newRoom);
498
-
499
- if (this._debug) {
500
- const rooms = Array.from(this._rooms.keys());
501
-
502
- this._logDebug(`${colors.blue("Created room:")} ${room}`);
503
- this._logDebug(
504
- `${colors.blue("Rooms available:")} ${rooms.join(", ")}`,
505
- );
506
- }
507
-
508
- return newRoom;
509
- }
510
-
511
- public static merge<TIO1 extends PluvIO, TIO2 extends PluvIO>(
512
- first: TIO1,
513
- second: TIO2,
514
- ): PluvIO<
515
- InferIOPlatform<TIO1>,
516
- InferIOAuthorize<TIO1>,
517
- InferIOContext<TIO1>,
518
- Spread<[InferIOInput<TIO1>, InferIOInput<TIO2>]>,
519
- Spread<[InferIOOutputBroadcast<TIO1>, InferIOOutputBroadcast<TIO2>]>,
520
- Spread<[InferIOOutputSelf<TIO1>, InferIOOutputSelf<TIO2>]>,
521
- Spread<[InferIOOutputSync<TIO1>, InferIOOutputSync<TIO2>]>
522
- > {
523
- const authorize = (first._authorize ??
524
- undefined) as InferIOAuthorize<TIO1>;
525
- const context = first._context as InferIOContext<TIO1>;
526
- const crdt = first._crdt;
527
- const debug = first._debug;
528
- const platform = first._platform as InferIOPlatform<TIO1>;
529
-
530
- const getInitialStorage =
531
- first._getInitialStorage as GetInitialStorageFn<
532
- InferIOPlatform<TIO1>
533
- >;
534
-
535
- const events1 = first._events;
536
- const events2 = second._events;
537
-
538
- const { onRoomDeleted, onStorageUpdated } = first._listeners;
539
-
540
- const events = Object.assign(Object.create(null), events1, events2);
541
-
542
- return new PluvIO({
543
- authorize,
544
- context,
545
- crdt,
546
- debug,
547
- events,
548
- getInitialStorage,
549
- onRoomDeleted,
550
- onStorageUpdated,
551
- platform,
552
- });
553
- }
554
-
555
- private _logDebug(...data: any[]): void {
556
- this._debug && console.log(...data);
557
- }
558
-
559
- private _purgeEmptyRooms(): void {
560
- const rooms = Array.from(this._rooms.entries());
561
-
562
- rooms.forEach(([id, room]) => {
563
- if (room.getSize()) return;
564
-
565
- this._rooms.delete(id);
246
+ debug: this._debug,
247
+ platform: this._platform,
248
+ router,
566
249
  });
567
250
  }
568
251
  }