@pluv/io 0.22.0 → 0.24.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/io",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "@pluv/io server-side websocket manager",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -21,16 +21,16 @@
21
21
  "jose": "^5.6.3",
22
22
  "kleur": "^4.1.5",
23
23
  "wonka": "^6.3.4",
24
- "@pluv/crdt": "^0.22.0",
25
- "@pluv/types": "^0.22.0"
24
+ "@pluv/crdt": "^0.24.0",
25
+ "@pluv/types": "^0.24.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "eslint": "^8.57.0",
29
29
  "genversion": "^3.2.0",
30
30
  "tsup": "^8.2.4",
31
31
  "typescript": "^5.5.4",
32
- "@pluv/tsconfig": "^0.22.0",
33
- "eslint-config-pluv": "^0.22.0"
32
+ "@pluv/tsconfig": "^0.24.0",
33
+ "eslint-config-pluv": "^0.24.0"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "pnpm run version && tsup src/index.ts --format esm,cjs --dts",
@@ -39,16 +39,16 @@ export abstract class AbstractPlatform<
39
39
  TPlatformContext extends Record<string, any> = {},
40
40
  TRoomContext extends Record<string, any> = {},
41
41
  > {
42
- private _initialized: boolean = false;
43
-
44
- readonly _ioContext: TPlatformContext | undefined;
45
- readonly _roomContext: TRoomContext | undefined;
42
+ protected readonly _ioContext: TPlatformContext | undefined;
43
+ protected readonly _roomContext: TRoomContext | undefined;
46
44
 
47
- abstract readonly _registrationMode: WebSocketRegistrationMode;
45
+ private _initialized: boolean = false;
48
46
 
49
47
  public persistance: AbstractPersistance;
50
48
  public pubSub: AbstractPubSub;
51
49
 
50
+ public abstract readonly _registrationMode: WebSocketRegistrationMode;
51
+
52
52
  constructor(config: AbstractPlatformConfig<TPlatformContext, TRoomContext> = {}) {
53
53
  const { context, persistance, pubSub } = config;
54
54
 
package/src/IORoom.ts CHANGED
@@ -5,9 +5,11 @@ import type {
5
5
  BaseIOEventRecord,
6
6
  EventMessage,
7
7
  IOAuthorize,
8
+ IOEventMessage,
8
9
  IOLike,
9
10
  Id,
10
11
  InferEventMessage,
12
+ InferEventsOutput,
11
13
  InferIOAuthorize,
12
14
  InferIOAuthorizeUser,
13
15
  InferIOInput,
@@ -16,12 +18,7 @@ import type {
16
18
  Maybe,
17
19
  } from "@pluv/types";
18
20
  import colors from "kleur";
19
- import type {
20
- AbstractPlatform,
21
- InferPlatformWebSocketSource,
22
- InferPlatformWebSocketType,
23
- InferRoomContextType,
24
- } from "./AbstractPlatform";
21
+ import type { AbstractPlatform, InferPlatformWebSocketSource, InferRoomContextType } from "./AbstractPlatform";
25
22
  import { AbstractCloseEvent, AbstractErrorEvent, AbstractMessageEvent, AbstractWebSocket } from "./AbstractWebSocket";
26
23
  import type { PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
27
24
  import { authorize } from "./authorize";
@@ -29,7 +26,7 @@ import { PING_TIMEOUT_MS } from "./constants";
29
26
  import type {
30
27
  EventResolverContext,
31
28
  IORoomListenerEvent,
32
- SendMessageOptions,
29
+ IORoomMessageEvent,
33
30
  WebSocketSession,
34
31
  WebSocketType,
35
32
  } from "./types";
@@ -43,8 +40,14 @@ interface BroadcastParams<TIO extends IORoom<any, any, any, any>> {
43
40
  senderId?: string;
44
41
  }
45
42
 
46
- interface IORoomListeners<TPlatform extends AbstractPlatform> {
47
- onDestroy: (event: IORoomListenerEvent<TPlatform>) => void;
43
+ export interface IORoomListeners<
44
+ TPlatform extends AbstractPlatform<any>,
45
+ TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>>,
46
+ TContext extends JsonObject,
47
+ TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
48
+ > {
49
+ onDestroy: (event: IORoomListenerEvent<TPlatform, TAuthorize, TContext, TEvents>) => void;
50
+ onMessage: (event: IORoomMessageEvent<TPlatform, TAuthorize, TContext, TEvents>) => void;
48
51
  }
49
52
 
50
53
  export type BroadcastProxy<TIO extends IORoom<any, any, any, any>> = (<TEvent extends keyof InferIOInput<TIO>>(
@@ -59,7 +62,7 @@ export type IORoomConfig<
59
62
  TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>> = BaseIOAuthorize,
60
63
  TContext extends JsonObject = {},
61
64
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
62
- > = Partial<IORoomListeners<TPlatform>> & {
65
+ > = Partial<IORoomListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
63
66
  authorize?: TAuthorize;
64
67
  context: TContext & InferRoomContextType<TPlatform>;
65
68
  crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
@@ -84,20 +87,19 @@ export class IORoom<
84
87
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
85
88
  > implements IOLike<TAuthorize, TEvents>
86
89
  {
90
+ public readonly id: string;
91
+ public readonly _authorize: TAuthorize | null = null;
92
+
93
+ private _doc: Promise<AbstractCrdtDoc<any>>;
94
+ private _uninitialize: Promise<() => Promise<void>> | null = null;
95
+
87
96
  private readonly _context: TContext & InferRoomContextType<TPlatform>;
88
97
  private readonly _debug: boolean;
89
98
  private readonly _docFactory: AbstractCrdtDocFactory<any>;
99
+ private readonly _listeners: IORoomListeners<TPlatform, TAuthorize, TContext, TEvents>;
90
100
  private readonly _platform: TPlatform;
91
-
92
- private _doc: AbstractCrdtDoc<any>;
93
- private _listeners: IORoomListeners<TPlatform>;
94
- private _sessions = new Map<string, AbstractWebSocket>();
95
- private _uninitialize: (() => Promise<void>) | null = null;
96
-
97
- readonly _authorize: TAuthorize | null = null;
98
- readonly _router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
99
-
100
- readonly id: string;
101
+ private readonly _router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
102
+ private readonly _sessions = new Map<string, AbstractWebSocket>();
101
103
 
102
104
  public get _events() {
103
105
  return this._router._events;
@@ -120,28 +122,30 @@ export class IORoom<
120
122
  }) as BroadcastProxy<this>;
121
123
  }
122
124
 
125
+ private get _initialized(): Promise<boolean> {
126
+ return Promise.resolve(!!this._uninitialize?.then((result) => !!result));
127
+ }
128
+
123
129
  constructor(id: string, config: IORoomConfig<TPlatform, TAuthorize, TContext, TEvents>) {
124
- const { authorize, context, crdt = noop, debug, onDestroy, platform, router } = config;
130
+ const { authorize, context, crdt = noop, debug, onDestroy, onMessage, platform, router } = config;
131
+
132
+ this.id = id;
125
133
 
126
134
  this._context = context;
127
135
  this._debug = debug;
128
136
  this._docFactory = crdt.doc(() => ({}));
129
- this._doc = this._docFactory.getEmpty();
130
137
  this._router = router;
131
138
  this._platform = platform.initialize({ context });
132
139
 
133
- this.id = id;
134
-
135
140
  this._listeners = {
136
141
  onDestroy: (event) => onDestroy?.(event),
142
+ onMessage: (event) => onMessage?.(event),
137
143
  };
138
144
 
139
145
  if (authorize) this._authorize = authorize;
140
146
 
141
147
  const webSockets = this._platform.getWebSockets() as readonly InferPlatformWebSocketSource<TPlatform>[];
142
148
 
143
- if (!webSockets.length) return;
144
-
145
149
  webSockets.forEach((webSocket) => {
146
150
  const deserialized = this._platform.getSerializedState(webSocket);
147
151
  const sessionId = this._platform.getSessionId(webSocket);
@@ -149,8 +153,15 @@ export class IORoom<
149
153
  if (!deserialized) return;
150
154
  if (typeof sessionId !== "string") return;
151
155
 
152
- this._sessions.set(sessionId, this._platform.convertWebSocket(webSocket, { room: this.id }));
156
+ const pluvWs = this._platform.convertWebSocket(webSocket, { room: this.id });
157
+
158
+ this._sessions.set(sessionId, pluvWs);
153
159
  });
160
+
161
+ const { doc, uninitialize } = this._initialize();
162
+
163
+ this._doc = doc;
164
+ this._uninitialize = uninitialize;
154
165
  }
155
166
 
156
167
  public getSize(): number {
@@ -171,32 +182,32 @@ export class IORoom<
171
182
  }, 0);
172
183
  }
173
184
 
174
- public onClose(webSocket: WebSocketType<TPlatform>): (event: AbstractCloseEvent) => void {
185
+ public onClose(webSocket: WebSocketType<TPlatform>): (event: AbstractCloseEvent) => Promise<void> {
175
186
  this._ensureDetached();
176
187
 
177
188
  const wsSession = this._getAbstractWs(webSocket);
178
189
 
179
- if (!wsSession) return (): void => undefined;
190
+ if (!wsSession) return async () => undefined;
180
191
 
181
192
  return this._onClose(wsSession);
182
193
  }
183
194
 
184
- public onError(webSocket: WebSocketType<TPlatform>): (event: AbstractErrorEvent) => void {
195
+ public onError(webSocket: WebSocketType<TPlatform>): (event: AbstractErrorEvent) => Promise<void> {
185
196
  this._ensureDetached();
186
197
 
187
198
  const wsSession = this._getAbstractWs(webSocket);
188
199
 
189
- if (!wsSession) return (): void => undefined;
200
+ if (!wsSession) return async () => undefined;
190
201
 
191
202
  return this._onClose(wsSession);
192
203
  }
193
204
 
194
- public onMessage(webSocket: WebSocketType<TPlatform>): (event: AbstractMessageEvent) => void {
205
+ public onMessage(webSocket: WebSocketType<TPlatform>): (event: AbstractMessageEvent) => Promise<void> {
195
206
  this._ensureDetached();
196
207
 
197
208
  const wsSession = this._getAbstractWs(webSocket);
198
209
 
199
- if (!wsSession) return (): void => undefined;
210
+ if (!wsSession) return async () => undefined;
200
211
 
201
212
  return this._onMessage(wsSession);
202
213
  }
@@ -207,7 +218,15 @@ export class IORoom<
207
218
  ): Promise<void> {
208
219
  const { token } = options;
209
220
 
210
- if (!this._uninitialize) await this._initialize();
221
+ const sessionId = this._platform.getSessionId(webSocket);
222
+ const sessionExists = typeof sessionId === "string" && this._sessions.has(sessionId);
223
+
224
+ if (sessionExists) return;
225
+ if (!(await this._initialized)) {
226
+ this._initialize();
227
+
228
+ await this._initialized;
229
+ }
211
230
 
212
231
  const user = await this._getAuthorizedUser(token);
213
232
  const ioAuthorize = this._getIOAuthorize();
@@ -218,7 +237,7 @@ export class IORoom<
218
237
  !!user &&
219
238
  (await this._getSessions().then((sessions) => sessions.some((session) => session.user?.id === user.id)));
220
239
 
221
- const pluvWs = await this._platform.convertWebSocket(webSocket, { room: this.id });
240
+ const pluvWs = this._platform.convertWebSocket(webSocket, { room: this.id });
222
241
 
223
242
  this._logDebug(`${colors.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
224
243
 
@@ -323,14 +342,6 @@ export class IORoom<
323
342
  return sessions.find((pluvWs) => pluvWs.webSocket === webSocket) ?? null;
324
343
  }
325
344
 
326
- private async _getSession(webSocket: WebSocketType<TPlatform>): Promise<WebSocketSession<TAuthorize>> {
327
- const pluvWs = this._getAbstractWs(webSocket);
328
-
329
- if (!pluvWs) throw new Error("Session could not be found");
330
-
331
- return await pluvWs.getSession<TAuthorize>();
332
- }
333
-
334
345
  private async _getAuthorizedUser(token: Maybe<string>): Promise<InferIOAuthorizeUser<InferIOAuthorize<this>>> {
335
346
  const ioAuthorize = this._getIOAuthorize();
336
347
 
@@ -366,6 +377,15 @@ export class IORoom<
366
377
  }
367
378
  }
368
379
 
380
+ private async _getInitialDoc(): Promise<AbstractCrdtDoc<any>> {
381
+ const doc = this._docFactory.getEmpty();
382
+ const encodedState = await this._platform.persistance.getStorageState(this.id);
383
+
384
+ if (typeof encodedState === "string") doc.applyEncodedState({ update: encodedState });
385
+
386
+ return doc;
387
+ }
388
+
369
389
  private _getIOAuthorize() {
370
390
  if (typeof this._authorize === "function") {
371
391
  return this._authorize(this._context);
@@ -392,58 +412,97 @@ export class IORoom<
392
412
  return procedure.config.input ? procedure.config.input.parse(message.data) : message.data;
393
413
  }
394
414
 
415
+ private async _getSession(webSocket: WebSocketType<TPlatform>): Promise<WebSocketSession<TAuthorize>> {
416
+ const pluvWs = this._getAbstractWs(webSocket);
417
+
418
+ if (!pluvWs) throw new Error("Session could not be found");
419
+
420
+ return await pluvWs.getSession<TAuthorize>();
421
+ }
422
+
395
423
  private async _getSessions() {
396
424
  const promises = Array.from(this._sessions.values()).map(async (pluvWs) => await pluvWs.getSession());
397
425
 
398
426
  return await Promise.all(promises);
399
427
  }
400
428
 
401
- private async _initialize(): Promise<void> {
402
- this._logDebug(`${colors.blue(`Initializing room ${this.id}:`)}`);
429
+ private _initialize() {
430
+ const promise = (async () => {
431
+ this._logDebug(`${colors.blue(`Initializing room ${this.id}:`)}`);
403
432
 
404
- if (!!this._uninitialize) {
405
- await this._uninitialize();
433
+ if (!!this._uninitialize) {
434
+ const uninitialize = (await this._uninitialize).bind(this);
435
+ await uninitialize();
406
436
 
407
- this._uninitialize = null;
408
- }
437
+ this._uninitialize = null;
438
+ }
409
439
 
410
- const pubSubId = await this._platform.pubSub.subscribe(
411
- this.id,
412
- async ({ options = {}, ...message }): Promise<void> => {
413
- const sessionId = message.connectionId;
414
- const user = message.user;
440
+ const pubSubId = await this._platform.pubSub.subscribe(
441
+ this.id,
442
+ async ({ options = {}, ...message }): Promise<void> => {
443
+ const sender: SendMessageSender = {
444
+ sessionId: message.connectionId,
445
+ user: message.user,
446
+ };
447
+
448
+ switch (options.type) {
449
+ case "self": {
450
+ await this._sendSelfMessage(message, sender);
451
+
452
+ return;
453
+ }
454
+ case "sync": {
455
+ await this._sendSyncMessage(message, sender);
456
+
457
+ return;
458
+ }
459
+ case "broadcast":
460
+ default: {
461
+ const sessionIds = options.sessionIds;
462
+
463
+ await this._sendBroadcastMessage(message, sender, sessionIds);
464
+ }
465
+ }
466
+ },
467
+ );
415
468
 
416
- await this._sendMessage(message, { sessionId, user }, options);
417
- },
418
- );
469
+ const doc = await this._getInitialDoc();
470
+ const uninitialize = async () => {
471
+ this._platform.pubSub.unsubscribe(pubSubId);
419
472
 
420
- this._uninitialize = async () => {
421
- this._platform.pubSub.unsubscribe(pubSubId);
473
+ const doc = await this._doc;
474
+ const encodedState = doc.getEncodedState();
422
475
 
423
- const encodedState = this._doc.getEncodedState();
476
+ doc.destroy();
477
+ this._doc = Promise.resolve(this._docFactory.getEmpty());
424
478
 
425
- this._doc.destroy();
426
- this._doc = this._docFactory.getEmpty();
479
+ this._listeners.onDestroy({
480
+ context: this._context,
481
+ encodedState,
482
+ room: this.id,
483
+ });
427
484
 
428
- this._listeners.onDestroy({
429
- ...this._context,
430
- encodedState,
431
- room: this.id,
432
- });
485
+ this._uninitialize = null;
486
+ };
433
487
 
434
- this._uninitialize = null;
435
- };
488
+ return { doc, uninitialize };
489
+ })();
490
+
491
+ this._doc = promise.then((result) => result.doc);
492
+ this._uninitialize = promise.then((result) => result.uninitialize);
493
+
494
+ return { doc: this._doc, uninitialize: this._uninitialize };
436
495
  }
437
496
 
438
497
  private _logDebug(...data: any[]): void {
439
498
  this._debug && console.log(...data);
440
499
  }
441
500
 
442
- private _onClose(webSocket: AbstractWebSocket): () => void {
443
- return (): void => {
444
- if (!this._uninitialize) return;
501
+ private _onClose(webSocket: AbstractWebSocket): () => Promise<void> {
502
+ return async (): Promise<void> => {
503
+ if (!(await this._initialized)) return;
445
504
 
446
- webSocket.state.quit = true;
505
+ webSocket.state = { ...webSocket.state, quit: true };
447
506
 
448
507
  this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${webSocket.sessionId}`);
449
508
  this._sessions.delete(webSocket.sessionId);
@@ -464,13 +523,18 @@ export class IORoom<
464
523
 
465
524
  if (size) return;
466
525
 
467
- this._uninitialize?.();
526
+ if (this._uninitialize) {
527
+ const uninitialize = (await this._uninitialize).bind(this);
528
+ await uninitialize();
529
+ }
468
530
  });
469
531
  };
470
532
  }
471
533
 
472
- private _onMessage(webSocket: AbstractWebSocket): (event: AbstractMessageEvent) => void {
534
+ private _onMessage(webSocket: AbstractWebSocket): (event: AbstractMessageEvent) => Promise<void> {
473
535
  return async (event: AbstractMessageEvent): Promise<void> => {
536
+ if (!(await this._initialized)) return;
537
+
474
538
  const pluvWs = this._getAbstractWs(webSocket as WebSocketType<TPlatform>);
475
539
 
476
540
  if (!pluvWs) throw new Error("Could not get session");
@@ -480,16 +544,15 @@ export class IORoom<
480
544
  this._getSessions(),
481
545
  ]);
482
546
 
547
+ const doc = await this._doc;
483
548
  const baseContext: EventResolverContext<TPlatform, TAuthorize, TContext> = {
484
549
  context: this._context,
485
- doc: this._doc,
550
+ doc,
486
551
  room: this.id,
487
552
  session,
488
553
  sessions,
489
554
  };
490
555
 
491
- if (!this._uninitialize) return;
492
-
493
556
  if (pluvWs.state.quit) {
494
557
  pluvWs.close(1011, "WebSocket broken.");
495
558
 
@@ -525,7 +588,7 @@ export class IORoom<
525
588
  TContext & InferRoomContextType<TPlatform>
526
589
  > = { ...baseContext, context: { ...this._context } };
527
590
 
528
- Promise.all([
591
+ await Promise.all([
529
592
  procedure.config.broadcast?.(inputs, extendedContext),
530
593
  procedure.config.self?.(inputs, extendedContext),
531
594
  procedure.config.sync?.(inputs, baseContext),
@@ -533,25 +596,33 @@ export class IORoom<
533
596
  const sessionId = session.id;
534
597
  const user = session.user;
535
598
 
536
- if (broadcast) {
537
- Object.entries(broadcast).forEach(([type, data]: any) => {
538
- this._broadcast({
539
- message: { data, type },
540
- senderId: sessionId,
541
- });
542
- });
543
- }
599
+ const handleBroadcast = async () => {
600
+ if (!broadcast) return;
601
+
602
+ await Promise.all(
603
+ Object.entries(broadcast).map(async ([type, data]: any) => {
604
+ await this._broadcast({
605
+ message: { data, type },
606
+ senderId: sessionId,
607
+ });
608
+ }),
609
+ );
610
+ };
611
+
612
+ const handleSelf = async () => {
613
+ if (!self) return;
544
614
 
545
- if (self) {
546
615
  await Promise.all(
547
616
  Object.entries(self).map(async ([type, data]) => {
548
617
  await this._sendSelfMessage({ data, type }, { sessionId, user });
549
618
  }),
550
619
  );
551
- }
620
+ };
552
621
 
553
- if (sync) {
554
- this._platform.pubSub.publish(this.id, {
622
+ const handleSync = async () => {
623
+ if (!sync) return;
624
+
625
+ await this._platform.pubSub.publish(this.id, {
555
626
  connectionId: session.id,
556
627
  options: { type: "sync" },
557
628
  room: this.id,
@@ -564,7 +635,9 @@ export class IORoom<
564
635
  await this._sendSelfMessage({ data, type }, { sessionId, user });
565
636
  }),
566
637
  );
567
- }
638
+ };
639
+
640
+ await Promise.all([handleBroadcast(), handleSelf(), handleSync()]);
568
641
  });
569
642
  };
570
643
  }
@@ -583,29 +656,22 @@ export class IORoom<
583
656
  }
584
657
  }
585
658
 
586
- private async _sendMessage(
587
- message: EventMessage<string, any>,
588
- sender: SendMessageSender | null,
589
- options: SendMessageOptions = {},
590
- ): Promise<void> {
591
- switch (options.type) {
592
- case "self": {
593
- await this._sendSelfMessage(message, sender);
659
+ private async _sendMessage(pluvWs: AbstractWebSocket<any>, message: IOEventMessage<any>): Promise<void> {
660
+ const { data, type } = message;
594
661
 
595
- return;
596
- }
597
- case "sync": {
598
- await this._sendSyncMessage(message, sender);
662
+ if (!(await this._initialized)) return;
599
663
 
600
- return;
601
- }
602
- case "broadcast":
603
- default: {
604
- const sessionIds = options.sessionIds;
664
+ const doc = await this._doc;
605
665
 
606
- await this._sendBroadcastMessage(message, sender, sessionIds);
607
- }
608
- }
666
+ this._listeners.onMessage({
667
+ context: this._context,
668
+ encodedState: doc.getEncodedState(),
669
+ message: { data, type } as InferEventMessage<InferEventsOutput<TEvents>, keyof InferEventsOutput<TEvents>>,
670
+ room: this.id,
671
+ user: message.user,
672
+ });
673
+
674
+ await Promise.resolve(pluvWs.sendMessage(message));
609
675
  }
610
676
 
611
677
  private async _sendBroadcastMessage(
@@ -628,7 +694,7 @@ export class IORoom<
628
694
  const session = await pluvWs.getSession();
629
695
  const user = session.user ?? null;
630
696
 
631
- pluvWs.sendMessage({ connectionId, data, room, type, user });
697
+ this._sendMessage(pluvWs, { connectionId, data, room, type, user });
632
698
  });
633
699
 
634
700
  await Promise.all(promises);
@@ -650,7 +716,7 @@ export class IORoom<
650
716
  const session = await pluvWs.getSession();
651
717
  const user = session.user ?? null;
652
718
 
653
- pluvWs.sendMessage({
719
+ this._sendMessage(pluvWs, {
654
720
  connectionId: senderId,
655
721
  room: this.id,
656
722
  user,
@@ -664,9 +730,10 @@ export class IORoom<
664
730
  ): Promise<void> {
665
731
  if (!sender) return;
666
732
 
733
+ const doc = await this._doc;
667
734
  const context: EventResolverContext<TPlatform, TAuthorize, TContext> = {
668
735
  context: this._context,
669
- doc: this._doc,
736
+ doc,
670
737
  room: this.id,
671
738
  session: null,
672
739
  sessions: await this._getSessions(),