@pluv/io 0.44.2 → 1.0.1

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/PluvServer.ts CHANGED
@@ -1,8 +1,19 @@
1
1
  import type { AbstractCrdtDocFactory } from "@pluv/crdt";
2
2
  import { noop } from "@pluv/crdt";
3
- import type { IOLike, Id, InferIOAuthorize, InferIOAuthorizeUser, JsonObject, Maybe } from "@pluv/types";
3
+ import type {
4
+ IOLike,
5
+ Id,
6
+ InferIOAuthorize,
7
+ InferIOAuthorizeUser,
8
+ JsonObject,
9
+ Maybe,
10
+ } from "@pluv/types";
4
11
  import colors from "kleur";
5
- import type { AbstractPlatform, InferInitContextType, InferRoomContextType } from "./AbstractPlatform";
12
+ import type {
13
+ AbstractPlatform,
14
+ InferInitContextType,
15
+ InferRoomContextType,
16
+ } from "./AbstractPlatform";
6
17
  import type { IORoomListeners } from "./IORoom";
7
18
  import { IORoom } from "./IORoom";
8
19
  import type { PluvIO } from "./PluvIO";
@@ -29,7 +40,11 @@ export type InferIORoom<TServer extends PluvServer<any, any, any, any>> =
29
40
 
30
41
  export type PluvServerConfig<
31
42
  TPlatform extends AbstractPlatform<any, any> = AbstractPlatform<any, any>,
32
- TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null = any,
43
+ TAuthorize extends PluvIOAuthorize<
44
+ TPlatform,
45
+ any,
46
+ InferInitContextType<TPlatform>
47
+ > | null = any,
33
48
  TContext extends Record<string, any> = {},
34
49
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
35
50
  > = Partial<PluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
@@ -60,11 +75,20 @@ export type CreateRoomOptions<
60
75
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
61
76
  > = keyof InferRoomContextType<TPlatform> extends never
62
77
  ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | []
63
- : [Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>];
78
+ : [
79
+ Id<
80
+ BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> &
81
+ InferRoomContextType<TPlatform>
82
+ >,
83
+ ];
64
84
 
65
85
  export class PluvServer<
66
86
  TPlatform extends AbstractPlatform<any, any> = AbstractPlatform<any, any>,
67
- TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null = any,
87
+ TAuthorize extends PluvIOAuthorize<
88
+ TPlatform,
89
+ any,
90
+ InferInitContextType<TPlatform>
91
+ > | null = any,
68
92
  TContext extends Record<string, any> = {},
69
93
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
70
94
  > implements IOLike<TAuthorize, TEvents>
@@ -126,17 +150,56 @@ export class PluvServer<
126
150
  })
127
151
  .self(async (data, { context, doc, room, session }) => {
128
152
  const oldState = await this._platform.persistence.getStorageState(room);
153
+ /**
154
+ * !HACK
155
+ * @description This is the frontend's initialStorage. We only want to
156
+ * apply this if the server's storage is empty (i.e. no initial storage
157
+ * has been applied)
158
+ */
129
159
  const update = (data as any)?.update as Maybe<string>;
130
160
 
131
- const updates: readonly Maybe<string>[] = [
132
- oldState ? null : await this._getInitialStorage?.({ context, room }),
133
- update,
134
- ];
161
+ if (!oldState) {
162
+ const loadedState = await this._getInitialStorage?.({ context, room });
135
163
 
136
- if (updates.some((_update) => !!_update)) {
137
- const encodedState = doc.batchApplyEncodedState({ updates }).getEncodedState();
164
+ if (!!loadedState) {
165
+ doc.applyEncodedState({ update: loadedState }).getEncodedState();
166
+ }
167
+ }
138
168
 
139
- await this._platform.persistence.setStorageState(room, encodedState);
169
+ /**
170
+ * @description Storage was already initialized. Don't overwrite the current
171
+ * storage state with the incoming initial storage. Return what the current state
172
+ * is without changes.
173
+ * @date May 7, 2025
174
+ */
175
+ if (!doc.isEmpty()) {
176
+ const encodedState = doc.getEncodedState();
177
+
178
+ return { $storageReceived: { state: encodedState } };
179
+ }
180
+
181
+ /**
182
+ * @description Storage was never initialized, and there is an incoming
183
+ * initialStorage from the client. Apply the initialStorage and persist the state
184
+ * as an update.
185
+ * @date May 7, 2025
186
+ */
187
+ if (!!update) {
188
+ const encodedState = doc.applyEncodedState({ update }).getEncodedState();
189
+ const storageSize = new TextEncoder().encode(encodedState).length;
190
+
191
+ if (
192
+ !!this._limits.storageMaxSize &&
193
+ storageSize > this._limits.storageMaxSize
194
+ ) {
195
+ throw new Error("Storage has exceeded the size limit");
196
+ }
197
+
198
+ await this._platform.persistence
199
+ .setStorageState(room, encodedState)
200
+ .catch((error) => {
201
+ this._logDebug(error);
202
+ });
140
203
 
141
204
  this._getListeners().onStorageUpdated({
142
205
  context,
@@ -147,12 +210,7 @@ export class PluvServer<
147
210
  });
148
211
  }
149
212
 
150
- const encodedState = (await this._platform.persistence.getStorageState(room)) ?? doc.getEncodedState();
151
- const storageSize = new TextEncoder().encode(encodedState).length;
152
-
153
- if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
154
- throw new Error("Storage has exceeded the size limit");
155
- }
213
+ const encodedState = doc.getEncodedState();
156
214
 
157
215
  return { $storageReceived: { state: encodedState } };
158
216
  }),
@@ -222,7 +280,10 @@ export class PluvServer<
222
280
  return { $storageUpdated: { state: encodedState } };
223
281
  }),
224
282
  });
225
- private readonly _context: PluvContext<TPlatform, TContext> = {} as PluvContext<TPlatform, TContext>;
283
+ private readonly _context: PluvContext<TPlatform, TContext> = {} as PluvContext<
284
+ TPlatform,
285
+ TContext
286
+ >;
226
287
  private readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
227
288
  private readonly _debug: boolean;
228
289
  private readonly _getInitialStorage: GetInitialStorageFn<TContext> | null = null;
@@ -233,7 +294,8 @@ export class PluvServer<
233
294
 
234
295
  public get fetch(): (...args: any[]) => Promise<any> {
235
296
  return ((...args: any[]): Promise<any> => {
236
- if (!this._platform._fetch) throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
297
+ if (!this._platform._fetch)
298
+ throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
237
299
 
238
300
  return this._platform._fetch(...args);
239
301
  }) as NonNullable<typeof this._platform._fetch>;
@@ -275,8 +337,13 @@ export class PluvServer<
275
337
  router = new PluvRouter<TPlatform, TAuthorize, TContext, TEvents>({} as TEvents),
276
338
  } = options;
277
339
 
278
- const { onRoomDeleted, onRoomMessage, onStorageUpdated, onUserConnected, onUserDisconnected } =
279
- options as Partial<BasePluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>>;
340
+ const {
341
+ onRoomDeleted,
342
+ onRoomMessage,
343
+ onStorageUpdated,
344
+ onUserConnected,
345
+ onUserDisconnected,
346
+ } = options as Partial<BasePluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>>;
280
347
 
281
348
  platform.validateConfig(options);
282
349
 
@@ -285,12 +352,9 @@ export class PluvServer<
285
352
  this._io = io;
286
353
  this._limits = limits;
287
354
  this._platform = platform;
288
- this._router = (router ? PluvRouter.merge(this._baseRouter, router) : this._baseRouter) as PluvRouter<
289
- TPlatform,
290
- TAuthorize,
291
- TContext,
292
- TEvents
293
- >;
355
+ this._router = (
356
+ router ? PluvRouter.merge(this._baseRouter, router) : this._baseRouter
357
+ ) as PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
294
358
 
295
359
  if (authorize) this._authorize = authorize;
296
360
  if (context) this._context = context;
@@ -309,21 +373,19 @@ export class PluvServer<
309
373
  room: string,
310
374
  ...options: CreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>
311
375
  ): IORoom<TPlatform, TAuthorize, TContext, TEvents> {
312
- const { _meta, debug, onDestroy, onMessage, ...platformRoomContext } = (options[0] ?? {}) as CreateRoomOptions<
313
- TPlatform,
314
- TAuthorize,
315
- TContext,
316
- TEvents
317
- >[0] & { _meta?: any };
376
+ const { _meta, debug, onDestroy, onMessage, ...platformRoomContext } = (options[0] ??
377
+ {}) as CreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>[0] & { _meta?: any };
318
378
 
319
379
  if (this._platform._config.handleMode !== "io") {
320
380
  throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
321
381
  }
322
382
 
323
- if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room)) throw new Error("Unsupported room name");
383
+ if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
384
+ throw new Error("Unsupported room name");
324
385
 
325
386
  const roomContext = platformRoomContext as InferRoomContextType<TPlatform>;
326
- const context: TContext = typeof this._context === "function" ? this._context(roomContext) : this._context;
387
+ const context: TContext =
388
+ typeof this._context === "function" ? this._context(roomContext) : this._context;
327
389
 
328
390
  const newRoom = new IORoom<TPlatform, TAuthorize, TContext, TEvents>(room, {
329
391
  ...(!!_meta ? { _meta } : {}),
package/src/PubSub.ts CHANGED
@@ -20,7 +20,10 @@ export class PubSub extends AbstractPubSub {
20
20
  Promise.resolve(subject.next(payload));
21
21
  }
22
22
 
23
- public subscribe(roomName: string, onMessage: (message: IOPubSubEventMessage<any>) => void): Promise<number> {
23
+ public subscribe(
24
+ roomName: string,
25
+ onMessage: (message: IOPubSubEventMessage<any>) => void,
26
+ ): Promise<number> {
24
27
  const id = this._currentSubscriptionId++;
25
28
  const refs = this._subscriptionsRefs.get(roomName) ?? [];
26
29
 
package/src/authorize.ts CHANGED
@@ -15,7 +15,10 @@ export interface JWT<TUser extends BaseUser> {
15
15
  user: TUser;
16
16
  }
17
17
 
18
- export type JWTEncodeParams<TUser extends BaseUser | null, TPlatform extends AbstractPlatform<any, any>> = {
18
+ export type JWTEncodeParams<
19
+ TUser extends BaseUser | null,
20
+ TPlatform extends AbstractPlatform<any, any>,
21
+ > = {
19
22
  maxAge?: number;
20
23
  room: string;
21
24
  user: TUser extends BaseUser ? TUser : "Error: Cannot create token without authorization!";
package/src/createIO.ts CHANGED
@@ -22,10 +22,18 @@ export const createIO = <
22
22
  TUser extends BaseUser | null = null,
23
23
  >(
24
24
  params: CreateIOParams<TPlatform, TContext, TUser>,
25
- ): PluvIO<TPlatform, PluvIOAuthorize<TPlatform, TUser, InferInitContextType<TPlatform>>, TContext> => {
25
+ ): PluvIO<
26
+ TPlatform,
27
+ PluvIOAuthorize<TPlatform, TUser, InferInitContextType<TPlatform>>,
28
+ TContext
29
+ > => {
26
30
  const { authorize, context, crdt, debug, limits, platform } = params;
27
31
 
28
- return new PluvIO<TPlatform, PluvIOAuthorize<TPlatform, TUser, InferInitContextType<TPlatform>>, TContext>({
32
+ return new PluvIO<
33
+ TPlatform,
34
+ PluvIOAuthorize<TPlatform, TUser, InferInitContextType<TPlatform>>,
35
+ TContext
36
+ >({
29
37
  authorize,
30
38
  context,
31
39
  crdt,
package/src/types.ts CHANGED
@@ -57,7 +57,9 @@ export interface EventResolverContext<
57
57
  garbageCollect: () => Promise<void>;
58
58
  presence: JsonObject | null;
59
59
  room: string;
60
- session: TKind extends "sync" ? WebSocketSession<TAuthorize> | null : WebSocketSession<TAuthorize>;
60
+ session: TKind extends "sync"
61
+ ? WebSocketSession<TAuthorize> | null
62
+ : WebSocketSession<TAuthorize>;
61
63
  sessions: readonly WebSocketSession<TAuthorize>[];
62
64
  time: number;
63
65
  }
@@ -79,11 +81,12 @@ export interface WebSocketSerializedState {
79
81
  timers: WebSocketSessionTimers;
80
82
  }
81
83
 
82
- export type WebSocketSession<TAuthorize extends IOAuthorize<any, any> | null = null> = WebSocketSerializedState & {
83
- id: string;
84
- user: InferIOAuthorizeUser<TAuthorize>;
85
- webSocket: AbstractWebSocket;
86
- };
84
+ export type WebSocketSession<TAuthorize extends IOAuthorize<any, any> | null = null> =
85
+ WebSocketSerializedState & {
86
+ id: string;
87
+ user: InferIOAuthorizeUser<TAuthorize>;
88
+ webSocket: AbstractWebSocket;
89
+ };
87
90
 
88
91
  export type MergeEventRecords<
89
92
  TEventRecords extends EventRecord<string, any>[],
@@ -143,7 +146,9 @@ export type PluvIOAuthorize<
143
146
  TContext extends Record<string, unknown> = {},
144
147
  > =
145
148
  | (TUser extends BaseUser ? ResolvedPluvIOAuthorize<TPlatform, TUser> : null)
146
- | ((context: TContext) => TUser extends BaseUser ? ResolvedPluvIOAuthorize<TPlatform, TUser> : null);
149
+ | ((
150
+ context: TContext,
151
+ ) => TUser extends BaseUser ? ResolvedPluvIOAuthorize<TPlatform, TUser> : null);
147
152
 
148
153
  export interface PluvIOLimits {
149
154
  /**
@@ -184,7 +189,10 @@ export type PluvIOListeners<
184
189
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
185
190
  > = UndefinedProps<
186
191
  BasePluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>,
187
- Exclude<keyof BasePluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>, InferPlatformListeners<TPlatform>>
192
+ Exclude<
193
+ keyof BasePluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>,
194
+ InferPlatformListeners<TPlatform>
195
+ >
188
196
  >;
189
197
 
190
198
  export type PluvIORouter<
@@ -200,14 +208,20 @@ export type PluvIORouter<
200
208
  export type InferPlatformConfig<TPlatform extends AbstractPlatform<any, any, any, any>> =
201
209
  TPlatform extends AbstractPlatform<any, any, any, infer IConfig> ? IConfig : never;
202
210
 
203
- export type InferPlatformAuthorizeProperties<TPlatform extends AbstractPlatform<any, any, any, any>> = keyof {
204
- [P in keyof PlatformConfig["authorize"] as InferPlatformConfig<TPlatform>["authorize"][P] extends true | undefined
211
+ export type InferPlatformAuthorizeProperties<
212
+ TPlatform extends AbstractPlatform<any, any, any, any>,
213
+ > = keyof {
214
+ [P in keyof PlatformConfig["authorize"] as InferPlatformConfig<TPlatform>["authorize"][P] extends
215
+ | true
216
+ | undefined
205
217
  ? P
206
218
  : never]: true;
207
219
  };
208
220
 
209
221
  export type InferPlatformListeners<TPlatform extends AbstractPlatform<any, any, any, any>> = keyof {
210
- [P in keyof PlatformConfig["listeners"] as InferPlatformConfig<TPlatform>["listeners"][P] extends true | undefined
222
+ [P in keyof PlatformConfig["listeners"] as InferPlatformConfig<TPlatform>["listeners"][P] extends
223
+ | true
224
+ | undefined
211
225
  ? P
212
226
  : never]: true;
213
227
  };
@@ -1,4 +1,7 @@
1
- export const pickBy = <T extends Record<string, any>>(obj: T, by: (value: T[keyof T]) => boolean): Partial<T> => {
1
+ export const pickBy = <T extends Record<string, any>>(
2
+ obj: T,
3
+ by: (value: T[keyof T]) => boolean,
4
+ ): Partial<T> => {
2
5
  const result: Partial<T> = {};
3
6
  const keys: (keyof T)[] = Object.keys(obj);
4
7
 
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '0.44.2'
2
+ export const version = '1.0.1'