@liveblocks/zustand 1.1.0 → 1.1.1-dual2

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.
@@ -0,0 +1,84 @@
1
+ import { JsonObject, LsonObject, BaseUserMeta, Json, Room, User, Status, Client } from '@liveblocks/client';
2
+ import { LegacyConnectionStatus } from '@liveblocks/core';
3
+ import { StoreMutatorIdentifier, StateCreator } from 'zustand';
4
+
5
+ declare type LiveblocksContext<TPresence extends JsonObject, TStorage extends LsonObject, TUserMeta extends BaseUserMeta, TRoomEvent extends Json> = {
6
+ /**
7
+ * Enters a room and starts sync it with zustand state
8
+ * @param roomId The id of the room
9
+ */
10
+ readonly enterRoom: (roomId: string) => void;
11
+ /**
12
+ * Leaves a room and stops sync it with zustand state.
13
+ * @param roomId The id of the room
14
+ */
15
+ readonly leaveRoom: (roomId: string) => void;
16
+ /**
17
+ * The room currently synced to your zustand state.
18
+ */
19
+ readonly room: Room<TPresence, TStorage, TUserMeta, TRoomEvent> | null;
20
+ /**
21
+ * Other users in the room. Empty no room is currently synced
22
+ */
23
+ readonly others: readonly User<TPresence, TUserMeta>[];
24
+ /**
25
+ * Whether or not the room storage is currently loading
26
+ */
27
+ readonly isStorageLoading: boolean;
28
+ /**
29
+ * Legacy connection status of the room.
30
+ *
31
+ * @deprecated This API will be removed in a future version of Liveblocks.
32
+ * Prefer using the newer `.status` property.
33
+ *
34
+ * We recommend making the following changes if you use these APIs:
35
+ *
36
+ * OLD STATUSES NEW STATUSES
37
+ * closed --> initial
38
+ * authenticating --> connecting
39
+ * connecting --> connecting
40
+ * open --> connected
41
+ * unavailable --> reconnecting
42
+ * failed --> disconnected
43
+ */
44
+ readonly connection: LegacyConnectionStatus;
45
+ /**
46
+ * Connection status of the room.
47
+ */
48
+ readonly status: Status;
49
+ };
50
+ /**
51
+ * @deprecated Renamed to WithLiveblocks<...>
52
+ */
53
+ declare type LiveblocksState<TState, TPresence extends JsonObject = JsonObject, TStorage extends LsonObject = LsonObject, TUserMeta extends BaseUserMeta = BaseUserMeta, TRoomEvent extends Json = Json> = WithLiveblocks<TState, TPresence, TStorage, TUserMeta, TRoomEvent>;
54
+ /**
55
+ * Adds the `liveblocks` property to your custom Zustand state.
56
+ */
57
+ declare type WithLiveblocks<TState, TPresence extends JsonObject = JsonObject, TStorage extends LsonObject = LsonObject, TUserMeta extends BaseUserMeta = BaseUserMeta, TRoomEvent extends Json = Json> = TState & {
58
+ readonly liveblocks: LiveblocksContext<TPresence, TStorage, TUserMeta, TRoomEvent>;
59
+ };
60
+ declare type Mapping<T> = {
61
+ [K in keyof T]?: boolean;
62
+ };
63
+ declare type Options<T> = {
64
+ /**
65
+ * Liveblocks client created by @liveblocks/client createClient
66
+ */
67
+ client: Client;
68
+ /**
69
+ * Mapping used to synchronize a part of your zustand state with one Liveblocks Room storage.
70
+ */
71
+ storageMapping?: Mapping<T>;
72
+ /**
73
+ * Mapping used to synchronize a part of your zustand state with one Liveblocks Room presence.
74
+ */
75
+ presenceMapping?: Mapping<T>;
76
+ };
77
+ declare type OuterLiveblocksMiddleware = <TState, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(config: StateCreator<TState, Mps, Mcs, Omit<TState, "liveblocks">>, options: Options<Omit<TState, "liveblocks">>) => StateCreator<TState, Mps, Mcs, TState>;
78
+ declare const liveblocks: OuterLiveblocksMiddleware;
79
+ /**
80
+ * @deprecated Renamed to `liveblocks`.
81
+ */
82
+ declare const middleware: OuterLiveblocksMiddleware;
83
+
84
+ export { LiveblocksContext, LiveblocksState, Mapping, WithLiveblocks, liveblocks, middleware };
package/dist/index.mjs ADDED
@@ -0,0 +1,275 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
32
+
33
+ // src/index.ts
34
+ import {
35
+ errorIf,
36
+ legacy_patchImmutableObject,
37
+ lsonToJson,
38
+ patchLiveObjectKey
39
+ } from "@liveblocks/core";
40
+ var ERROR_PREFIX = "Invalid @liveblocks/zustand middleware config.";
41
+ function mappingToFunctionIsNotAllowed(key) {
42
+ return new Error(
43
+ `${ERROR_PREFIX} mapping.${key} is invalid. Mapping to a function is not allowed.`
44
+ );
45
+ }
46
+ var middlewareImpl = (config, options) => {
47
+ const { client, presenceMapping, storageMapping } = validateOptions(options);
48
+ return (set, get, api) => {
49
+ let maybeRoom = null;
50
+ let isPatching = false;
51
+ let storageRoot = null;
52
+ let unsubscribeCallbacks = [];
53
+ function enterRoom(roomId) {
54
+ if (storageRoot) {
55
+ return;
56
+ }
57
+ const initialPresence = selectFields(
58
+ get(),
59
+ presenceMapping
60
+ );
61
+ const room = client.enter(roomId, {
62
+ initialPresence
63
+ });
64
+ maybeRoom = room;
65
+ updateLiveblocksContext(set, { isStorageLoading: true, room });
66
+ unsubscribeCallbacks.push(
67
+ room.events.others.subscribe(({ others }) => {
68
+ updateLiveblocksContext(set, { others });
69
+ })
70
+ );
71
+ unsubscribeCallbacks.push(
72
+ room.events.connection.subscribe(() => {
73
+ updateLiveblocksContext(set, {
74
+ connection: room.getConnectionState(),
75
+ status: room.getStatus()
76
+ });
77
+ })
78
+ );
79
+ unsubscribeCallbacks.push(
80
+ room.events.me.subscribe(() => {
81
+ if (isPatching === false) {
82
+ set(
83
+ selectFields(
84
+ room.getPresence(),
85
+ presenceMapping
86
+ )
87
+ );
88
+ }
89
+ })
90
+ );
91
+ void room.getStorage().then(({ root }) => {
92
+ const updates = {};
93
+ room.batch(() => {
94
+ for (const key in storageMapping) {
95
+ const liveblocksStatePart = root.get(key);
96
+ if (liveblocksStatePart === void 0) {
97
+ updates[key] = get()[key];
98
+ patchLiveObjectKey(root, key, void 0, get()[key]);
99
+ } else {
100
+ updates[key] = lsonToJson(
101
+ liveblocksStatePart
102
+ );
103
+ }
104
+ }
105
+ });
106
+ set(updates);
107
+ storageRoot = root;
108
+ unsubscribeCallbacks.push(
109
+ room.subscribe(
110
+ root,
111
+ (updates2) => {
112
+ if (isPatching === false) {
113
+ set(patchState(get(), updates2, storageMapping));
114
+ }
115
+ },
116
+ { isDeep: true }
117
+ )
118
+ );
119
+ updateLiveblocksContext(set, {
120
+ isStorageLoading: false
121
+ });
122
+ });
123
+ }
124
+ function leaveRoom(roomId) {
125
+ for (const unsubscribe of unsubscribeCallbacks) {
126
+ unsubscribe();
127
+ }
128
+ storageRoot = null;
129
+ maybeRoom = null;
130
+ isPatching = false;
131
+ unsubscribeCallbacks = [];
132
+ client.leave(roomId);
133
+ updateLiveblocksContext(set, {
134
+ others: [],
135
+ connection: "closed",
136
+ isStorageLoading: false,
137
+ room: null
138
+ });
139
+ }
140
+ const store = config(
141
+ (args) => {
142
+ const _a = get(), { liveblocks: _ } = _a, oldState = __objRest(_a, ["liveblocks"]);
143
+ set(args);
144
+ const _b = get(), { liveblocks: __ } = _b, newState = __objRest(_b, ["liveblocks"]);
145
+ if (maybeRoom) {
146
+ const room = maybeRoom;
147
+ isPatching = true;
148
+ updatePresence(room, oldState, newState, presenceMapping);
149
+ room.batch(() => {
150
+ if (storageRoot) {
151
+ patchLiveblocksStorage(
152
+ storageRoot,
153
+ oldState,
154
+ newState,
155
+ storageMapping
156
+ );
157
+ }
158
+ });
159
+ isPatching = false;
160
+ }
161
+ },
162
+ get,
163
+ api
164
+ );
165
+ return __spreadProps(__spreadValues({}, store), {
166
+ liveblocks: {
167
+ enterRoom,
168
+ leaveRoom,
169
+ room: null,
170
+ others: [],
171
+ connection: "closed",
172
+ isStorageLoading: false
173
+ }
174
+ });
175
+ };
176
+ };
177
+ var liveblocks = middlewareImpl;
178
+ var middleware = liveblocks;
179
+ function patchState(state, updates, mapping) {
180
+ const partialState = {};
181
+ for (const key in mapping) {
182
+ partialState[key] = state[key];
183
+ }
184
+ const patched = legacy_patchImmutableObject(partialState, updates);
185
+ const result = {};
186
+ for (const key in mapping) {
187
+ result[key] = patched[key];
188
+ }
189
+ return result;
190
+ }
191
+ function selectFields(presence, mapping) {
192
+ const partialState = {};
193
+ for (const key in mapping) {
194
+ partialState[key] = presence[key];
195
+ }
196
+ return partialState;
197
+ }
198
+ function updateLiveblocksContext(set, partial) {
199
+ set((state) => ({ liveblocks: __spreadValues(__spreadValues({}, state.liveblocks), partial) }));
200
+ }
201
+ function updatePresence(room, oldState, newState, presenceMapping) {
202
+ for (const key in presenceMapping) {
203
+ if (typeof newState[key] === "function") {
204
+ throw mappingToFunctionIsNotAllowed(key);
205
+ }
206
+ if (oldState[key] !== newState[key]) {
207
+ const val = newState == null ? void 0 : newState[key];
208
+ const patch = {};
209
+ patch[key] = val;
210
+ room.updatePresence(patch);
211
+ }
212
+ }
213
+ }
214
+ function patchLiveblocksStorage(root, oldState, newState, mapping) {
215
+ for (const key in mapping) {
216
+ if (process.env.NODE_ENV !== "production" && typeof newState[key] === "function") {
217
+ throw mappingToFunctionIsNotAllowed(key);
218
+ }
219
+ if (oldState[key] !== newState[key]) {
220
+ const oldVal = oldState[key];
221
+ const newVal = newState[key];
222
+ patchLiveObjectKey(root, key, oldVal, newVal);
223
+ }
224
+ }
225
+ }
226
+ function isObject(value) {
227
+ return Object.prototype.toString.call(value) === "[object Object]";
228
+ }
229
+ function validateNoDuplicateKeys(storageMapping, presenceMapping) {
230
+ for (const key in storageMapping) {
231
+ if (presenceMapping[key] !== void 0) {
232
+ throw new Error(
233
+ `${ERROR_PREFIX} "${key}" is mapped on both presenceMapping and storageMapping. A key shouldn't exist on both mapping.`
234
+ );
235
+ }
236
+ }
237
+ }
238
+ function validateMapping(mapping, mappingType) {
239
+ errorIf(
240
+ !isObject(mapping),
241
+ `${ERROR_PREFIX} ${mappingType} should be an object where the values are boolean.`
242
+ );
243
+ const result = {};
244
+ for (const key in mapping) {
245
+ errorIf(
246
+ typeof mapping[key] !== "boolean",
247
+ `${ERROR_PREFIX} ${mappingType}.${key} value should be a boolean`
248
+ );
249
+ if (mapping[key] === true) {
250
+ result[key] = true;
251
+ }
252
+ }
253
+ return result;
254
+ }
255
+ function validateOptions(options) {
256
+ var _a, _b;
257
+ const client = options.client;
258
+ errorIf(!client, `${ERROR_PREFIX} client is missing`);
259
+ const storageMapping = validateMapping(
260
+ (_a = options.storageMapping) != null ? _a : {},
261
+ "storageMapping"
262
+ );
263
+ const presenceMapping = validateMapping(
264
+ (_b = options.presenceMapping) != null ? _b : {},
265
+ "presenceMapping"
266
+ );
267
+ if (process.env.NODE_ENV !== "production") {
268
+ validateNoDuplicateKeys(storageMapping, presenceMapping);
269
+ }
270
+ return { client, storageMapping, presenceMapping };
271
+ }
272
+ export {
273
+ liveblocks,
274
+ middleware
275
+ };
package/package.json CHANGED
@@ -1,10 +1,23 @@
1
1
  {
2
2
  "name": "@liveblocks/zustand",
3
- "version": "1.1.0",
3
+ "version": "1.1.1-dual2",
4
4
  "description": "A middleware to integrate Liveblocks into Zustand stores. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "module": "./dist/index.mjs",
17
+ "default": "./dist/index.js"
18
+ }
19
+ }
20
+ },
8
21
  "files": [
9
22
  "dist/**",
10
23
  "README.md"
@@ -12,15 +25,16 @@
12
25
  "scripts": {
13
26
  "dev": "tsup --watch",
14
27
  "build": "tsup",
15
- "format": "eslint --fix src/; prettier --write src/",
28
+ "format": "(eslint --fix src/ || true) && prettier --write src/",
16
29
  "lint": "eslint src/",
30
+ "lint:package": "publint --strict && attw --pack",
17
31
  "test": "jest --silent --verbose --color=always",
18
32
  "test:types": "tsd",
19
33
  "test:watch": "jest --silent --verbose --color=always --watch"
20
34
  },
21
35
  "dependencies": {
22
- "@liveblocks/client": "1.1.0",
23
- "@liveblocks/core": "1.1.0"
36
+ "@liveblocks/client": "1.1.1-dual2",
37
+ "@liveblocks/core": "1.1.1-dual2"
24
38
  },
25
39
  "peerDependencies": {
26
40
  "zustand": "^4.1.3"