@liveblocks/core 2.15.2 → 2.16.0-rc1

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/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "2.15.2";
9
+ var PKG_VERSION = "2.16.0-rc1";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -166,13 +166,18 @@ function wrapWithTitle(method) {
166
166
  var warnWithTitle = wrapWithTitle("warn");
167
167
  var errorWithTitle = wrapWithTitle("error");
168
168
 
169
+ // src/lib/guards.ts
170
+ function isPlainObject(blob) {
171
+ return blob !== null && typeof blob === "object" && Object.prototype.toString.call(blob) === "[object Object]";
172
+ }
173
+ function isStartsWithOperator(blob) {
174
+ return isPlainObject(blob) && typeof blob.startsWith === "string";
175
+ }
176
+
169
177
  // src/lib/utils.ts
170
178
  function raise(msg) {
171
179
  throw new Error(msg);
172
180
  }
173
- function isPlainObject(blob) {
174
- return blob !== null && typeof blob === "object" && Object.prototype.toString.call(blob) === "[object Object]";
175
- }
176
181
  function entries(obj) {
177
182
  return Object.entries(obj);
178
183
  }
@@ -254,13 +259,48 @@ function memoizeOnSuccess(factoryFn) {
254
259
  }
255
260
 
256
261
  // src/lib/autoRetry.ts
257
- var HttpError = class extends Error {
258
- constructor(message, status, details) {
262
+ var HttpError = class _HttpError extends Error {
263
+ response;
264
+ details;
265
+ constructor(message, response, details) {
259
266
  super(message);
260
- this.message = message;
261
- this.status = status;
267
+ this.name = "HttpError";
268
+ this.response = response;
262
269
  this.details = details;
263
270
  }
271
+ static async fromResponse(response) {
272
+ let bodyAsText;
273
+ try {
274
+ bodyAsText = await response.text();
275
+ } catch {
276
+ }
277
+ const bodyAsJson = bodyAsText ? tryParseJson(bodyAsText) : void 0;
278
+ let bodyAsJsonObject;
279
+ if (isPlainObject(bodyAsJson)) {
280
+ bodyAsJsonObject = bodyAsJson;
281
+ }
282
+ let message = "";
283
+ message ||= typeof bodyAsJsonObject?.message === "string" ? bodyAsJsonObject.message : "";
284
+ message ||= typeof bodyAsJsonObject?.error === "string" ? bodyAsJsonObject.error : "";
285
+ if (bodyAsJson === void 0) {
286
+ message ||= bodyAsText || "";
287
+ }
288
+ message ||= response.statusText;
289
+ let path;
290
+ try {
291
+ path = new URL(response.url).pathname;
292
+ } catch {
293
+ }
294
+ message += path !== void 0 ? ` (got status ${response.status} from ${path})` : ` (got status ${response.status})`;
295
+ const details = bodyAsJsonObject;
296
+ return new _HttpError(message, response, details);
297
+ }
298
+ /**
299
+ * Convenience accessor for response.status.
300
+ */
301
+ get status() {
302
+ return this.response.status;
303
+ }
264
304
  };
265
305
  var DONT_RETRY_4XX = (x) => x instanceof HttpError && x.status >= 400 && x.status < 500;
266
306
  async function autoRetry(promiseFn, maxTries, backoff, shouldStopRetrying = DONT_RETRY_4XX) {
@@ -326,7 +366,12 @@ function makeEventSource() {
326
366
  }).finally(() => unsub?.());
327
367
  }
328
368
  function notify(event) {
329
- _observers.forEach((callback) => callback(event));
369
+ let called = false;
370
+ for (const callback of _observers) {
371
+ callback(event);
372
+ called = true;
373
+ }
374
+ return called;
330
375
  }
331
376
  function count() {
332
377
  return _observers.size;
@@ -367,8 +412,9 @@ function makeBufferableEventSource() {
367
412
  function notifyOrBuffer(event) {
368
413
  if (_buffer !== null) {
369
414
  _buffer.push(event);
415
+ return false;
370
416
  } else {
371
- eventSource2.notify(event);
417
+ return eventSource2.notify(event);
372
418
  }
373
419
  }
374
420
  return {
@@ -673,32 +719,15 @@ var MutableSignal = class extends AbstractSignal {
673
719
  };
674
720
 
675
721
  // src/lib/stringify.ts
676
- var EXPLICIT_UNDEFINED_PLACEHOLDER = "_explicit_undefined";
677
722
  function replacer(_key, value) {
678
723
  return value !== null && typeof value === "object" && !Array.isArray(value) ? Object.keys(value).sort().reduce((sorted, key) => {
679
724
  sorted[key] = value[key];
680
725
  return sorted;
681
- }, {}) : value === void 0 ? EXPLICIT_UNDEFINED_PLACEHOLDER : value;
682
- }
683
- function reviver(key, value) {
684
- if (!key && value === EXPLICIT_UNDEFINED_PLACEHOLDER) {
685
- return void 0;
686
- }
687
- if (value && typeof value === "object") {
688
- for (const k in value) {
689
- if (value[k] === EXPLICIT_UNDEFINED_PLACEHOLDER) {
690
- Object.defineProperty(value, k, { value: void 0 });
691
- }
692
- }
693
- }
694
- return value;
726
+ }, {}) : value;
695
727
  }
696
728
  function stringify(value) {
697
729
  return JSON.stringify(value, replacer);
698
730
  }
699
- function unstringify(value) {
700
- return JSON.parse(value, reviver);
701
- }
702
731
 
703
732
  // src/lib/batch.ts
704
733
  var DEFAULT_SIZE = 50;
@@ -928,10 +957,12 @@ function objectToQuery(obj) {
928
957
  }
929
958
  if (isSimpleValue(value)) {
930
959
  keyValuePairs.push([key, value]);
931
- } else if (isValueWithOperator(value)) {
932
- keyValuePairsWithOperator.push([key, value]);
933
- } else if (typeof value === "object" && !("startsWith" in value)) {
934
- indexedKeys.push([key, value]);
960
+ } else if (isPlainObject(value)) {
961
+ if (isStartsWithOperator(value)) {
962
+ keyValuePairsWithOperator.push([key, value]);
963
+ } else {
964
+ indexedKeys.push([key, value]);
965
+ }
935
966
  }
936
967
  });
937
968
  filterList = [
@@ -948,7 +979,7 @@ function objectToQuery(obj) {
948
979
  }
949
980
  if (isSimpleValue(nestedValue)) {
950
981
  nKeyValuePairs.push([formatFilterKey(key, nestedKey), nestedValue]);
951
- } else if (isValueWithOperator(nestedValue)) {
982
+ } else if (isStartsWithOperator(nestedValue)) {
952
983
  nKeyValuePairsWithOperator.push([
953
984
  formatFilterKey(key, nestedKey),
954
985
  nestedValue
@@ -961,9 +992,7 @@ function objectToQuery(obj) {
961
992
  ...getFiltersFromKeyValuePairsWithOperator(nKeyValuePairsWithOperator)
962
993
  ];
963
994
  });
964
- return filterList.map(
965
- ({ key, operator, value }) => formatFilter(key, operator, formatFilterValue(value))
966
- ).join(" AND ");
995
+ return filterList.map(({ key, operator, value }) => `${key}${operator}${quote(value)}`).join(" ");
967
996
  }
968
997
  var getFiltersFromKeyValuePairs = (keyValuePairs) => {
969
998
  const filters = [];
@@ -990,38 +1019,20 @@ var getFiltersFromKeyValuePairsWithOperator = (keyValuePairsWithOperator) => {
990
1019
  return filters;
991
1020
  };
992
1021
  var isSimpleValue = (value) => {
993
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
994
- return true;
995
- }
996
- return false;
997
- };
998
- var isValueWithOperator = (value) => {
999
- if (typeof value === "object" && value !== null && "startsWith" in value) {
1000
- return true;
1001
- }
1002
- return false;
1003
- };
1004
- var formatFilter = (key, operator, value) => {
1005
- return `${key}${operator}${value}`;
1022
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
1006
1023
  };
1007
1024
  var formatFilterKey = (key, nestedKey) => {
1008
1025
  if (nestedKey) {
1009
- return `${key}[${JSON.stringify(nestedKey)}]`;
1026
+ return `${key}[${quote(nestedKey)}]`;
1010
1027
  }
1011
1028
  return key;
1012
1029
  };
1013
- var formatFilterValue = (value) => {
1014
- if (typeof value === "string") {
1015
- if (isStringEmpty(value)) {
1016
- throw new Error("Value cannot be empty");
1017
- }
1018
- return JSON.stringify(value);
1019
- }
1020
- return value.toString();
1021
- };
1022
1030
  var isStringEmpty = (value) => {
1023
1031
  return !value || value.toString().trim() === "";
1024
1032
  };
1033
+ function quote(value) {
1034
+ return typeof value !== "string" || value.includes("'") ? JSON.stringify(value) : `'${value}'`;
1035
+ }
1025
1036
 
1026
1037
  // src/lib/url.ts
1027
1038
  function toURLSearchParams(params) {
@@ -1846,14 +1857,7 @@ var HttpClient = class {
1846
1857
  async #fetch(endpoint, authValue, options, params) {
1847
1858
  const response = await this.#rawFetch(endpoint, authValue, options, params);
1848
1859
  if (!response.ok) {
1849
- let error3;
1850
- try {
1851
- const errorBody = await response.json();
1852
- error3 = new HttpError(errorBody.message, response.status, errorBody);
1853
- } catch {
1854
- error3 = new HttpError(response.statusText, response.status);
1855
- }
1856
- throw error3;
1860
+ throw await HttpError.fromResponse(response);
1857
1861
  }
1858
1862
  let body;
1859
1863
  try {
@@ -2480,6 +2484,7 @@ function toNewConnectionStatus(machine) {
2480
2484
  return machine.context.successCount > 0 ? "reconnecting" : "connecting";
2481
2485
  case "@idle.failed":
2482
2486
  return "disconnected";
2487
+ // istanbul ignore next
2483
2488
  default:
2484
2489
  return assertNever(state, "Unknown state");
2485
2490
  }
@@ -2496,13 +2501,6 @@ var StopRetrying = class extends Error {
2496
2501
  super(reason);
2497
2502
  }
2498
2503
  };
2499
- var LiveblocksError = class extends Error {
2500
- /** @internal */
2501
- constructor(message, code) {
2502
- super(message);
2503
- this.code = code;
2504
- }
2505
- };
2506
2504
  function nextBackoffDelay(currentDelay, delays) {
2507
2505
  return delays.find((delay) => delay > currentDelay) ?? delays[delays.length - 1];
2508
2506
  }
@@ -2612,11 +2610,10 @@ var assign = (patch) => (ctx) => ctx.patch(patch);
2612
2610
  function createConnectionStateMachine(delegates, options) {
2613
2611
  const onMessage = makeBufferableEventSource();
2614
2612
  onMessage.pause();
2615
- const onLiveblocksError = makeEventSource();
2616
- function fireErrorEvent(errmsg, errcode) {
2613
+ const onConnectionError = makeEventSource();
2614
+ function fireErrorEvent(message, code) {
2617
2615
  return () => {
2618
- const err = new LiveblocksError(errmsg, errcode);
2619
- onLiveblocksError.notify(err);
2616
+ onConnectionError.notify({ message, code });
2620
2617
  };
2621
2618
  }
2622
2619
  const initialContext = {
@@ -2974,7 +2971,7 @@ function createConnectionStateMachine(delegates, options) {
2974
2971
  didConnect,
2975
2972
  didDisconnect,
2976
2973
  onMessage: onMessage.observable,
2977
- onLiveblocksError: onLiveblocksError.observable
2974
+ onConnectionError: onConnectionError.observable
2978
2975
  }
2979
2976
  };
2980
2977
  }
@@ -3360,6 +3357,12 @@ function setupDevTools(getAllRooms) {
3360
3357
  _devtoolsSetupHasRun = true;
3361
3358
  onMessageFromPanel.subscribe((msg) => {
3362
3359
  switch (msg.msg) {
3360
+ // When a devtool panel sends an explicit "connect" message back to this
3361
+ // live running client (in response to the "wake-up-devtools" message,
3362
+ // or when the devtool panel is opened for the first time), it means that it's okay to
3363
+ // start emitting messages.
3364
+ // Before this explicit acknowledgement, any call to sendToPanel() will
3365
+ // be a no-op.
3363
3366
  case "connect": {
3364
3367
  activateBridge(true);
3365
3368
  for (const roomId of getAllRooms()) {
@@ -3500,6 +3503,8 @@ function linkDevTools(roomId, room) {
3500
3503
  // roomChannelListeners registry
3501
3504
  onMessageFromPanel.subscribe((msg) => {
3502
3505
  switch (msg.msg) {
3506
+ // Sent by the devtool panel when it wants to receive the sync stream
3507
+ // for a room
3503
3508
  case "room::subscribe": {
3504
3509
  if (msg.roomId === roomId) {
3505
3510
  startSyncStream(room);
@@ -6230,6 +6235,83 @@ var ManagedOthers = class {
6230
6235
  }
6231
6236
  };
6232
6237
 
6238
+ // src/types/LiveblocksError.ts
6239
+ var LiveblocksError = class _LiveblocksError extends Error {
6240
+ context;
6241
+ constructor(message, context, cause) {
6242
+ super(message, { cause });
6243
+ this.context = context;
6244
+ this.name = "LiveblocksError";
6245
+ }
6246
+ /** Convenience accessor for error.context.roomId (if available) */
6247
+ get roomId() {
6248
+ return this.context.roomId;
6249
+ }
6250
+ /** @deprecated Prefer using `context.code` instead, to enable type narrowing */
6251
+ get code() {
6252
+ return this.context.code;
6253
+ }
6254
+ /**
6255
+ * Creates a LiveblocksError from a generic error, by attaching Liveblocks
6256
+ * contextual information like room ID, thread ID, etc.
6257
+ */
6258
+ static from(context, cause) {
6259
+ return new _LiveblocksError(
6260
+ defaultMessageFromContext(context),
6261
+ context,
6262
+ cause
6263
+ );
6264
+ }
6265
+ };
6266
+ function defaultMessageFromContext(context) {
6267
+ switch (context.type) {
6268
+ case "ROOM_CONNECTION_ERROR": {
6269
+ switch (context.code) {
6270
+ case 4001:
6271
+ return "Not allowed to connect to the room";
6272
+ case 4005:
6273
+ return "Room is already full";
6274
+ case 4006:
6275
+ return "Kicked out of the room, because the room ID changed";
6276
+ default:
6277
+ return "Could not connect to the room";
6278
+ }
6279
+ }
6280
+ case "CREATE_THREAD_ERROR":
6281
+ return "Could not create new thread";
6282
+ case "DELETE_THREAD_ERROR":
6283
+ return "Could not delete thread";
6284
+ case "EDIT_THREAD_METADATA_ERROR":
6285
+ return "Could not edit thread metadata";
6286
+ case "MARK_THREAD_AS_RESOLVED_ERROR":
6287
+ return "Could not mark thread as resolved";
6288
+ case "MARK_THREAD_AS_UNRESOLVED_ERROR":
6289
+ return "Could not mark thread as unresolved";
6290
+ case "CREATE_COMMENT_ERROR":
6291
+ return "Could not create new comment";
6292
+ case "EDIT_COMMENT_ERROR":
6293
+ return "Could not edit comment";
6294
+ case "DELETE_COMMENT_ERROR":
6295
+ return "Could not delete comment";
6296
+ case "ADD_REACTION_ERROR":
6297
+ return "Could not add reaction";
6298
+ case "REMOVE_REACTION_ERROR":
6299
+ return "Could not remove reaction";
6300
+ case "MARK_INBOX_NOTIFICATION_AS_READ_ERROR":
6301
+ return "Could not mark inbox notification as read";
6302
+ case "DELETE_INBOX_NOTIFICATION_ERROR":
6303
+ return "Could not delete inbox notification";
6304
+ case "MARK_ALL_INBOX_NOTIFICATIONS_AS_READ_ERROR":
6305
+ return "Could not mark all inbox notifications as read";
6306
+ case "DELETE_ALL_INBOX_NOTIFICATIONS_ERROR":
6307
+ return "Could not delete all inbox notifications";
6308
+ case "UPDATE_NOTIFICATION_SETTINGS_ERROR":
6309
+ return "Could not update notification settings";
6310
+ default:
6311
+ return assertNever(context, "Unhandled case");
6312
+ }
6313
+ }
6314
+
6233
6315
  // src/room.ts
6234
6316
  var MAX_SOCKET_MESSAGE_SIZE = 1024 * 1024 - 1024;
6235
6317
  function makeIdFactory(connectionId) {
@@ -6394,13 +6476,17 @@ function createRoom(options, config) {
6394
6476
  managedSocket.events.statusDidChange.subscribe(handleConnectionLossEvent);
6395
6477
  managedSocket.events.didConnect.subscribe(onDidConnect);
6396
6478
  managedSocket.events.didDisconnect.subscribe(onDidDisconnect);
6397
- managedSocket.events.onLiveblocksError.subscribe((err) => {
6398
- if (process.env.NODE_ENV !== "production") {
6399
- error2(
6400
- `Connection to websocket server closed. Reason: ${err.message} (code: ${err.code}).`
6401
- );
6479
+ managedSocket.events.onConnectionError.subscribe(({ message, code }) => {
6480
+ const type = "ROOM_CONNECTION_ERROR";
6481
+ const err = new LiveblocksError(message, { type, code, roomId });
6482
+ const didNotify = config.errorEventSource.notify(err);
6483
+ if (!didNotify) {
6484
+ if (process.env.NODE_ENV !== "production") {
6485
+ error2(
6486
+ `Connection to websocket server closed. Reason: ${message} (code: ${code}).`
6487
+ );
6488
+ }
6402
6489
  }
6403
- eventHub.error.notify(err);
6404
6490
  });
6405
6491
  const pool = {
6406
6492
  roomId: config.roomId,
@@ -6463,7 +6549,6 @@ function createRoom(options, config) {
6463
6549
  self: makeEventSource(),
6464
6550
  myPresence: makeEventSource(),
6465
6551
  others: makeEventSource(),
6466
- error: makeEventSource(),
6467
6552
  storageBatch: makeEventSource(),
6468
6553
  history: makeEventSource(),
6469
6554
  storageDidLoad: makeEventSource(),
@@ -6942,6 +7027,7 @@ function createRoom(options, config) {
6942
7027
  processInitialStorage(message);
6943
7028
  break;
6944
7029
  }
7030
+ // Write event
6945
7031
  case 201 /* UPDATE_STORAGE */: {
6946
7032
  const applyResult = applyOps(message.ops, false);
6947
7033
  for (const [key, value] of applyResult.updates.storageUpdates) {
@@ -6952,6 +7038,11 @@ function createRoom(options, config) {
6952
7038
  }
6953
7039
  break;
6954
7040
  }
7041
+ // Receiving a RejectedOps message in the client means that the server is no
7042
+ // longer in sync with the client. Trying to synchronize the client again by
7043
+ // rolling back particular Ops may be hard/impossible. It's fine to not try and
7044
+ // accept the out-of-sync reality and throw an error. We look at this kind of bug
7045
+ // as a developer-owned bug. In production, these errors are not expected to happen.
6955
7046
  case 299 /* REJECT_STORAGE_OP */: {
6956
7047
  errorWithTitle(
6957
7048
  "Storage mutation rejection error",
@@ -7295,7 +7386,6 @@ ${Array.from(traces).join("\n\n")}`
7295
7386
  others: eventHub.others.observable,
7296
7387
  self: eventHub.self.observable,
7297
7388
  myPresence: eventHub.myPresence.observable,
7298
- error: eventHub.error.observable,
7299
7389
  /** @deprecated */
7300
7390
  storage: eventHub.storageBatch.observable,
7301
7391
  storageBatch: eventHub.storageBatch.observable,
@@ -7486,7 +7576,11 @@ ${Array.from(traces).join("\n\n")}`
7486
7576
  attachmentUrlsStore: httpClient.getOrCreateAttachmentUrlsStore(roomId)
7487
7577
  },
7488
7578
  id: config.roomId,
7489
- subscribe: makeClassicSubscribeFn(events),
7579
+ subscribe: makeClassicSubscribeFn(
7580
+ config.roomId,
7581
+ events,
7582
+ config.errorEventSource
7583
+ ),
7490
7584
  connect: () => managedSocket.connect(),
7491
7585
  reconnect: () => managedSocket.reconnect(),
7492
7586
  disconnect: () => managedSocket.disconnect(),
@@ -7555,7 +7649,7 @@ ${Array.from(traces).join("\n\n")}`
7555
7649
  { enumerable: false }
7556
7650
  );
7557
7651
  }
7558
- function makeClassicSubscribeFn(events) {
7652
+ function makeClassicSubscribeFn(roomId, events, errorEvents) {
7559
7653
  function subscribeToLiveStructureDeeply(node, callback) {
7560
7654
  return events.storageBatch.subscribe((updates) => {
7561
7655
  const relatedUpdates = updates.filter(
@@ -7595,8 +7689,13 @@ function makeClassicSubscribeFn(events) {
7595
7689
  return cb(others, internalEvent);
7596
7690
  });
7597
7691
  }
7598
- case "error":
7599
- return events.error.subscribe(callback);
7692
+ case "error": {
7693
+ return errorEvents.subscribe((err) => {
7694
+ if (err.roomId === roomId) {
7695
+ return callback(err);
7696
+ }
7697
+ });
7698
+ }
7600
7699
  case "status":
7601
7700
  return events.status.subscribe(callback);
7602
7701
  case "lost-connection":
@@ -7613,6 +7712,7 @@ function makeClassicSubscribeFn(events) {
7613
7712
  return events.comments.subscribe(
7614
7713
  callback
7615
7714
  );
7715
+ // istanbul ignore next
7616
7716
  default:
7617
7717
  return assertNever(
7618
7718
  first,
@@ -7767,6 +7867,7 @@ function createClient(options) {
7767
7867
  },
7768
7868
  enableDebugLogging: clientOptions.enableDebugLogging,
7769
7869
  baseUrl,
7870
+ errorEventSource: liveblocksErrorSource,
7770
7871
  unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP,
7771
7872
  unstable_streamData: !!clientOptions.unstable_streamData,
7772
7873
  roomHttpClient: httpClient,
@@ -7849,6 +7950,7 @@ function createClient(options) {
7849
7950
  }
7850
7951
  const syncStatusSources = [];
7851
7952
  const syncStatusSignal = new Signal("synchronized");
7953
+ const liveblocksErrorSource = makeEventSource();
7852
7954
  function getSyncStatus() {
7853
7955
  const status = syncStatusSignal.get();
7854
7956
  return status === "synchronizing" ? status : "synchronized";
@@ -7908,6 +8010,7 @@ function createClient(options) {
7908
8010
  },
7909
8011
  getSyncStatus,
7910
8012
  events: {
8013
+ error: liveblocksErrorSource,
7911
8014
  syncStatus: syncStatusSignal
7912
8015
  },
7913
8016
  // Internal
@@ -7923,7 +8026,14 @@ function createClient(options) {
7923
8026
  httpClient,
7924
8027
  // Type-level helper only, it's effectively only an identity-function at runtime
7925
8028
  as: () => client,
7926
- createSyncSource
8029
+ createSyncSource,
8030
+ emitError: (context, cause) => {
8031
+ const error3 = LiveblocksError.from(context, cause);
8032
+ const didNotify = liveblocksErrorSource.notify(error3);
8033
+ if (!didNotify) {
8034
+ error2(error3.message);
8035
+ }
8036
+ }
7927
8037
  }
7928
8038
  },
7929
8039
  kInternal,
@@ -8901,6 +9011,7 @@ export {
8901
9011
  LiveList,
8902
9012
  LiveMap,
8903
9013
  LiveObject,
9014
+ LiveblocksError,
8904
9015
  MutableSignal,
8905
9016
  NotificationsApiError,
8906
9017
  OpCode,
@@ -8950,6 +9061,7 @@ export {
8950
9061
  isLiveNode,
8951
9062
  isPlainObject,
8952
9063
  isRootCrdt,
9064
+ isStartsWithOperator,
8953
9065
  kInternal,
8954
9066
  legacy_patchImmutableObject,
8955
9067
  lsonToJson,
@@ -8971,7 +9083,6 @@ export {
8971
9083
  toAbsoluteUrl,
8972
9084
  toPlainLson,
8973
9085
  tryParseJson,
8974
- unstringify,
8975
9086
  url,
8976
9087
  urljoin,
8977
9088
  wait,