@liveblocks/client 0.13.2 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/AbstractCrdt.d.ts +8 -4
- package/lib/cjs/AbstractCrdt.js +2 -2
- package/lib/cjs/LiveList.d.ts +9 -4
- package/lib/cjs/LiveList.js +58 -9
- package/lib/cjs/LiveMap.d.ts +7 -3
- package/lib/cjs/LiveMap.js +23 -5
- package/lib/cjs/LiveObject.d.ts +17 -7
- package/lib/cjs/LiveObject.js +45 -15
- package/lib/cjs/LiveRegister.d.ts +8 -4
- package/lib/cjs/LiveRegister.js +17 -4
- package/lib/cjs/client.js +50 -7
- package/lib/cjs/{immutable/index.d.ts → immutable.d.ts} +5 -3
- package/lib/cjs/{immutable/index.js → immutable.js} +98 -21
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/live.d.ts +7 -0
- package/lib/cjs/room.d.ts +17 -9
- package/lib/cjs/room.js +203 -81
- package/lib/cjs/types.d.ts +26 -1
- package/lib/cjs/utils.d.ts +2 -1
- package/lib/cjs/utils.js +76 -1
- package/lib/esm/AbstractCrdt.d.ts +8 -4
- package/lib/esm/AbstractCrdt.js +2 -2
- package/lib/esm/LiveList.d.ts +9 -4
- package/lib/esm/LiveList.js +59 -10
- package/lib/esm/LiveMap.d.ts +7 -3
- package/lib/esm/LiveMap.js +23 -5
- package/lib/esm/LiveObject.d.ts +17 -7
- package/lib/esm/LiveObject.js +45 -15
- package/lib/esm/LiveRegister.d.ts +8 -4
- package/lib/esm/LiveRegister.js +18 -5
- package/lib/esm/client.js +50 -7
- package/lib/esm/{immutable/index.d.ts → immutable.d.ts} +5 -3
- package/lib/esm/{immutable/index.js → immutable.js} +96 -21
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/live.d.ts +7 -0
- package/lib/esm/room.d.ts +17 -9
- package/lib/esm/room.js +203 -62
- package/lib/esm/types.d.ts +26 -1
- package/lib/esm/utils.d.ts +2 -1
- package/lib/esm/utils.js +75 -1
- package/package.json +6 -2
package/lib/esm/client.js
CHANGED
|
@@ -26,11 +26,7 @@ import { createRoom } from "./room";
|
|
|
26
26
|
*/
|
|
27
27
|
export function createClient(options) {
|
|
28
28
|
const clientOptions = options;
|
|
29
|
-
|
|
30
|
-
if (clientOptions.throttle < 80 || clientOptions.throttle > 1000) {
|
|
31
|
-
throw new Error("Liveblocks client throttle should be between 80 and 1000 ms");
|
|
32
|
-
}
|
|
33
|
-
}
|
|
29
|
+
const throttleDelay = getThrottleDelayFromOptions(options);
|
|
34
30
|
const rooms = new Map();
|
|
35
31
|
function getRoom(roomId) {
|
|
36
32
|
const internalRoom = rooms.get(roomId);
|
|
@@ -41,9 +37,21 @@ export function createClient(options) {
|
|
|
41
37
|
if (internalRoom) {
|
|
42
38
|
return internalRoom.room;
|
|
43
39
|
}
|
|
44
|
-
internalRoom = createRoom(
|
|
40
|
+
internalRoom = createRoom({
|
|
41
|
+
defaultPresence: options.defaultPresence,
|
|
42
|
+
defaultStorageRoot: options.defaultStorageRoot,
|
|
43
|
+
}, {
|
|
44
|
+
room: roomId,
|
|
45
|
+
throttleDelay,
|
|
46
|
+
WebSocketPolyfill: clientOptions.WebSocketPolyfill,
|
|
47
|
+
fetchPolyfill: clientOptions.fetchPolyfill,
|
|
48
|
+
liveblocksServer: clientOptions.liveblocksServer || "wss://liveblocks.net/v5",
|
|
49
|
+
authentication: prepareAuthentication(clientOptions),
|
|
50
|
+
});
|
|
45
51
|
rooms.set(roomId, internalRoom);
|
|
46
|
-
|
|
52
|
+
if (!options.DO_NOT_USE_withoutConnecting) {
|
|
53
|
+
internalRoom.connect();
|
|
54
|
+
}
|
|
47
55
|
return internalRoom.room;
|
|
48
56
|
}
|
|
49
57
|
function leave(roomId) {
|
|
@@ -74,3 +82,38 @@ export function createClient(options) {
|
|
|
74
82
|
leave,
|
|
75
83
|
};
|
|
76
84
|
}
|
|
85
|
+
function getThrottleDelayFromOptions(options) {
|
|
86
|
+
if (options.throttle === undefined) {
|
|
87
|
+
return 100;
|
|
88
|
+
}
|
|
89
|
+
if (typeof options.throttle !== "number" ||
|
|
90
|
+
options.throttle < 80 ||
|
|
91
|
+
options.throttle > 1000) {
|
|
92
|
+
throw new Error("throttle should be a number between 80 and 1000.");
|
|
93
|
+
}
|
|
94
|
+
return options.throttle;
|
|
95
|
+
}
|
|
96
|
+
function prepareAuthentication(clientOptions) {
|
|
97
|
+
// TODO: throw descriptive errors for invalid options
|
|
98
|
+
if (typeof clientOptions.publicApiKey === "string") {
|
|
99
|
+
return {
|
|
100
|
+
type: "public",
|
|
101
|
+
publicApiKey: clientOptions.publicApiKey,
|
|
102
|
+
url: clientOptions.publicAuthorizeEndpoint ||
|
|
103
|
+
"https://liveblocks.io/api/public/authorize",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
else if (typeof clientOptions.authEndpoint === "string") {
|
|
107
|
+
return {
|
|
108
|
+
type: "private",
|
|
109
|
+
url: clientOptions.authEndpoint,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
else if (typeof clientOptions.authEndpoint === "function") {
|
|
113
|
+
return {
|
|
114
|
+
type: "custom",
|
|
115
|
+
callback: clientOptions.authEndpoint,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
throw new Error("Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient");
|
|
119
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { LiveList } from "
|
|
2
|
-
import { LiveObject } from "
|
|
3
|
-
import { StorageUpdate } from "
|
|
1
|
+
import { LiveList } from "./LiveList";
|
|
2
|
+
import { LiveObject } from "./LiveObject";
|
|
3
|
+
import { StorageUpdate } from "./types";
|
|
4
4
|
export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
|
|
5
|
+
export declare function liveNodeToJson(value: any): any;
|
|
5
6
|
export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
|
|
7
|
+
export declare function patchLiveObjectKey<T>(liveObject: LiveObject<T>, key: keyof T, prev: any, next: any): void;
|
|
6
8
|
export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
|
|
7
9
|
export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { LiveList } from "
|
|
2
|
-
import { LiveMap } from "
|
|
3
|
-
import { LiveObject } from "
|
|
1
|
+
import { LiveList } from "./LiveList";
|
|
2
|
+
import { LiveMap } from "./LiveMap";
|
|
3
|
+
import { LiveObject } from "./LiveObject";
|
|
4
|
+
import { LiveRegister } from "./LiveRegister";
|
|
4
5
|
export function liveObjectToJson(liveObject) {
|
|
5
6
|
const result = {};
|
|
6
7
|
const obj = liveObject.toObject();
|
|
@@ -20,7 +21,7 @@ function liveMapToJson(map) {
|
|
|
20
21
|
function liveListToJson(value) {
|
|
21
22
|
return value.toArray().map(liveNodeToJson);
|
|
22
23
|
}
|
|
23
|
-
function liveNodeToJson(value) {
|
|
24
|
+
export function liveNodeToJson(value) {
|
|
24
25
|
if (value instanceof LiveObject) {
|
|
25
26
|
return liveObjectToJson(value);
|
|
26
27
|
}
|
|
@@ -30,6 +31,9 @@ function liveNodeToJson(value) {
|
|
|
30
31
|
else if (value instanceof LiveMap) {
|
|
31
32
|
return liveMapToJson(value);
|
|
32
33
|
}
|
|
34
|
+
else if (value instanceof LiveRegister) {
|
|
35
|
+
return value.data;
|
|
36
|
+
}
|
|
33
37
|
return value;
|
|
34
38
|
}
|
|
35
39
|
function isPlainObject(obj) {
|
|
@@ -97,8 +101,10 @@ export function patchLiveList(liveList, prev, next) {
|
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
else if (i > nextEnd) {
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
let localI = i;
|
|
105
|
+
while (localI <= prevEnd) {
|
|
106
|
+
liveList.delete(i);
|
|
107
|
+
localI++;
|
|
102
108
|
}
|
|
103
109
|
}
|
|
104
110
|
else {
|
|
@@ -127,21 +133,35 @@ export function patchLiveList(liveList, prev, next) {
|
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
}
|
|
136
|
+
export function patchLiveObjectKey(liveObject, key, prev, next) {
|
|
137
|
+
const value = liveObject.get(key);
|
|
138
|
+
if (next === undefined) {
|
|
139
|
+
liveObject.delete(key);
|
|
140
|
+
}
|
|
141
|
+
else if (value === undefined) {
|
|
142
|
+
liveObject.set(key, anyToCrdt(next));
|
|
143
|
+
}
|
|
144
|
+
else if (prev === next) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
else if (value instanceof LiveList &&
|
|
148
|
+
Array.isArray(prev) &&
|
|
149
|
+
Array.isArray(next)) {
|
|
150
|
+
patchLiveList(value, prev, next);
|
|
151
|
+
}
|
|
152
|
+
else if (value instanceof LiveObject &&
|
|
153
|
+
isPlainObject(prev) &&
|
|
154
|
+
isPlainObject(next)) {
|
|
155
|
+
patchLiveObject(value, prev, next);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
liveObject.set(key, anyToCrdt(next));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
130
161
|
export function patchLiveObject(root, prev, next) {
|
|
131
162
|
const updates = {};
|
|
132
163
|
for (const key in next) {
|
|
133
|
-
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
else if (Array.isArray(prev[key]) && Array.isArray(next[key])) {
|
|
137
|
-
patchLiveList(root.get(key), prev[key], next[key]);
|
|
138
|
-
}
|
|
139
|
-
else if (isPlainObject(prev[key]) && isPlainObject(next[key])) {
|
|
140
|
-
patchLiveObject(root.get(key), prev[key], next[key]);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
updates[key] = anyToCrdt(next[key]);
|
|
144
|
-
}
|
|
164
|
+
patchLiveObjectKey(root, key, prev[key], next[key]);
|
|
145
165
|
}
|
|
146
166
|
for (const key in prev) {
|
|
147
167
|
if (next[key] === undefined) {
|
|
@@ -173,6 +193,7 @@ function patchImmutableObjectWithUpdate(state, update) {
|
|
|
173
193
|
return patchImmutableNode(state, path, update);
|
|
174
194
|
}
|
|
175
195
|
function patchImmutableNode(state, path, update) {
|
|
196
|
+
var _a, _b, _c, _d;
|
|
176
197
|
const pathItem = path.pop();
|
|
177
198
|
if (pathItem === undefined) {
|
|
178
199
|
switch (update.type) {
|
|
@@ -180,19 +201,73 @@ function patchImmutableNode(state, path, update) {
|
|
|
180
201
|
if (typeof state !== "object") {
|
|
181
202
|
throw new Error("Internal: received update on LiveObject but state was not an object");
|
|
182
203
|
}
|
|
183
|
-
|
|
204
|
+
let newState = Object.assign({}, state);
|
|
205
|
+
for (const key in update.updates) {
|
|
206
|
+
if (((_a = update.updates[key]) === null || _a === void 0 ? void 0 : _a.type) === "update") {
|
|
207
|
+
newState[key] = liveNodeToJson(update.node.get(key));
|
|
208
|
+
}
|
|
209
|
+
else if (((_b = update.updates[key]) === null || _b === void 0 ? void 0 : _b.type) === "delete") {
|
|
210
|
+
delete newState[key];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return newState;
|
|
184
214
|
}
|
|
185
215
|
case "LiveList": {
|
|
186
216
|
if (Array.isArray(state) === false) {
|
|
187
217
|
throw new Error("Internal: received update on LiveList but state was not an array");
|
|
188
218
|
}
|
|
189
|
-
|
|
219
|
+
let newState = state.map((x) => x);
|
|
220
|
+
for (const listUpdate of update.updates) {
|
|
221
|
+
if (listUpdate.type === "insert") {
|
|
222
|
+
if (listUpdate.index === newState.length) {
|
|
223
|
+
newState.push(liveNodeToJson(listUpdate.item));
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
newState = [
|
|
227
|
+
...newState.slice(0, listUpdate.index),
|
|
228
|
+
liveNodeToJson(listUpdate.item),
|
|
229
|
+
...newState.slice(listUpdate.index),
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (listUpdate.type === "delete") {
|
|
234
|
+
newState.splice(listUpdate.index, 1);
|
|
235
|
+
}
|
|
236
|
+
else if (listUpdate.type === "move") {
|
|
237
|
+
if (listUpdate.previousIndex > listUpdate.index) {
|
|
238
|
+
newState = [
|
|
239
|
+
...newState.slice(0, listUpdate.index),
|
|
240
|
+
liveNodeToJson(listUpdate.item),
|
|
241
|
+
...newState.slice(listUpdate.index, listUpdate.previousIndex),
|
|
242
|
+
...newState.slice(listUpdate.previousIndex + 1),
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
newState = [
|
|
247
|
+
...newState.slice(0, listUpdate.previousIndex),
|
|
248
|
+
...newState.slice(listUpdate.previousIndex + 1, listUpdate.index + 1),
|
|
249
|
+
liveNodeToJson(listUpdate.item),
|
|
250
|
+
...newState.slice(listUpdate.index + 1),
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return newState;
|
|
190
256
|
}
|
|
191
257
|
case "LiveMap": {
|
|
192
258
|
if (typeof state !== "object") {
|
|
193
259
|
throw new Error("Internal: received update on LiveMap but state was not an object");
|
|
194
260
|
}
|
|
195
|
-
|
|
261
|
+
let newState = Object.assign({}, state);
|
|
262
|
+
for (const key in update.updates) {
|
|
263
|
+
if (((_c = update.updates[key]) === null || _c === void 0 ? void 0 : _c.type) === "update") {
|
|
264
|
+
newState[key] = liveNodeToJson(update.node.get(key));
|
|
265
|
+
}
|
|
266
|
+
else if (((_d = update.updates[key]) === null || _d === void 0 ? void 0 : _d.type) === "delete") {
|
|
267
|
+
delete newState[key];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return newState;
|
|
196
271
|
}
|
|
197
272
|
}
|
|
198
273
|
}
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { LiveObject } from "./LiveObject";
|
|
2
2
|
export { LiveMap } from "./LiveMap";
|
|
3
3
|
export { LiveList } from "./LiveList";
|
|
4
|
-
export type { Others, Presence, Room, Client, User } from "./types";
|
|
4
|
+
export type { Others, Presence, Room, Client, User, BroadcastOptions, } from "./types";
|
|
5
5
|
export { createClient } from "./client";
|
package/lib/esm/live.d.ts
CHANGED
|
@@ -122,6 +122,7 @@ export declare type UpdateObjectOp = {
|
|
|
122
122
|
};
|
|
123
123
|
};
|
|
124
124
|
export declare type CreateObjectOp = {
|
|
125
|
+
opId?: string;
|
|
125
126
|
id: string;
|
|
126
127
|
type: OpType.CreateObject;
|
|
127
128
|
parentId?: string;
|
|
@@ -131,18 +132,21 @@ export declare type CreateObjectOp = {
|
|
|
131
132
|
};
|
|
132
133
|
};
|
|
133
134
|
export declare type CreateListOp = {
|
|
135
|
+
opId?: string;
|
|
134
136
|
id: string;
|
|
135
137
|
type: OpType.CreateList;
|
|
136
138
|
parentId: string;
|
|
137
139
|
parentKey: string;
|
|
138
140
|
};
|
|
139
141
|
export declare type CreateMapOp = {
|
|
142
|
+
opId?: string;
|
|
140
143
|
id: string;
|
|
141
144
|
type: OpType.CreateMap;
|
|
142
145
|
parentId: string;
|
|
143
146
|
parentKey: string;
|
|
144
147
|
};
|
|
145
148
|
export declare type CreateRegisterOp = {
|
|
149
|
+
opId?: string;
|
|
146
150
|
id: string;
|
|
147
151
|
type: OpType.CreateRegister;
|
|
148
152
|
parentId: string;
|
|
@@ -150,15 +154,18 @@ export declare type CreateRegisterOp = {
|
|
|
150
154
|
data: any;
|
|
151
155
|
};
|
|
152
156
|
export declare type DeleteCrdtOp = {
|
|
157
|
+
opId?: string;
|
|
153
158
|
id: string;
|
|
154
159
|
type: OpType.DeleteCrdt;
|
|
155
160
|
};
|
|
156
161
|
export declare type SetParentKeyOp = {
|
|
162
|
+
opId?: string;
|
|
157
163
|
id: string;
|
|
158
164
|
type: OpType.SetParentKey;
|
|
159
165
|
parentKey: string;
|
|
160
166
|
};
|
|
161
167
|
export declare type DeleteObjectKeyOp = {
|
|
168
|
+
opId?: string;
|
|
162
169
|
id: string;
|
|
163
170
|
type: OpType.DeleteObjectKey;
|
|
164
171
|
key: string;
|
package/lib/esm/room.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Others, Presence,
|
|
1
|
+
import { Others, Presence, Room, MyPresenceCallback, OthersEventCallback, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback, StorageCallback, StorageUpdate, BroadcastOptions, AuthorizeResponse, Authentication } from "./types";
|
|
2
2
|
import { ClientMessage, Op } from "./live";
|
|
3
3
|
import { LiveMap } from "./LiveMap";
|
|
4
4
|
import { LiveObject } from "./LiveObject";
|
|
@@ -11,6 +11,7 @@ declare type HistoryItem = Array<Op | {
|
|
|
11
11
|
declare type IdFactory = () => string;
|
|
12
12
|
export declare type State = {
|
|
13
13
|
connection: Connection;
|
|
14
|
+
lastConnectionId: number | null;
|
|
14
15
|
socket: WebSocket | null;
|
|
15
16
|
lastFlushTime: number;
|
|
16
17
|
buffer: {
|
|
@@ -62,9 +63,10 @@ export declare type State = {
|
|
|
62
63
|
nodes: Set<AbstractCrdt>;
|
|
63
64
|
};
|
|
64
65
|
};
|
|
66
|
+
offlineOperations: Map<string, Op>;
|
|
65
67
|
};
|
|
66
68
|
export declare type Effects = {
|
|
67
|
-
authenticate(): void;
|
|
69
|
+
authenticate(auth: (room: string) => Promise<AuthorizeResponse>, createWebSocket: (token: string) => WebSocket): void;
|
|
68
70
|
send(messages: ClientMessage[]): void;
|
|
69
71
|
delayFlush(delay: number): number;
|
|
70
72
|
startHeartbeatInterval(): number;
|
|
@@ -73,13 +75,13 @@ export declare type Effects = {
|
|
|
73
75
|
};
|
|
74
76
|
declare type Context = {
|
|
75
77
|
room: string;
|
|
76
|
-
authEndpoint: AuthEndpoint;
|
|
77
|
-
liveblocksServer: string;
|
|
78
78
|
throttleDelay: number;
|
|
79
|
-
|
|
79
|
+
fetchPolyfill?: typeof fetch;
|
|
80
|
+
WebSocketPolyfill?: typeof WebSocket;
|
|
81
|
+
authentication: Authentication;
|
|
82
|
+
liveblocksServer: string;
|
|
80
83
|
};
|
|
81
84
|
export declare function makeStateMachine(state: State, context: Context, mockedEffects?: Effects): {
|
|
82
|
-
onOpen: () => void;
|
|
83
85
|
onClose: (event: {
|
|
84
86
|
code: number;
|
|
85
87
|
wasClean: boolean;
|
|
@@ -89,6 +91,12 @@ export declare function makeStateMachine(state: State, context: Context, mockedE
|
|
|
89
91
|
authenticationSuccess: (token: AuthenticationToken, socket: WebSocket) => void;
|
|
90
92
|
heartbeat: () => void;
|
|
91
93
|
onNavigatorOnline: () => void;
|
|
94
|
+
simulateSocketClose: () => void;
|
|
95
|
+
simulateSendCloseEvent: (event: {
|
|
96
|
+
code: number;
|
|
97
|
+
wasClean: boolean;
|
|
98
|
+
reason: any;
|
|
99
|
+
}) => void;
|
|
92
100
|
onVisibilityChange: (visibilityState: VisibilityState) => void;
|
|
93
101
|
getUndoStack: () => HistoryItem[];
|
|
94
102
|
getItemsCount: () => number;
|
|
@@ -118,7 +126,7 @@ export declare function makeStateMachine(state: State, context: Context, mockedE
|
|
|
118
126
|
updatePresence: <T_4 extends Presence>(overrides: Partial<T_4>, options?: {
|
|
119
127
|
addToHistory: boolean;
|
|
120
128
|
} | undefined) => void;
|
|
121
|
-
broadcastEvent: (event: any) => void;
|
|
129
|
+
broadcastEvent: (event: any, options?: BroadcastOptions) => void;
|
|
122
130
|
batch: (callback: () => void) => void;
|
|
123
131
|
undo: () => void;
|
|
124
132
|
redo: () => void;
|
|
@@ -144,8 +152,8 @@ export declare type InternalRoom = {
|
|
|
144
152
|
onNavigatorOnline: () => void;
|
|
145
153
|
onVisibilityChange: (visibilityState: VisibilityState) => void;
|
|
146
154
|
};
|
|
147
|
-
export declare function createRoom(
|
|
155
|
+
export declare function createRoom(options: {
|
|
148
156
|
defaultPresence?: Presence;
|
|
149
157
|
defaultStorageRoot?: Record<string, any>;
|
|
150
|
-
}): InternalRoom;
|
|
158
|
+
}, context: Context): InternalRoom;
|
|
151
159
|
export {};
|