@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.
@@ -1,9 +1,9 @@
1
1
 
2
- > @pluv/io@0.44.2 build /home/runner/work/pluv/pluv/packages/io
2
+ > @pluv/io@1.0.1 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@0.44.2 version /home/runner/work/pluv/pluv/packages/io
6
+ > @pluv/io@1.0.1 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
- CJS dist/index.js 52.90 KB
16
- CJS ⚡️ Build success in 124ms
17
- ESM dist/index.mjs 50.73 KB
18
- ESM ⚡️ Build success in 125ms
15
+ CJS dist/index.js 53.34 KB
16
+ CJS ⚡️ Build success in 119ms
17
+ ESM dist/index.mjs 51.16 KB
18
+ ESM ⚡️ Build success in 132ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 3111ms
21
- DTS dist/index.d.mts 32.19 KB
22
- DTS dist/index.d.ts 32.19 KB
20
+ DTS ⚡️ Build success in 3284ms
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,29 @@
1
1
  # @pluv/io
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 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.
8
+ - @pluv/crdt@1.0.1
9
+ - @pluv/types@1.0.1
10
+
11
+ ## 1.0.0
12
+
13
+ ### Major Changes
14
+
15
+ - af94706: pluv.io is now stable and production ready!
16
+
17
+ With this v1 release, pluv.io will now follow [semantic versioning](https://semver.org/) with more comprehensive release notes for future changes to the library.
18
+
19
+ Checkout the [full documentation here](https://pluv.io/docs/introduction) to get started today!
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [af94706]
24
+ - @pluv/crdt@1.0.0
25
+ - @pluv/types@1.0.0
26
+
3
27
  ## 0.44.2
4
28
 
5
29
  ### Patch Changes
package/README.md CHANGED
@@ -1,16 +1,11 @@
1
- > **Disclaimer:**
2
- > This package is currently in preview and may have breaking changes between versions. Please wait for a `v1.0.0` stable release before using this in production.
3
-
4
- <h1 align="center">
5
- <br />
6
- <img src="https://github.com/pluv-io/pluv/blob/master/assets/pluv-icon-192x192.png?raw=true" alt="Pluv.IO" width="180" style="border-radius:16px" />
7
- <br />
8
- <a href="https://pluv.io/docs/introduction">Pluv.IO (preview)</a>
9
- <br />
10
- </h1>
1
+ <div align="center">
2
+ <a href="https://pluv.io/docs/introduction">
3
+ <img src="https://github.com/pluv-io/pluv/blob/master/assets/pluv-icon-192x192.png?raw=true" alt="Pluv.IO" width="156" style="border-radius:16px" />
4
+ </a>
5
+ </div>
11
6
 
12
- <h3 align="center">Multi-platform, E2E type-safe realtime packages</h3>
13
- <h4 align="center">💕 Inspired by <a href="https://trpc.io">trpc</a> 💕 <a href="https://docs.yjs.dev/">yjs</a> 💕 and <a href="https://developers.cloudflare.com/">Cloudflare</a> 💕 </h4>
7
+ <h1 align="center">pluv.io</h1>
8
+ <h3 align="center">TypeSafe Primitives for a Realtime Web</h3>
14
9
 
15
10
  <p align="center">
16
11
  <a href="https://www.npmjs.com/package/@pluv/io">
@@ -25,92 +20,167 @@
25
20
  <img src="https://badgen.net/badge/-/TypeScript?icon=typescript&label&labelColor=blue&color=555555" alt="TypeScript" />
26
21
  </p>
27
22
 
28
- <p align="center">
29
- <a href="#intro">Intro</a> •
30
- <a href="#usage">Usage</a> •
31
- <a href="#related">Related</a> •
32
- <a href="#credits">Credits</a> •
33
- <a href="#license">License</a>
34
- </p>
35
-
36
- ## Intro
37
-
38
- pluv.io allows you to more easily build real-time collaborative experiences with a fully end-to-end type-safe api and the ecosystem of existing CRDT implementations such as **[yjs](https://docs.yjs.dev/)**.
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>
39
26
 
40
- **👉 See full documentation on [pluv.io](https://pluv.io/docs/introduction). 👈**
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>
41
28
 
42
- ### Features
29
+ ## Intro
43
30
 
44
- - ✅ Automatic type safety
31
+ **[pluv.io](https://pluv.io) allows you to more easily build realtime multiplayer experiences with a fully typesafe API and powerful abstractions as primitives, so that you can focus on building for your end users.**
32
+
33
+ Self-host on [Cloudflare Workers](https://pluv.io/docs/quickstart/cloudflare-workers) or [Node.js](https://pluv.io/docs/quickstart/nodejs); or [get started on the pluv.io network](https://pluv.io/signup).
34
+
35
+ ## Preview
36
+
37
+ Create your pluv.io backend
38
+
39
+ ```ts
40
+ // backend
41
+ const io = createIO(platformNode({ crdt: yjs }));
42
+
43
+ export const ioServer = io.server({
44
+ router: io.router({
45
+ sendGreeting: io.procedure
46
+ .input(z.object({ message: z.string() }))
47
+ .broadcast(({ message }) => ({
48
+ receiveGreeting: { message }
49
+ }))
50
+ })
51
+ });
52
+ ```
53
+
54
+ Create your frontend client with your backend types
55
+
56
+ ```ts
57
+ // frontend
58
+ const types = infer((i) => ({ io: i<typeof ioServer> }));
59
+ const io = createClient({
60
+ types,
61
+ initialStorage: yjs.doc(() => ({
62
+ messages: yjs.array<string>(),
63
+ })),
64
+ presence: z.object({
65
+ selectionId: z.string().nullable()
66
+ })
67
+ });
68
+
69
+ const {
70
+ event,
71
+ useBroadcast,
72
+ useMyPresence,
73
+ useOthers,
74
+ useStorage
75
+ } = createBundle(io);
76
+ ```
77
+
78
+ Use powerful primitives to build realtime features
79
+
80
+ ```tsx
81
+ // react
82
+ event.receiveGreeting.useEvent(({ data }) => { /* ... */});
83
+ // ^? const data: { message: string }
84
+
85
+ const broadcast = useBroadcast();
86
+
87
+ broadcast.sendGreeting({ message: "hello world" });
88
+ // ^? const sendGreeting: (data: { message: string }) => void
89
+
90
+ const [mySelection, update] = useMyPresence((presence) => {
91
+ // ^? const mySelection: string | null
92
+ return presence.selectionId;
93
+ });
94
+
95
+ const others = useOthers((others) => {
96
+ // ^? const others = string[]
97
+ return others.map((other) => other.presence.selectionId);
98
+ });
99
+
100
+ const [
101
+ messages,
102
+ // ^? const messages: string[] | null
103
+ sharedType
104
+ // ^? YArray<string> | null
105
+ ] = useStorage("messages");
106
+ ```
107
+
108
+ ## Documentation
109
+
110
+ The full documentation is available at [pluv.io](https://pluv.io/docs/introduction).
111
+
112
+ ## Features
113
+
114
+ - ✅ Automatic type-safety
45
115
  - ✅ Basic events
46
116
  - ✅ Rooms
47
117
  - ✅ Authentication
48
118
  - ✅ Awareness + Presence
49
119
  - ✅ [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type)
50
- - ✅ [Yjs](https://docs.yjs.dev/)
51
- - ✅ **Shared Types**
52
- - ✅ [Map](https://docs.yjs.dev/api/shared-types/y.map)
53
- - ✅ [Array](https://docs.yjs.dev/api/shared-types/y.array)
54
- - ✅ [Text](https://docs.yjs.dev/api/shared-types/y.text)
55
- - ✅ [XmlFragment](https://docs.yjs.dev/api/shared-types/y.xmlfragment)
56
- - ✅ [XmlElement](https://docs.yjs.dev/api/shared-types/y.xmlelement)
57
- - ✅ [XmlText](https://docs.yjs.dev/api/shared-types/y.xmltext)
58
- - ✅ [Loro](https://loro.dev/)
59
- - ✅ **Containers**
60
- - ✅ List
61
- - ✅ Map
62
- - ✅ Text
63
- - ⬜ Tree
120
+ - ✅ [Yjs](https://docs.yjs.dev/)
121
+ - ⬜ [Provider](https://github.com/yjs/yjs?tab=readme-ov-file#providers) (Coming soon!)
122
+ - ✅ **Shared Types**
123
+ - ✅ [Map](https://docs.yjs.dev/api/shared-types/y.map)
124
+ - ✅ [Array](https://docs.yjs.dev/api/shared-types/y.array)
125
+ - ✅ [Text](https://docs.yjs.dev/api/shared-types/y.text)
126
+ - ✅ [XmlFragment](https://docs.yjs.dev/api/shared-types/y.xmlfragment)
127
+ - ✅ [XmlElement](https://docs.yjs.dev/api/shared-types/y.xmlelement)
128
+ - ✅ [XmlText](https://docs.yjs.dev/api/shared-types/y.xmltext)
129
+ - ✅ [Loro](https://loro.dev/) (preview)
130
+ - ✅ **Containers**
131
+ - ✅ List
132
+ - ✅ Map
133
+ - ✅ Text
134
+ - ⬜ Tree
64
135
  - ⬜ Studio (admin & developer panel)
65
136
 
66
- ### Runtimes
137
+ ## Runtimes
67
138
 
68
139
  - ✅ [Cloudflare Workers](https://workers.cloudflare.com/)
140
+ - **WebSocket API**
141
+ - ✅ [Hibernation API](https://developers.cloudflare.com/durable-objects/best-practices/websockets/#websocket-hibernation-api) (default, recommended)
142
+ - ✅ [Standard API](https://developers.cloudflare.com/durable-objects/best-practices/websockets/#websocket-standard-api)
143
+ - **State Persistence**
144
+ - ✅ [SQLite-backed Durable Objects](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) (default, recommended)
145
+ - ✅ [Key-value storage-backed Durable Object](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#create-durable-object-class-with-key-value-storage)
69
146
  - ✅ [Node.js](https://nodejs.org/)
70
- - ✅ **PubSubs**
71
- - ✅ [Redis](https://redis.io/)
72
- - ⬜ [RabbitMQ](https://www.rabbitmq.com/)
73
- - ⬜ [Kafka](https://kafka.apache.org/)
74
- - ✅ **CRDT State Persistence**
75
- - ✅ [Redis](https://redis.io/)
76
- - ⬜ [Prisma](https://www.prisma.io/)
147
+ - ✅ **PubSubs**
148
+ - ✅ [Redis](https://redis.io/) (preview)
149
+ - ⬜ [MQTT](https://mqtt.org/)
150
+ - ✅ **State Persistence**
151
+ - ✅ In-memory (default)
152
+ - ✅ [Redis](https://redis.io/) (preview)
77
153
 
78
- ### Frontends
154
+ ## Frontends Frameworks
79
155
 
80
156
  - ✅ [React.js](https://react.dev/)
81
157
  - ⬜ [Solid.js](https://www.solidjs.com/)
82
158
  - ⬜ [Vue.js](https://vuejs.org/)
83
159
  - ⬜ [Svelte](https://svelte.dev/)
84
160
 
85
- ## Usage
86
-
87
- Before diving into documentation, check out usage instructions for your selected platform:
88
-
89
- ### Documentation
90
-
91
- Documentation is available at [pluv.io](https://pluv.io/docs/introduction).
92
-
93
- ## Related
161
+ ## Packages
94
162
 
163
+ - [@pluv/io](https://www.npmjs.com/package/@pluv/io) - Server
95
164
  - [@pluv/client](https://www.npmjs.com/package/@pluv/client) - Framework agnostic client
96
- - [@pluv/crdt-loro](https://www.npmjs.com/package/@pluv/crdt-loro) - Loro CRDT for Pluv.IO
97
- - [@pluv/crdt-yjs](https://www.npmjs.com/package/@pluv/crdt-yjs) - Yjs CRDT for Pluv.IO
165
+ - [@pluv/react](https://www.npmjs.com/package/@pluv/react) - Integrate @pluv/client with React.js
166
+ - [@pluv/platform-pluv](https://www.npmjs.com/package/@pluv/platform-node) - Adapter to run on pluv.io
167
+ - [@pluv/platform-cloudflare](https://www.npmjs.com/package/@pluv/platform-cloudflare) - Adapter to run on Cloudflare Workers
168
+ - [@pluv/platform-node](https://www.npmjs.com/package/@pluv/platform-node) - Adapter to run on Node.js
169
+ - [@pluv/crdt-yjs](https://www.npmjs.com/package/@pluv/crdt-yjs) - Yjs CRDT adapter
170
+ - [@pluv/crdt-loro](https://www.npmjs.com/package/@pluv/crdt-loro) - Loro CRDT adapter
98
171
  - [@pluv/persistence-redis](https://www.npmjs.com/package/@pluv/persistence-redis) - Persistence for storage on distributed systems (Node.js only)
99
- - [@pluv/platform-cloudflare](https://www.npmjs.com/package/@pluv/platform-cloudflare) - Adapter to run @pluv/io on Cloudflare Workers
100
- - [@pluv/platform-node](https://www.npmjs.com/package/@pluv/platform-node) - Adapter to run @pluv/io on Node.js
172
+ - [@pluv/persistence-cloudflare-transactional-storage](https://www.npmjs.com/package/@pluv/persistence-cloudflare-transactional-storage) - Durable Object persistent state for WebSocket hibernation
101
173
  - [@pluv/pubsub-redis](https://www.npmjs.com/package/@pluv/pubsub-redis) - PubSub for rooms across distributed systems
102
- - [@pluv/react](https://www.npmjs.com/package/@pluv/react) - Integrate @pluv/client with React.js
103
174
 
104
175
  ## Credits
105
176
 
106
177
  This software uses the following open source tooling and libraries:
107
178
 
108
- - [Loro](https://loro.dev/)
109
- - [Yjs](https://yjs.dev/)
110
- - [Node.js](https://nodejs.org/)
111
179
  - [Cloudflare Workers](https://workers.cloudflare.com/)
112
- - [ioredis](https://github.com/luin/ioredis)
180
+ - [Node.js](https://nodejs.org/)
113
181
  - [React.js](https://reactjs.org/)
182
+ - [Yjs](https://yjs.dev/)
183
+ - [Loro](https://loro.dev/)
114
184
 
115
185
  ## License
116
186
 
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.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 = "0.44.2";
1159
+ var version = "1.0.1";
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: { 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,
@@ -1202,11 +1229,7 @@ var PluvServer = class {
1202
1229
  webSocket: session == null ? void 0 : session.webSocket.webSocket
1203
1230
  });
1204
1231
  }
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
- }
1232
+ const encodedState = doc.getEncodedState();
1210
1233
  return { $storageReceived: { state: encodedState } };
1211
1234
  })),
1212
1235
  $ping: this._procedure.self((data, { session }) => {
@@ -1274,7 +1297,13 @@ var PluvServer = class {
1274
1297
  platform,
1275
1298
  router = new PluvRouter({})
1276
1299
  } = options;
1277
- const { onRoomDeleted, onRoomMessage, onStorageUpdated, onUserConnected, onUserDisconnected } = options;
1300
+ const {
1301
+ onRoomDeleted,
1302
+ onRoomMessage,
1303
+ onStorageUpdated,
1304
+ onUserConnected,
1305
+ onUserDisconnected
1306
+ } = options;
1278
1307
  platform.validateConfig(options);
1279
1308
  this._crdt = crdt;
1280
1309
  this._debug = debug;
@@ -1295,7 +1324,8 @@ var PluvServer = class {
1295
1324
  }
1296
1325
  get fetch() {
1297
1326
  return (...args) => {
1298
- if (!this._platform._fetch) throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
1327
+ if (!this._platform._fetch)
1328
+ throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
1299
1329
  return this._platform._fetch(...args);
1300
1330
  };
1301
1331
  }
@@ -1321,7 +1351,8 @@ var PluvServer = class {
1321
1351
  if (this._platform._config.handleMode !== "io") {
1322
1352
  throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
1323
1353
  }
1324
- if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room)) throw new Error("Unsupported room name");
1354
+ if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
1355
+ throw new Error("Unsupported room name");
1325
1356
  const roomContext = platformRoomContext;
1326
1357
  const context = typeof this._context === "function" ? this._context(roomContext) : this._context;
1327
1358
  const newRoom = new IORoom(room, __spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), {
@@ -1391,7 +1422,8 @@ var PluvIO = class {
1391
1422
  createToken(params) {
1392
1423
  return __async(this, null, function* () {
1393
1424
  var _a;
1394
- if (!this._authorize) throw new Error("IO does not specify authorize during initialization.");
1425
+ if (!this._authorize)
1426
+ throw new Error("IO does not specify authorize during initialization.");
1395
1427
  const ioAuthorize = this._getIOAuthorize(params);
1396
1428
  const user = params.user;
1397
1429
  const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
@@ -1409,7 +1441,9 @@ var PluvIO = class {
1409
1441
  if (this._platform._createToken) return yield this._platform._createToken(params);
1410
1442
  const secret = (_a = ioAuthorize == null ? void 0 : ioAuthorize.secret) != null ? _a : null;
1411
1443
  if (!secret) throw new Error("`authorize` was specified without a valid secret");
1412
- return yield authorize({ platform: this._platform, secret }).encode(params);
1444
+ return yield authorize({ platform: this._platform, secret }).encode(
1445
+ params
1446
+ );
1413
1447
  });
1414
1448
  }
1415
1449
  mergeRouters(...routers) {