@pluv/react 0.17.2 → 0.18.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @pluv/react@0.17.2 build /home/runner/work/pluv/pluv/packages/react
2
+ > @pluv/react@0.18.0 build /home/runner/work/pluv/pluv/packages/react
3
3
  > tsup src/index.ts --format esm,cjs --dts --external react
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,11 +8,11 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- CJS dist/index.js 12.53 KB
11
+ CJS dist/index.js 12.77 KB
12
12
  CJS ⚡️ Build success in 100ms
13
- ESM dist/index.mjs 9.96 KB
14
- ESM ⚡️ Build success in 101ms
13
+ ESM dist/index.mjs 10.18 KB
14
+ ESM ⚡️ Build success in 100ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 4953ms
17
- DTS dist/index.d.mts 5.05 KB
18
- DTS dist/index.d.ts 5.05 KB
16
+ DTS ⚡️ Build success in 3856ms
17
+ DTS dist/index.d.mts 5.38 KB
18
+ DTS dist/index.d.ts 5.38 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,170 @@
1
1
  # @pluv/react
2
2
 
3
+ ## 0.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 99b5ca9: ## Breaking Changes
8
+
9
+ - `@pluv/io` has been updated to introduce `PluvProcedure`, `PluvRouter` and `PluvServer`. This change is intended to improve the ergonomics of declaring events and simplifying inferences of event types.
10
+
11
+ ### Before:
12
+
13
+ ```ts
14
+ // backend/io.ts
15
+
16
+ import { createIO } from "@pluv/io";
17
+ import { createPluvHandler, platformNode } from "@pluv/platform-node";
18
+ import { z } from "zod";
19
+
20
+ export const io = createIO({
21
+ platform: platformNode(),
22
+ })
23
+ .event("SEND_MESSAGE", {
24
+ input: z.object({ message: z.string() }),
25
+ resolver: ({ message }) => ({ RECEIVE_MESSAGE: { message } }),
26
+ })
27
+ .event("DOUBLE_VALUE", {
28
+ input: z.object({ value: z.number() }),
29
+ resolver: ({ value }) => ({ VALUE_DOUBLED: { value: value * 2 } }),
30
+ });
31
+
32
+ const Pluv = createPluvHandler({
33
+ io,
34
+ /* ... */
35
+ });
36
+ ```
37
+
38
+ ```ts
39
+ // frontend/pluv.ts
40
+
41
+ import { createClient } from "@pluv/react";
42
+ import type { io } from "../backend/io";
43
+
44
+ const client = createClient<typeof io>({
45
+ /* ... */
46
+ });
47
+ ```
48
+
49
+ ### Now:
50
+
51
+ ```ts
52
+ import { createIO } from "@pluv/io";
53
+ import { createPluvHandler, platformNode } from "@pluv/platform-node";
54
+ import { z } from "zod";
55
+
56
+ const io = createIO({
57
+ platform: platformNode(),
58
+ });
59
+
60
+ const router = io.router({
61
+ SEND_MESSAGE: io.procedure
62
+ .input(z.object({ message: z.string() }))
63
+ .broadcast(({ message }) => ({
64
+ RECEIVE_MESSAGE: { message },
65
+ })),
66
+ DOUBLE_VALUE: io.procedure
67
+ .input(z.object({ value: z.number() }))
68
+ .broadcast(({ value }) => ({
69
+ VALUE_DOUBLED: { value: value * 2 },
70
+ })),
71
+ });
72
+
73
+ export const ioServer = io.server({ router });
74
+
75
+ const Pluv = createPluvHandler({
76
+ io: ioServer, // <- This uses the PluvServer now
77
+ /* ... */
78
+ });
79
+ ```
80
+
81
+ ```ts
82
+ // frontend/pluv.ts
83
+
84
+ import { createClient } from "@pluv/react";
85
+ import type { ioServer } from "../backend/io";
86
+
87
+ // This users the PluvServer type now
88
+ const client = createClient<typeof ioServer>({
89
+ /* ... */
90
+ });
91
+ ```
92
+
93
+ - `PluvRouter` instances can also be merged via the `mergeRouters` method, which effectively performs an `Object.assign` of the events object and returns a new `PluvRouter` with the correct types:
94
+
95
+ ```ts
96
+ const router = io.mergeRouters(router1, router2);
97
+ ```
98
+
99
+ - 07a7725: Added broadcast proxy as a new way to broadcast events to other users.
100
+
101
+ ```tsx
102
+ // backend
103
+
104
+ const router = io.router({
105
+ SEND_MESSAGE: io.procedure
106
+ .input(z.object({ message: z.string() }))
107
+ .broadcast(({ message }) => ({ RECEIVE_MESSAGE: { message } })),
108
+ });
109
+
110
+ // frontend
111
+ const pluv = createRoomBundle(/* ... */);
112
+
113
+ const broadcast = pluv.useBroadcast();
114
+
115
+ // Both of the examples below are equivalent.
116
+
117
+ broadcast("SEND_MESSAGE", { message: "Hello world~!" });
118
+
119
+ broadcast.SEND_MESSAGE({ message: "Hello world~!" });
120
+ ```
121
+
122
+ ### Patch Changes
123
+
124
+ - e587c18: Added event proxy as a new way to listen to events.
125
+
126
+ ```tsx
127
+ // backend
128
+
129
+ const router = io.router({
130
+ SEND_MESSAGE: io.procedure
131
+ .input(z.object({ message: z.string() }))
132
+ .broadcast(({ message }) => ({ RECEIVE_MESSAGE: { message } })),
133
+ });
134
+
135
+ // frontend
136
+ const pluv = createRoomBundle(/* ... */);
137
+
138
+ // Both of the examples below are equivalent.
139
+
140
+ pluv.useEvent("RECEIVE_MESSAGE", ({ data }) => {
141
+ const { message } = data;
142
+
143
+ console.log(message);
144
+ });
145
+
146
+ pluv.event.RECEIVE_MESSAGE.useEvent(({ data }) => {
147
+ const { message } = data;
148
+
149
+ console.log(message);
150
+ });
151
+ ```
152
+
153
+ - Updated dependencies [06c67be]
154
+ - Updated dependencies [99b5ca9]
155
+ - Updated dependencies [df1342c]
156
+ - @pluv/client@0.18.0
157
+ - @pluv/types@0.18.0
158
+ - @pluv/crdt@0.18.0
159
+
160
+ ## 0.17.3
161
+
162
+ ### Patch Changes
163
+
164
+ - @pluv/client@0.17.3
165
+ - @pluv/crdt@0.17.3
166
+ - @pluv/types@0.17.3
167
+
3
168
  ## 0.17.2
4
169
 
5
170
  ### Patch Changes
package/README.md CHANGED
@@ -76,18 +76,28 @@ export const {
76
76
 
77
77
  export const {
78
78
  // components
79
+ MockedRoomProvider,
79
80
  PluvRoomProvider,
80
81
 
82
+ // utils
83
+ event,
84
+
81
85
  // hooks
82
86
  useBroadcast,
87
+ useCanRedo,
88
+ useCanUndo,
83
89
  useConnection,
90
+ useDoc,
84
91
  useEvent,
85
92
  useMyPresence,
86
93
  useMyself,
87
94
  useOther,
88
95
  useOthers,
96
+ useRedo,
89
97
  useRoom,
90
98
  useStorage,
99
+ useTransact,
100
+ useUndo,
91
101
  } = createRoomBundle({
92
102
  initialStorage: yjs.doc(() => ({
93
103
  messages: yjs.array([
@@ -120,7 +130,7 @@ export const MyPage: FC<Record<string, never>> = () => {
120
130
  export const MyRoom: FC<Record<string, never>> = () => {
121
131
  const broadcast = useBroadcast();
122
132
 
123
- useEvent("RECEIVE_MESSAGE", ({ data }) => {
133
+ event.RECEIVE_MESSAGE.useEvent(({ data }) => {
124
134
  // data is typed as { message: string }
125
135
  console.log(data.message);
126
136
  });
@@ -128,7 +138,7 @@ export const MyRoom: FC<Record<string, never>> = () => {
128
138
  return (
129
139
  <button
130
140
  onClick={() => {
131
- broadcast("SEND_MESSAGE", {
141
+ broadcast.SEND_MESSAGE({
132
142
  // TypeScript will require that message is typed as string
133
143
  message: "Hello world!",
134
144
  });
package/dist/index.d.mts CHANGED
@@ -3,7 +3,7 @@ export { ConnectionState, createClient } from '@pluv/client';
3
3
  import { AbstractCrdtType, AbstractCrdtDocFactory, AbstractCrdtDoc, InferCrdtStorageJson } from '@pluv/crdt';
4
4
  export { AbstractCrdtType, InferCrdtStorageJson } from '@pluv/crdt';
5
5
  import * as _pluv_types from '@pluv/types';
6
- import { IOLike, JsonObject, InputZodLike, InferIOInput, Id, InferIOOutput, IOEventMessage } from '@pluv/types';
6
+ import { IOLike, JsonObject, InputZodLike, Id, InferIOOutput, IOEventMessage, InferIOInput } from '@pluv/types';
7
7
  export { BaseUser, EventMessage, EventRecord, IOEventMessage } from '@pluv/types';
8
8
  import { FC, Dispatch, ReactNode } from 'react';
9
9
 
@@ -32,19 +32,25 @@ interface SubscriptionHookOptions<T extends unknown> {
32
32
  isEqual?: (a: T, b: T) => boolean;
33
33
  }
34
34
  type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
35
+ type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & {
36
+ [event in keyof InferIOInput<TIO>]: (callback: Id<InferIOInput<TIO>[event]>) => void;
37
+ };
38
+ type EventProxy<TIO extends IOLike> = {
39
+ [event in keyof InferIOOutput<TIO>]: {
40
+ useEvent: (callback: (data: Id<IOEventMessage<TIO, event>>) => void) => void;
41
+ };
42
+ };
35
43
  interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TStorage extends Record<string, AbstractCrdtType<any, any>>> {
36
44
  MockedRoomProvider: FC<MockedRoomProviderProps<TIO, TPresence, TStorage>>;
37
45
  PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TPresence, TStorage>>;
38
- useBroadcast: () => <TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void;
46
+ event: EventProxy<TIO>;
47
+ useBroadcast: () => BroadcastProxy<TIO>;
39
48
  useCanRedo: () => boolean;
40
49
  useCanUndo: () => boolean;
41
50
  useConnection: <T extends unknown = WebSocketConnection>(selector: (connection: WebSocketConnection) => T, options?: SubscriptionHookOptions<Id<T>>) => Id<T>;
42
51
  useDoc: () => AbstractCrdtDoc<TStorage>;
43
52
  useEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
44
- useMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [
45
- myPresence: Id<T>,
46
- updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>
47
- ];
53
+ useMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
48
54
  useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
49
55
  useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
50
56
  useOthers: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[], options?: SubscriptionHookOptions<readonly Id<T>[]>) => readonly Id<T>[];
@@ -64,6 +70,6 @@ interface CreateBundle<TIO extends IOLike> {
64
70
  PluvProvider: FC<PluvProviderProps>;
65
71
  useClient: () => PluvClient<TIO>;
66
72
  }
67
- declare const createBundle: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any, any>, {}, {}, {}, {}>>(client: PluvClient<TIO>) => CreateBundle<TIO>;
73
+ declare const createBundle: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any, any>, {}>>(client: PluvClient<TIO>) => CreateBundle<TIO>;
68
74
 
69
75
  export { type CreateBundle, type CreateRoomBundle, type CreateRoomBundleOptions, type InferRoomStorage, type PluvProviderProps, type PluvRoomProviderProps, type SubscriptionHookOptions, createBundle };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { ConnectionState, createClient } from '@pluv/client';
3
3
  import { AbstractCrdtType, AbstractCrdtDocFactory, AbstractCrdtDoc, InferCrdtStorageJson } from '@pluv/crdt';
4
4
  export { AbstractCrdtType, InferCrdtStorageJson } from '@pluv/crdt';
5
5
  import * as _pluv_types from '@pluv/types';
6
- import { IOLike, JsonObject, InputZodLike, InferIOInput, Id, InferIOOutput, IOEventMessage } from '@pluv/types';
6
+ import { IOLike, JsonObject, InputZodLike, Id, InferIOOutput, IOEventMessage, InferIOInput } from '@pluv/types';
7
7
  export { BaseUser, EventMessage, EventRecord, IOEventMessage } from '@pluv/types';
8
8
  import { FC, Dispatch, ReactNode } from 'react';
9
9
 
@@ -32,19 +32,25 @@ interface SubscriptionHookOptions<T extends unknown> {
32
32
  isEqual?: (a: T, b: T) => boolean;
33
33
  }
34
34
  type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
35
+ type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & {
36
+ [event in keyof InferIOInput<TIO>]: (callback: Id<InferIOInput<TIO>[event]>) => void;
37
+ };
38
+ type EventProxy<TIO extends IOLike> = {
39
+ [event in keyof InferIOOutput<TIO>]: {
40
+ useEvent: (callback: (data: Id<IOEventMessage<TIO, event>>) => void) => void;
41
+ };
42
+ };
35
43
  interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TStorage extends Record<string, AbstractCrdtType<any, any>>> {
36
44
  MockedRoomProvider: FC<MockedRoomProviderProps<TIO, TPresence, TStorage>>;
37
45
  PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TPresence, TStorage>>;
38
- useBroadcast: () => <TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void;
46
+ event: EventProxy<TIO>;
47
+ useBroadcast: () => BroadcastProxy<TIO>;
39
48
  useCanRedo: () => boolean;
40
49
  useCanUndo: () => boolean;
41
50
  useConnection: <T extends unknown = WebSocketConnection>(selector: (connection: WebSocketConnection) => T, options?: SubscriptionHookOptions<Id<T>>) => Id<T>;
42
51
  useDoc: () => AbstractCrdtDoc<TStorage>;
43
52
  useEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
44
- useMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [
45
- myPresence: Id<T>,
46
- updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>
47
- ];
53
+ useMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
48
54
  useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
49
55
  useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
50
56
  useOthers: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[], options?: SubscriptionHookOptions<readonly Id<T>[]>) => readonly Id<T>[];
@@ -64,6 +70,6 @@ interface CreateBundle<TIO extends IOLike> {
64
70
  PluvProvider: FC<PluvProviderProps>;
65
71
  useClient: () => PluvClient<TIO>;
66
72
  }
67
- declare const createBundle: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any, any>, {}, {}, {}, {}>>(client: PluvClient<TIO>) => CreateBundle<TIO>;
73
+ declare const createBundle: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any, any>, {}>>(client: PluvClient<TIO>) => CreateBundle<TIO>;
68
74
 
69
75
  export { type CreateBundle, type CreateRoomBundle, type CreateRoomBundleOptions, type InferRoomStorage, type PluvProviderProps, type PluvRoomProviderProps, type SubscriptionHookOptions, createBundle };
package/dist/index.js CHANGED
@@ -74,13 +74,7 @@ var createRoomBundle = (client, options) => {
74
74
  const PluvRoomContext = (0, import_react2.createContext)(null);
75
75
  const MockedRoomContext = (0, import_react2.createContext)({ room: null });
76
76
  const MockedRoomProvider = (0, import_react2.memo)((props) => {
77
- const {
78
- children,
79
- events,
80
- initialPresence,
81
- initialStorage,
82
- room: _room
83
- } = props;
77
+ const { children, events, initialPresence, initialStorage, room: _room } = props;
84
78
  const [room] = (0, import_react2.useState)(() => {
85
79
  return new import_client.MockedRoom(_room, {
86
80
  events,
@@ -93,14 +87,7 @@ var createRoomBundle = (client, options) => {
93
87
  });
94
88
  MockedRoomProvider.displayName = "MockedRoomProvider";
95
89
  const PluvRoomProvider = (0, import_react2.memo)((props) => {
96
- const {
97
- children,
98
- debug,
99
- initialPresence,
100
- initialStorage,
101
- onAuthorizationFail,
102
- room: _room
103
- } = props;
90
+ const { children, debug, initialPresence, initialStorage, onAuthorizationFail, room: _room } = props;
104
91
  const rerender = useRerender();
105
92
  const { room: mockedRoom } = (0, import_react2.useContext)(MockedRoomContext);
106
93
  const [room] = (0, import_react2.useState)(() => {
@@ -133,41 +120,34 @@ var createRoomBundle = (client, options) => {
133
120
  const useRoom = () => (0, import_react2.useContext)(PluvRoomContext);
134
121
  const useBroadcast = () => {
135
122
  const room = useRoom();
136
- return (0, import_react2.useCallback)(
137
- (event, data) => {
138
- room.broadcast(event, data);
123
+ const broadcast = (0, import_react2.useCallback)(
124
+ (event2, data) => {
125
+ room.broadcast(event2, data);
139
126
  },
140
127
  [room]
141
128
  );
129
+ return (0, import_react2.useMemo)(() => {
130
+ return new Proxy(broadcast, {
131
+ get(fn, prop) {
132
+ return (data) => {
133
+ return fn(prop, data);
134
+ };
135
+ }
136
+ });
137
+ }, [broadcast]);
142
138
  };
143
139
  const useCanRedo = () => {
144
140
  const room = useRoom();
145
- const subscribe = (0, import_react2.useCallback)(
146
- (onStoreChange) => room.storageRoot(onStoreChange),
147
- [room]
148
- );
141
+ const subscribe = (0, import_react2.useCallback)((onStoreChange) => room.storageRoot(onStoreChange), [room]);
149
142
  const getSnapshot = (0, import_react2.useCallback)(() => room.canRedo(), [room]);
150
- const canRedo = (0, import_with_selector.useSyncExternalStoreWithSelector)(
151
- subscribe,
152
- getSnapshot,
153
- getSnapshot,
154
- identity
155
- );
143
+ const canRedo = (0, import_with_selector.useSyncExternalStoreWithSelector)(subscribe, getSnapshot, getSnapshot, identity);
156
144
  return canRedo;
157
145
  };
158
146
  const useCanUndo = () => {
159
147
  const room = useRoom();
160
- const subscribe = (0, import_react2.useCallback)(
161
- (onStoreChange) => room.storageRoot(onStoreChange),
162
- [room]
163
- );
148
+ const subscribe = (0, import_react2.useCallback)((onStoreChange) => room.storageRoot(onStoreChange), [room]);
164
149
  const getSnapshot = (0, import_react2.useCallback)(() => room.canUndo(), [room]);
165
- const canRedo = (0, import_with_selector.useSyncExternalStoreWithSelector)(
166
- subscribe,
167
- getSnapshot,
168
- getSnapshot,
169
- identity
170
- );
150
+ const canRedo = (0, import_with_selector.useSyncExternalStoreWithSelector)(subscribe, getSnapshot, getSnapshot, identity);
171
151
  return canRedo;
172
152
  };
173
153
  const useConnection = (selector = identity, options2) => {
@@ -180,10 +160,7 @@ var createRoomBundle = (client, options) => {
180
160
  [room]
181
161
  );
182
162
  const getSnapshot = room.getConnection;
183
- const _selector = (0, import_react2.useCallback)(
184
- (snapshot) => selector(snapshot),
185
- [selector]
186
- );
163
+ const _selector = (0, import_react2.useCallback)((snapshot) => selector(snapshot), [selector]);
187
164
  return (0, import_with_selector.useSyncExternalStoreWithSelector)(
188
165
  subscribe,
189
166
  getSnapshot,
@@ -205,6 +182,17 @@ var createRoomBundle = (client, options) => {
205
182
  };
206
183
  }, [callback, room, type]);
207
184
  };
185
+ const event = new Proxy(
186
+ {},
187
+ {
188
+ get(_, prop) {
189
+ const useProxyEvent = (callback) => {
190
+ return useEvent(prop, callback);
191
+ };
192
+ return { useEvent: useProxyEvent };
193
+ }
194
+ }
195
+ );
208
196
  const useMyPresence = (selector = identity, options2) => {
209
197
  var _a2;
210
198
  const room = useRoom();
@@ -215,10 +203,7 @@ var createRoomBundle = (client, options) => {
215
203
  [room]
216
204
  );
217
205
  const getSnapshot = room.getMyPresence;
218
- const _selector = (0, import_react2.useCallback)(
219
- (snapshot) => selector(snapshot),
220
- [selector]
221
- );
206
+ const _selector = (0, import_react2.useCallback)((snapshot) => selector(snapshot), [selector]);
222
207
  const myPresence = (0, import_with_selector.useSyncExternalStoreWithSelector)(
223
208
  subscribe,
224
209
  getSnapshot,
@@ -268,10 +253,7 @@ var createRoomBundle = (client, options) => {
268
253
  },
269
254
  [room, connectionId]
270
255
  );
271
- const getSnapshot = (0, import_react2.useCallback)(
272
- () => room.getOther(connectionId),
273
- [room, connectionId]
274
- );
256
+ const getSnapshot = (0, import_react2.useCallback)(() => room.getOther(connectionId), [room, connectionId]);
275
257
  const _selector = (0, import_react2.useCallback)(
276
258
  (snapshot) => {
277
259
  return !snapshot ? null : selector(snapshot);
@@ -315,10 +297,7 @@ var createRoomBundle = (client, options) => {
315
297
  room.subscribe("storage-loaded", () => {
316
298
  rerender();
317
299
  });
318
- const subscribe = (0, import_react2.useCallback)(
319
- (onStoreChange) => room.storage(key, onStoreChange),
320
- [key, room]
321
- );
300
+ const subscribe = (0, import_react2.useCallback)((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
322
301
  const getSnapshot = (0, import_react2.useCallback)(() => {
323
302
  var _a3;
324
303
  return (_a3 = room.getStorage(key)) == null ? void 0 : _a3.toJson();
@@ -351,6 +330,8 @@ var createRoomBundle = (client, options) => {
351
330
  // components
352
331
  MockedRoomProvider,
353
332
  PluvRoomProvider,
333
+ // proxies
334
+ event,
354
335
  // hooks
355
336
  useBroadcast,
356
337
  useCanRedo,
package/dist/index.mjs CHANGED
@@ -8,14 +8,7 @@ import { createContext as createContext2, memo as memo2, useContext as useContex
8
8
  import { MockedRoom } from "@pluv/client";
9
9
  import { noop } from "@pluv/crdt";
10
10
  import fastDeepEqual from "fast-deep-equal";
11
- import {
12
- createContext,
13
- memo,
14
- useCallback as useCallback2,
15
- useContext,
16
- useEffect,
17
- useState as useState2
18
- } from "react";
11
+ import { createContext, memo, useCallback as useCallback2, useContext, useEffect, useMemo, useState as useState2 } from "react";
19
12
 
20
13
  // src/internal/hooks/useRerender.ts
21
14
  import { useCallback, useState } from "react";
@@ -45,13 +38,7 @@ var createRoomBundle = (client, options) => {
45
38
  const PluvRoomContext = createContext(null);
46
39
  const MockedRoomContext = createContext({ room: null });
47
40
  const MockedRoomProvider = memo((props) => {
48
- const {
49
- children,
50
- events,
51
- initialPresence,
52
- initialStorage,
53
- room: _room
54
- } = props;
41
+ const { children, events, initialPresence, initialStorage, room: _room } = props;
55
42
  const [room] = useState2(() => {
56
43
  return new MockedRoom(_room, {
57
44
  events,
@@ -64,14 +51,7 @@ var createRoomBundle = (client, options) => {
64
51
  });
65
52
  MockedRoomProvider.displayName = "MockedRoomProvider";
66
53
  const PluvRoomProvider = memo((props) => {
67
- const {
68
- children,
69
- debug,
70
- initialPresence,
71
- initialStorage,
72
- onAuthorizationFail,
73
- room: _room
74
- } = props;
54
+ const { children, debug, initialPresence, initialStorage, onAuthorizationFail, room: _room } = props;
75
55
  const rerender = useRerender();
76
56
  const { room: mockedRoom } = useContext(MockedRoomContext);
77
57
  const [room] = useState2(() => {
@@ -104,41 +84,34 @@ var createRoomBundle = (client, options) => {
104
84
  const useRoom = () => useContext(PluvRoomContext);
105
85
  const useBroadcast = () => {
106
86
  const room = useRoom();
107
- return useCallback2(
108
- (event, data) => {
109
- room.broadcast(event, data);
87
+ const broadcast = useCallback2(
88
+ (event2, data) => {
89
+ room.broadcast(event2, data);
110
90
  },
111
91
  [room]
112
92
  );
93
+ return useMemo(() => {
94
+ return new Proxy(broadcast, {
95
+ get(fn, prop) {
96
+ return (data) => {
97
+ return fn(prop, data);
98
+ };
99
+ }
100
+ });
101
+ }, [broadcast]);
113
102
  };
114
103
  const useCanRedo = () => {
115
104
  const room = useRoom();
116
- const subscribe = useCallback2(
117
- (onStoreChange) => room.storageRoot(onStoreChange),
118
- [room]
119
- );
105
+ const subscribe = useCallback2((onStoreChange) => room.storageRoot(onStoreChange), [room]);
120
106
  const getSnapshot = useCallback2(() => room.canRedo(), [room]);
121
- const canRedo = useSyncExternalStoreWithSelector(
122
- subscribe,
123
- getSnapshot,
124
- getSnapshot,
125
- identity
126
- );
107
+ const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
127
108
  return canRedo;
128
109
  };
129
110
  const useCanUndo = () => {
130
111
  const room = useRoom();
131
- const subscribe = useCallback2(
132
- (onStoreChange) => room.storageRoot(onStoreChange),
133
- [room]
134
- );
112
+ const subscribe = useCallback2((onStoreChange) => room.storageRoot(onStoreChange), [room]);
135
113
  const getSnapshot = useCallback2(() => room.canUndo(), [room]);
136
- const canRedo = useSyncExternalStoreWithSelector(
137
- subscribe,
138
- getSnapshot,
139
- getSnapshot,
140
- identity
141
- );
114
+ const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
142
115
  return canRedo;
143
116
  };
144
117
  const useConnection = (selector = identity, options2) => {
@@ -151,10 +124,7 @@ var createRoomBundle = (client, options) => {
151
124
  [room]
152
125
  );
153
126
  const getSnapshot = room.getConnection;
154
- const _selector = useCallback2(
155
- (snapshot) => selector(snapshot),
156
- [selector]
157
- );
127
+ const _selector = useCallback2((snapshot) => selector(snapshot), [selector]);
158
128
  return useSyncExternalStoreWithSelector(
159
129
  subscribe,
160
130
  getSnapshot,
@@ -176,6 +146,17 @@ var createRoomBundle = (client, options) => {
176
146
  };
177
147
  }, [callback, room, type]);
178
148
  };
149
+ const event = new Proxy(
150
+ {},
151
+ {
152
+ get(_, prop) {
153
+ const useProxyEvent = (callback) => {
154
+ return useEvent(prop, callback);
155
+ };
156
+ return { useEvent: useProxyEvent };
157
+ }
158
+ }
159
+ );
179
160
  const useMyPresence = (selector = identity, options2) => {
180
161
  var _a2;
181
162
  const room = useRoom();
@@ -186,10 +167,7 @@ var createRoomBundle = (client, options) => {
186
167
  [room]
187
168
  );
188
169
  const getSnapshot = room.getMyPresence;
189
- const _selector = useCallback2(
190
- (snapshot) => selector(snapshot),
191
- [selector]
192
- );
170
+ const _selector = useCallback2((snapshot) => selector(snapshot), [selector]);
193
171
  const myPresence = useSyncExternalStoreWithSelector(
194
172
  subscribe,
195
173
  getSnapshot,
@@ -239,10 +217,7 @@ var createRoomBundle = (client, options) => {
239
217
  },
240
218
  [room, connectionId]
241
219
  );
242
- const getSnapshot = useCallback2(
243
- () => room.getOther(connectionId),
244
- [room, connectionId]
245
- );
220
+ const getSnapshot = useCallback2(() => room.getOther(connectionId), [room, connectionId]);
246
221
  const _selector = useCallback2(
247
222
  (snapshot) => {
248
223
  return !snapshot ? null : selector(snapshot);
@@ -286,10 +261,7 @@ var createRoomBundle = (client, options) => {
286
261
  room.subscribe("storage-loaded", () => {
287
262
  rerender();
288
263
  });
289
- const subscribe = useCallback2(
290
- (onStoreChange) => room.storage(key, onStoreChange),
291
- [key, room]
292
- );
264
+ const subscribe = useCallback2((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
293
265
  const getSnapshot = useCallback2(() => {
294
266
  var _a3;
295
267
  return (_a3 = room.getStorage(key)) == null ? void 0 : _a3.toJson();
@@ -322,6 +294,8 @@ var createRoomBundle = (client, options) => {
322
294
  // components
323
295
  MockedRoomProvider,
324
296
  PluvRoomProvider,
297
+ // proxies
298
+ event,
325
299
  // hooks
326
300
  useBroadcast,
327
301
  useCanRedo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/react",
3
- "version": "0.17.2",
3
+ "version": "0.18.0",
4
4
  "description": "@pluv/client React.js bindings",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -17,24 +17,24 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@types/react": "^18.2.78",
20
+ "@types/react": "^18.3.3",
21
21
  "@types/use-sync-external-store": "^0.0.6",
22
22
  "fast-deep-equal": "^3.1.3",
23
- "use-sync-external-store": "^1.2.0",
24
- "@pluv/client": "^0.17.2",
25
- "@pluv/crdt": "^0.17.2",
26
- "@pluv/types": "^0.17.2"
23
+ "use-sync-external-store": "^1.2.2",
24
+ "@pluv/client": "^0.18.0",
25
+ "@pluv/crdt": "^0.18.0",
26
+ "@pluv/types": "^0.18.0"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "react": "^17.0.0 || ^18.0.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "eslint": "^8.57.0",
33
- "react": "^18.2.0",
33
+ "react": "^18.3.1",
34
34
  "tsup": "^8.0.2",
35
35
  "typescript": "^5.4.5",
36
- "@pluv/tsconfig": "^0.17.2",
37
- "eslint-config-pluv": "^0.17.2"
36
+ "@pluv/tsconfig": "^0.18.0",
37
+ "eslint-config-pluv": "^0.18.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsup src/index.ts --format esm,cjs --dts --external react",
@@ -3,10 +3,7 @@ import type { AbstractCrdtType } from "@pluv/crdt";
3
3
  import type { IOLike, JsonObject } from "@pluv/types";
4
4
  import type { FC, ReactNode } from "react";
5
5
  import { createContext, memo, useContext } from "react";
6
- import type {
7
- CreateRoomBundle,
8
- CreateRoomBundleOptions,
9
- } from "./createRoomBundle";
6
+ import type { CreateRoomBundle, CreateRoomBundleOptions } from "./createRoomBundle";
10
7
  import { createRoomBundle as _createRoomBundle } from "./createRoomBundle";
11
8
 
12
9
  export interface PluvProviderProps {
@@ -29,9 +26,7 @@ export interface CreateBundle<TIO extends IOLike> {
29
26
  useClient: () => PluvClient<TIO>;
30
27
  }
31
28
 
32
- export const createBundle = <TIO extends IOLike>(
33
- client: PluvClient<TIO>,
34
- ): CreateBundle<TIO> => {
29
+ export const createBundle = <TIO extends IOLike>(client: PluvClient<TIO>): CreateBundle<TIO> => {
35
30
  /**
36
31
  * !HACK
37
32
  * @description We'll let the context error out if client is not provided,
@@ -44,11 +39,7 @@ export const createBundle = <TIO extends IOLike>(
44
39
  const PluvProvider = memo<PluvProviderProps>((props) => {
45
40
  const { children } = props;
46
41
 
47
- return (
48
- <PluvContext.Provider value={client}>
49
- {children}
50
- </PluvContext.Provider>
51
- );
42
+ return <PluvContext.Provider value={client}>{children}</PluvContext.Provider>;
52
43
  });
53
44
 
54
45
  PluvProvider.displayName = "PluvProvider";
@@ -60,8 +51,7 @@ export const createBundle = <TIO extends IOLike>(
60
51
  TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
61
52
  >(
62
53
  options: CreateRoomBundleOptions<TIO, TPresence, TStorage>,
63
- ): CreateRoomBundle<TIO, TPresence, TStorage> =>
64
- _createRoomBundle(client, options);
54
+ ): CreateRoomBundle<TIO, TPresence, TStorage> => _createRoomBundle(client, options);
65
55
 
66
56
  return {
67
57
  createRoomBundle,
@@ -9,38 +9,13 @@ import type {
9
9
  WebSocketConnection,
10
10
  } from "@pluv/client";
11
11
  import { MockedRoom } from "@pluv/client";
12
- import type {
13
- AbstractCrdtDoc,
14
- AbstractCrdtDocFactory,
15
- AbstractCrdtType,
16
- InferCrdtStorageJson,
17
- } from "@pluv/crdt";
12
+ import type { AbstractCrdtDoc, AbstractCrdtDocFactory, AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
18
13
  import { noop } from "@pluv/crdt";
19
- import type {
20
- IOEventMessage,
21
- IOLike,
22
- Id,
23
- InferIOInput,
24
- InferIOOutput,
25
- InputZodLike,
26
- JsonObject,
27
- } from "@pluv/types";
14
+ import type { IOEventMessage, IOLike, Id, InferIOInput, InferIOOutput, InputZodLike, JsonObject } from "@pluv/types";
28
15
  import fastDeepEqual from "fast-deep-equal";
29
16
  import type { Dispatch, FC, ReactNode } from "react";
30
- import {
31
- createContext,
32
- memo,
33
- useCallback,
34
- useContext,
35
- useEffect,
36
- useState,
37
- } from "react";
38
- import {
39
- identity,
40
- shallowArrayEqual,
41
- useRerender,
42
- useSyncExternalStoreWithSelector,
43
- } from "./internal";
17
+ import { createContext, memo, useCallback, useContext, useEffect, useMemo, useState } from "react";
18
+ import { identity, shallowArrayEqual, useRerender, useSyncExternalStoreWithSelector } from "./internal";
44
19
 
45
20
  export type CreateRoomBundleOptions<
46
21
  TIO extends IOLike,
@@ -59,9 +34,7 @@ type BaseRoomProviderProps<
59
34
  children?: ReactNode;
60
35
  initialStorage?: keyof TStorage extends never ? never : () => TStorage;
61
36
  room: string;
62
- } & (keyof TPresence extends never
63
- ? { initialPresence?: never }
64
- : { initialPresence: TPresence });
37
+ } & (keyof TPresence extends never ? { initialPresence?: never } : { initialPresence: TPresence });
65
38
 
66
39
  export type MockedRoomProviderProps<
67
40
  TIO extends IOLike,
@@ -88,6 +61,19 @@ export type UpdateMyPresenceAction<TPresence extends JsonObject> =
88
61
  | Partial<TPresence>
89
62
  | ((oldPresence: TPresence | null) => Partial<TPresence>);
90
63
 
64
+ export type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(
65
+ event: TEvent,
66
+ data: Id<InferIOInput<TIO>[TEvent]>,
67
+ ) => void) & {
68
+ [event in keyof InferIOInput<TIO>]: (callback: Id<InferIOInput<TIO>[event]>) => void;
69
+ };
70
+
71
+ export type EventProxy<TIO extends IOLike> = {
72
+ [event in keyof InferIOOutput<TIO>]: {
73
+ useEvent: (callback: (data: Id<IOEventMessage<TIO, event>>) => void) => void;
74
+ };
75
+ };
76
+
91
77
  export interface CreateRoomBundle<
92
78
  TIO extends IOLike,
93
79
  TPresence extends JsonObject,
@@ -97,11 +83,11 @@ export interface CreateRoomBundle<
97
83
  MockedRoomProvider: FC<MockedRoomProviderProps<TIO, TPresence, TStorage>>;
98
84
  PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TPresence, TStorage>>;
99
85
 
86
+ // proxies
87
+ event: EventProxy<TIO>;
88
+
100
89
  // hooks
101
- useBroadcast: () => <TEvent extends keyof InferIOInput<TIO>>(
102
- event: TEvent,
103
- data: Id<InferIOInput<TIO>[TEvent]>,
104
- ) => void;
90
+ useBroadcast: () => BroadcastProxy<TIO>;
105
91
  useCanRedo: () => boolean;
106
92
  useCanUndo: () => boolean;
107
93
  useConnection: <T extends unknown = WebSocketConnection>(
@@ -116,10 +102,7 @@ export interface CreateRoomBundle<
116
102
  useMyPresence: <T extends unknown = TPresence>(
117
103
  selector?: (myPresence: TPresence) => T,
118
104
  options?: SubscriptionHookOptions<Id<T> | null>,
119
- ) => [
120
- myPresence: Id<T>,
121
- updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>,
122
- ];
105
+ ) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
123
106
  useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(
124
107
  selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T,
125
108
  options?: SubscriptionHookOptions<Id<T> | null>,
@@ -135,27 +118,17 @@ export interface CreateRoomBundle<
135
118
  ) => readonly Id<T>[];
136
119
  useRedo: () => () => void;
137
120
  useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
138
- useStorage: <
139
- TKey extends keyof TStorage,
140
- TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>,
141
- >(
121
+ useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(
142
122
  key: TKey,
143
123
  selector?: (data: InferCrdtStorageJson<TStorage[TKey]>) => TData,
144
124
  options?: SubscriptionHookOptions<TData | null>,
145
125
  ) => [data: TData | null, sharedType: TStorage[TKey] | null];
146
- useTransact: () => (
147
- fn: (storage: TStorage) => void,
148
- origin?: string,
149
- ) => void;
126
+ useTransact: () => (fn: (storage: TStorage) => void, origin?: string) => void;
150
127
  useUndo: () => () => void;
151
128
  }
152
129
 
153
- export type InferRoomStorage<
154
- TRoomBundle extends CreateRoomBundle<any, any, any>,
155
- > =
156
- TRoomBundle extends CreateRoomBundle<any, any, infer IStorage>
157
- ? IStorage
158
- : never;
130
+ export type InferRoomStorage<TRoomBundle extends CreateRoomBundle<any, any, any>> =
131
+ TRoomBundle extends CreateRoomBundle<any, any, infer IStorage> ? IStorage : never;
159
132
 
160
133
  export const createRoomBundle = <
161
134
  TIO extends IOLike,
@@ -165,8 +138,7 @@ export const createRoomBundle = <
165
138
  client: PluvClient<TIO>,
166
139
  options: CreateRoomBundleOptions<TIO, TPresence, TStorage>,
167
140
  ): CreateRoomBundle<TIO, TPresence, TStorage> => {
168
- const _initialStorage = (options.initialStorage ??
169
- noop.doc()) as AbstractCrdtDocFactory<TStorage>;
141
+ const _initialStorage = (options.initialStorage ?? noop.doc()) as AbstractCrdtDocFactory<TStorage>;
170
142
 
171
143
  /**
172
144
  * !HACK
@@ -175,59 +147,36 @@ export const createRoomBundle = <
175
147
  * @author leedavidcs
176
148
  * @date November 11, 2022
177
149
  */
178
- const PluvRoomContext = createContext<
179
- AbstractRoom<TIO, TPresence, TStorage>
180
- >(null as any);
150
+ const PluvRoomContext = createContext<AbstractRoom<TIO, TPresence, TStorage>>(null as any);
181
151
 
182
152
  const MockedRoomContext = createContext<{
183
153
  room: AbstractRoom<TIO, TPresence, TStorage> | null;
184
154
  }>({ room: null });
185
155
 
186
- const MockedRoomProvider = memo<
187
- MockedRoomProviderProps<TIO, TPresence, TStorage>
188
- >((props) => {
189
- const {
190
- children,
191
- events,
192
- initialPresence,
193
- initialStorage,
194
- room: _room,
195
- } = props;
156
+ const MockedRoomProvider = memo<MockedRoomProviderProps<TIO, TPresence, TStorage>>((props) => {
157
+ const { children, events, initialPresence, initialStorage, room: _room } = props;
196
158
 
197
159
  const [room] = useState<MockedRoom<TIO, TPresence, TStorage>>(() => {
198
160
  return new MockedRoom<TIO, TPresence, TStorage>(_room, {
199
161
  events,
200
162
  initialPresence,
201
163
  initialStorage:
202
- typeof initialStorage === "function"
203
- ? _initialStorage.getFactory(initialStorage)
204
- : _initialStorage,
164
+ typeof initialStorage === "function" ? _initialStorage.getFactory(initialStorage) : _initialStorage,
205
165
  presence: options.presence,
206
166
  });
207
167
  });
208
168
 
209
169
  return (
210
170
  <MockedRoomContext.Provider value={{ room }}>
211
- <PluvRoomContext.Provider value={room}>
212
- {children}
213
- </PluvRoomContext.Provider>
171
+ <PluvRoomContext.Provider value={room}>{children}</PluvRoomContext.Provider>
214
172
  </MockedRoomContext.Provider>
215
173
  );
216
174
  });
217
175
 
218
176
  MockedRoomProvider.displayName = "MockedRoomProvider";
219
177
 
220
- const PluvRoomProvider = memo<
221
- PluvRoomProviderProps<TIO, TPresence, TStorage>
222
- >((props) => {
223
- const {
224
- children,
225
- debug,
226
- initialPresence,
227
- initialStorage,
228
- onAuthorizationFail,
229
- room: _room,
230
- } = props;
178
+ const PluvRoomProvider = memo<PluvRoomProviderProps<TIO, TPresence, TStorage>>((props) => {
179
+ const { children, debug, initialPresence, initialStorage, onAuthorizationFail, room: _room } = props;
231
180
 
232
181
  const rerender = useRerender();
233
182
  const { room: mockedRoom } = useContext(MockedRoomContext);
@@ -238,9 +187,7 @@ export const createRoomBundle = <
238
187
  debug,
239
188
  initialPresence,
240
189
  initialStorage:
241
- typeof initialStorage === "function"
242
- ? _initialStorage.getFactory(initialStorage)
243
- : _initialStorage,
190
+ typeof initialStorage === "function" ? _initialStorage.getFactory(initialStorage) : _initialStorage,
244
191
  presence: options.presence,
245
192
  onAuthorizationFail,
246
193
  });
@@ -264,50 +211,42 @@ export const createRoomBundle = <
264
211
  };
265
212
  }, [room]);
266
213
 
267
- return (
268
- <PluvRoomContext.Provider value={mockedRoom ?? room}>
269
- {children}
270
- </PluvRoomContext.Provider>
271
- );
214
+ return <PluvRoomContext.Provider value={mockedRoom ?? room}>{children}</PluvRoomContext.Provider>;
272
215
  });
273
216
 
274
217
  PluvRoomProvider.displayName = "PluvRoomProvider";
275
218
 
276
219
  const useRoom = () => useContext(PluvRoomContext);
277
220
 
278
- const useBroadcast = (): (<TEvent extends keyof InferIOInput<TIO>>(
279
- event: TEvent,
280
- data: Id<InferIOInput<TIO>[TEvent]>,
281
- ) => void) => {
221
+ const useBroadcast = (): BroadcastProxy<TIO> => {
282
222
  const room = useRoom();
283
223
 
284
- return useCallback(
285
- <TEvent extends keyof InferIOInput<TIO>>(
286
- event: TEvent,
287
- data: Id<InferIOInput<TIO>[TEvent]>,
288
- ) => {
224
+ const broadcast = useCallback(
225
+ <TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => {
289
226
  room.broadcast(event, data);
290
227
  },
291
228
  [room],
292
229
  );
230
+
231
+ return useMemo((): BroadcastProxy<TIO> => {
232
+ return new Proxy(broadcast, {
233
+ get(fn, prop) {
234
+ return (data: Id<InferIOInput<TIO>[keyof InferIOInput<TIO>]>): void => {
235
+ return fn(prop as keyof InferIOInput<TIO>, data);
236
+ };
237
+ },
238
+ }) as BroadcastProxy<TIO>;
239
+ }, [broadcast]);
293
240
  };
294
241
 
295
242
  const useCanRedo = (): boolean => {
296
243
  const room = useRoom();
297
244
 
298
- const subscribe = useCallback(
299
- (onStoreChange: () => void) => room.storageRoot(onStoreChange),
300
- [room],
301
- );
245
+ const subscribe = useCallback((onStoreChange: () => void) => room.storageRoot(onStoreChange), [room]);
302
246
 
303
247
  const getSnapshot = useCallback((): boolean => room.canRedo(), [room]);
304
248
 
305
- const canRedo = useSyncExternalStoreWithSelector(
306
- subscribe,
307
- getSnapshot,
308
- getSnapshot,
309
- identity,
310
- );
249
+ const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
311
250
 
312
251
  return canRedo;
313
252
  };
@@ -315,19 +254,11 @@ export const createRoomBundle = <
315
254
  const useCanUndo = (): boolean => {
316
255
  const room = useRoom();
317
256
 
318
- const subscribe = useCallback(
319
- (onStoreChange: () => void) => room.storageRoot(onStoreChange),
320
- [room],
321
- );
257
+ const subscribe = useCallback((onStoreChange: () => void) => room.storageRoot(onStoreChange), [room]);
322
258
 
323
259
  const getSnapshot = useCallback((): boolean => room.canUndo(), [room]);
324
260
 
325
- const canRedo = useSyncExternalStoreWithSelector(
326
- subscribe,
327
- getSnapshot,
328
- getSnapshot,
329
- identity,
330
- );
261
+ const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
331
262
 
332
263
  return canRedo;
333
264
  };
@@ -347,10 +278,7 @@ export const createRoomBundle = <
347
278
 
348
279
  const getSnapshot = room.getConnection;
349
280
 
350
- const _selector = useCallback(
351
- (snapshot: WebSocketConnection) => selector(snapshot) as Id<T>,
352
- [selector],
353
- );
281
+ const _selector = useCallback((snapshot: WebSocketConnection) => selector(snapshot) as Id<T>, [selector]);
354
282
 
355
283
  return useSyncExternalStoreWithSelector(
356
284
  subscribe,
@@ -382,6 +310,21 @@ export const createRoomBundle = <
382
310
  }, [callback, room, type]);
383
311
  };
384
312
 
313
+ const event = new Proxy(
314
+ {},
315
+ {
316
+ get(_, prop) {
317
+ const useProxyEvent = (
318
+ callback: (data: Id<IOEventMessage<TIO, keyof InferIOOutput<TIO>>>) => void,
319
+ ): void => {
320
+ return useEvent(prop as keyof InferIOOutput<TIO>, callback);
321
+ };
322
+
323
+ return { useEvent: useProxyEvent };
324
+ },
325
+ },
326
+ ) as EventProxy<TIO>;
327
+
385
328
  const useMyPresence = <T extends unknown = TPresence>(
386
329
  selector = identity as (myPresence: TPresence) => T,
387
330
  options?: SubscriptionHookOptions<Id<T> | null>,
@@ -397,10 +340,7 @@ export const createRoomBundle = <
397
340
 
398
341
  const getSnapshot = room.getMyPresence;
399
342
 
400
- const _selector = useCallback(
401
- (snapshot: TPresence) => selector(snapshot) as Id<T>,
402
- [selector],
403
- );
343
+ const _selector = useCallback((snapshot: TPresence) => selector(snapshot) as Id<T>, [selector]);
404
344
 
405
345
  const myPresence = useSyncExternalStoreWithSelector(
406
346
  subscribe,
@@ -410,18 +350,14 @@ export const createRoomBundle = <
410
350
  options?.isEqual ?? fastDeepEqual,
411
351
  );
412
352
 
413
- const updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>> =
414
- useCallback(
415
- (value: UpdateMyPresenceAction<TPresence>): void => {
416
- const presence =
417
- typeof value === "function"
418
- ? value(room.getMyPresence())
419
- : value;
353
+ const updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>> = useCallback(
354
+ (value: UpdateMyPresenceAction<TPresence>): void => {
355
+ const presence = typeof value === "function" ? value(room.getMyPresence()) : value;
420
356
 
421
- room.updateMyPresence(presence);
422
- },
423
- [room],
424
- );
357
+ room.updateMyPresence(presence);
358
+ },
359
+ [room],
360
+ );
425
361
 
426
362
  return [myPresence, updateMyPresence];
427
363
  };
@@ -471,10 +407,7 @@ export const createRoomBundle = <
471
407
  [room, connectionId],
472
408
  );
473
409
 
474
- const getSnapshot = useCallback(
475
- () => room.getOther(connectionId),
476
- [room, connectionId],
477
- );
410
+ const getSnapshot = useCallback(() => room.getOther(connectionId), [room, connectionId]);
478
411
 
479
412
  const _selector = useCallback(
480
413
  (snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
@@ -493,9 +426,7 @@ export const createRoomBundle = <
493
426
  };
494
427
 
495
428
  const useOthers = <T extends unknown = UserInfo<TIO, TPresence>>(
496
- selector = identity as (
497
- other: readonly Id<UserInfo<TIO, TPresence>>[],
498
- ) => T[],
429
+ selector = identity as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[],
499
430
  options?: SubscriptionHookOptions<readonly Id<T>[]>,
500
431
  ): readonly Id<T>[] => {
501
432
  const room = useRoom();
@@ -513,9 +444,7 @@ export const createRoomBundle = <
513
444
  subscribe,
514
445
  getSnapshot,
515
446
  getSnapshot,
516
- selector as (
517
- other: readonly Id<UserInfo<TIO, TPresence>>[],
518
- ) => Id<T>[],
447
+ selector as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => Id<T>[],
519
448
  options?.isEqual ?? shallowArrayEqual,
520
449
  );
521
450
  };
@@ -526,14 +455,9 @@ export const createRoomBundle = <
526
455
  return room.redo;
527
456
  };
528
457
 
529
- const useStorage = <
530
- TKey extends keyof TStorage,
531
- TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>,
532
- >(
458
+ const useStorage = <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(
533
459
  key: TKey,
534
- selector = identity as (
535
- data: InferCrdtStorageJson<TStorage[TKey]>,
536
- ) => TData,
460
+ selector = identity as (data: InferCrdtStorageJson<TStorage[TKey]>) => TData,
537
461
  options?: SubscriptionHookOptions<TData | null>,
538
462
  ): [data: TData | null, sharedType: TStorage[TKey] | null] => {
539
463
  const room = useRoom();
@@ -543,14 +467,9 @@ export const createRoomBundle = <
543
467
  rerender();
544
468
  });
545
469
 
546
- const subscribe = useCallback(
547
- (onStoreChange: () => void) => room.storage(key, onStoreChange),
548
- [key, room],
549
- );
470
+ const subscribe = useCallback((onStoreChange: () => void) => room.storage(key, onStoreChange), [key, room]);
550
471
 
551
- const getSnapshot = useCallback((): InferCrdtStorageJson<
552
- TStorage[TKey]
553
- > | null => {
472
+ const getSnapshot = useCallback((): InferCrdtStorageJson<TStorage[TKey]> | null => {
554
473
  return room.getStorage(key)?.toJson();
555
474
  }, [key, room]);
556
475
 
@@ -591,6 +510,9 @@ export const createRoomBundle = <
591
510
  MockedRoomProvider,
592
511
  PluvRoomProvider,
593
512
 
513
+ // proxies
514
+ event,
515
+
594
516
  // hooks
595
517
  useBroadcast,
596
518
  useCanRedo,
package/src/index.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  export { ConnectionState, createClient } from "@pluv/client";
2
2
  export type { AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
3
- export type {
4
- BaseUser,
5
- EventMessage,
6
- EventRecord,
7
- IOEventMessage,
8
- } from "@pluv/types";
3
+ export type { BaseUser, EventMessage, EventRecord, IOEventMessage } from "@pluv/types";
9
4
  export { createBundle } from "./createBundle";
10
5
  export type { CreateBundle, PluvProviderProps } from "./createBundle";
11
6
  export type {
@@ -4,12 +4,7 @@ import { identity } from "../utils";
4
4
  export const useSyncExternalStore = <TSnapshot extends unknown>(
5
5
  subscribe: (onStoreChange: () => void) => () => void,
6
6
  getSnapshot: () => TSnapshot,
7
- getServerSnapshot: undefined | null | (() => TSnapshot)
7
+ getServerSnapshot: undefined | null | (() => TSnapshot),
8
8
  ) => {
9
- return useSyncExternalStoreWithSelector(
10
- subscribe,
11
- getSnapshot,
12
- getServerSnapshot,
13
- identity
14
- );
9
+ return useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, identity);
15
10
  };
@@ -1,6 +1,2 @@
1
- export {
2
- useRerender,
3
- useSyncExternalStore,
4
- useSyncExternalStoreWithSelector,
5
- } from "./hooks";
1
+ export { useRerender, useSyncExternalStore, useSyncExternalStoreWithSelector } from "./hooks";
6
2
  export { identity, shallowArrayEqual } from "./utils";
@@ -1,6 +1,3 @@
1
- export const shallowArrayEqual = <T extends unknown>(
2
- a: readonly T[] | T[],
3
- b: readonly T[] | T[]
4
- ): boolean => {
1
+ export const shallowArrayEqual = <T extends unknown>(a: readonly T[] | T[], b: readonly T[] | T[]): boolean => {
5
2
  return a.length === b.length && a.every((item, i) => item === b[i]);
6
3
  };