@pluv/io 0.3.8 → 0.4.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
@@ -1,22 +1,24 @@
1
1
  import type {
2
+ BaseIOAuthorize,
2
3
  EventRecord,
4
+ IOAuthorize,
5
+ IOLike,
3
6
  InferIOAuthorize,
4
7
  InferIOAuthorizeUser,
5
8
  InferIOInput,
6
- InferIOOutput,
7
- IOAuthorize,
8
- IOLike,
9
+ InferIOOutputBroadcast,
10
+ InferIOOutputSelf,
11
+ InferIOOutputSync,
9
12
  JsonObject,
10
- Spread,
11
- BaseIOAuthorize,
12
- MaybePromise,
13
13
  Maybe,
14
+ MaybePromise,
15
+ Spread,
14
16
  } from "@pluv/types";
15
17
  import colors from "kleur";
16
18
  import type { AbstractPlatform } from "./AbstractPlatform";
19
+ import { IORoom } from "./IORoom";
17
20
  import type { JWTEncodeParams } from "./authorize";
18
21
  import { authorize } from "./authorize";
19
- import { IORoom } from "./IORoom";
20
22
  import type { EventConfig, InferEventConfig } from "./types";
21
23
 
22
24
  export type InferIOContext<TIO extends PluvIO<any, any, any, any, any>> =
@@ -50,12 +52,20 @@ export type PluvIOConfig<
50
52
  TAuthorize extends IOAuthorize<any, any> | null = null,
51
53
  TContext extends JsonObject = {},
52
54
  TInput extends EventRecord<string, any> = {},
53
- TOutput extends EventRecord<string, any> = {}
55
+ TOutputBroadcast extends EventRecord<string, any> = {},
56
+ TOutputSelf extends EventRecord<string, any> = {},
57
+ TOutputSync extends EventRecord<string, any> = {}
54
58
  > = Partial<PluvIOListeners> & {
55
59
  authorize?: TAuthorize;
56
60
  context?: TContext;
57
61
  debug?: boolean;
58
- events?: InferEventConfig<TContext, TInput, TOutput>;
62
+ events?: InferEventConfig<
63
+ TContext,
64
+ TInput,
65
+ TOutputBroadcast,
66
+ TOutputSelf,
67
+ TOutputSync
68
+ >;
59
69
  initialStorage?: (room: string) => MaybePromise<Maybe<string>>;
60
70
  platform: TPlatform;
61
71
  };
@@ -69,8 +79,11 @@ export class PluvIO<
69
79
  TAuthorize extends IOAuthorize<any, any> = BaseIOAuthorize,
70
80
  TContext extends JsonObject = {},
71
81
  TInput extends EventRecord<string, any> = {},
72
- TOutput extends EventRecord<string, any> = {}
73
- > implements IOLike<TAuthorize, TInput, TOutput>
82
+ TOutputBroadcast extends EventRecord<string, any> = {},
83
+ TOutputSelf extends EventRecord<string, any> = {},
84
+ TOutputSync extends EventRecord<string, any> = {}
85
+ > implements
86
+ IOLike<TAuthorize, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>
74
87
  {
75
88
  private _context: TContext = {} as TContext;
76
89
  private _initialStorage:
@@ -79,50 +92,112 @@ export class PluvIO<
79
92
  private _listeners: PluvIOListeners;
80
93
  private _rooms = new Map<
81
94
  string,
82
- IORoom<TPlatform, TAuthorize, TContext, TInput, TOutput>
95
+ IORoom<
96
+ TPlatform,
97
+ TAuthorize,
98
+ TContext,
99
+ TInput,
100
+ TOutputBroadcast,
101
+ TOutputSelf,
102
+ TOutputSync
103
+ >
83
104
  >();
84
105
 
85
106
  readonly _authorize: TAuthorize | null = null;
86
107
  readonly _debug: boolean;
87
- readonly _events: InferEventConfig<TContext, TInput, TOutput> = {
88
- $PING: {
89
- resolver: (data, { session }) => {
90
- if (!session) return {};
108
+ readonly _events: InferEventConfig<
109
+ TContext,
110
+ TInput,
111
+ TOutputBroadcast,
112
+ TOutputSelf,
113
+ TOutputSync
114
+ > = {
115
+ $GET_OTHERS: {
116
+ resolver: {
117
+ sync: (data, { room, session, sessions }) => {
118
+ const others = Array.from(sessions.entries())
119
+ .filter(([, wsSession]) => wsSession.id !== session?.id)
120
+ .reduce<{
121
+ [connectionId: string]: {
122
+ connectionId: string;
123
+ room: string | null;
124
+ user: InferIOAuthorizeUser<TAuthorize>;
125
+ };
126
+ }>(
127
+ (acc, [connectionId, { presence, user }]) => ({
128
+ ...acc,
129
+ [connectionId]: {
130
+ connectionId,
131
+ presence,
132
+ room,
133
+ user: user as InferIOAuthorizeUser<TAuthorize>,
134
+ },
135
+ }),
136
+ {}
137
+ );
138
+
139
+ return { $OTHERS_RECEIVED: { others } };
140
+ },
141
+ },
142
+ },
143
+ $INITIALIZE_SESSION: {
144
+ resolver: {
145
+ broadcast: ({ presence }, { session }) => {
146
+ if (!session) return {};
147
+
148
+ session.presence = presence;
149
+
150
+ return {
151
+ $USER_JOINED: {
152
+ connectionId: session.id,
153
+ user: session.user,
154
+ presence,
155
+ },
156
+ };
157
+ },
158
+ self: async ({ update }, { doc, room }) => {
159
+ const oldState =
160
+ await this._platform.persistance.getStorageState(room);
161
+
162
+ const updates: readonly Maybe<string>[] = [
163
+ oldState ? null : await this._initialStorage?.(room),
164
+ update,
165
+ ];
166
+
167
+ if (updates.some((_update) => !!_update)) {
168
+ const state = updates
169
+ .reduce((_doc, _up) => _doc.applyUpdate(_up), doc)
170
+ .encodeStateAsUpdate();
171
+
172
+ await this._platform.persistance.setStorageState(
173
+ room,
174
+ state
175
+ );
91
176
 
92
- const currentTime = new Date().getTime();
177
+ this._listeners.onStorageUpdated(room, state);
178
+ }
93
179
 
94
- session.timers.ping = currentTime;
180
+ const state =
181
+ (await this._platform.persistance.getStorageState(
182
+ room
183
+ )) ?? doc.encodeStateAsUpdate();
95
184
 
96
- return { $PONG: {} };
185
+ return { $STORAGE_RECEIVED: { state } };
186
+ },
97
187
  },
98
- options: { type: "self" },
99
188
  },
100
- $GET_OTHERS: {
101
- resolver: (data, { room, session, sessions }) => {
102
- const others = Array.from(sessions.entries())
103
- .filter(([, wsSession]) => wsSession.id !== session?.id)
104
- .reduce<{
105
- [connectionId: string]: {
106
- connectionId: string;
107
- room: string | null;
108
- user: InferIOAuthorizeUser<TAuthorize>;
109
- };
110
- }>(
111
- (acc, [connectionId, { presence, user }]) => ({
112
- ...acc,
113
- [connectionId]: {
114
- connectionId,
115
- presence,
116
- room,
117
- user: user as InferIOAuthorizeUser<TAuthorize>,
118
- },
119
- }),
120
- {}
121
- );
189
+ $PING: {
190
+ resolver: {
191
+ self: (data, { session }) => {
192
+ if (!session) return {};
193
+
194
+ const currentTime = new Date().getTime();
195
+
196
+ session.timers.ping = currentTime;
122
197
 
123
- return { $OTHERS_RECEIVED: { others } };
198
+ return { $PONG: {} };
199
+ },
124
200
  },
125
- options: { type: "sync" },
126
201
  },
127
202
  $UPDATE_PRESENCE: {
128
203
  resolver: ({ presence }, { session }) => {
@@ -158,11 +233,25 @@ export class PluvIO<
158
233
  return { $STORAGE_UPDATED: { state } };
159
234
  },
160
235
  },
161
- } as InferEventConfig<TContext, TInput, TOutput>;
236
+ } as InferEventConfig<
237
+ TContext,
238
+ TInput,
239
+ TOutputBroadcast,
240
+ TOutputSelf,
241
+ TOutputSync
242
+ >;
162
243
  readonly _platform: TPlatform;
163
244
 
164
245
  constructor(
165
- options: PluvIOConfig<TPlatform, TAuthorize, TContext, TInput, TOutput>
246
+ options: PluvIOConfig<
247
+ TPlatform,
248
+ TAuthorize,
249
+ TContext,
250
+ TInput,
251
+ TOutputBroadcast,
252
+ TOutputSelf,
253
+ TOutputSync
254
+ >
166
255
  ) {
167
256
  const {
168
257
  authorize,
@@ -211,16 +300,26 @@ export class PluvIO<
211
300
  public event<
212
301
  TEvent extends string,
213
302
  TData extends JsonObject = {},
214
- TResult extends EventRecord<string, any> = {}
303
+ TResultBroadcast extends EventRecord<string, any> = {},
304
+ TResultSelf extends EventRecord<string, any> = {},
305
+ TResultSync extends EventRecord<string, any> = {}
215
306
  >(
216
307
  event: TEvent,
217
- config: EventConfig<TContext, TData, TResult>
308
+ config: EventConfig<
309
+ TContext,
310
+ TData,
311
+ TResultBroadcast,
312
+ TResultSelf,
313
+ TResultSync
314
+ >
218
315
  ): PluvIO<
219
316
  TPlatform,
220
317
  TAuthorize,
221
318
  TContext,
222
319
  Spread<[TInput, EventRecord<TEvent, TData>]>,
223
- Spread<[TOutput, TResult]>
320
+ Spread<[TOutputBroadcast, TResultBroadcast]>,
321
+ Spread<[TOutputSelf, TResultSelf]>,
322
+ Spread<[TOutputSync, TResultSync]>
224
323
  > {
225
324
  if (event.startsWith("$")) {
226
325
  throw new Error('Event name must not start with "$".');
@@ -231,7 +330,11 @@ export class PluvIO<
231
330
  } as InferEventConfig<
232
331
  TContext,
233
332
  EventRecord<TEvent, TData>,
234
- TResult extends EventRecord<string, any> ? TResult : {}
333
+ TResultBroadcast extends EventRecord<string, any>
334
+ ? TResultBroadcast
335
+ : {},
336
+ TResultSelf extends EventRecord<string, any> ? TResultSelf : {},
337
+ TResultSync extends EventRecord<string, any> ? TResultSync : {}
235
338
  >;
236
339
 
237
340
  /**
@@ -259,7 +362,15 @@ export class PluvIO<
259
362
  public getRoom(
260
363
  room: string,
261
364
  options: GetRoomOptions = {}
262
- ): IORoom<TPlatform, TAuthorize, TContext, TInput, TOutput> {
365
+ ): IORoom<
366
+ TPlatform,
367
+ TAuthorize,
368
+ TContext,
369
+ TInput,
370
+ TOutputBroadcast,
371
+ TOutputSelf,
372
+ TOutputSync
373
+ > {
263
374
  const { debug } = options;
264
375
 
265
376
  this._purgeEmptyRooms();
@@ -278,7 +389,9 @@ export class PluvIO<
278
389
  TAuthorize,
279
390
  TContext,
280
391
  TInput,
281
- TOutput
392
+ TOutputBroadcast,
393
+ TOutputSelf,
394
+ TOutputSync
282
395
  >(room, {
283
396
  authorize: this._authorize ?? undefined,
284
397
  context: this._context,
@@ -291,6 +404,7 @@ export class PluvIO<
291
404
  );
292
405
 
293
406
  this._rooms.delete(room);
407
+ this._listeners.onRoomDeleted(room, encodedState);
294
408
 
295
409
  if (this._debug) {
296
410
  const rooms = Array.from(this._rooms.keys());
@@ -300,12 +414,6 @@ export class PluvIO<
300
414
  `${colors.blue("Rooms available:")} ${rooms.join(", ")}`
301
415
  );
302
416
  }
303
-
304
- this._platform.persistance.getSize(room).then((size) => {
305
- if (size) return;
306
-
307
- this._listeners.onRoomDeleted(room, encodedState);
308
- });
309
417
  },
310
418
  platform,
311
419
  });
@@ -332,7 +440,9 @@ export class PluvIO<
332
440
  InferIOAuthorize<TIO1>,
333
441
  InferIOContext<TIO1>,
334
442
  Spread<[InferIOInput<TIO1>, InferIOInput<TIO2>]>,
335
- Spread<[InferIOOutput<TIO1>, InferIOOutput<TIO2>]>
443
+ Spread<[InferIOOutputBroadcast<TIO1>, InferIOOutputBroadcast<TIO2>]>,
444
+ Spread<[InferIOOutputSelf<TIO1>, InferIOOutputSelf<TIO2>]>,
445
+ Spread<[InferIOOutputSync<TIO1>, InferIOOutputSync<TIO2>]>
336
446
  > {
337
447
  const authorize = (first._authorize ??
338
448
  undefined) as InferIOAuthorize<TIO1>;
package/src/types.ts CHANGED
@@ -25,11 +25,20 @@ declare global {
25
25
  export interface EventConfig<
26
26
  TContext extends JsonObject = {},
27
27
  TData extends JsonObject = {},
28
- TResult extends EventRecord<string, any> = {}
28
+ TResultBroadcast extends EventRecord<string, any> = {},
29
+ TResultSelf extends EventRecord<string, any> = {},
30
+ TResultSync extends EventRecord<string, any> = {}
29
31
  > {
30
32
  input?: InputZodLike<TData>;
31
- options?: SendMessageOptions;
32
- resolver: EventResolver<TContext, TData, TResult>;
33
+ resolver:
34
+ | EventResolver<TContext, TData, TResultBroadcast>
35
+ | EventResolverObject<
36
+ TContext,
37
+ TData,
38
+ TResultBroadcast,
39
+ TResultSelf,
40
+ TResultSync
41
+ >;
33
42
  }
34
43
 
35
44
  export type EventConfigType = "broadcast" | "self" | "sync";
@@ -37,11 +46,11 @@ export type EventConfigType = "broadcast" | "self" | "sync";
37
46
  export type EventResolver<
38
47
  TContext extends JsonObject = {},
39
48
  TData extends JsonObject = {},
40
- TResult extends EventRecord<string, any> = {}
49
+ TResultBroadcast extends EventRecord<string, any> = {}
41
50
  > = (
42
51
  data: TData,
43
52
  context: EventResolverContext<TContext>
44
- ) => MaybePromise<TResult | void>;
53
+ ) => MaybePromise<TResultBroadcast | void>;
45
54
 
46
55
  export interface EventResolverContext<TContext extends JsonObject = {}> {
47
56
  context: TContext;
@@ -63,13 +72,35 @@ export interface EventResolverContext<TContext extends JsonObject = {}> {
63
72
  sessions: Map<string, WebSocketSession>;
64
73
  }
65
74
 
75
+ export type EventResolverObject<
76
+ TContext extends JsonObject = {},
77
+ TData extends JsonObject = {},
78
+ TResultBroadcast extends EventRecord<string, any> = {},
79
+ TResultSelf extends EventRecord<string, any> = {},
80
+ TResultSync extends EventRecord<string, any> = {}
81
+ > = {
82
+ broadcast?: EventResolver<TContext, TData, TResultBroadcast>;
83
+ self?: EventResolver<TContext, TData, TResultSelf>;
84
+ sync?: EventResolver<TContext, TData, TResultSync>;
85
+ };
86
+
66
87
  export type InferEventConfig<
67
88
  TContext extends JsonObject = {},
68
89
  TInput extends EventRecord<string, any> = {},
69
- TOutput extends EventRecord<string, any> = {}
90
+ TOutputBroadcast extends EventRecord<string, any> = {},
91
+ TOutputSelf extends EventRecord<string, any> = {},
92
+ TOutputSync extends EventRecord<string, any> = {}
70
93
  > = {
71
94
  [P in keyof TInput]: P extends string
72
- ? Id<EventConfig<TContext, TInput[P], TOutput>>
95
+ ? Id<
96
+ EventConfig<
97
+ TContext,
98
+ TInput[P],
99
+ TOutputBroadcast,
100
+ TOutputSelf,
101
+ TOutputSync
102
+ >
103
+ >
73
104
  : never;
74
105
  };
75
106