@pluv/io 1.0.0 → 1.0.2

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.
@@ -1,9 +1,9 @@
1
1
 
2
- > @pluv/io@1.0.0 build /home/runner/work/pluv/pluv/packages/io
2
+ > @pluv/io@1.0.2 build /home/runner/work/pluv/pluv/packages/io
3
3
  > pnpm run version && tsup src/index.ts --format esm,cjs --dts
4
4
 
5
5
 
6
- > @pluv/io@1.0.0 version /home/runner/work/pluv/pluv/packages/io
6
+ > @pluv/io@1.0.2 version /home/runner/work/pluv/pluv/packages/io
7
7
  > genversion ./src/version.generated.ts --esm
8
8
 
9
9
  CLI Building entry: src/index.ts
@@ -12,11 +12,11 @@
12
12
  CLI Target: es6
13
13
  ESM Build start
14
14
  CJS Build start
15
- ESM dist/index.mjs 50.73 KB
16
- ESM ⚡️ Build success in 132ms
17
- CJS dist/index.js 52.90 KB
18
- CJS ⚡️ Build success in 133ms
15
+ ESM dist/index.mjs 51.32 KB
16
+ ESM ⚡️ Build success in 143ms
17
+ CJS dist/index.js 53.49 KB
18
+ CJS ⚡️ Build success in 146ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 2976ms
21
- DTS dist/index.d.mts 32.19 KB
22
- DTS dist/index.d.ts 32.19 KB
20
+ DTS ⚡️ Build success in 3112ms
21
+ DTS dist/index.d.mts 32.20 KB
22
+ DTS dist/index.d.ts 32.20 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @pluv/io
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - @pluv/crdt@1.0.2
8
+ - @pluv/types@1.0.2
9
+
10
+ ## 1.0.1
11
+
12
+ ### Patch Changes
13
+
14
+ - c583be9: Fixed storage race condition that occurred when 2 users joined a room near simultaneously that would cause users to have mismatched storage states from the server (and thereby preventing updates from properly registering) until the user reconnected.
15
+ - @pluv/crdt@1.0.1
16
+ - @pluv/types@1.0.1
17
+
3
18
  ## 1.0.0
4
19
 
5
20
  ### Major Changes
package/README.md CHANGED
@@ -19,6 +19,11 @@
19
19
  </a>
20
20
  <img src="https://badgen.net/badge/-/TypeScript?icon=typescript&label&labelColor=blue&color=555555" alt="TypeScript" />
21
21
  </p>
22
+
23
+ <div align="center">
24
+ <a href="https://www.producthunt.com/posts/pluv-io?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-pluv&#0045;io" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=961147&theme=light&t=1746462368894" alt="pluv&#0046;io - Open&#0032;source&#0032;multiplayer&#0032;APIs&#0044;&#0032;powered&#0032;by&#0032;TypeScript | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
25
+ </div>
26
+
22
27
  <h4 align="center">💕 Inspired by <a href="https://trpc.io">trpc</a> 💕 <a href="https://docs.yjs.dev/">yjs</a> 💕 <br />and <a href="https://workers.cloudflare.com/">Cloudflare Workers</a> 💕</h4>
23
28
 
24
29
  ## Intro
package/dist/index.d.mts CHANGED
@@ -445,7 +445,9 @@ type PluvServerConfig<TPlatform extends AbstractPlatform<any, any> = AbstractPla
445
445
  type BaseCreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = Partial<IORoomListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
446
446
  debug?: boolean;
447
447
  };
448
- type CreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = keyof InferRoomContextType<TPlatform> extends never ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | [] : [Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>];
448
+ type CreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = keyof InferRoomContextType<TPlatform> extends never ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | [] : [
449
+ Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>
450
+ ];
449
451
  declare class PluvServer<TPlatform extends AbstractPlatform<any, any> = AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null = any, TContext extends Record<string, any> = {}, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {}> implements IOLike<TAuthorize, TEvents> {
450
452
  readonly version: string;
451
453
  private readonly _authorize;
package/dist/index.d.ts CHANGED
@@ -445,7 +445,9 @@ type PluvServerConfig<TPlatform extends AbstractPlatform<any, any> = AbstractPla
445
445
  type BaseCreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = Partial<IORoomListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
446
446
  debug?: boolean;
447
447
  };
448
- type CreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = keyof InferRoomContextType<TPlatform> extends never ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | [] : [Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>];
448
+ type CreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = keyof InferRoomContextType<TPlatform> extends never ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | [] : [
449
+ Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>
450
+ ];
449
451
  declare class PluvServer<TPlatform extends AbstractPlatform<any, any> = AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null = any, TContext extends Record<string, any> = {}, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {}> implements IOLike<TAuthorize, TEvents> {
450
452
  readonly version: string;
451
453
  private readonly _authorize;
package/dist/index.js CHANGED
@@ -343,7 +343,9 @@ var PluvProcedure = class _PluvProcedure {
343
343
  }));
344
344
  }
345
345
  input(input) {
346
- return new _PluvProcedure({ input });
346
+ return new _PluvProcedure({
347
+ input
348
+ });
347
349
  }
348
350
  self(resolver) {
349
351
  const { self } = this.config;
@@ -380,7 +382,10 @@ var PluvRouter = class _PluvRouter {
380
382
  this._defs = { events };
381
383
  }
382
384
  static merge(...routers) {
383
- const events = Object.assign(/* @__PURE__ */ Object.create(null), ...routers.map((router) => router._defs.events));
385
+ const events = Object.assign(
386
+ /* @__PURE__ */ Object.create(null),
387
+ ...routers.map((router) => router._defs.events)
388
+ );
384
389
  return new _PluvRouter(events);
385
390
  }
386
391
  _isValidEventName(name) {
@@ -583,7 +588,9 @@ var IORoom = class {
583
588
  }));
584
589
  this._addUserSession(user.id, pluvWs.sessionId);
585
590
  }
586
- this._logDebug(`${import_kleur.default.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
591
+ this._logDebug(
592
+ `${import_kleur.default.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`
593
+ );
587
594
  yield this._platform.acceptWebSocket(pluvWs);
588
595
  this._sessions.set(pluvWs.sessionId, pluvWs);
589
596
  yield this._platform.persistence.addUser(this.id, pluvWs.sessionId, user != null ? user : {});
@@ -596,7 +603,9 @@ var IORoom = class {
596
603
  }
597
604
  yield this._emitRegistered(pluvWs);
598
605
  const size = this.getSize();
599
- this._logDebug(`${import_kleur.default.blue(`Registered connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
606
+ this._logDebug(
607
+ `${import_kleur.default.blue(`Registered connection for room ${this.id}:`)} ${pluvWs.sessionId}`
608
+ );
600
609
  this._logDebug(`${import_kleur.default.blue(`Room ${this.id} size:`)} ${size}`);
601
610
  });
602
611
  }
@@ -627,7 +636,9 @@ var IORoom = class {
627
636
  const closeWebSocket = (webSocket) => __async(this, null, function* () {
628
637
  webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
629
638
  const sessionId = webSocket.sessionId;
630
- this._logDebug(`${import_kleur.default.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`);
639
+ this._logDebug(
640
+ `${import_kleur.default.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`
641
+ );
631
642
  this._sessions.delete(sessionId);
632
643
  yield this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
633
644
  yield this._broadcast({
@@ -651,7 +662,9 @@ var IORoom = class {
651
662
  } catch (error) {
652
663
  console.error(error);
653
664
  }
654
- this._logDebug(`${import_kleur.default.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
665
+ this._logDebug(
666
+ `${import_kleur.default.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`
667
+ );
655
668
  });
656
669
  const promises = webSockets.map((webSocket) => __async(this, null, function* () {
657
670
  yield closeWebSocket(webSocket).catch(() => null);
@@ -668,9 +681,9 @@ var IORoom = class {
668
681
  _emitQuitters() {
669
682
  return __async(this, null, function* () {
670
683
  const currentTime = (/* @__PURE__ */ new Date()).getTime();
671
- const quitters = Array.from(this._sessions.values()).filter(
672
- (pluvWs) => pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS
673
- );
684
+ const quitters = Array.from(this._sessions.values()).filter((pluvWs) => {
685
+ return pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS;
686
+ });
674
687
  this._closeWebSockets(quitters);
675
688
  });
676
689
  }
@@ -680,20 +693,21 @@ var IORoom = class {
680
693
  const sessionId = session.id;
681
694
  const presence = session.presence;
682
695
  const user = session.user;
696
+ const doc = yield this._doc;
697
+ const encodedState = doc.isEmpty() ? null : doc.getEncodedState();
683
698
  yield this._sendSelfMessage(
684
699
  {
685
700
  type: "$registered",
686
701
  data: {
687
702
  presence,
688
703
  sessionId,
704
+ state: encodedState,
689
705
  timers: { presence: session.timers.presence }
690
706
  }
691
707
  },
692
708
  { sessionId, user }
693
709
  );
694
710
  try {
695
- const doc = yield this._doc;
696
- const encodedState = doc.getEncodedState();
697
711
  yield Promise.resolve(
698
712
  this._listeners.onUserConnected({
699
713
  context: this._context,
@@ -740,7 +754,8 @@ var IORoom = class {
740
754
  const ioAuthorize = this._getIOAuthorize(options);
741
755
  if (!ioAuthorize) return null;
742
756
  if (!token) return null;
743
- if (!ioAuthorize.secret) throw new Error("`authorize` was specified without a valid secret");
757
+ if (!ioAuthorize.secret)
758
+ throw new Error("`authorize` was specified without a valid secret");
744
759
  const payload = yield authorize({
745
760
  platform: this._platform,
746
761
  secret: ioAuthorize.secret
@@ -1141,7 +1156,7 @@ var pickBy = (obj, by) => {
1141
1156
  };
1142
1157
 
1143
1158
  // src/version.generated.ts
1144
- var version = "1.0.0";
1159
+ var version = "1.0.2";
1145
1160
 
1146
1161
  // src/PluvServer.ts
1147
1162
  var PluvServer = class {
@@ -1184,16 +1199,28 @@ var PluvServer = class {
1184
1199
  }
1185
1200
  };
1186
1201
  }).self((_0, _1) => __async(this, [_0, _1], function* (data, { context, doc, room, session }) {
1187
- var _a, _b;
1202
+ var _a;
1188
1203
  const oldState = yield this._platform.persistence.getStorageState(room);
1189
1204
  const update = data == null ? void 0 : data.update;
1190
- const updates = [
1191
- oldState ? null : yield (_a = this._getInitialStorage) == null ? void 0 : _a.call(this, { context, room }),
1192
- update
1193
- ];
1194
- if (updates.some((_update) => !!_update)) {
1195
- const encodedState2 = doc.batchApplyEncodedState({ updates }).getEncodedState();
1196
- yield this._platform.persistence.setStorageState(room, encodedState2);
1205
+ if (!oldState) {
1206
+ const loadedState = yield (_a = this._getInitialStorage) == null ? void 0 : _a.call(this, { context, room });
1207
+ if (!!loadedState) {
1208
+ doc.applyEncodedState({ update: loadedState }).getEncodedState();
1209
+ }
1210
+ }
1211
+ if (!doc.isEmpty()) {
1212
+ const encodedState2 = doc.getEncodedState();
1213
+ return { $storageReceived: { changeKind: "unchanged", state: encodedState2 } };
1214
+ }
1215
+ if (!!update) {
1216
+ const encodedState2 = doc.applyEncodedState({ update }).getEncodedState();
1217
+ const storageSize = new TextEncoder().encode(encodedState2).length;
1218
+ if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
1219
+ throw new Error("Storage has exceeded the size limit");
1220
+ }
1221
+ yield this._platform.persistence.setStorageState(room, encodedState2).catch((error) => {
1222
+ this._logDebug(error);
1223
+ });
1197
1224
  this._getListeners().onStorageUpdated({
1198
1225
  context,
1199
1226
  encodedState: encodedState2,
@@ -1201,13 +1228,10 @@ var PluvServer = class {
1201
1228
  user: session == null ? void 0 : session.user,
1202
1229
  webSocket: session == null ? void 0 : session.webSocket.webSocket
1203
1230
  });
1231
+ return { $storageReceived: { changeKind: "initialized", state: encodedState2 } };
1204
1232
  }
1205
- const encodedState = (_b = yield this._platform.persistence.getStorageState(room)) != null ? _b : doc.getEncodedState();
1206
- const storageSize = new TextEncoder().encode(encodedState).length;
1207
- if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
1208
- throw new Error("Storage has exceeded the size limit");
1209
- }
1210
- return { $storageReceived: { state: encodedState } };
1233
+ const encodedState = doc.getEncodedState();
1234
+ return { $storageReceived: { changeKind: "empty", state: encodedState } };
1211
1235
  })),
1212
1236
  $ping: this._procedure.self((data, { session }) => {
1213
1237
  if (!session) return {};
@@ -1274,7 +1298,13 @@ var PluvServer = class {
1274
1298
  platform,
1275
1299
  router = new PluvRouter({})
1276
1300
  } = options;
1277
- const { onRoomDeleted, onRoomMessage, onStorageUpdated, onUserConnected, onUserDisconnected } = options;
1301
+ const {
1302
+ onRoomDeleted,
1303
+ onRoomMessage,
1304
+ onStorageUpdated,
1305
+ onUserConnected,
1306
+ onUserDisconnected
1307
+ } = options;
1278
1308
  platform.validateConfig(options);
1279
1309
  this._crdt = crdt;
1280
1310
  this._debug = debug;
@@ -1295,7 +1325,8 @@ var PluvServer = class {
1295
1325
  }
1296
1326
  get fetch() {
1297
1327
  return (...args) => {
1298
- if (!this._platform._fetch) throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
1328
+ if (!this._platform._fetch)
1329
+ throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
1299
1330
  return this._platform._fetch(...args);
1300
1331
  };
1301
1332
  }
@@ -1321,7 +1352,8 @@ var PluvServer = class {
1321
1352
  if (this._platform._config.handleMode !== "io") {
1322
1353
  throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
1323
1354
  }
1324
- if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room)) throw new Error("Unsupported room name");
1355
+ if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
1356
+ throw new Error("Unsupported room name");
1325
1357
  const roomContext = platformRoomContext;
1326
1358
  const context = typeof this._context === "function" ? this._context(roomContext) : this._context;
1327
1359
  const newRoom = new IORoom(room, __spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), {
@@ -1391,7 +1423,8 @@ var PluvIO = class {
1391
1423
  createToken(params) {
1392
1424
  return __async(this, null, function* () {
1393
1425
  var _a;
1394
- if (!this._authorize) throw new Error("IO does not specify authorize during initialization.");
1426
+ if (!this._authorize)
1427
+ throw new Error("IO does not specify authorize during initialization.");
1395
1428
  const ioAuthorize = this._getIOAuthorize(params);
1396
1429
  const user = params.user;
1397
1430
  const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
@@ -1409,7 +1442,9 @@ var PluvIO = class {
1409
1442
  if (this._platform._createToken) return yield this._platform._createToken(params);
1410
1443
  const secret = (_a = ioAuthorize == null ? void 0 : ioAuthorize.secret) != null ? _a : null;
1411
1444
  if (!secret) throw new Error("`authorize` was specified without a valid secret");
1412
- return yield authorize({ platform: this._platform, secret }).encode(params);
1445
+ return yield authorize({ platform: this._platform, secret }).encode(
1446
+ params
1447
+ );
1413
1448
  });
1414
1449
  }
1415
1450
  mergeRouters(...routers) {
package/dist/index.mjs CHANGED
@@ -301,7 +301,9 @@ var PluvProcedure = class _PluvProcedure {
301
301
  }));
302
302
  }
303
303
  input(input) {
304
- return new _PluvProcedure({ input });
304
+ return new _PluvProcedure({
305
+ input
306
+ });
305
307
  }
306
308
  self(resolver) {
307
309
  const { self } = this.config;
@@ -338,7 +340,10 @@ var PluvRouter = class _PluvRouter {
338
340
  this._defs = { events };
339
341
  }
340
342
  static merge(...routers) {
341
- const events = Object.assign(/* @__PURE__ */ Object.create(null), ...routers.map((router) => router._defs.events));
343
+ const events = Object.assign(
344
+ /* @__PURE__ */ Object.create(null),
345
+ ...routers.map((router) => router._defs.events)
346
+ );
342
347
  return new _PluvRouter(events);
343
348
  }
344
349
  _isValidEventName(name) {
@@ -541,7 +546,9 @@ var IORoom = class {
541
546
  }));
542
547
  this._addUserSession(user.id, pluvWs.sessionId);
543
548
  }
544
- this._logDebug(`${colors.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
549
+ this._logDebug(
550
+ `${colors.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`
551
+ );
545
552
  yield this._platform.acceptWebSocket(pluvWs);
546
553
  this._sessions.set(pluvWs.sessionId, pluvWs);
547
554
  yield this._platform.persistence.addUser(this.id, pluvWs.sessionId, user != null ? user : {});
@@ -554,7 +561,9 @@ var IORoom = class {
554
561
  }
555
562
  yield this._emitRegistered(pluvWs);
556
563
  const size = this.getSize();
557
- this._logDebug(`${colors.blue(`Registered connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
564
+ this._logDebug(
565
+ `${colors.blue(`Registered connection for room ${this.id}:`)} ${pluvWs.sessionId}`
566
+ );
558
567
  this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
559
568
  });
560
569
  }
@@ -585,7 +594,9 @@ var IORoom = class {
585
594
  const closeWebSocket = (webSocket) => __async(this, null, function* () {
586
595
  webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
587
596
  const sessionId = webSocket.sessionId;
588
- this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`);
597
+ this._logDebug(
598
+ `${colors.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`
599
+ );
589
600
  this._sessions.delete(sessionId);
590
601
  yield this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
591
602
  yield this._broadcast({
@@ -609,7 +620,9 @@ var IORoom = class {
609
620
  } catch (error) {
610
621
  console.error(error);
611
622
  }
612
- this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
623
+ this._logDebug(
624
+ `${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`
625
+ );
613
626
  });
614
627
  const promises = webSockets.map((webSocket) => __async(this, null, function* () {
615
628
  yield closeWebSocket(webSocket).catch(() => null);
@@ -626,9 +639,9 @@ var IORoom = class {
626
639
  _emitQuitters() {
627
640
  return __async(this, null, function* () {
628
641
  const currentTime = (/* @__PURE__ */ new Date()).getTime();
629
- const quitters = Array.from(this._sessions.values()).filter(
630
- (pluvWs) => pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS
631
- );
642
+ const quitters = Array.from(this._sessions.values()).filter((pluvWs) => {
643
+ return pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS;
644
+ });
632
645
  this._closeWebSockets(quitters);
633
646
  });
634
647
  }
@@ -638,20 +651,21 @@ var IORoom = class {
638
651
  const sessionId = session.id;
639
652
  const presence = session.presence;
640
653
  const user = session.user;
654
+ const doc = yield this._doc;
655
+ const encodedState = doc.isEmpty() ? null : doc.getEncodedState();
641
656
  yield this._sendSelfMessage(
642
657
  {
643
658
  type: "$registered",
644
659
  data: {
645
660
  presence,
646
661
  sessionId,
662
+ state: encodedState,
647
663
  timers: { presence: session.timers.presence }
648
664
  }
649
665
  },
650
666
  { sessionId, user }
651
667
  );
652
668
  try {
653
- const doc = yield this._doc;
654
- const encodedState = doc.getEncodedState();
655
669
  yield Promise.resolve(
656
670
  this._listeners.onUserConnected({
657
671
  context: this._context,
@@ -698,7 +712,8 @@ var IORoom = class {
698
712
  const ioAuthorize = this._getIOAuthorize(options);
699
713
  if (!ioAuthorize) return null;
700
714
  if (!token) return null;
701
- if (!ioAuthorize.secret) throw new Error("`authorize` was specified without a valid secret");
715
+ if (!ioAuthorize.secret)
716
+ throw new Error("`authorize` was specified without a valid secret");
702
717
  const payload = yield authorize({
703
718
  platform: this._platform,
704
719
  secret: ioAuthorize.secret
@@ -1099,7 +1114,7 @@ var pickBy = (obj, by) => {
1099
1114
  };
1100
1115
 
1101
1116
  // src/version.generated.ts
1102
- var version = "1.0.0";
1117
+ var version = "1.0.2";
1103
1118
 
1104
1119
  // src/PluvServer.ts
1105
1120
  var PluvServer = class {
@@ -1142,16 +1157,28 @@ var PluvServer = class {
1142
1157
  }
1143
1158
  };
1144
1159
  }).self((_0, _1) => __async(this, [_0, _1], function* (data, { context, doc, room, session }) {
1145
- var _a, _b;
1160
+ var _a;
1146
1161
  const oldState = yield this._platform.persistence.getStorageState(room);
1147
1162
  const update = data == null ? void 0 : data.update;
1148
- const updates = [
1149
- oldState ? null : yield (_a = this._getInitialStorage) == null ? void 0 : _a.call(this, { context, room }),
1150
- update
1151
- ];
1152
- if (updates.some((_update) => !!_update)) {
1153
- const encodedState2 = doc.batchApplyEncodedState({ updates }).getEncodedState();
1154
- yield this._platform.persistence.setStorageState(room, encodedState2);
1163
+ if (!oldState) {
1164
+ const loadedState = yield (_a = this._getInitialStorage) == null ? void 0 : _a.call(this, { context, room });
1165
+ if (!!loadedState) {
1166
+ doc.applyEncodedState({ update: loadedState }).getEncodedState();
1167
+ }
1168
+ }
1169
+ if (!doc.isEmpty()) {
1170
+ const encodedState2 = doc.getEncodedState();
1171
+ return { $storageReceived: { changeKind: "unchanged", state: encodedState2 } };
1172
+ }
1173
+ if (!!update) {
1174
+ const encodedState2 = doc.applyEncodedState({ update }).getEncodedState();
1175
+ const storageSize = new TextEncoder().encode(encodedState2).length;
1176
+ if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
1177
+ throw new Error("Storage has exceeded the size limit");
1178
+ }
1179
+ yield this._platform.persistence.setStorageState(room, encodedState2).catch((error) => {
1180
+ this._logDebug(error);
1181
+ });
1155
1182
  this._getListeners().onStorageUpdated({
1156
1183
  context,
1157
1184
  encodedState: encodedState2,
@@ -1159,13 +1186,10 @@ var PluvServer = class {
1159
1186
  user: session == null ? void 0 : session.user,
1160
1187
  webSocket: session == null ? void 0 : session.webSocket.webSocket
1161
1188
  });
1189
+ return { $storageReceived: { changeKind: "initialized", state: encodedState2 } };
1162
1190
  }
1163
- const encodedState = (_b = yield this._platform.persistence.getStorageState(room)) != null ? _b : doc.getEncodedState();
1164
- const storageSize = new TextEncoder().encode(encodedState).length;
1165
- if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
1166
- throw new Error("Storage has exceeded the size limit");
1167
- }
1168
- return { $storageReceived: { state: encodedState } };
1191
+ const encodedState = doc.getEncodedState();
1192
+ return { $storageReceived: { changeKind: "empty", state: encodedState } };
1169
1193
  })),
1170
1194
  $ping: this._procedure.self((data, { session }) => {
1171
1195
  if (!session) return {};
@@ -1232,7 +1256,13 @@ var PluvServer = class {
1232
1256
  platform,
1233
1257
  router = new PluvRouter({})
1234
1258
  } = options;
1235
- const { onRoomDeleted, onRoomMessage, onStorageUpdated, onUserConnected, onUserDisconnected } = options;
1259
+ const {
1260
+ onRoomDeleted,
1261
+ onRoomMessage,
1262
+ onStorageUpdated,
1263
+ onUserConnected,
1264
+ onUserDisconnected
1265
+ } = options;
1236
1266
  platform.validateConfig(options);
1237
1267
  this._crdt = crdt;
1238
1268
  this._debug = debug;
@@ -1253,7 +1283,8 @@ var PluvServer = class {
1253
1283
  }
1254
1284
  get fetch() {
1255
1285
  return (...args) => {
1256
- if (!this._platform._fetch) throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
1286
+ if (!this._platform._fetch)
1287
+ throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
1257
1288
  return this._platform._fetch(...args);
1258
1289
  };
1259
1290
  }
@@ -1279,7 +1310,8 @@ var PluvServer = class {
1279
1310
  if (this._platform._config.handleMode !== "io") {
1280
1311
  throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
1281
1312
  }
1282
- if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room)) throw new Error("Unsupported room name");
1313
+ if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
1314
+ throw new Error("Unsupported room name");
1283
1315
  const roomContext = platformRoomContext;
1284
1316
  const context = typeof this._context === "function" ? this._context(roomContext) : this._context;
1285
1317
  const newRoom = new IORoom(room, __spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), {
@@ -1349,7 +1381,8 @@ var PluvIO = class {
1349
1381
  createToken(params) {
1350
1382
  return __async(this, null, function* () {
1351
1383
  var _a;
1352
- if (!this._authorize) throw new Error("IO does not specify authorize during initialization.");
1384
+ if (!this._authorize)
1385
+ throw new Error("IO does not specify authorize during initialization.");
1353
1386
  const ioAuthorize = this._getIOAuthorize(params);
1354
1387
  const user = params.user;
1355
1388
  const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
@@ -1367,7 +1400,9 @@ var PluvIO = class {
1367
1400
  if (this._platform._createToken) return yield this._platform._createToken(params);
1368
1401
  const secret = (_a = ioAuthorize == null ? void 0 : ioAuthorize.secret) != null ? _a : null;
1369
1402
  if (!secret) throw new Error("`authorize` was specified without a valid secret");
1370
- return yield authorize({ platform: this._platform, secret }).encode(params);
1403
+ return yield authorize({ platform: this._platform, secret }).encode(
1404
+ params
1405
+ );
1371
1406
  });
1372
1407
  }
1373
1408
  mergeRouters(...routers) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/io",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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.10.0",
22
22
  "kleur": "^4.1.5",
23
23
  "wonka": "^6.3.5",
24
- "@pluv/crdt": "^1.0.0",
25
- "@pluv/types": "^1.0.0"
24
+ "@pluv/crdt": "^1.0.2",
25
+ "@pluv/types": "^1.0.2"
26
26
  },
27
27
  "devDependencies": {
28
28
  "eslint": "^9.25.1",
29
29
  "genversion": "^3.2.0",
30
30
  "tsup": "^8.4.0",
31
31
  "typescript": "^5.8.3",
32
- "@pluv/tsconfig": "^1.0.0",
33
- "eslint-config-pluv": "^1.0.0"
32
+ "@pluv/tsconfig": "^1.0.2",
33
+ "eslint-config-pluv": "^1.0.2"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "pnpm run version && tsup src/index.ts --format esm,cjs --dts",
@@ -1,7 +1,11 @@
1
1
  import type { JsonObject } from "@pluv/types";
2
2
 
3
3
  export abstract class AbstractPersistence {
4
- public abstract addUser(room: string, connectionId: string, user: JsonObject | null): Promise<void>;
4
+ public abstract addUser(
5
+ room: string,
6
+ connectionId: string,
7
+ user: JsonObject | null,
8
+ ): Promise<void>;
5
9
 
6
10
  public abstract deleteStorageState(room: string): Promise<void>;
7
11
 
@@ -13,7 +17,9 @@ export abstract class AbstractPersistence {
13
17
 
14
18
  public abstract getUser(room: string, connectionId: string): Promise<JsonObject | null>;
15
19
 
16
- public abstract getUsers(room: string): Promise<Map<[connectionId: string][0], JsonObject | null>>;
20
+ public abstract getUsers(
21
+ room: string,
22
+ ): Promise<Map<[connectionId: string][0], JsonObject | null>>;
17
23
 
18
24
  public abstract getUsersSize(room: string): Promise<number>;
19
25
 
@@ -10,7 +10,9 @@ export type InferPlatformWebSocketType<TPlatform extends AbstractPlatform> =
10
10
  TPlatform extends AbstractPlatform<infer IAbstractWebSocket> ? IAbstractWebSocket : never;
11
11
 
12
12
  export type InferPlatformWebSocketSource<TPlatform> =
13
- TPlatform extends AbstractPlatform<infer IAbstractWebSocket> ? InferWebSocketSource<IAbstractWebSocket> : never;
13
+ TPlatform extends AbstractPlatform<infer IAbstractWebSocket>
14
+ ? InferWebSocketSource<IAbstractWebSocket>
15
+ : never;
14
16
 
15
17
  export type InferInitContextType<TPlatform extends AbstractPlatform> =
16
18
  TPlatform extends AbstractPlatform<any, infer IInitContext> ? IInitContext : never;
@@ -66,7 +68,9 @@ export abstract class AbstractPlatform<
66
68
 
67
69
  public abstract getLastPing(webSocket: AbstractWebSocket<any, any>): number | null;
68
70
 
69
- public abstract getSerializedState(webSocket: InferWebSocketSource<TWebSocket>): WebSocketSerializedState | null;
71
+ public abstract getSerializedState(
72
+ webSocket: InferWebSocketSource<TWebSocket>,
73
+ ): WebSocketSerializedState | null;
70
74
 
71
75
  public abstract getSessionId(webSocket: InferWebSocketSource<TWebSocket>): string | null;
72
76
 
@@ -54,7 +54,10 @@ export interface AbstractWebSocketConfig {
54
54
  room: string;
55
55
  }
56
56
 
57
- export abstract class AbstractWebSocket<TWebSocket = any, TAuthorize extends IOAuthorize<any, any> | null = null> {
57
+ export abstract class AbstractWebSocket<
58
+ TWebSocket = any,
59
+ TAuthorize extends IOAuthorize<any, any> | null = null,
60
+ > {
58
61
  /** The connection is not yet open. */
59
62
  public readonly CONNECTING = 0;
60
63
  /** The connection is open and ready to communicate. */
@@ -96,7 +99,10 @@ export abstract class AbstractWebSocket<TWebSocket = any, TAuthorize extends IOA
96
99
  * result.
97
100
  * @date August 28, 2022
98
101
  */
99
- public abstract addEventListener<TType extends EventType>(type: TType, handler: AbstractListener<TType>): void;
102
+ public abstract addEventListener<TType extends EventType>(
103
+ type: TType,
104
+ handler: AbstractListener<TType>,
105
+ ): void;
100
106
 
101
107
  public abstract close(code?: number | undefined, reason?: string | undefined): void;
102
108
 
@@ -120,7 +126,9 @@ export abstract class AbstractWebSocket<TWebSocket = any, TAuthorize extends IOA
120
126
  });
121
127
  }
122
128
 
123
- public sendMessage<TMessage extends IOEventMessage<any> = IOEventMessage<any>>(data: TMessage): void {
129
+ public sendMessage<TMessage extends IOEventMessage<any> = IOEventMessage<any>>(
130
+ data: TMessage,
131
+ ): void {
124
132
  return this.send(JSON.stringify(data));
125
133
  }
126
134
  }