{"version":3,"sources":["/home/runner/work/liveblocks/liveblocks/packages/liveblocks-core/dist/index.cjs","../src/version.ts","../src/dupe-detection.ts","../src/convert-plain-data.ts","../src/lib/fancy-console.ts","../src/lib/guards.ts","../src/lib/utils.ts","../src/lib/autoRetry.ts","../src/lib/controlledPromise.ts","../src/lib/EventSource.ts","../src/lib/freeze.ts","../src/lib/signals.ts","../src/lib/stringify.ts","../src/lib/batch.ts","../src/lib/chunk.ts","../src/lib/nanoid.ts","../src/lib/createIds.ts","../src/lib/DefaultMap.ts","../src/lib/objectToQuery.ts","../src/lib/url.ts","../src/api-client.ts","../src/lib/assert.ts","../src/lib/fsm.ts","../src/protocol/ServerMsg.ts","../src/types/IWebSocket.ts","../src/connection.ts","../src/internal.ts","../src/lib/shallow.ts","../src/lib/SortedList.ts","../src/lib/TreePool.ts","../src/protocol/AuthToken.ts","../src/types/ai.ts","../src/ai.ts","../src/auth-manager.ts","../src/constants.ts","../src/devtools/bridge.ts","../src/devtools/index.ts","../src/protocol/NotificationSettings.ts","../src/lib/position.ts","../src/protocol/Op.ts","../src/crdts/AbstractCrdt.ts","../src/protocol/SerializedCrdt.ts","../src/crdts/LiveRegister.ts","../src/crdts/LiveList.ts","../src/crdts/LiveMap.ts","../src/crdts/LiveObject.ts","../src/crdts/liveblocks-helpers.ts","../src/lib/debug.ts","../src/lib/Deque.ts","../src/lib/Json.ts","../src/protocol/ClientMsg.ts","../src/refs/ManagedOthers.ts","../src/types/LiveblocksError.ts","../src/room.ts","../src/client.ts","../src/comments/comment-body.ts","../src/comments/comment-url.ts","../src/crdts/utils.ts","../src/immutable.ts","../src/lib/abortController.ts","../src/lib/deprecation.ts","../src/lib/Poller.ts","../src/protocol/Subscriptions.ts","../src/types/Others.ts","../src/index.ts"],"names":["error","eventSource","call","batch","ServerMsgCode","WebsocketCloseCodes","Permission","message","OpCode","CrdtType","id","parentKey","before","entries","values","ClientMsgCode","options","updates","event","now","url","BACKOFF_DELAYS","TextEditorType"],"mappings":"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AAA;AAEyB,EAAA;AACA,IAAA;AACX,IAAA;AACG,IAAA;AACjB,EAAA;AAE8B,EAAA;AACP,IAAA;AACvB,EAAA;AAKsB,kBAAA;AACU,kBAAA;AACD,kBAAA;AAErB,EAAA;AACH,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAAA;AAGA,IAAA;AACF,EAAA;AACF;A7D+lUoC;AACA;A8D/xUlC;AAG4B,EAAA;AACA,IAAA;AAC5B,EAAA;AAE+B,EAAA;AACjC;A9D8xUoC;AACA;A+D/yU7B;AACK,EAAA;AACD,EAAA;AACG,EAAA;AAHFC,EAAAA;AAAA;A/DszUwB;AACA;AgEl1UD;AhEo1UC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/liveblocks/liveblocks/packages/liveblocks-core/dist/index.cjs","sourcesContent":[null,"declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/core\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n","import { PKG_NAME, PKG_VERSION } from \"./version\";\n\nconst g = (\n  typeof globalThis !== \"undefined\"\n    ? globalThis\n    : typeof window !== \"undefined\"\n      ? window\n      : typeof global !== \"undefined\"\n        ? global\n        : {}\n) as { [key: symbol]: string };\n\nconst crossLinkedDocs = \"https://liveblocks.io/docs/errors/cross-linked\";\nconst dupesDocs = \"https://liveblocks.io/docs/errors/dupes\";\nconst SPACE = \" \"; // Important space to make sure links in errors are clickable in all browsers\n\nfunction error(msg: string): void {\n  if (process.env.NODE_ENV === \"production\") {\n    // eslint-disable-next-line rulesdir/console-must-be-fancy\n    console.error(msg);\n  } else {\n    throw new Error(msg);\n  }\n}\n\n/**\n * Throws an error if multiple copies of a Liveblocks package are being loaded\n * at runtime. This likely indicates a packaging issue with the project.\n */\nexport function detectDupes(\n  pkgName: string,\n  pkgVersion: string | false, // false if not built yet\n  pkgFormat: string | false // false if not built yet\n): void {\n  const pkgId = Symbol.for(pkgName);\n  const pkgBuildInfo = pkgFormat\n    ? `${pkgVersion || \"dev\"} (${pkgFormat})`\n    : pkgVersion || \"dev\";\n\n  if (!g[pkgId]) {\n    g[pkgId] = pkgBuildInfo;\n  } else if (g[pkgId] === pkgBuildInfo) {\n    // Allow it, see https://github.com/liveblocks/liveblocks/pull/1004\n  } else {\n    const msg = [\n      `Multiple copies of Liveblocks are being loaded in your project. This will cause issues! See ${\n        dupesDocs + SPACE\n      }`,\n      \"\",\n      \"Conflicts:\",\n      `- ${pkgName} ${g[pkgId]} (already loaded)`,\n      `- ${pkgName} ${pkgBuildInfo} (trying to load this now)`,\n    ].join(\"\\n\");\n    error(msg);\n  }\n\n  if (pkgVersion && PKG_VERSION && pkgVersion !== PKG_VERSION) {\n    error(\n      [\n        `Cross-linked versions of Liveblocks found, which will cause issues! See ${\n          crossLinkedDocs + SPACE\n        }`,\n        \"\",\n        \"Conflicts:\",\n        `- ${PKG_NAME} is at ${PKG_VERSION}`,\n        `- ${pkgName} is at ${pkgVersion}`,\n        \"\",\n        \"Always upgrade all Liveblocks packages to the same version number.\",\n      ].join(\"\\n\")\n    );\n  }\n}\n","import type {\n  BaseMetadata,\n  CommentData,\n  CommentDataPlain,\n  CommentUserReaction,\n  CommentUserReactionPlain,\n  ThreadData,\n  ThreadDataPlain,\n  ThreadDeleteInfo,\n  ThreadDeleteInfoPlain,\n} from \"./protocol/Comments\";\nimport type {\n  InboxNotificationData,\n  InboxNotificationDataPlain,\n  InboxNotificationDeleteInfo,\n  InboxNotificationDeleteInfoPlain,\n} from \"./protocol/InboxNotifications\";\nimport type {\n  SubscriptionData,\n  SubscriptionDataPlain,\n  SubscriptionDeleteInfo,\n  SubscriptionDeleteInfoPlain,\n  UserSubscriptionData,\n  UserSubscriptionDataPlain,\n} from \"./protocol/Subscriptions\";\n\n/**\n * Converts a plain comment data object (usually returned by the API) to a comment data object that can be used by the client.\n * This is necessary because the plain data object stores dates as ISO strings, but the client expects them as Date objects.\n * @param data The plain comment data object (usually returned by the API)\n * @returns The rich comment data object that can be used by the client.\n */\nexport function convertToCommentData(data: CommentDataPlain): CommentData {\n  const editedAt = data.editedAt ? new Date(data.editedAt) : undefined;\n  const createdAt = new Date(data.createdAt);\n  const reactions = data.reactions.map((reaction) => ({\n    ...reaction,\n    createdAt: new Date(reaction.createdAt),\n  }));\n\n  if (data.body) {\n    return {\n      ...data,\n      reactions,\n      createdAt,\n      editedAt,\n    };\n  } else {\n    const deletedAt = new Date(data.deletedAt);\n    return {\n      ...data,\n      reactions,\n      createdAt,\n      editedAt,\n      deletedAt,\n    };\n  }\n}\n\n/**\n * Converts a plain thread data object (usually returned by the API) to a thread data object that can be used by the client.\n * This is necessary because the plain data object stores dates as ISO strings, but the client expects them as Date objects.\n * @param data The plain thread data object (usually returned by the API)\n * @returns The rich thread data object that can be used by the client.\n */\nexport function convertToThreadData<M extends BaseMetadata>(\n  data: ThreadDataPlain<M>\n): ThreadData<M> {\n  const createdAt = new Date(data.createdAt);\n  const updatedAt = new Date(data.updatedAt);\n\n  const comments = data.comments.map((comment) =>\n    convertToCommentData(comment)\n  );\n\n  return {\n    ...data,\n    createdAt,\n    updatedAt,\n    comments,\n  };\n}\n\n/**\n * Converts a plain comment reaction object (usually returned by the API) to a comment reaction object that can be used by the client.\n * This is necessary because the plain data object stores dates as ISO strings, but the client expects them as Date objects.\n * @param data The plain comment reaction object (usually returned by the API)\n * @returns The rich comment reaction object that can be used by the client.\n */\nexport function convertToCommentUserReaction(\n  data: CommentUserReactionPlain\n): CommentUserReaction {\n  return {\n    ...data,\n    createdAt: new Date(data.createdAt),\n  };\n}\n\n/**\n * Converts a plain inbox notification data object (usually returned by the API) to an inbox notification data object that can be used by the client.\n * This is necessary because the plain data object stores dates as ISO strings, but the client expects them as Date objects.\n * @param data The plain inbox notification data object (usually returned by the API)\n * @returns The rich inbox notification data object that can be used by the client.\n */\nexport function convertToInboxNotificationData(\n  data: InboxNotificationDataPlain\n): InboxNotificationData {\n  const notifiedAt = new Date(data.notifiedAt);\n  const readAt = data.readAt ? new Date(data.readAt) : null;\n\n  if (\"activities\" in data) {\n    const activities = data.activities.map((activity) => ({\n      ...activity,\n      createdAt: new Date(activity.createdAt),\n    }));\n\n    return {\n      ...data,\n      notifiedAt,\n      readAt,\n      activities,\n    };\n  }\n\n  return {\n    ...data,\n    notifiedAt,\n    readAt,\n  };\n}\n\n/**\n * Converts a plain subscription data object (usually returned by the API) to a subscription data object that can be used by the client.\n * This is necessary because the plain data object stores dates as ISO strings, but the client expects them as Date objects.\n * @param data The plain subscription data object (usually returned by the API)\n * @returns The rich subscription data object that can be used by the client.\n */\nexport function convertToSubscriptionData(\n  data: SubscriptionDataPlain\n): SubscriptionData {\n  const createdAt = new Date(data.createdAt);\n\n  return {\n    ...data,\n    createdAt,\n  };\n}\n\n/**\n * Converts a plain user subscription data object (usually returned by the API) to a user subscription data object that can be used by the client.\n * This is necessary because the plain data object stores dates as ISO strings, but the client expects them as Date objects.\n * @param data The plain user subscription data object (usually returned by the API)\n * @returns The rich user subscription data object that can be used by the client.\n */\nexport function convertToUserSubscriptionData(\n  data: UserSubscriptionDataPlain\n): UserSubscriptionData {\n  const createdAt = new Date(data.createdAt);\n\n  return {\n    ...data,\n    createdAt,\n  };\n}\n\nexport function convertToThreadDeleteInfo(\n  data: ThreadDeleteInfoPlain\n): ThreadDeleteInfo {\n  const deletedAt = new Date(data.deletedAt);\n\n  return {\n    ...data,\n    deletedAt,\n  };\n}\n\nexport function convertToInboxNotificationDeleteInfo(\n  data: InboxNotificationDeleteInfoPlain\n): InboxNotificationDeleteInfo {\n  const deletedAt = new Date(data.deletedAt);\n\n  return {\n    ...data,\n    deletedAt,\n  };\n}\n\nexport function convertToSubscriptionDeleteInfo(\n  data: SubscriptionDeleteInfoPlain\n): SubscriptionDeleteInfo {\n  const deletedAt = new Date(data.deletedAt);\n\n  return {\n    ...data,\n    deletedAt,\n  };\n}\n","/* eslint-disable rulesdir/console-must-be-fancy */\n\nconst badge =\n  \"background:#0e0d12;border-radius:9999px;color:#fff;padding:3px 7px;font-family:sans-serif;font-weight:600;\";\nconst bold = \"font-weight:600\";\n\nfunction wrap(\n  method: \"log\" | \"warn\" | \"error\"\n): (message: string, ...args: readonly unknown[]) => void {\n  return typeof window === \"undefined\" || process.env.NODE_ENV === \"test\"\n    ? console[method]\n    : /* istanbul ignore next */\n      (message, ...args) =>\n        console[method](\"%cLiveblocks\", badge, message, ...args);\n}\n\n// export const log = wrap(\"log\");\nexport const warn = wrap(\"warn\");\nexport const error = wrap(\"error\");\n\nfunction wrapWithTitle(\n  method: \"log\" | \"warn\" | \"error\"\n): (title: string, message: string, ...args: readonly unknown[]) => void {\n  return typeof window === \"undefined\" || process.env.NODE_ENV === \"test\"\n    ? console[method]\n    : /* istanbul ignore next */\n      (title, message, ...args) =>\n        console[method](\n          `%cLiveblocks%c ${title}`,\n          badge,\n          bold,\n          message,\n          ...args\n        );\n}\n\n// export const logWithTitle = wrapWithTitle(\"log\");\nexport const warnWithTitle = wrapWithTitle(\"warn\");\nexport const errorWithTitle = wrapWithTitle(\"error\");\n","export function isDefined<T>(value: T): value is NonNullable<T> {\n  return value !== null && value !== undefined;\n}\n\nexport function isPlainObject(\n  blob: unknown\n): blob is { [key: string]: unknown } {\n  // Implementation borrowed from pojo decoder, see\n  // https://github.com/nvie/decoders/blob/78849f843193647eb6b5307240387bdcff7161fb/src/lib/objects.js#L10-L41\n  return (\n    blob !== null &&\n    typeof blob === \"object\" &&\n    Object.prototype.toString.call(blob) === \"[object Object]\"\n  );\n}\n\n/**\n * Check if value is of shape { startsWith: string }\n */\nexport function isStartsWithOperator(\n  blob: unknown\n): blob is { startsWith: string } {\n  return isPlainObject(blob) && typeof blob.startsWith === \"string\";\n}\n","import type { Json } from \"./Json\";\n\ndeclare const brand: unique symbol;\nexport type Brand<T, TBrand extends string> = T & { [brand]: TBrand };\n\nexport type DistributiveOmit<T, K extends PropertyKey> = T extends any\n  ? Omit<T, K>\n  : never;\n\n// export type DistributivePick<T, K extends keyof T> = T extends any\n//   ? Pick<T, K>\n//   : never;\n\n/**\n * Throw an error, but as an expression instead of a statement.\n */\nexport function raise(msg: string): never {\n  throw new Error(msg);\n}\n\n/**\n * Drop-in replacement for Object.entries() that retains better types.\n */\nexport function entries<\n  O extends { [key: string]: unknown },\n  K extends keyof O,\n>(obj: O): [K, O[K]][] {\n  return Object.entries(obj) as [K, O[K]][];\n}\n\n/**\n * Drop-in replacement for Object.keys() that retains better types.\n */\nexport function keys<O extends { [key: string]: unknown }, K extends keyof O>(\n  obj: O\n): K[] {\n  return Object.keys(obj) as K[];\n}\n\n/**\n * Drop-in replacement for Object.values() that retains better types.\n */\nexport function values<O extends Record<string, unknown>>(\n  obj: O\n): O[keyof O][] {\n  return Object.values(obj) as O[keyof O][];\n}\n\n/**\n * Drop-in replacement for Object.create() that retains better types.\n */\nexport function create<O extends Record<string, unknown>>(\n  obj: O | null,\n  descriptors?: PropertyDescriptorMap & ThisType<O>\n): O {\n  if (typeof descriptors !== \"undefined\") {\n    return Object.create(obj, descriptors) as O;\n  }\n\n  return Object.create(obj) as O;\n}\n\n/**\n * Creates a new object by mapping a function over all values. Keys remain the\n * same. Think Array.prototype.map(), but for values in an object.\n */\nexport function mapValues<V, O extends Record<string, unknown>>(\n  obj: O,\n  mapFn: (value: O[keyof O], key: keyof O) => V\n): { [K in keyof O]: V } {\n  const result = {} as { [K in keyof O]: V };\n  for (const pair of Object.entries(obj)) {\n    const key: keyof O = pair[0];\n    if (key === \"__proto__\") {\n      // Avoid setting dangerous __proto__ keys\n      continue;\n    }\n    const value = pair[1] as O[keyof O];\n    result[key] = mapFn(value, key);\n  }\n  return result;\n}\n\n/**\n * Alternative to JSON.parse() that will not throw in production. If the passed\n * string cannot be parsed, this will return `undefined`.\n */\nexport function tryParseJson(rawMessage: string): Json | undefined {\n  try {\n    // eslint-disable-next-line no-restricted-syntax\n    return JSON.parse(rawMessage) as Json;\n  } catch (e) {\n    return undefined;\n  }\n}\n\n/**\n * Deep-clones a JSON-serializable value.\n *\n * NOTE: We should be able to replace `deepClone` by `structuredClone` once\n * we've upgraded to Node 18.\n */\nexport function deepClone<T extends Json>(value: T): T {\n  // NOTE: In this case, the combination of JSON.parse() and JSON.stringify\n  // won't lead to type unsafety, so this use case is okay.\n  // eslint-disable-next-line no-restricted-syntax\n  return JSON.parse(JSON.stringify(value)) as T;\n}\n\n/**\n * Decode base64 string.\n */\nexport function b64decode(b64value: string): string {\n  try {\n    const formattedValue = b64value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n    const decodedValue = decodeURIComponent(\n      atob(formattedValue)\n        .split(\"\")\n        .map(function (c) {\n          return \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2);\n        })\n        .join(\"\")\n    );\n\n    return decodedValue;\n  } catch (err) {\n    return atob(b64value);\n  }\n}\n\n/**\n * Mutates the array in-place by removing the first occurrence of `item` from\n * the array.\n */\nexport function remove<T>(array: T[], item: T): void {\n  for (let i = 0; i < array.length; i++) {\n    if (array[i] === item) {\n      array.splice(i, 1);\n      break;\n    }\n  }\n}\n\n/**\n * Removes null and undefined values from the array, and reflects this in the\n * output type.\n */\nexport function compact<T>(items: readonly T[]): NonNullable<T>[] {\n  return items.filter(\n    (item: T): item is NonNullable<T> => item !== null && item !== undefined\n  );\n}\n\nexport type RemoveUndefinedValues<T> = {\n  [K in keyof T]-?: Exclude<T[K], undefined>;\n};\n\n/**\n * Returns a new object instance where all explictly-undefined values are\n * removed.\n */\nexport function compactObject<O extends Record<string, unknown>>(\n  obj: O\n): RemoveUndefinedValues<O> {\n  const newObj = { ...obj };\n  Object.keys(obj).forEach((k) => {\n    const key = k as keyof O;\n    if (newObj[key] === undefined) {\n      delete newObj[key];\n    }\n  });\n  return newObj as RemoveUndefinedValues<O>;\n}\n\n/**\n * Returns a promise that resolves after the given number of milliseconds.\n */\nexport function wait(millis: number): Promise<void> {\n  return new Promise((res) => setTimeout(res, millis));\n}\n\n/**\n * Returns whatever the given promise returns, but will be rejected with\n * a \"Timed out\" error if the given promise does not return or reject within\n * the given timeout period (in milliseconds).\n */\nexport async function withTimeout<T>(\n  promise: Promise<T>,\n  millis: number,\n  errmsg: string\n): Promise<T> {\n  let timerID: ReturnType<typeof setTimeout> | undefined;\n  const timer$ = new Promise<never>((_, reject) => {\n    timerID = setTimeout(() => {\n      reject(new Error(errmsg));\n    }, millis);\n  });\n  return (\n    Promise\n      // Race the given promise against the timer. Whichever one finishes\n      // first wins the race.\n      .race([promise, timer$])\n\n      // Either way, clear the timeout, no matter who won\n      .finally(() => clearTimeout(timerID))\n  );\n}\n\n/**\n * Memoize a promise factory, so that each subsequent call will return the same\n * pending or success promise. If the promise rejects, will retain that failed\n * promise for a small time period, after which the next attempt will reset the\n * memoized value.\n */\nexport function memoizeOnSuccess<T>(\n  factoryFn: () => Promise<T>\n): () => Promise<T> {\n  let cached: Promise<T> | null = null;\n  return () => {\n    if (cached === null) {\n      cached = factoryFn().catch((err) => {\n        //\n        // Keep returning the failed promise for any calls to the memoized\n        // promise for the next 5 seconds. This time period is a bit arbitrary,\n        // but exists to make this play nicely with frameworks like React.\n        //\n        // In React, after a component is suspended and its promise is\n        // rejected, React will re-render the component, and expect the next\n        // call to this function to return the rejected promise, so its error\n        // can be shown. If we immediately reset this value, then such next\n        // render would instantly trigger a new promise which would trigger an\n        // infinite loop and keeping the component in loading state forever.\n        //\n        setTimeout(() => {\n          cached = null;\n        }, 5_000);\n        throw err;\n      });\n    }\n    return cached;\n  };\n}\n","import * as console from \"./fancy-console\";\nimport { isPlainObject } from \"./guards\";\nimport type { JsonObject } from \"./Json\";\nimport { tryParseJson, wait } from \"./utils\";\n\nexport class HttpError extends Error {\n  public response: Response;\n  public details?: JsonObject;\n\n  private constructor(\n    message: string,\n    response: Response,\n    details?: JsonObject\n  ) {\n    super(message);\n    this.name = \"HttpError\";\n    this.response = response;\n    this.details = details;\n  }\n\n  static async fromResponse(response: Response): Promise<HttpError> {\n    // Try to extract `message` and `details` from the response, and\n    // construct the HttpError instance\n    let bodyAsText: string | undefined;\n    try {\n      bodyAsText = await response.text();\n    } catch {\n      // Ignore\n    }\n\n    const bodyAsJson = bodyAsText ? tryParseJson(bodyAsText) : undefined;\n\n    let bodyAsJsonObject: JsonObject | undefined;\n    if (isPlainObject(bodyAsJson)) {\n      bodyAsJsonObject = bodyAsJson;\n    }\n\n    let message = \"\";\n    message ||=\n      typeof bodyAsJsonObject?.message === \"string\"\n        ? bodyAsJsonObject.message\n        : \"\";\n    message ||=\n      typeof bodyAsJsonObject?.error === \"string\" ? bodyAsJsonObject.error : \"\";\n    if (bodyAsJson === undefined) {\n      message ||= bodyAsText || \"\";\n    }\n    message ||= response.statusText;\n\n    let path: string | undefined;\n    try {\n      path = new URL(response.url).pathname;\n    } catch {\n      // Ignore\n    }\n    message +=\n      path !== undefined\n        ? ` (got status ${response.status} from ${path})`\n        : ` (got status ${response.status})`;\n\n    const details = bodyAsJsonObject;\n    return new HttpError(message, response, details);\n  }\n\n  /**\n   * Convenience accessor for response.status.\n   */\n  get status(): number {\n    return this.response.status;\n  }\n}\n\nconst DONT_RETRY_4XX = (x: unknown) =>\n  x instanceof HttpError && x.status >= 400 && x.status < 500;\n\n/**\n * Wraps a promise factory. Will create promises until one succeeds. If\n * a promise rejects, it will retry calling the factory for at most `maxTries`\n * times. Between each attempt, it will inject a a backoff delay (in millis)\n * from the given array. If the array contains fewer items then `maxTries`,\n * then the last backoff number will be used indefinitely.\n *\n * If the last attempt is rejected too, the returned promise will fail too.\n *\n * @param promiseFn The promise factory to execute\n * @param maxTries The number of total tries (must be >=1)\n * @param backoff An array of timings to inject between each promise attempt\n * @param shouldStopRetrying An optional function to not auto-retry on certain errors\n */\nexport async function autoRetry<T>(\n  promiseFn: () => Promise<T>,\n  maxTries: number,\n  backoff: number[],\n  shouldStopRetrying: (err: any) => boolean = DONT_RETRY_4XX\n): Promise<T> {\n  const fallbackBackoff = backoff.length > 0 ? backoff[backoff.length - 1] : 0;\n\n  let attempt = 0;\n\n  while (true) {\n    attempt++;\n\n    try {\n      return await promiseFn();\n    } catch (err) {\n      if (shouldStopRetrying(err)) {\n        throw err;\n      }\n\n      if (attempt >= maxTries) {\n        // Fail the entire promise right now\n        throw new Error(`Failed after ${maxTries} attempts: ${String(err)}`);\n      }\n    }\n\n    // Do another retry\n    const delay = backoff[attempt - 1] ?? fallbackBackoff;\n\n    console.warn(\n      `Attempt ${attempt} was unsuccessful. Retrying in ${delay} milliseconds.`\n    );\n    await wait(delay);\n  }\n}\n","export type ControlledPromise<T> = {\n  promise: Promise<T>;\n  resolve: (value: T) => void;\n  reject: (reason: unknown) => void;\n};\n\n/**\n * Returns a pair of a Promise, and a resolve function that can be passed\n * around to resolve the promise \"from anywhere\".\n *\n * The Promise will remain unresolved, until the resolve function is called.\n * Once the resolve function is called with a value, the Promise will resolve\n * to that value.\n *\n * Calling the resolve function beyond the first time is a no-op.\n */\nexport function controlledPromise<T>(): [\n  promise: Promise<T>,\n  resolve: (value: T) => void,\n  reject: (reason: unknown) => void,\n] {\n  let resolve: ((value: T) => void) | undefined;\n  let reject: ((reason: unknown) => void) | undefined;\n  const promise = new Promise<T>((res, rej) => {\n    resolve = res;\n    reject = rej;\n  });\n  // eslint-disable-next-line no-restricted-syntax\n  return [promise, resolve!, reject!];\n}\n\n/**\n * Drop-in replacement for the ES2024 Promise.withResolvers() API.\n */\nexport function Promise_withResolvers<T>(): ControlledPromise<T> {\n  const [promise, resolve, reject] = controlledPromise<T>();\n  return { promise, resolve, reject };\n}\n","export type Callback<T> = (event: T) => void;\nexport type UnsubscribeCallback = () => void;\n\nexport type Observable<T> = {\n  /**\n   * Register a callback function to be called whenever the event source emits\n   * an event.\n   */\n  subscribe(callback: Callback<T>): UnsubscribeCallback;\n  /**\n   * Register a one-time callback function to be called whenever the event\n   * source emits an event. After the event fires, the callback is\n   * auto-unsubscribed.\n   */\n  subscribeOnce(callback: Callback<T>): UnsubscribeCallback;\n  /**\n   * Returns a promise that will resolve when an event is emitted by this\n   * event source. Optionally, specify a predicate that has to match. The first\n   * event matching that predicate will then resolve the promise.\n   */\n  waitUntil(predicate?: (event: T) => boolean): Promise<T>;\n};\n\nexport type EventSource<T> = Observable<T> & {\n  /**\n   * Notify all subscribers about the event. Will return `false` if there\n   * weren't any subscribers at the time the .notify() was called, or `true` if\n   * there was at least one subscriber.\n   */\n  notify(event: T): boolean;\n  /**\n   * Returns the number of active subscribers.\n   */\n  count(): number;\n  /**\n   * Observable instance, which can be used to subscribe to this event source\n   * in a readonly fashion. Safe to publicly expose.\n   */\n  observable: Observable<T>;\n  /**\n   * Disposes of this event source.\n   *\n   * Will clears all registered event listeners. None of the registered\n   * functions will ever get called again.\n   *\n   * WARNING!\n   * Be careful when using this API, because the subscribers may not have any\n   * idea they won't be notified anymore.\n   */\n  // NOTE: This can eventually become [Symbol.dispose] when it's widely\n  // available in all browsers\n  dispose(): void;\n};\n\nexport type BufferableEventSource<T> = EventSource<T> & {\n  /**\n   * Pauses event delivery until unpaused. Any .notify() calls made while\n   * paused will get buffered into memory and emitted later.\n   */\n  pause(): void;\n  /**\n   * Emits all in-memory buffered events, and unpauses. Any .notify() calls\n   * made after this will be synchronously delivered again.\n   */\n  unpause(): void;\n};\n\nexport type EventEmitter<T> = (event: T) => void;\n\n/**\n * makeEventSource allows you to generate a subscribe/notify pair of functions\n * to make subscribing easy and to get notified about events.\n *\n * The events are anonymous, so you can use it to define events, like so:\n *\n *   const event1 = makeEventSource();\n *   const event2 = makeEventSource();\n *\n *   event1.subscribe(foo);\n *   event1.subscribe(bar);\n *   event2.subscribe(qux);\n *\n *   // Unsubscription is pretty standard\n *   const unsub = event2.subscribe(foo);\n *   unsub();\n *\n *   event1.notify();  // Now foo and bar will get called\n *   event2.notify();  // Now qux will get called (but foo will not, since it's unsubscribed)\n *\n */\nexport function makeEventSource<T>(): EventSource<T> {\n  const _observers = new Set<Callback<T>>();\n\n  function subscribe(callback: Callback<T>): UnsubscribeCallback {\n    _observers.add(callback);\n    return () => _observers.delete(callback);\n  }\n\n  function subscribeOnce(callback: Callback<T>): UnsubscribeCallback {\n    const unsub = subscribe((event: T) => {\n      unsub();\n      return callback(event);\n    });\n    return unsub;\n  }\n\n  async function waitUntil(predicate?: (event: T) => boolean): Promise<T> {\n    let unsub: () => void | undefined;\n    return new Promise<T>((res) => {\n      unsub = subscribe((event) => {\n        if (predicate === undefined || predicate(event)) {\n          res(event);\n        }\n      });\n    }).finally(() => unsub?.());\n  }\n\n  function notify(event: T) {\n    let called = false;\n    for (const callback of _observers) {\n      callback(event);\n      called = true;\n    }\n    return called;\n  }\n\n  function count() {\n    return _observers.size;\n  }\n\n  return {\n    // Private/internal control over event emission\n    notify,\n    subscribe,\n    subscribeOnce,\n    count,\n\n    waitUntil,\n\n    dispose(): void {\n      _observers.clear();\n    },\n\n    // Publicly exposable subscription API\n    observable: {\n      subscribe,\n      subscribeOnce,\n      waitUntil,\n    },\n  };\n}\n\nexport function makeBufferableEventSource<T>(): BufferableEventSource<T> {\n  const eventSource = makeEventSource<T>();\n  let _buffer: T[] | null = null;\n\n  function pause(): void {\n    _buffer = [];\n  }\n\n  function unpause(): void {\n    if (_buffer === null) {\n      // Already unpaused\n      return;\n    }\n\n    for (const event of _buffer) {\n      eventSource.notify(event);\n    }\n    _buffer = null;\n  }\n\n  function notifyOrBuffer(event: T) {\n    if (_buffer !== null) {\n      _buffer.push(event);\n      return false;\n    } else {\n      return eventSource.notify(event);\n    }\n  }\n\n  return {\n    ...eventSource,\n    notify: notifyOrBuffer,\n    pause,\n    unpause,\n\n    dispose(): void {\n      eventSource.dispose();\n      if (_buffer !== null) {\n        _buffer.length = 0;\n      }\n    },\n  };\n}\n","/**\n * Freezes the given argument, but only in development builds. In production\n * builds, this is a no-op for performance reasons.\n */\nexport const freeze: typeof Object.freeze =\n  process.env.NODE_ENV === \"production\"\n    ? /* istanbul ignore next */ (((x: unknown) => x) as typeof Object.freeze)\n    : Object.freeze;\n","import type {\n  Callback,\n  EventSource,\n  Observable,\n  UnsubscribeCallback,\n} from \"../lib/EventSource\";\nimport { makeEventSource } from \"../lib/EventSource\";\nimport { freeze } from \"../lib/freeze\";\nimport type { JsonObject } from \"../lib/Json\";\nimport { compactObject, raise } from \"../lib/utils\";\n\nconst kSinks = Symbol(\"kSinks\");\nconst kTrigger = Symbol(\"kTrigger\");\n\n//\n// Before the batch is run, all sinks (recursively all the way down) are marked\n// dirty. This already is enough if we only ever use .get() calls.\n//\n// However, to ensure active subscription notifications also work, we need to\n// keep track of which Signals to notify. Any time the value of a Signal\n// changes, the Signal itself will notify its own subscribers, but \"sinks\" are\n// not \"normal\" subscribers.\n//\n// By treating sinks slightly differently, we can keep track of sink uniqueness\n// across the entire signal network, ensuring a sink will only be notified once\n// if more than one of its dependent Signals have changed.\n//\n// For example:\n//\n//      A\n//    /   \\\n//   B     C\n//    \\   /\n//      D - - - - ( has 1 normal subscriber )\n//\n// Here, B and C are sinks of A, and D is a sink of both B and C.\n//\n// Here's what will happen when A changes:\n//\n// - If A changes, then all sinks (B, C, and D) will be marked dirty.\n//\n// - Because some of A's sinks are being watched (in this case, D has at least\n//   one subscriber), A will trigger B and C that its value has changed.\n//\n// - Both B and C re-evaluate and may or may not have changed. Three\n//   possibilities:\n//   1. Neither B and C have changed → D will *NOT* be triggered\n//   2. Either B or C has changed    → D *will* be triggered\n//   3. Both B and C have changed    → D *will* be triggered (but only once!)\n//\n// - If in the previous step D has been triggered, it will re-evaluate. If it\n//   has changed itself, it will notify its normal subscriber.\n//\nlet signalsToTrigger: Set<AbstractSignal<any>> | null = null;\n\n//\n// If a derived signal is currently being computed, there is a global \"signals\n// that have been read\" registry that every call to `someSignal.get()` will\n// register itself under.\n//\nlet trackedReads: Set<AbstractSignal<any>> | null = null;\n\n/**\n * Runs a callback function that is allowed to change multiple signals. At the\n * end of the batch, all changed signals will be notified (at most once).\n *\n * Nesting batches is supported.\n */\nexport function batch(callback: Callback<void>): void {\n  if (signalsToTrigger !== null) {\n    // Already inside another batch, just run this inner callback\n    callback();\n    return;\n  }\n\n  signalsToTrigger = new Set();\n  try {\n    callback();\n  } finally {\n    for (const signal of signalsToTrigger) {\n      signal[kTrigger]();\n    }\n    signalsToTrigger = null;\n  }\n}\n\n/**\n * Ensures that the signal will be notified at the end of the current batch.\n * This should only be called within a batch callback. It's safe to call this\n * while notifications are being rolled out.\n */\nfunction enqueueTrigger(signal: AbstractSignal<any>) {\n  if (!signalsToTrigger) raise(\"Expected to be in an active batch\");\n  signalsToTrigger.add(signal);\n}\n\n/**\n * Patches a target object by \"merging in\" the provided fields. Patch\n * fields that are explicitly-undefined will delete keys from the target\n * object. Will return a new object.\n *\n * Important guarantee:\n * If the patch effectively did not mutate the target object because the\n * patch fields have the same value as the original, then the original\n * object reference will be returned.\n */\nexport function merge<T>(target: T, patch: Partial<T>): T {\n  let updated = false;\n  const newValue = { ...target };\n\n  Object.keys(patch).forEach((k) => {\n    const key = k as keyof T;\n    const val = patch[key];\n    if (newValue[key] !== val) {\n      if (val === undefined) {\n        delete newValue[key];\n      } else {\n        newValue[key] = val as T[keyof T];\n      }\n      updated = true;\n    }\n  });\n\n  return updated ? newValue : target;\n}\n\nexport type SignalType<S extends ISignal<any>> =\n  S extends ISignal<infer T> ? T : never;\n\nexport interface ISignal<T> {\n  get(): T;\n  subscribe(callback: Callback<void>): UnsubscribeCallback;\n  addSink(sink: DerivedSignal<unknown>): void;\n  removeSink(sink: DerivedSignal<unknown>): void;\n}\n\n/**\n * Base functionality every Signal implementation needs.\n */\nabstract class AbstractSignal<T> implements ISignal<T>, Observable<void> {\n  /** @internal */\n  protected readonly equals: (a: T, b: T) => boolean;\n  readonly #eventSource: EventSource<void>;\n  /** @internal */\n  public readonly [kSinks]: Set<DerivedSignal<unknown>>;\n\n  constructor(equals?: (a: T, b: T) => boolean) {\n    this.equals = equals ?? Object.is;\n    this.#eventSource = makeEventSource<void>();\n    this[kSinks] = new Set();\n\n    // Bind common methods to self\n    this.get = this.get.bind(this);\n    this.subscribe = this.subscribe.bind(this);\n    this.subscribeOnce = this.subscribeOnce.bind(this);\n  }\n\n  dispose(): void {\n    this.#eventSource.dispose();\n\n    // @ts-expect-error make disposed object completely unusable\n    this.#eventSource = \"(disposed)\";\n    // @ts-expect-error make disposed object completely unusable\n    this.equals = \"(disposed)\";\n  }\n\n  // Concrete subclasses implement this method in different ways\n  abstract get(): T;\n\n  get hasWatchers(): boolean {\n    if (this.#eventSource.count() > 0) return true;\n\n    for (const sink of this[kSinks]) {\n      if (sink.hasWatchers) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  public [kTrigger](): void {\n    this.#eventSource.notify();\n\n    // While Signals are being triggered in the current unroll, we can enqueue\n    // more signals to trigger (which will get added to the current unroll)\n    for (const sink of this[kSinks]) {\n      enqueueTrigger(sink);\n    }\n  }\n\n  subscribe(callback: Callback<void>): UnsubscribeCallback {\n    // If this is the first subscriber, we need to perform an initial .get()\n    // now in case this is a DerivedSignal that has not been evaluated yet. The\n    // reason we need to do this is that the .get() itself will register this\n    // signal as sinks of the dependent signals, so we will actually get\n    // notified here when one of the dependent signals changes.\n    if (this.#eventSource.count() === 0) {\n      this.get();\n    }\n    return this.#eventSource.subscribe(callback);\n  }\n\n  subscribeOnce(callback: Callback<void>): UnsubscribeCallback {\n    const unsub = this.subscribe(() => {\n      unsub();\n      return callback();\n    });\n    return unsub;\n  }\n\n  waitUntil(): never {\n    throw new Error(\"waitUntil not supported on Signals\");\n  }\n\n  markSinksDirty(): void {\n    for (const sink of this[kSinks]) {\n      sink.markDirty();\n    }\n  }\n\n  addSink(sink: DerivedSignal<unknown>): void {\n    this[kSinks].add(sink);\n  }\n\n  removeSink(sink: DerivedSignal<unknown>): void {\n    this[kSinks].delete(sink);\n  }\n\n  asReadonly(): ISignal<T> {\n    return this;\n  }\n}\n\n// NOTE: This class is pretty similar to the Signal.State proposal\nexport class Signal<T> extends AbstractSignal<T> {\n  #value: T;\n\n  constructor(value: T, equals?: (a: T, b: T) => boolean) {\n    super(equals);\n    this.#value = freeze(value);\n  }\n\n  dispose(): void {\n    super.dispose();\n    // @ts-expect-error make disposed object completely unusable\n    this.#value = \"(disposed)\";\n  }\n\n  get(): T {\n    trackedReads?.add(this);\n    return this.#value;\n  }\n\n  set(newValue: T | ((oldValue: T) => T)): void {\n    batch(() => {\n      if (typeof newValue === \"function\") {\n        newValue = (newValue as (oldValue: T) => T)(this.#value);\n      }\n      if (!this.equals(this.#value, newValue)) {\n        this.#value = freeze(newValue);\n        this.markSinksDirty();\n        enqueueTrigger(this);\n      }\n    });\n  }\n}\n\nexport class PatchableSignal<J extends JsonObject> extends Signal<J> {\n  constructor(data: J) {\n    super(freeze(compactObject(data)));\n  }\n\n  set(): void {\n    throw new Error(\"Don't call .set() directly, use .patch()\");\n  }\n\n  /**\n   * Patches the current object.\n   */\n  patch(patch: Partial<J>): void {\n    super.set((old) => merge(old, patch));\n  }\n}\n\n/**\n * Placeholder for a deferred computation that has yet to happen on-demand in\n * the future.\n */\nconst INITIAL = Symbol();\n\n// NOTE: This class is pretty similar to the Signal.Computed proposal\nexport class DerivedSignal<T> extends AbstractSignal<T> {\n  #prevValue: T;\n  #dirty: boolean; // When true, the value in #value may not be up-to-date and needs re-checking\n\n  #sources: Set<ISignal<unknown>>;\n  readonly #deps: readonly ISignal<unknown>[];\n  readonly #transform: (...values: unknown[]) => T;\n\n  // Overload 1\n  static from<Ts extends unknown[], V>(...args: [...signals: { [K in keyof Ts]: ISignal<Ts[K]> }, transform: (...values: Ts) => V]): DerivedSignal<V>; // prettier-ignore\n  // Overload 2\n  static from<Ts extends unknown[], V>(...args: [...signals: { [K in keyof Ts]: ISignal<Ts[K]> }, transform: (...values: Ts) => V, equals: (a: V, b: V) => boolean]): DerivedSignal<V>; // prettier-ignore\n  static from<Ts extends unknown[], V>(\n    // prettier-ignore\n    ...args: [\n      ...signals: { [K in keyof Ts]: ISignal<Ts[K]> },\n      transform: (...values: Ts) => V,\n      equals?: (a: V, b: V) => boolean,\n    ]\n  ): DerivedSignal<V> {\n    const last = args.pop();\n    if (typeof last !== \"function\")\n      raise(\"Invalid .from() call, last argument expected to be a function\");\n\n    if (typeof args[args.length - 1] === \"function\") {\n      // Overload 2\n      const equals = last as (a: V, b: V) => boolean;\n      const transform = args.pop() as (...values: unknown[]) => V;\n      return new DerivedSignal(args as ISignal<unknown>[], transform, equals);\n    } else {\n      // Overload 1\n      const transform = last as (...values: unknown[]) => V;\n      return new DerivedSignal(args as ISignal<unknown>[], transform);\n    }\n  }\n\n  private constructor(\n    deps: ISignal<unknown>[],\n    transform: (...values: unknown[]) => T,\n    equals?: (a: T, b: T) => boolean\n  ) {\n    super(equals);\n    this.#dirty = true;\n    this.#prevValue = INITIAL as unknown as T;\n    this.#deps = deps;\n    this.#sources = new Set();\n    this.#transform = transform;\n  }\n\n  dispose(): void {\n    for (const src of this.#sources) {\n      src.removeSink(this as DerivedSignal<unknown>);\n    }\n\n    // @ts-expect-error make disposed object completely unusable\n    this.#prevValue = \"(disposed)\";\n    // @ts-expect-error make disposed object completely unusable\n    this.#sources = \"(disposed)\";\n    // @ts-expect-error make disposed object completely unusable\n    this.#deps = \"(disposed)\";\n    // @ts-expect-error make disposed object completely unusable\n    this.#transform = \"(disposed)\";\n  }\n\n  get isDirty(): boolean {\n    return this.#dirty;\n  }\n\n  #recompute(): boolean {\n    const oldTrackedReads = trackedReads;\n\n    let derived;\n    trackedReads = new Set();\n    try {\n      derived = this.#transform(...this.#deps.map((p) => p.get()));\n    } finally {\n      const oldSources = this.#sources;\n      this.#sources = new Set();\n\n      for (const sig of trackedReads) {\n        this.#sources.add(sig);\n        oldSources.delete(sig);\n      }\n\n      for (const oldSource of oldSources) {\n        oldSource.removeSink(this as DerivedSignal<unknown>);\n      }\n      for (const newSource of this.#sources) {\n        newSource.addSink(this as DerivedSignal<unknown>);\n      }\n\n      trackedReads = oldTrackedReads;\n    }\n\n    this.#dirty = false;\n\n    // Only emit a change to watchers if the value actually changed\n    if (!this.equals(this.#prevValue, derived)) {\n      this.#prevValue = derived;\n      return true;\n    }\n    return false;\n  }\n\n  markDirty(): void {\n    if (!this.#dirty) {\n      this.#dirty = true;\n      this.markSinksDirty();\n    }\n  }\n\n  get(): T {\n    if (this.#dirty) {\n      this.#recompute();\n    }\n    trackedReads?.add(this);\n    return this.#prevValue;\n  }\n\n  /**\n   * Called by the Signal system if one or more of the dependent signals have\n   * changed. In the case of a DerivedSignal, we'll only want to re-evaluate\n   * the actual value if it's being watched, or any of their sinks are being\n   * watched actively.\n   */\n  public [kTrigger](): void {\n    if (!this.hasWatchers) {\n      // If there are no watchers for this signal, we don't need to\n      // re-evaluate. We can postpone re-evaluation until the next .get() call.\n      return;\n    }\n\n    // Re-evaluate the current derived signal's value and if needed,\n    // notify sinks. At this point, all sinks should already have been\n    // marked dirty, so we won't have to do that again here now.\n    const updated = this.#recompute();\n    if (updated) {\n      super[kTrigger](); // Actually notify subscribers\n    }\n  }\n}\n\n/**\n * A MutableSignal is a bit like Signal, except its state is managed by\n * a single value whose reference does not change but is mutated.\n *\n * Similar to how useSyncExternalState() works in React, there is a way to read\n * the current state at any point in time synchronously, and a way to update\n * its reference.\n */\nexport class MutableSignal<T extends object> extends AbstractSignal<T> {\n  readonly #state: T;\n\n  constructor(initialState: T) {\n    super();\n    this.#state = initialState;\n  }\n\n  dispose(): void {\n    super.dispose();\n    // @ts-expect-error make disposed object completely unusable\n    this.#state = \"(disposed)\";\n  }\n\n  get(): T {\n    trackedReads?.add(this);\n    return this.#state;\n  }\n\n  /**\n   * Invokes a callback function that is allowed to mutate the given state\n   * value. Do not change the value outside of the callback.\n   *\n   * If the callback explicitly returns `false`, it's assumed that the state\n   * was not changed.\n   */\n  mutate(callback?: (state: T) => void | boolean): void {\n    batch(() => {\n      const result = callback ? callback(this.#state) : true;\n      if (result !== null && typeof result === \"object\" && \"then\" in result) {\n        raise(\"MutableSignal.mutate() does not support async callbacks\");\n      }\n\n      if (result !== false) {\n        this.markSinksDirty();\n        enqueueTrigger(this);\n      }\n    });\n  }\n}\n","/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable @typescript-eslint/no-unsafe-argument */\n\nfunction replacer(_key: string, value: unknown) {\n  return value !== null && typeof value === \"object\" && !Array.isArray(value)\n    ? Object.keys(value)\n        .sort()\n        .reduce((sorted, key) => {\n          // @ts-expect-error this is fine\n          sorted[key] = value[key];\n          return sorted;\n        }, {})\n    : value;\n}\n\n/**\n * Like JSON.stringify(), but using stable (sorted) object key order, so that\n * it returns the same value for the same keys, no matter their order.\n */\nexport function stableStringify(value: unknown): string {\n  return JSON.stringify(value, replacer);\n}\n\n/**\n * Drop-in replacement for JSON.stringify(), which will log any payload to the\n * console if it could not be stringified somehow.\n */\nexport function stringifyOrLog(value: unknown): string {\n  try {\n    return JSON.stringify(value);\n  } catch (err) {\n    /* eslint-disable rulesdir/console-must-be-fancy */\n    console.error(`Could not stringify: ${(err as Error).message}`);\n    console.error(value);\n    /* eslint-enable rulesdir/console-must-be-fancy */\n    throw err;\n  }\n}\n","import type { AsyncResult } from \"./AsyncResult\";\nimport { Promise_withResolvers } from \"./controlledPromise\";\nimport type { Callback, UnsubscribeCallback } from \"./EventSource\";\nimport { MutableSignal } from \"./signals\";\nimport { stableStringify } from \"./stringify\";\n\nconst DEFAULT_SIZE = 50;\n\ntype Resolve<T> = (value: T) => void;\ntype Reject = (reason?: unknown) => void;\n\nexport type BatchCallback<O, I> = (\n  inputs: I[]\n) => (O | Error)[] | Promise<(O | Error)[]>;\n\nexport type BatchStore<O, I> = {\n  subscribe: (callback: Callback<void>) => UnsubscribeCallback;\n  enqueue: (input: I) => Promise<void>;\n  getItemState: (input: I) => AsyncResult<O> | undefined;\n  invalidate: (inputs?: I[]) => void;\n\n  /**\n   * @internal\n   */\n  readonly batch: Batch<O, I>;\n\n  /**\n   * @internal\n   *\n   * Only for testing.\n   */\n  _cacheKeys: () => string[];\n};\n\ninterface BatchOptions {\n  /**\n   * How many calls to batch together at most.\n   */\n  size?: number;\n\n  /**\n   * How long to wait before flushing the batch.\n   */\n  delay: number;\n}\n\nclass BatchCall<O, I> {\n  readonly input: I;\n  readonly resolve: Resolve<O>;\n  readonly reject: Reject;\n  readonly promise: Promise<O>;\n\n  constructor(input: I) {\n    this.input = input;\n\n    const { promise, resolve, reject } = Promise_withResolvers<O>();\n    this.promise = promise;\n    this.resolve = resolve;\n    this.reject = reject;\n  }\n}\n\n/**\n * Batch calls to a function, either by number of calls or by a maximum delay.\n */\nexport class Batch<O, I> {\n  #queue: BatchCall<O, I>[] = [];\n  #callback: BatchCallback<O, I>;\n  #size: number;\n  #delay: number;\n  #delayTimeoutId?: ReturnType<typeof setTimeout>;\n  public error = false;\n\n  constructor(callback: BatchCallback<O, I>, options: BatchOptions) {\n    this.#callback = callback;\n    this.#size = options.size ?? DEFAULT_SIZE;\n    this.#delay = options.delay;\n  }\n\n  #clearDelayTimeout(): void {\n    if (this.#delayTimeoutId !== undefined) {\n      clearTimeout(this.#delayTimeoutId);\n      this.#delayTimeoutId = undefined;\n    }\n  }\n\n  #schedule() {\n    if (this.#queue.length === this.#size) {\n      // If the queue is full, flush it immediately.\n      void this.#flush();\n    } else if (this.#queue.length === 1) {\n      // If the call is the first in the queue, schedule a flush.\n      this.#clearDelayTimeout();\n      this.#delayTimeoutId = setTimeout(() => void this.#flush(), this.#delay);\n    }\n  }\n\n  async #flush(): Promise<void> {\n    // If the queue is empty, don't call the callback.\n    if (this.#queue.length === 0) {\n      return;\n    }\n\n    // Empty the queue and get its calls.\n    const calls = this.#queue.splice(0);\n    const inputs = calls.map((call) => call.input);\n\n    try {\n      // Call the batch callback with the queued arguments.\n      const results = await this.#callback(inputs);\n      this.error = false;\n\n      // Resolve or reject each call.\n      calls.forEach((call, index) => {\n        const result = results?.[index];\n\n        if (!Array.isArray(results)) {\n          call.reject(new Error(\"Callback must return an array.\"));\n        } else if (calls.length !== results.length) {\n          call.reject(\n            new Error(\n              `Callback must return an array of the same length as the number of provided items. Expected ${calls.length}, but got ${results.length}.`\n            )\n          );\n        } else if (result instanceof Error) {\n          call.reject(result);\n        } else {\n          call.resolve(result);\n        }\n      });\n    } catch (error) {\n      this.error = true;\n\n      // Reject all calls if the whole batch errored or was rejected.\n      calls.forEach((call) => {\n        call.reject(error);\n      });\n    }\n  }\n\n  get(input: I): Promise<O> {\n    // Check if there's already an identical call in the queue.\n    const existingCall = this.#queue.find(\n      (call) => stableStringify(call.input) === stableStringify(input)\n    );\n\n    // If an existing call exists, return its promise.\n    if (existingCall) {\n      return existingCall.promise;\n    }\n\n    // If no existing call exists, add the call to the queue and schedule a flush.\n    const call = new BatchCall<O, I>(input);\n    this.#queue.push(call);\n    this.#schedule();\n\n    return call.promise;\n  }\n\n  clear(): void {\n    this.#queue = [];\n    this.error = false;\n    this.#clearDelayTimeout();\n  }\n}\n\n/**\n * Create a store around a Batch.\n * Each call will be cached and get its own state in addition to being batched.\n */\nexport function createBatchStore<O, I>(batch: Batch<O, I>): BatchStore<O, I> {\n  const signal = new MutableSignal(new Map<string, AsyncResult<O>>());\n\n  function getCacheKey(args: I): string {\n    return stableStringify(args);\n  }\n\n  function update(cacheKey: string, state: AsyncResult<O>) {\n    signal.mutate((cache) => {\n      cache.set(cacheKey, state);\n    });\n  }\n\n  function invalidate(inputs?: I[]): void {\n    signal.mutate((cache) => {\n      if (Array.isArray(inputs)) {\n        // Invalidate the specific calls.\n        for (const input of inputs) {\n          cache.delete(getCacheKey(input));\n        }\n      } else {\n        // Invalidate all calls.\n        cache.clear();\n      }\n    });\n  }\n\n  async function enqueue(input: I): Promise<void> {\n    const cacheKey = getCacheKey(input);\n\n    // If this call already has a state, return early.\n    const cache = signal.get();\n    if (cache.has(cacheKey)) {\n      return;\n    }\n\n    try {\n      // Set the state to loading.\n      update(cacheKey, { isLoading: true });\n\n      // Wait for the batch to process this call.\n      const result = await batch.get(input);\n\n      // Set the state to the result.\n      update(cacheKey, { isLoading: false, data: result });\n    } catch (error) {\n      // // TODO: Differentiate whole batch errors from individual errors.\n      // if (batch.error) {\n      //   // If the whole batch errored, clear the state.\n      //   // TODO: Keep track of retries and only clear the state a few times because it will be retried each time.\n      //   //       Also implement exponential backoff to delay retries to avoid hammering `resolveUsers`.\n      //   update(cacheKey, undefined);\n      // } else {\n      //   // Otherwise, keep individual errors to avoid repeatedly loading the same error.\n      //   update(cacheKey, {\n      //     isLoading: false,\n      //     error: error as Error,\n      //   });\n      // }\n\n      // If there was an error (for various reasons), set the state to the error.\n      update(cacheKey, {\n        isLoading: false,\n        error: error as Error,\n      });\n    }\n  }\n\n  function getItemState(input: I): AsyncResult<O> | undefined {\n    const cacheKey = getCacheKey(input);\n    const cache = signal.get();\n    return cache.get(cacheKey);\n  }\n\n  /** @internal - Only for testing */\n  function _cacheKeys() {\n    const cache = signal.get();\n    return [...cache.keys()];\n  }\n\n  return {\n    subscribe: signal.subscribe,\n    enqueue,\n    getItemState,\n    invalidate,\n\n    batch,\n    _cacheKeys,\n  };\n}\n","export function chunk<T>(array: T[], size: number): T[][] {\n  const chunks: T[][] = [];\n\n  for (let i = 0, j = array.length; i < j; i += size) {\n    chunks.push(array.slice(i, i + size));\n  }\n\n  return chunks;\n}\n","// Inlined version of 3.3.7 of nanoid.js\n// https://www.npmjs.com/package/nanoid/v/3.3.7?activeTab=code\nexport const nanoid = (t = 21): string =>\n  crypto\n    .getRandomValues(new Uint8Array(t))\n    .reduce(\n      (t, e) =>\n        (t +=\n          (e &= 63) < 36\n            ? e.toString(36)\n            : e < 62\n              ? (e - 26).toString(36).toUpperCase()\n              : e < 63\n                ? \"_\"\n                : \"-\"),\n      \"\"\n    );\n","import { nanoid } from \"./nanoid\";\n\nconst THREAD_ID_PREFIX = \"th\";\nconst COMMENT_ID_PREFIX = \"cm\";\nconst COMMENT_ATTACHMENT_ID_PREFIX = \"at\";\nconst INBOX_NOTIFICATION_ID_PREFIX = \"in\";\n\nfunction createOptimisticId(prefix: string): string {\n  return `${prefix}_${nanoid()}`;\n}\n\nexport function createThreadId(): string {\n  return createOptimisticId(THREAD_ID_PREFIX);\n}\n\nexport function createCommentId(): string {\n  return createOptimisticId(COMMENT_ID_PREFIX);\n}\n\nexport function createCommentAttachmentId(): string {\n  return createOptimisticId(COMMENT_ATTACHMENT_ID_PREFIX);\n}\n\nexport function createInboxNotificationId(): string {\n  return createOptimisticId(INBOX_NOTIFICATION_ID_PREFIX);\n}\n","import { raise } from \"./utils\";\n\n/**\n * Like ES6 map, but takes a default (factory) function which will be used\n * to create entries for missing keys on the fly.\n *\n * Useful for code like:\n *\n *   const map = new DefaultMap(() => []);\n *   map.getOrCreate('foo').push('hello');\n *   map.getOrCreate('foo').push('world');\n *   map.getOrCreate('foo')\n *   // ['hello', 'world']\n *\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n  #defaultFn?: (key: K) => V;\n\n  /**\n   * If the default function is not provided to the constructor, it has to be\n   * provided in each .getOrCreate() call individually.\n   */\n  constructor(\n    defaultFn?: (key: K) => V,\n    entries?: readonly (readonly [K, V])[] | null\n  ) {\n    super(entries);\n    this.#defaultFn = defaultFn;\n  }\n\n  /**\n   * Gets the value at the given key, or creates it.\n   *\n   * Difference from normal Map: if the key does not exist, it will be created\n   * on the fly using the factory function, and that value will get returned\n   * instead of `undefined`.\n   */\n  getOrCreate(key: K, defaultFn?: (key: K) => V): V {\n    if (super.has(key)) {\n      // eslint-disable-next-line no-restricted-syntax\n      return super.get(key)!;\n    } else {\n      const fn =\n        defaultFn ??\n        this.#defaultFn ??\n        raise(\"DefaultMap used without a factory function\");\n\n      const value = fn(key);\n      this.set(key, value);\n      return value;\n    }\n  }\n}\n","import { isPlainObject, isStartsWithOperator } from \"./guards\";\n\n/**\n * Converts an object to a query string\n * Example:\n * ```ts\n * const query = objectToQuery({\n *   resolved: true,\n *   metadata: {\n *     status: \"open\",\n *     priority: 3,\n *     org: {\n *       startsWith: \"liveblocks:\",\n *     },\n *   },\n * });\n *\n * console.log(query);\n * // resolved:true AND metadata[\"status\"]:open AND metadata[\"priority\"]:3 AND metadata[\"org\"]^\"liveblocks:\"\n * ```\n */\ntype SimpleFilterValue = string | number | boolean | null;\ntype OperatorFilterValue = { startsWith: string };\n\ntype FilterValue = SimpleFilterValue | OperatorFilterValue;\n\ntype Filter = NumberFilter | StringFilter | BooleanFilter | NullFilter;\n\ntype NumberFilter = {\n  key: string;\n  operator: \":\";\n  value: number;\n};\n\ntype StringFilter = {\n  key: string;\n  operator: \":\" | \"^\";\n  value: string;\n};\n\ntype BooleanFilter = {\n  key: string;\n  operator: \":\";\n  value: boolean;\n};\n\ntype NullFilter = {\n  key: string;\n  operator: \":\";\n  value: null;\n};\n\n/**\n * Converts an object to a query string\n * @example\n * ```ts\n * const query = objectToQuery({\n *  metadata: {\n *    status: \"open\",\n *    priority: 3,\n *    org: {\n *      startsWith: \"liveblocks:\",\n *    },\n *  },\n * });\n * console.log(query);\n * // metadata[\"status\"]:\"open\" AND metadata[\"priority\"]:3 AND metadata[\"org\"]^\"liveblocks:\"\n * ```\n * @example\n * ```ts\n * const query = objectToQuery({\n *  resolved: true,\n *  roomId: {\n *   startsWith: \"engineering:\",\n *  },\n * });\n * console.log(query);\n * // resolved:true AND roomId^\"engineering:\"\n * ```\n *\n */\n\nconst identifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\nexport function objectToQuery(obj: {\n  [key: string]:\n    | FilterValue\n    | { [key: string]: FilterValue | undefined }\n    | undefined;\n}): string {\n  let filterList: Filter[] = [];\n  const entries = Object.entries(obj);\n\n  const keyValuePairs: [string, string | number | boolean | null][] = [];\n  const keyValuePairsWithOperator: [string, Record<\"startsWith\", string>][] =\n    [];\n  const indexedKeys: [string, Record<string, FilterValue | undefined>][] = [];\n\n  entries.forEach(([key, value]) => {\n    if (!identifierRegex.test(key)) {\n      throw new Error(\"Key must only contain letters, numbers, _\");\n    }\n\n    if (isSimpleValue(value)) {\n      keyValuePairs.push([key, value]);\n    } else if (isPlainObject(value)) {\n      if (isStartsWithOperator(value)) {\n        keyValuePairsWithOperator.push([key, value]);\n      } else {\n        indexedKeys.push([key, value]);\n      }\n    }\n  });\n\n  filterList = [\n    ...getFiltersFromKeyValuePairs(keyValuePairs),\n    ...getFiltersFromKeyValuePairsWithOperator(keyValuePairsWithOperator),\n  ];\n\n  indexedKeys.forEach(([key, value]) => {\n    const nestedEntries = Object.entries(value);\n    const nKeyValuePairs: [string, SimpleFilterValue][] = [];\n    const nKeyValuePairsWithOperator: [string, OperatorFilterValue][] = [];\n    nestedEntries.forEach(([nestedKey, nestedValue]) => {\n      if (isStringEmpty(nestedKey)) {\n        throw new Error(\"Key cannot be empty\");\n      }\n\n      if (isSimpleValue(nestedValue)) {\n        nKeyValuePairs.push([formatFilterKey(key, nestedKey), nestedValue]);\n      } else if (isStartsWithOperator(nestedValue)) {\n        nKeyValuePairsWithOperator.push([\n          formatFilterKey(key, nestedKey),\n          nestedValue,\n        ]);\n      }\n    });\n    filterList = [\n      ...filterList,\n      ...getFiltersFromKeyValuePairs(nKeyValuePairs),\n      ...getFiltersFromKeyValuePairsWithOperator(nKeyValuePairsWithOperator),\n    ];\n  });\n\n  return filterList\n    .map(({ key, operator, value }) => `${key}${operator}${quote(value)}`)\n    .join(\" \");\n}\n\nconst getFiltersFromKeyValuePairs = (\n  keyValuePairs: [string, string | number | boolean | null][]\n): Filter[] => {\n  const filters: Filter[] = [];\n  keyValuePairs.forEach(([key, value]) => {\n    filters.push({\n      key,\n      operator: \":\",\n      value,\n    });\n  });\n\n  return filters;\n};\n\nconst getFiltersFromKeyValuePairsWithOperator = (\n  keyValuePairsWithOperator: [string, { startsWith: string }][]\n): Filter[] => {\n  const filters: Filter[] = [];\n  keyValuePairsWithOperator.forEach(([key, value]) => {\n    if (\"startsWith\" in value && typeof value.startsWith === \"string\") {\n      filters.push({\n        key,\n        operator: \"^\",\n        value: value.startsWith,\n      });\n    }\n  });\n\n  return filters;\n};\n\nconst isSimpleValue = (value: unknown) => {\n  return (\n    typeof value === \"string\" ||\n    typeof value === \"number\" ||\n    typeof value === \"boolean\" ||\n    value === null\n  );\n};\n\nconst formatFilterKey = (key: string, nestedKey?: string) => {\n  if (nestedKey) {\n    return `${key}[${quote(nestedKey)}]`;\n  }\n  return key;\n};\n\nconst isStringEmpty = (value: string) => {\n  return !value || value.toString().trim() === \"\";\n};\n\n/**\n * Quotes and escapes a string. Prefer to use single quotes when possible, but\n * falls back to JSON.stringify() (which uses double-quotes) when necessary.\n */\nexport function quote(input: unknown): string {\n  const result = JSON.stringify(input);\n  if (typeof input !== \"string\") {\n    return result;\n  }\n\n  if (result.includes(\"'\")) {\n    return result;\n  }\n\n  // See if we can turn this string into a single-quoted string, because those\n  // generally are more readable in URLs\n  return `'${result.slice(1, -1).replace(/\\\\\"/g, '\"')}'`;\n}\n","import type { Brand } from \"./utils\";\n\nexport type QueryParams =\n  | Record<string, string | number | null | undefined>\n  | URLSearchParams;\n\n/**\n * Safely but conveniently build a URLSearchParams instance from a given\n * dictionary of values. For example:\n *\n *   {\n *     \"foo\": \"bar+qux/baz\",\n *     \"empty\": \"\",\n *     \"n\": 42,\n *     \"nope\": undefined,\n *     \"alsonope\": null,\n *   }\n *\n * Will produce a value that will get serialized as\n * `foo=bar%2Bqux%2Fbaz&empty=&n=42`.\n *\n * Notice how the number is converted to its string representation\n * automatically and the `null`/`undefined` values simply don't end up in the\n * URL.\n */\nfunction toURLSearchParams(\n  params: Record<string, string | number | null | undefined>\n): URLSearchParams {\n  const result = new URLSearchParams();\n  for (const [key, value] of Object.entries(params)) {\n    if (value !== undefined && value !== null) {\n      result.set(key, value.toString());\n    }\n  }\n  return result;\n}\n\n/**\n * Concatenates a path to an existing URL.\n */\nexport function urljoin(\n  baseUrl: string | URL,\n  path: string,\n  params?: QueryParams\n): string {\n  // First, sanitize by removing user/passwd/search/hash parts from the URL\n  const url = new URL(path, baseUrl);\n  if (params !== undefined) {\n    url.search = (\n      params instanceof URLSearchParams ? params : toURLSearchParams(params)\n    ).toString();\n  }\n  return url.toString();\n}\n\n/**\n * A string that is guaranteed to be URL safe (where all arguments are properly\n * encoded), only obtainable as the result of using `url` template strings.\n */\nexport type URLSafeString = Brand<string, \"URLSafeString\">;\n\n/**\n * Builds a URL where each \"hole\" in the template string will automatically be\n * encodeURIComponent()-escaped, so it's impossible to build invalid URLs.\n */\nexport function url(\n  strings: TemplateStringsArray,\n  ...values: string[]\n): URLSafeString {\n  return strings.reduce(\n    (result, str, i) => result + encodeURIComponent(values[i - 1] ?? \"\") + str\n  ) as URLSafeString;\n}\n","import type { AuthManager, AuthValue } from \"./auth-manager\";\nimport {\n  convertToCommentData,\n  convertToCommentUserReaction,\n  convertToInboxNotificationData,\n  convertToInboxNotificationDeleteInfo,\n  convertToSubscriptionData,\n  convertToSubscriptionDeleteInfo,\n  convertToThreadData,\n  convertToThreadDeleteInfo,\n} from \"./convert-plain-data\";\nimport { autoRetry, HttpError } from \"./lib/autoRetry\";\nimport type { BatchStore } from \"./lib/batch\";\nimport { Batch, createBatchStore } from \"./lib/batch\";\nimport { chunk } from \"./lib/chunk\";\nimport { createCommentId, createThreadId } from \"./lib/createIds\";\nimport type { DateToString } from \"./lib/DateToString\";\nimport { DefaultMap } from \"./lib/DefaultMap\";\nimport type { Json, JsonObject } from \"./lib/Json\";\nimport { objectToQuery } from \"./lib/objectToQuery\";\nimport type { Signal } from \"./lib/signals\";\nimport { stringifyOrLog as stringify } from \"./lib/stringify\";\nimport type { QueryParams, URLSafeString } from \"./lib/url\";\nimport { url, urljoin } from \"./lib/url\";\nimport { raise } from \"./lib/utils\";\nimport type {\n  ContextualPromptContext,\n  ContextualPromptResponse,\n} from \"./protocol/Ai\";\nimport type { Permission } from \"./protocol/AuthToken\";\nimport type { ClientMsg } from \"./protocol/ClientMsg\";\nimport type {\n  BaseMetadata,\n  CommentAttachment,\n  CommentBody,\n  CommentData,\n  CommentDataPlain,\n  CommentLocalAttachment,\n  CommentUserReaction,\n  CommentUserReactionPlain,\n  QueryMetadata,\n  ThreadData,\n  ThreadDataPlain,\n  ThreadDeleteInfo,\n  ThreadDeleteInfoPlain,\n} from \"./protocol/Comments\";\nimport type {\n  InboxNotificationData,\n  InboxNotificationDataPlain,\n  InboxNotificationDeleteInfo,\n  InboxNotificationDeleteInfoPlain,\n} from \"./protocol/InboxNotifications\";\nimport type {\n  NotificationSettingsPlain,\n  PartialNotificationSettings,\n} from \"./protocol/NotificationSettings\";\nimport type { RoomSubscriptionSettings } from \"./protocol/RoomSubscriptionSettings\";\nimport type { IdTuple, SerializedCrdt } from \"./protocol/SerializedCrdt\";\nimport type {\n  SubscriptionData,\n  SubscriptionDataPlain,\n  SubscriptionDeleteInfo,\n  SubscriptionDeleteInfoPlain,\n} from \"./protocol/Subscriptions\";\nimport type { HistoryVersion } from \"./protocol/VersionHistory\";\nimport type { TextEditorType } from \"./types/Others\";\nimport type { Patchable } from \"./types/Patchable\";\nimport { PKG_VERSION } from \"./version\";\n\nexport interface RoomHttpApi<M extends BaseMetadata> {\n  getThreads(options: {\n    roomId: string;\n    cursor?: string;\n    query?: {\n      resolved?: boolean;\n      metadata?: Partial<QueryMetadata<M>>;\n    };\n  }): Promise<{\n    threads: ThreadData<M>[];\n    inboxNotifications: InboxNotificationData[];\n    subscriptions: SubscriptionData[];\n    requestedAt: Date;\n    nextCursor: string | null;\n    permissionHints: Record<string, Permission[]>;\n  }>;\n\n  getThreadsSince(options: {\n    roomId: string;\n    since: Date;\n    signal?: AbortSignal;\n  }): Promise<{\n    threads: {\n      updated: ThreadData<M>[];\n      deleted: ThreadDeleteInfo[];\n    };\n    inboxNotifications: {\n      updated: InboxNotificationData[];\n      deleted: InboxNotificationDeleteInfo[];\n    };\n    subscriptions: {\n      updated: SubscriptionData[];\n      deleted: SubscriptionDeleteInfo[];\n    };\n    requestedAt: Date;\n    permissionHints: Record<string, Permission[]>;\n  }>;\n\n  createThread({\n    roomId,\n    metadata,\n    body,\n    commentId,\n    threadId,\n    attachmentIds,\n  }: {\n    roomId: string;\n    threadId?: string;\n    commentId?: string;\n    metadata: M | undefined;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }): Promise<ThreadData<M>>;\n\n  getThread(options: { roomId: string; threadId: string }): Promise<{\n    thread?: ThreadData<M>;\n    inboxNotification?: InboxNotificationData;\n    subscription?: SubscriptionData;\n  }>;\n\n  deleteThread({\n    roomId,\n    threadId,\n  }: {\n    roomId: string;\n    threadId: string;\n  }): Promise<void>;\n\n  editThreadMetadata({\n    roomId,\n    metadata,\n    threadId,\n  }: {\n    roomId: string;\n    metadata: Patchable<M>;\n    threadId: string;\n  }): Promise<M>;\n\n  createComment({\n    roomId,\n    threadId,\n    commentId,\n    body,\n    attachmentIds,\n  }: {\n    roomId: string;\n    threadId: string;\n    commentId?: string;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }): Promise<CommentData>;\n\n  editComment({\n    roomId,\n    threadId,\n    commentId,\n    body,\n    attachmentIds,\n  }: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }): Promise<CommentData>;\n\n  deleteComment({\n    roomId,\n    threadId,\n    commentId,\n  }: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n  }): Promise<void>;\n\n  addReaction({\n    roomId,\n    threadId,\n    commentId,\n    emoji,\n  }: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }): Promise<CommentUserReaction>;\n\n  removeReaction({\n    roomId,\n    threadId,\n    commentId,\n    emoji,\n  }: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }): Promise<void>;\n\n  markThreadAsResolved({\n    roomId,\n    threadId,\n  }: {\n    roomId: string;\n    threadId: string;\n  }): Promise<void>;\n\n  markThreadAsUnresolved({\n    roomId,\n    threadId,\n  }: {\n    roomId: string;\n    threadId: string;\n  }): Promise<void>;\n\n  subscribeToThread({\n    roomId,\n    threadId,\n  }: {\n    roomId: string;\n    threadId: string;\n  }): Promise<SubscriptionData>;\n\n  unsubscribeFromThread({\n    roomId,\n    threadId,\n  }: {\n    roomId: string;\n    threadId: string;\n  }): Promise<void>;\n\n  // Notifications\n  markRoomInboxNotificationAsRead({\n    roomId,\n    inboxNotificationId,\n  }: {\n    roomId: string;\n    inboxNotificationId: string;\n  }): Promise<string>;\n\n  getSubscriptionSettings({\n    roomId,\n    signal,\n  }: {\n    roomId: string;\n    signal?: AbortSignal;\n  }): Promise<RoomSubscriptionSettings>;\n\n  updateSubscriptionSettings({\n    roomId,\n    settings,\n  }: {\n    roomId: string;\n    settings: Partial<RoomSubscriptionSettings>;\n  }): Promise<RoomSubscriptionSettings>;\n\n  // Attachments\n  getAttachmentUrl(options: {\n    roomId: string;\n    attachmentId: string;\n  }): Promise<string>;\n\n  uploadAttachment({\n    roomId,\n    attachment,\n    signal,\n  }: {\n    roomId: string;\n    attachment: CommentLocalAttachment;\n    signal?: AbortSignal;\n  }): Promise<CommentAttachment>;\n\n  getOrCreateAttachmentUrlsStore(roomId: string): BatchStore<string, string>;\n\n  uploadChatAttachment({\n    chatId,\n    attachment,\n    signal,\n  }: {\n    chatId: string;\n    attachment: { id: string; file: File };\n    signal?: AbortSignal;\n  }): Promise<void>;\n\n  getOrCreateChatAttachmentUrlsStore(\n    chatId: string\n  ): BatchStore<string, string>;\n  getChatAttachmentUrl(options: { attachmentId: string }): Promise<string>;\n\n  // Text editor\n  createTextMention({\n    roomId,\n    userId,\n    mentionId,\n  }: {\n    roomId: string;\n    userId: string;\n    mentionId: string;\n  }): Promise<void>;\n\n  deleteTextMention({\n    roomId,\n    mentionId,\n  }: {\n    roomId: string;\n    mentionId: string;\n  }): Promise<void>;\n\n  getTextVersion({\n    roomId,\n    versionId,\n  }: {\n    roomId: string;\n    versionId: string;\n  }): Promise<Response>;\n\n  createTextVersion({ roomId }: { roomId: string }): Promise<void>;\n\n  reportTextEditor({\n    roomId,\n    type,\n    rootKey,\n  }: {\n    roomId: string;\n    type: TextEditorType;\n    rootKey: string;\n  }): Promise<void>;\n\n  listTextVersions({ roomId }: { roomId: string }): Promise<{\n    versions: {\n      type: \"historyVersion\";\n      kind: \"yjs\";\n      id: string;\n      authors: {\n        id: string;\n      }[];\n      createdAt: Date;\n    }[];\n    requestedAt: Date;\n  }>;\n\n  listTextVersionsSince({\n    roomId,\n    since,\n    signal,\n  }: {\n    roomId: string;\n    since: Date;\n    signal?: AbortSignal;\n  }): Promise<{\n    versions: {\n      type: \"historyVersion\";\n      kind: \"yjs\";\n      id: string;\n      authors: {\n        id: string;\n      }[];\n      createdAt: Date;\n    }[];\n    requestedAt: Date;\n  }>;\n\n  streamStorage(options: {\n    roomId: string;\n  }): Promise<IdTuple<SerializedCrdt>[]>;\n\n  sendMessages<P extends JsonObject, E extends Json>(options: {\n    roomId: string;\n    nonce: string | undefined;\n    messages: ClientMsg<P, E>[];\n  }): Promise<Response>;\n\n  executeContextualPrompt({\n    roomId,\n    prompt,\n    context,\n    signal,\n  }: {\n    roomId: string;\n    prompt: string;\n    context: ContextualPromptContext;\n    previous?: {\n      prompt: string;\n      response: ContextualPromptResponse;\n    };\n    signal: AbortSignal;\n  }): Promise<string>;\n}\n\nexport interface NotificationHttpApi<M extends BaseMetadata> {\n  getInboxNotifications(options?: { cursor?: string }): Promise<{\n    inboxNotifications: InboxNotificationData[];\n    threads: ThreadData<M>[];\n    subscriptions: SubscriptionData[];\n    nextCursor: string | null;\n    requestedAt: Date;\n  }>;\n\n  getInboxNotificationsSince(options: {\n    since: Date;\n    signal?: AbortSignal;\n  }): Promise<{\n    inboxNotifications: {\n      updated: InboxNotificationData[];\n      deleted: InboxNotificationDeleteInfo[];\n    };\n    threads: {\n      updated: ThreadData<M>[];\n      deleted: ThreadDeleteInfo[];\n    };\n    subscriptions: {\n      updated: SubscriptionData[];\n      deleted: SubscriptionDeleteInfo[];\n    };\n    requestedAt: Date;\n  }>;\n\n  getUnreadInboxNotificationsCount(): Promise<number>;\n\n  markAllInboxNotificationsAsRead(): Promise<void>;\n\n  markInboxNotificationAsRead(inboxNotificationId: string): Promise<void>;\n\n  deleteAllInboxNotifications(): Promise<void>;\n\n  deleteInboxNotification(inboxNotificationId: string): Promise<void>;\n\n  getNotificationSettings(options?: {\n    signal?: AbortSignal;\n  }): Promise<NotificationSettingsPlain>;\n\n  updateNotificationSettings(\n    settings: PartialNotificationSettings\n  ): Promise<NotificationSettingsPlain>;\n}\n\nexport interface LiveblocksHttpApi<M extends BaseMetadata>\n  extends RoomHttpApi<M>,\n    NotificationHttpApi<M> {\n  getUserThreads_experimental(options?: {\n    cursor?: string;\n    query?: {\n      resolved?: boolean;\n      metadata?: Partial<QueryMetadata<M>>;\n    };\n  }): Promise<{\n    threads: ThreadData<M>[];\n    inboxNotifications: InboxNotificationData[];\n    subscriptions: SubscriptionData[];\n    nextCursor: string | null;\n    requestedAt: Date;\n    permissionHints: Record<string, Permission[]>;\n  }>;\n\n  getUserThreadsSince_experimental(options: {\n    since: Date;\n    signal?: AbortSignal;\n  }): Promise<{\n    inboxNotifications: {\n      updated: InboxNotificationData[];\n      deleted: InboxNotificationDeleteInfo[];\n    };\n    threads: {\n      updated: ThreadData<M>[];\n      deleted: ThreadDeleteInfo[];\n    };\n    subscriptions: {\n      updated: SubscriptionData[];\n      deleted: SubscriptionDeleteInfo[];\n    };\n    requestedAt: Date;\n    permissionHints: Record<string, Permission[]>;\n  }>;\n}\n\nexport function createApiClient<M extends BaseMetadata>({\n  baseUrl,\n  authManager,\n  currentUserId,\n  fetchPolyfill,\n}: {\n  baseUrl: string;\n  authManager: AuthManager;\n  currentUserId: Signal<string | undefined>;\n  fetchPolyfill: typeof fetch;\n}): LiveblocksHttpApi<M> {\n  const httpClient = new HttpClient(baseUrl, fetchPolyfill);\n\n  /* -------------------------------------------------------------------------------------------------\n   * Threads (Room level)\n   * -----------------------------------------------------------------------------------------------*/\n  async function getThreadsSince(options: {\n    roomId: string;\n    since: Date;\n    signal?: AbortSignal;\n  }) {\n    const result = await httpClient.get<{\n      data: ThreadDataPlain<M>[];\n      inboxNotifications: InboxNotificationDataPlain[];\n      subscriptions: SubscriptionDataPlain[];\n      deletedThreads: ThreadDeleteInfoPlain[];\n      deletedInboxNotifications: InboxNotificationDeleteInfoPlain[];\n      deletedSubscriptions: SubscriptionDeleteInfoPlain[];\n      meta: {\n        requestedAt: string;\n        permissionHints: Record<string, Permission[]>;\n      };\n    }>(\n      url`/v2/c/rooms/${options.roomId}/threads/delta`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      {\n        since: options.since.toISOString(),\n      },\n      { signal: options.signal }\n    );\n\n    return {\n      threads: {\n        updated: result.data.map(convertToThreadData),\n        deleted: result.deletedThreads.map(convertToThreadDeleteInfo),\n      },\n      inboxNotifications: {\n        updated: result.inboxNotifications.map(convertToInboxNotificationData),\n        deleted: result.deletedInboxNotifications.map(\n          convertToInboxNotificationDeleteInfo\n        ),\n      },\n      subscriptions: {\n        updated: result.subscriptions.map(convertToSubscriptionData),\n        deleted: result.deletedSubscriptions.map(\n          convertToSubscriptionDeleteInfo\n        ),\n      },\n      requestedAt: new Date(result.meta.requestedAt),\n      permissionHints: result.meta.permissionHints,\n    };\n  }\n\n  async function getThreads(options: {\n    roomId: string;\n    cursor?: string;\n    query?: {\n      resolved?: boolean;\n      metadata?: Partial<QueryMetadata<M>>;\n    };\n  }) {\n    let query: string | undefined;\n\n    if (options.query) {\n      query = objectToQuery(options.query);\n    }\n\n    const PAGE_SIZE = 50;\n\n    try {\n      const result = await httpClient.get<{\n        data: ThreadDataPlain<M>[];\n        inboxNotifications: InboxNotificationDataPlain[];\n        subscriptions: SubscriptionDataPlain[];\n        deletedThreads: ThreadDeleteInfoPlain[];\n        deletedInboxNotifications: InboxNotificationDeleteInfoPlain[];\n        deletedSubscriptions: SubscriptionDeleteInfoPlain[];\n        meta: {\n          requestedAt: string;\n          nextCursor: string | null;\n          permissionHints: Record<string, Permission[]>;\n        };\n      }>(\n        url`/v2/c/rooms/${options.roomId}/threads`,\n        await authManager.getAuthValue({\n          requestedScope: \"comments:read\",\n          roomId: options.roomId,\n        }),\n        {\n          cursor: options.cursor,\n          query,\n          limit: PAGE_SIZE,\n        }\n      );\n\n      return {\n        threads: result.data.map(convertToThreadData),\n        inboxNotifications: result.inboxNotifications.map(\n          convertToInboxNotificationData\n        ),\n        subscriptions: result.subscriptions.map(convertToSubscriptionData),\n        nextCursor: result.meta.nextCursor,\n        requestedAt: new Date(result.meta.requestedAt),\n        permissionHints: result.meta.permissionHints,\n      };\n    } catch (err) {\n      if (err instanceof HttpError && err.status === 404) {\n        // If the room does (not) yet exist, the response will be a 404 error\n        // response which we'll interpret as an empty list of threads.\n        return {\n          threads: [],\n          inboxNotifications: [],\n          subscriptions: [],\n          nextCursor: null,\n          //\n          // HACK\n          // requestedAt needs to be a *server* timestamp here. However, on\n          // this 404 error response, there is no such timestamp. So out of\n          // pure necessity we'll fall back to a local timestamp instead (and\n          // allow for a possible 6 hour clock difference between client and\n          // server).\n          //\n          requestedAt: new Date(Date.now() - 6 * 60 * 60 * 1000),\n          permissionHints: {},\n        };\n      }\n\n      throw err;\n    }\n  }\n\n  async function createThread(options: {\n    roomId: string;\n    threadId?: string;\n    commentId?: string;\n    metadata: M | undefined;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }) {\n    const commentId = options.commentId ?? createCommentId();\n    const threadId = options.threadId ?? createThreadId();\n\n    const thread = await httpClient.post<ThreadDataPlain<M>>(\n      url`/v2/c/rooms/${options.roomId}/threads`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      {\n        id: threadId,\n        comment: {\n          id: commentId,\n          body: options.body,\n          attachmentIds: options.attachmentIds,\n        },\n        metadata: options.metadata,\n      }\n    );\n\n    return convertToThreadData<M>(thread);\n  }\n\n  async function deleteThread(options: { roomId: string; threadId: string }) {\n    await httpClient.delete(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  async function getThread(options: { roomId: string; threadId: string }) {\n    const response = await httpClient.rawGet(\n      url`/v2/c/rooms/${options.roomId}/thread-with-notification/${options.threadId}`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n\n    if (response.ok) {\n      const json = (await response.json()) as {\n        thread: ThreadDataPlain<M>;\n        inboxNotification?: InboxNotificationDataPlain;\n        subscription?: SubscriptionDataPlain;\n      };\n\n      return {\n        thread: convertToThreadData(json.thread),\n        inboxNotification: json.inboxNotification\n          ? convertToInboxNotificationData(json.inboxNotification)\n          : undefined,\n        subscription: json.subscription\n          ? convertToSubscriptionData(json.subscription)\n          : undefined,\n      };\n    } else if (response.status === 404) {\n      return {\n        thread: undefined,\n        inboxNotification: undefined,\n        subscription: undefined,\n      };\n    } else {\n      throw new Error(\n        `There was an error while getting thread ${options.threadId}.`\n      );\n    }\n  }\n\n  async function editThreadMetadata(options: {\n    roomId: string;\n    metadata: Patchable<M>;\n    threadId: string;\n  }) {\n    return await httpClient.post<M>(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/metadata`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      options.metadata\n    );\n  }\n\n  async function createComment(options: {\n    roomId: string;\n    threadId: string;\n    commentId?: string;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }) {\n    const commentId = options.commentId ?? createCommentId();\n    const comment = await httpClient.post<CommentDataPlain>(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      {\n        id: commentId,\n        body: options.body,\n        attachmentIds: options.attachmentIds,\n      }\n    );\n    return convertToCommentData(comment);\n  }\n\n  async function editComment(options: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }) {\n    const comment = await httpClient.post<CommentDataPlain>(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      {\n        body: options.body,\n        attachmentIds: options.attachmentIds,\n      }\n    );\n\n    return convertToCommentData(comment);\n  }\n\n  async function deleteComment(options: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n  }) {\n    await httpClient.delete(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  async function addReaction(options: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }) {\n    const reaction = await httpClient.post<CommentUserReactionPlain>(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}/reactions`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      { emoji: options.emoji }\n    );\n\n    return convertToCommentUserReaction(reaction);\n  }\n\n  async function removeReaction(options: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }) {\n    await httpClient.delete<CommentDataPlain>(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}/reactions/${options.emoji}`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  async function markThreadAsResolved(options: {\n    roomId: string;\n    threadId: string;\n  }) {\n    await httpClient.post(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/mark-as-resolved`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  async function markThreadAsUnresolved(options: {\n    roomId: string;\n    threadId: string;\n  }) {\n    await httpClient.post(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/mark-as-unresolved`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  async function subscribeToThread(options: {\n    roomId: string;\n    threadId: string;\n  }) {\n    const subscription = await httpClient.post<SubscriptionDataPlain>(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/subscribe`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n\n    return convertToSubscriptionData(subscription);\n  }\n\n  async function unsubscribeFromThread(options: {\n    roomId: string;\n    threadId: string;\n  }) {\n    await httpClient.post(\n      url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/unsubscribe`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  /* -------------------------------------------------------------------------------------------------\n   * Attachments (Room level)\n   * -----------------------------------------------------------------------------------------------*/\n  async function uploadAttachment(options: {\n    roomId: string;\n    attachment: CommentLocalAttachment;\n    signal?: AbortSignal;\n  }): Promise<CommentAttachment> {\n    const roomId = options.roomId;\n    const abortSignal = options.signal;\n    const attachment = options.attachment;\n\n    const abortError = abortSignal\n      ? new DOMException(\n          `Upload of attachment ${options.attachment.id} was aborted.`,\n          \"AbortError\"\n        )\n      : undefined;\n\n    if (abortSignal?.aborted) {\n      throw abortError;\n    }\n\n    const handleRetryError = (err: Error) => {\n      if (abortSignal?.aborted) {\n        throw abortError;\n      }\n\n      if (err instanceof HttpError && err.status === 413) {\n        throw err;\n      }\n\n      return false;\n    };\n\n    const ATTACHMENT_PART_SIZE = 5 * 1024 * 1024; // 5 MB\n    const RETRY_ATTEMPTS = 10;\n    const RETRY_DELAYS = [\n      2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000,\n    ];\n\n    function splitFileIntoParts(file: File) {\n      const parts: { partNumber: number; part: Blob }[] = [];\n\n      let start = 0;\n\n      while (start < file.size) {\n        const end = Math.min(start + ATTACHMENT_PART_SIZE, file.size);\n\n        parts.push({\n          partNumber: parts.length + 1,\n          part: file.slice(start, end),\n        });\n\n        start = end;\n      }\n\n      return parts;\n    }\n\n    if (attachment.size <= ATTACHMENT_PART_SIZE) {\n      // If the file is small enough, upload it in a single request\n      return autoRetry(\n        async () =>\n          httpClient.putBlob<CommentAttachment>(\n            url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/upload/${encodeURIComponent(attachment.name)}`,\n            await authManager.getAuthValue({\n              requestedScope: \"comments:read\",\n              roomId,\n            }),\n            attachment.file,\n            { fileSize: attachment.size },\n            { signal: abortSignal }\n          ),\n        RETRY_ATTEMPTS,\n        RETRY_DELAYS,\n        handleRetryError\n      );\n    } else {\n      // Otherwise, upload it in multiple parts\n      let uploadId: string | undefined;\n      const uploadedParts: {\n        etag: string;\n        partNumber: number;\n      }[] = [];\n\n      // Create a multi-part upload\n      const createMultiPartUpload = await autoRetry(\n        async () =>\n          httpClient.post<{\n            uploadId: string;\n            key: string;\n          }>(\n            url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/multipart/${encodeURIComponent(attachment.name)}`,\n            await authManager.getAuthValue({\n              requestedScope: \"comments:read\",\n              roomId,\n            }),\n            undefined,\n            { signal: abortSignal },\n            { fileSize: attachment.size }\n          ),\n        RETRY_ATTEMPTS,\n        RETRY_DELAYS,\n        handleRetryError\n      );\n\n      try {\n        uploadId = createMultiPartUpload.uploadId;\n\n        const parts = splitFileIntoParts(attachment.file);\n\n        // Check if the upload was aborted\n        if (abortSignal?.aborted) {\n          throw abortError;\n        }\n\n        const batches = chunk(parts, 5);\n\n        // Batches are uploaded one after the other\n        for (const parts of batches) {\n          const uploadedPartsPromises: Promise<{\n            partNumber: number;\n            etag: string;\n          }>[] = [];\n\n          for (const { part, partNumber } of parts) {\n            uploadedPartsPromises.push(\n              autoRetry(\n                async () =>\n                  httpClient.putBlob<{\n                    partNumber: number;\n                    etag: string;\n                  }>(\n                    url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/multipart/${createMultiPartUpload.uploadId}/${String(partNumber)}`,\n                    await authManager.getAuthValue({\n                      requestedScope: \"comments:read\",\n                      roomId,\n                    }),\n                    part,\n                    undefined,\n                    { signal: abortSignal }\n                  ),\n                RETRY_ATTEMPTS,\n                RETRY_DELAYS,\n                handleRetryError\n              )\n            );\n          }\n\n          // Parts are uploaded in parallel\n          uploadedParts.push(...(await Promise.all(uploadedPartsPromises)));\n        }\n\n        // Check if the upload was aborted\n        if (abortSignal?.aborted) {\n          throw abortError;\n        }\n\n        const sortedUploadedParts = uploadedParts.sort(\n          (a, b) => a.partNumber - b.partNumber\n        );\n\n        return httpClient.post<CommentAttachment>(\n          url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/multipart/${uploadId}/complete`,\n          await authManager.getAuthValue({\n            requestedScope: \"comments:read\",\n            roomId,\n          }),\n          { parts: sortedUploadedParts },\n          { signal: abortSignal }\n        );\n      } catch (error) {\n        if (\n          uploadId &&\n          (error as Error)?.name &&\n          ((error as Error).name === \"AbortError\" ||\n            (error as Error).name === \"TimeoutError\")\n        ) {\n          try {\n            // Abort the multi-part upload if it was created\n            await httpClient.rawDelete(\n              url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/multipart/${uploadId}`,\n              await authManager.getAuthValue({\n                requestedScope: \"comments:read\",\n                roomId,\n              })\n            );\n          } catch (error) {\n            // Ignore the error, we are probably offline\n          }\n        }\n\n        throw error;\n      }\n    }\n  }\n\n  const attachmentUrlsBatchStoresByRoom = new DefaultMap<\n    string,\n    BatchStore<string, string>\n  >((roomId) => {\n    const batch = new Batch<string, string>(\n      async (batchedAttachmentIds) => {\n        const attachmentIds = batchedAttachmentIds.flat();\n        const { urls } = await httpClient.post<{\n          urls: (string | null)[];\n        }>(\n          url`/v2/c/rooms/${roomId}/attachments/presigned-urls`,\n          await authManager.getAuthValue({\n            requestedScope: \"comments:read\",\n            roomId,\n          }),\n          { attachmentIds }\n        );\n\n        return urls.map(\n          (url) =>\n            url ??\n            new Error(\"There was an error while getting this attachment's URL\")\n        );\n      },\n      { delay: 50 }\n    );\n    return createBatchStore(batch);\n  });\n\n  function getOrCreateAttachmentUrlsStore(\n    roomId: string\n  ): BatchStore<string, string> {\n    return attachmentUrlsBatchStoresByRoom.getOrCreate(roomId);\n  }\n\n  function getAttachmentUrl(options: { roomId: string; attachmentId: string }) {\n    const batch = getOrCreateAttachmentUrlsStore(options.roomId).batch;\n    return batch.get(options.attachmentId);\n  }\n\n  /* -------------------------------------------------------------------------------------------------\n   * Attachments (Chat level)\n   * -----------------------------------------------------------------------------------------------*/\n  async function uploadChatAttachment(options: {\n    chatId: string;\n    attachment: {\n      id: string;\n      file: File;\n    };\n    signal?: AbortSignal;\n  }): Promise<void> {\n    const { chatId, attachment, signal } = options;\n    const userId = currentUserId.get();\n    if (userId === undefined) {\n      throw new Error(\"Attachment upload requires an authenticated user.\");\n    }\n    const ATTACHMENT_PART_SIZE = 5 * 1024 * 1024; // 5 MB\n\n    if (options.attachment.file.size <= ATTACHMENT_PART_SIZE) {\n      await httpClient.putBlob(\n        url`/v2/c/chats/${chatId}/attachments/${attachment.id}/upload/${encodeURIComponent(attachment.file.name)}`,\n        await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n        attachment.file,\n        { fileSize: attachment.file.size },\n        { signal }\n      );\n    } else {\n      const multipartUpload = await httpClient.post<{\n        uploadId: string;\n        key: string;\n      }>(\n        url`/v2/c/chats/${chatId}/attachments/${attachment.id}/multipart/${encodeURIComponent(attachment.file.name)}`,\n        await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n        undefined,\n        { signal },\n        { fileSize: attachment.file.size }\n      );\n\n      try {\n        const uploadedParts: { etag: string; number: number }[] = [];\n\n        const parts: { number: number; part: Blob }[] = [];\n        let start = 0;\n        while (start < attachment.file.size) {\n          const end = Math.min(\n            start + ATTACHMENT_PART_SIZE,\n            attachment.file.size\n          );\n          parts.push({\n            number: parts.length + 1,\n            part: attachment.file.slice(start, end),\n          });\n          start = end;\n        }\n\n        uploadedParts.push(\n          ...(await Promise.all(\n            parts.map(async ({ number, part }) => {\n              return await httpClient.putBlob<{\n                etag: string;\n                number: number;\n              }>(\n                url`/v2/c/chats/${chatId}/attachments/${attachment.id}/multipart/${multipartUpload.uploadId}/${String(number)}`,\n                await authManager.getAuthValue({\n                  requestedScope: \"comments:read\",\n                }),\n                part,\n                undefined,\n                { signal }\n              );\n            })\n          ))\n        );\n\n        await httpClient.post(\n          url`/v2/c/chats/${chatId}/attachments/${attachment.id}/multipart/${multipartUpload.uploadId}/complete`,\n          await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n          { parts: uploadedParts.sort((a, b) => a.number - b.number) },\n          { signal }\n        );\n      } catch (err) {\n        try {\n          await httpClient.delete(\n            url`/v2/c/chats/${chatId}/attachments/${attachment.id}/multipart/${multipartUpload.uploadId}`,\n            await authManager.getAuthValue({ requestedScope: \"comments:read\" })\n          );\n        } catch (err) {\n          // Ignore the error, we are probably offline\n        }\n        throw err;\n      }\n    }\n  }\n\n  const attachmentUrlsBatchStoresByChat = new DefaultMap<\n    string,\n    BatchStore<string, string>\n  >((chatId) => {\n    const batch = new Batch<string, string>(\n      async (batchedAttachmentIds) => {\n        const attachmentIds = batchedAttachmentIds.flat();\n        const { urls } = await httpClient.post<{\n          urls: (string | null)[];\n        }>(\n          url`/v2/c/chats/${chatId}/attachments/presigned-urls`,\n          await authManager.getAuthValue({\n            requestedScope: \"comments:read\",\n          }),\n          { attachmentIds }\n        );\n\n        return urls.map(\n          (url) =>\n            url ??\n            new Error(\"There was an error while getting this attachment's URL\")\n        );\n      },\n      { delay: 50 }\n    );\n    return createBatchStore(batch);\n  });\n\n  function getOrCreateChatAttachmentUrlsStore(\n    chatId: string\n  ): BatchStore<string, string> {\n    return attachmentUrlsBatchStoresByChat.getOrCreate(chatId);\n  }\n\n  function getChatAttachmentUrl(options: {\n    chatId: string;\n    attachmentId: string;\n  }) {\n    const batch = getOrCreateChatAttachmentUrlsStore(options.chatId).batch;\n    return batch.get(options.attachmentId);\n  }\n\n  /* -------------------------------------------------------------------------------------------------\n   * Notifications (Room level)\n   * -----------------------------------------------------------------------------------------------*/\n  async function getSubscriptionSettings(options: {\n    roomId: string;\n    signal?: AbortSignal;\n  }): Promise<RoomSubscriptionSettings> {\n    return httpClient.get<RoomSubscriptionSettings>(\n      url`/v2/c/rooms/${options.roomId}/subscription-settings`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      undefined,\n      {\n        signal: options.signal,\n      }\n    );\n  }\n\n  async function updateSubscriptionSettings(options: {\n    roomId: string;\n    settings: Partial<RoomSubscriptionSettings>;\n  }): Promise<RoomSubscriptionSettings> {\n    return httpClient.post<RoomSubscriptionSettings>(\n      url`/v2/c/rooms/${options.roomId}/subscription-settings`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      options.settings\n    );\n  }\n\n  const markAsReadBatchesByRoom = new DefaultMap<string, Batch<string, string>>(\n    (roomId) =>\n      new Batch<string, string>(\n        async (batchedInboxNotificationIds) => {\n          const inboxNotificationIds = batchedInboxNotificationIds.flat();\n          // This method (and the following batch handling) isn't the same as the one in\n          // src/notifications.ts, this one is room-based: /v2/c/rooms/<roomId>/inbox-notifications/read.\n          //\n          // The reason for this is that unlike the room-based Comments ones, the Notifications endpoints\n          // don't work with a public key. Since `markThreadAsRead` needs to mark the related inbox notifications\n          // as read, this room-based method is necessary to keep all Comments features working with a public key.\n          await httpClient.post(\n            url`/v2/c/rooms/${roomId}/inbox-notifications/read`,\n            await authManager.getAuthValue({\n              requestedScope: \"comments:read\",\n              roomId,\n            }),\n            { inboxNotificationIds }\n          );\n          return inboxNotificationIds;\n        },\n        { delay: 50 }\n      )\n  );\n\n  async function markRoomInboxNotificationAsRead(options: {\n    roomId: string;\n    inboxNotificationId: string;\n  }) {\n    const batch = markAsReadBatchesByRoom.getOrCreate(options.roomId);\n    return batch.get(options.inboxNotificationId);\n  }\n\n  /* -------------------------------------------------------------------------------------------------\n   * Text editor (Room level)\n   * -----------------------------------------------------------------------------------------------*/\n  async function createTextMention(options: {\n    roomId: string;\n    userId: string;\n    mentionId: string;\n  }) {\n    await httpClient.rawPost(\n      url`/v2/c/rooms/${options.roomId}/text-mentions`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      {\n        userId: options.userId,\n        mentionId: options.mentionId,\n      }\n    );\n  }\n\n  async function deleteTextMention(options: {\n    roomId: string;\n    mentionId: string;\n  }) {\n    await httpClient.rawDelete(\n      url`/v2/c/rooms/${options.roomId}/text-mentions/${options.mentionId}`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  async function getTextVersion(options: {\n    roomId: string;\n    versionId: string;\n  }) {\n    return httpClient.rawGet(\n      url`/v2/c/rooms/${options.roomId}/y-version/${options.versionId}`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  async function createTextVersion(options: { roomId: string }) {\n    await httpClient.rawPost(\n      url`/v2/c/rooms/${options.roomId}/version`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n  }\n\n  async function reportTextEditor(options: {\n    roomId: string;\n    type: TextEditorType;\n    rootKey: string;\n  }) {\n    await httpClient.rawPost(\n      url`/v2/c/rooms/${options.roomId}/text-metadata`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      {\n        type: options.type,\n        rootKey: options.rootKey,\n      }\n    );\n  }\n\n  async function executeContextualPrompt(options: {\n    roomId: string;\n    prompt: string;\n    context: ContextualPromptContext;\n    previous?: {\n      prompt: string;\n      response: ContextualPromptResponse;\n    };\n    signal: AbortSignal;\n  }): Promise<string> {\n    const result = await httpClient.post<{\n      content: { type: \"text\"; text: string }[];\n    }>(\n      url`/v2/c/rooms/${options.roomId}/ai/contextual-prompt`,\n      await authManager.getAuthValue({\n        requestedScope: \"room:read\",\n        roomId: options.roomId,\n      }),\n      {\n        prompt: options.prompt,\n        context: {\n          beforeSelection: options.context.beforeSelection,\n          selection: options.context.selection,\n          afterSelection: options.context.afterSelection,\n        },\n        previous: options.previous,\n      },\n      { signal: options.signal }\n    );\n    if (!result || result.content.length === 0) {\n      throw new Error(\"No content returned from server\");\n    }\n    return result.content[0].text;\n  }\n\n  async function listTextVersions(options: { roomId: string }) {\n    const result = await httpClient.get<{\n      versions: DateToString<HistoryVersion>[];\n      meta: {\n        requestedAt: string;\n      };\n    }>(\n      url`/v2/c/rooms/${options.roomId}/versions`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      })\n    );\n\n    return {\n      versions: result.versions.map(({ createdAt, ...version }) => {\n        return {\n          createdAt: new Date(createdAt),\n          ...version,\n        };\n      }),\n      requestedAt: new Date(result.meta.requestedAt),\n    };\n  }\n\n  async function listTextVersionsSince(options: {\n    roomId: string;\n    since: Date;\n    signal?: AbortSignal;\n  }) {\n    const result = await httpClient.get<{\n      versions: DateToString<HistoryVersion>[];\n      meta: {\n        requestedAt: string;\n      };\n    }>(\n      url`/v2/c/rooms/${options.roomId}/versions/delta`,\n      await authManager.getAuthValue({\n        requestedScope: \"comments:read\",\n        roomId: options.roomId,\n      }),\n      { since: options.since.toISOString() },\n      { signal: options.signal }\n    );\n\n    return {\n      versions: result.versions.map(({ createdAt, ...version }) => {\n        return {\n          createdAt: new Date(createdAt),\n          ...version,\n        };\n      }),\n      requestedAt: new Date(result.meta.requestedAt),\n    };\n  }\n\n  async function streamStorage(options: { roomId: string }) {\n    const result = await httpClient.rawGet(\n      url`/v2/c/rooms/${options.roomId}/storage`,\n      await authManager.getAuthValue({\n        requestedScope: \"room:read\",\n        roomId: options.roomId,\n      })\n    );\n    return (await result.json()) as IdTuple<SerializedCrdt>[];\n  }\n\n  async function sendMessages<P extends JsonObject, E extends Json>(options: {\n    roomId: string;\n    nonce: string | undefined;\n    messages: ClientMsg<P, E>[];\n  }) {\n    return httpClient.rawPost(\n      url`/v2/c/rooms/${options.roomId}/send-message`,\n      await authManager.getAuthValue({\n        requestedScope: \"room:read\",\n        roomId: options.roomId,\n      }),\n      {\n        nonce: options.nonce,\n        messages: options.messages,\n      }\n    );\n  }\n\n  /* -------------------------------------------------------------------------------------------------\n   * Inbox notifications (User-level)\n   * -----------------------------------------------------------------------------------------------*/\n  async function getInboxNotifications(options?: { cursor?: string }) {\n    const PAGE_SIZE = 50;\n\n    const json = await httpClient.get<{\n      threads: ThreadDataPlain<M>[];\n      inboxNotifications: InboxNotificationDataPlain[];\n      subscriptions: SubscriptionDataPlain[];\n      meta: {\n        requestedAt: string;\n        nextCursor: string | null;\n      };\n    }>(\n      url`/v2/c/inbox-notifications`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n      {\n        cursor: options?.cursor,\n        limit: PAGE_SIZE,\n      }\n    );\n\n    return {\n      inboxNotifications: json.inboxNotifications.map(\n        convertToInboxNotificationData\n      ),\n      threads: json.threads.map(convertToThreadData),\n      subscriptions: json.subscriptions.map(convertToSubscriptionData),\n      nextCursor: json.meta.nextCursor,\n      requestedAt: new Date(json.meta.requestedAt),\n    };\n  }\n\n  async function getInboxNotificationsSince(options: {\n    since: Date;\n    signal?: AbortSignal;\n  }) {\n    const json = await httpClient.get<{\n      threads: ThreadDataPlain<M>[];\n      inboxNotifications: InboxNotificationDataPlain[];\n      subscriptions: SubscriptionDataPlain[];\n      deletedThreads: ThreadDeleteInfoPlain[];\n      deletedInboxNotifications: InboxNotificationDeleteInfoPlain[];\n      deletedSubscriptions: SubscriptionDeleteInfoPlain[];\n      meta: {\n        requestedAt: string;\n      };\n    }>(\n      url`/v2/c/inbox-notifications/delta`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n      { since: options.since.toISOString() },\n      { signal: options.signal }\n    );\n    return {\n      inboxNotifications: {\n        updated: json.inboxNotifications.map(convertToInboxNotificationData),\n        deleted: json.deletedInboxNotifications.map(\n          convertToInboxNotificationDeleteInfo\n        ),\n      },\n      threads: {\n        updated: json.threads.map(convertToThreadData),\n        deleted: json.deletedThreads.map(convertToThreadDeleteInfo),\n      },\n      subscriptions: {\n        updated: json.subscriptions.map(convertToSubscriptionData),\n        deleted: json.deletedSubscriptions.map(convertToSubscriptionDeleteInfo),\n      },\n      requestedAt: new Date(json.meta.requestedAt),\n    };\n  }\n\n  async function getUnreadInboxNotificationsCount() {\n    const { count } = await httpClient.get<{ count: number }>(\n      url`/v2/c/inbox-notifications/count`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" })\n    );\n    return count;\n  }\n\n  async function markAllInboxNotificationsAsRead() {\n    await httpClient.post(\n      url`/v2/c/inbox-notifications/read`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n      {\n        inboxNotificationIds: \"all\",\n      }\n    );\n  }\n\n  async function markInboxNotificationsAsRead(inboxNotificationIds: string[]) {\n    await httpClient.post(\n      url`/v2/c/inbox-notifications/read`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n      {\n        inboxNotificationIds,\n      }\n    );\n  }\n\n  const batchedMarkInboxNotificationsAsRead = new Batch<string, string>(\n    async (batchedInboxNotificationIds) => {\n      const inboxNotificationIds = batchedInboxNotificationIds.flat();\n\n      await markInboxNotificationsAsRead(inboxNotificationIds);\n\n      return inboxNotificationIds;\n    },\n    { delay: 50 }\n  );\n\n  async function markInboxNotificationAsRead(inboxNotificationId: string) {\n    await batchedMarkInboxNotificationsAsRead.get(inboxNotificationId);\n  }\n\n  async function deleteAllInboxNotifications() {\n    await httpClient.delete(\n      url`/v2/c/inbox-notifications`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" })\n    );\n  }\n\n  async function deleteInboxNotification(inboxNotificationId: string) {\n    await httpClient.delete(\n      url`/v2/c/inbox-notifications/${inboxNotificationId}`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" })\n    );\n  }\n\n  /* -------------------------------------------------------------------------------------------------\n   * Notifications settings (Project level)\n   * -------------------------------------------------------------------------------------------------\n   */\n  async function getNotificationSettings(options?: {\n    signal?: AbortSignal;\n  }): Promise<NotificationSettingsPlain> {\n    return httpClient.get<NotificationSettingsPlain>(\n      url`/v2/c/notification-settings`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n      undefined,\n      { signal: options?.signal }\n    );\n  }\n\n  async function updateNotificationSettings(\n    settings: PartialNotificationSettings\n  ): Promise<NotificationSettingsPlain> {\n    return httpClient.post<NotificationSettingsPlain>(\n      url`/v2/c/notification-settings`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n      settings\n    );\n  }\n\n  /* -------------------------------------------------------------------------------------------------\n   * User threads\n   * -------------------------------------------------------------------------------------------------\n   */\n  async function getUserThreads_experimental(options?: {\n    cursor?: string;\n    query?: {\n      resolved?: boolean;\n      metadata?: Partial<QueryMetadata<M>>;\n    };\n  }) {\n    let query: string | undefined;\n\n    if (options?.query) {\n      query = objectToQuery(options.query);\n    }\n\n    const PAGE_SIZE = 50;\n\n    const json = await httpClient.get<{\n      threads: ThreadDataPlain<M>[];\n      inboxNotifications: InboxNotificationDataPlain[];\n      subscriptions: SubscriptionDataPlain[];\n      deletedThreads: ThreadDeleteInfoPlain[];\n      deletedInboxNotifications: InboxNotificationDeleteInfoPlain[];\n      deletedSubscriptions: SubscriptionDeleteInfoPlain[];\n      meta: {\n        requestedAt: string;\n        nextCursor: string | null;\n        permissionHints: Record<string, Permission[]>;\n      };\n    }>(\n      url`/v2/c/threads`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n      {\n        cursor: options?.cursor,\n        query,\n        limit: PAGE_SIZE,\n      }\n    );\n\n    return {\n      threads: json.threads.map(convertToThreadData),\n      inboxNotifications: json.inboxNotifications.map(\n        convertToInboxNotificationData\n      ),\n      subscriptions: json.subscriptions.map(convertToSubscriptionData),\n      nextCursor: json.meta.nextCursor,\n      requestedAt: new Date(json.meta.requestedAt),\n      permissionHints: json.meta.permissionHints,\n    };\n  }\n\n  async function getUserThreadsSince_experimental<\n    M extends BaseMetadata,\n  >(options: { since: Date; signal?: AbortSignal }) {\n    const json = await httpClient.get<{\n      threads: ThreadDataPlain<M>[];\n      inboxNotifications: InboxNotificationDataPlain[];\n      subscriptions: SubscriptionDataPlain[];\n      deletedThreads: ThreadDeleteInfoPlain[];\n      deletedInboxNotifications: InboxNotificationDeleteInfoPlain[];\n      deletedSubscriptions: SubscriptionDeleteInfoPlain[];\n      meta: {\n        requestedAt: string;\n        permissionHints: Record<string, Permission[]>;\n      };\n    }>(\n      url`/v2/c/threads/delta`,\n      await authManager.getAuthValue({ requestedScope: \"comments:read\" }),\n      { since: options.since.toISOString() },\n      { signal: options.signal }\n    );\n\n    return {\n      threads: {\n        updated: json.threads.map(convertToThreadData),\n        deleted: json.deletedThreads.map(convertToThreadDeleteInfo),\n      },\n      inboxNotifications: {\n        updated: json.inboxNotifications.map(convertToInboxNotificationData),\n        deleted: json.deletedInboxNotifications.map(\n          convertToInboxNotificationDeleteInfo\n        ),\n      },\n      subscriptions: {\n        updated: json.subscriptions.map(convertToSubscriptionData),\n        deleted: json.deletedSubscriptions.map(convertToSubscriptionDeleteInfo),\n      },\n      requestedAt: new Date(json.meta.requestedAt),\n      permissionHints: json.meta.permissionHints,\n    };\n  }\n\n  return {\n    // Room threads\n    getThreads,\n    getThreadsSince,\n    createThread,\n    getThread,\n    deleteThread,\n    editThreadMetadata,\n    createComment,\n    editComment,\n    deleteComment,\n    addReaction,\n    removeReaction,\n    markThreadAsResolved,\n    markThreadAsUnresolved,\n    subscribeToThread,\n    unsubscribeFromThread,\n    markRoomInboxNotificationAsRead,\n    // Room subscription settings\n    getSubscriptionSettings,\n    updateSubscriptionSettings,\n    // Room text editor\n    createTextMention,\n    deleteTextMention,\n    getTextVersion,\n    createTextVersion,\n    reportTextEditor,\n    listTextVersions,\n    listTextVersionsSince,\n    // Room attachments\n    getAttachmentUrl,\n    uploadAttachment,\n    getOrCreateAttachmentUrlsStore,\n    // User attachments\n    uploadChatAttachment,\n    getOrCreateChatAttachmentUrlsStore,\n    getChatAttachmentUrl,\n    // Room storage\n    streamStorage,\n    sendMessages,\n    // Notifications\n    getInboxNotifications,\n    getInboxNotificationsSince,\n    getUnreadInboxNotificationsCount,\n    markAllInboxNotificationsAsRead,\n    markInboxNotificationAsRead,\n    deleteAllInboxNotifications,\n    deleteInboxNotification,\n    getNotificationSettings,\n    updateNotificationSettings,\n    // User threads\n    getUserThreads_experimental,\n    getUserThreadsSince_experimental,\n    // AI\n    executeContextualPrompt,\n  };\n}\n\nexport function getBearerTokenFromAuthValue(authValue: AuthValue): string {\n  if (authValue.type === \"public\") {\n    return authValue.publicApiKey;\n  } else {\n    return authValue.token.raw;\n  }\n}\n\n/**\n * @internal\n *\n * Small HTTP client for client-only REST API requests (e.g. /v2/c/* URLs).\n * These URLs all use public key, ID token, or access token authorization. This\n * HTTP client can be shared and used by both the Liveblocks Client and\n * Liveblocks Room instances internally to talk to our client-only REST API\n * backend.\n */\nclass HttpClient {\n  #baseUrl: string;\n  #fetchPolyfill: typeof fetch;\n\n  constructor(baseUrl: string, fetchPolyfill: typeof fetch) {\n    this.#baseUrl = baseUrl;\n    this.#fetchPolyfill = fetchPolyfill;\n  }\n\n  // ------------------------------------------------------------------\n  // Public methods\n  // ------------------------------------------------------------------\n\n  /**\n   * Constructs and makes the HTTP request, but does not handle the response.\n   *\n   * This is what .rawFetch() does:    👈 This method!\n   *   1. Set Content-Type header\n   *   2. Set Authorization header\n   *   3. Call the callback to obtain the `authValue` to use in the Authorization header\n   *\n   * This is what .fetch() does ON TOP of that:\n   *   4. Parse response body as Json\n   *   5. ...but silently return `{}` if that parsing fails\n   *   6. Throw HttpError if response is an error\n   */\n  async #rawFetch(\n    endpoint: URLSafeString,\n    authValue: AuthValue,\n    options?: RequestInit,\n    params?: QueryParams\n  ): Promise<Response> {\n    if (!endpoint.startsWith(\"/v2/c/\")) {\n      raise(\"This client can only be used to make /v2/c/* requests\");\n    }\n\n    const url = urljoin(this.#baseUrl, endpoint, params);\n    return await this.#fetchPolyfill(url, {\n      ...options,\n      headers: {\n        // These headers are default, but can be overriden by custom headers\n        \"Content-Type\": \"application/json; charset=utf-8\",\n\n        // Possible header overrides\n        ...options?.headers,\n\n        // Cannot be overriden by custom headers\n        Authorization: `Bearer ${getBearerTokenFromAuthValue(authValue)}`,\n        \"X-LB-Client\": PKG_VERSION || \"dev\",\n      },\n    });\n  }\n\n  /**\n   * Constructs, makes the HTTP request, and handles the response by parsing\n   * JSON and/or throwing an HttpError if it failed.\n   *\n   * This is what .rawFetch() does:\n   *   1. Set Content-Type header\n   *   2. Set Authorization header\n   *   3. Call the callback to obtain the `authValue` to use in the Authorization header\n   *\n   * This is what .fetch() does ON TOP of that:   👈 This method!\n   *   4. Parse response body as Json\n   *   5. ...but silently return `{}` if that parsing fails (🤔)\n   *   6. Throw HttpError if response is an error\n   */\n  async #fetch<T extends JsonObject>(\n    endpoint: URLSafeString,\n    authValue: AuthValue,\n    options?: RequestInit,\n    params?: QueryParams\n  ): Promise<T> {\n    const response = await this.#rawFetch(endpoint, authValue, options, params);\n\n    if (!response.ok) {\n      throw await HttpError.fromResponse(response);\n    }\n\n    let body;\n    try {\n      body = (await response.json()) as T;\n    } catch {\n      // TODO This looks wrong 🤔 !\n      // TODO Should we not be throwing this error if something fails to parse?\n      body = {} as T;\n    }\n    return body;\n  }\n\n  /**\n   * Makes a GET request and returns the raw response.\n   * Won't throw if the reponse is a non-2xx.\n   * @deprecated Ideally, use .get() instead.\n   */\n  public async rawGet(\n    endpoint: URLSafeString,\n    authValue: AuthValue,\n    params?: QueryParams,\n    options?: Omit<RequestInit, \"body\" | \"method\" | \"headers\">\n  ): Promise<Response> {\n    return await this.#rawFetch(endpoint, authValue, options, params);\n  }\n\n  /**\n   * Makes a POST request and returns the raw response.\n   * Won't throw if the reponse is a non-2xx.\n   * @deprecated Ideally, use .post() instead.\n   */\n  public async rawPost(\n    endpoint: URLSafeString,\n    authValue: AuthValue,\n    body?: JsonObject\n  ): Promise<Response> {\n    return await this.#rawFetch(endpoint, authValue, {\n      method: \"POST\",\n      body: stringify(body),\n    });\n  }\n\n  /**\n   * Makes a DELETE request and returns the raw response.\n   * Won't throw if the reponse is a non-2xx.\n   * @deprecated Ideally, use .delete() instead.\n   */\n  public async rawDelete(\n    endpoint: URLSafeString,\n    authValue: AuthValue\n  ): Promise<Response> {\n    return await this.#rawFetch(endpoint, authValue, { method: \"DELETE\" });\n  }\n\n  /**\n   * Makes a GET request, and return the JSON response.\n   * Will throw if the reponse is a non-2xx.\n   */\n  public async get<T extends JsonObject>(\n    endpoint: URLSafeString,\n    authValue: AuthValue,\n    params?: QueryParams,\n    options?: Omit<RequestInit, \"body\" | \"method\" | \"headers\">\n  ): Promise<T> {\n    return await this.#fetch<T>(endpoint, authValue, options, params);\n  }\n\n  /**\n   * Makes a POST request, and return the JSON response.\n   * Will throw if the reponse is a non-2xx.\n   */\n  public async post<T extends JsonObject>(\n    endpoint: URLSafeString,\n    authValue: AuthValue,\n    body?: JsonObject,\n    options?: Omit<RequestInit, \"body\" | \"method\" | \"headers\">,\n    params?: QueryParams\n  ): Promise<T> {\n    return await this.#fetch<T>(\n      endpoint,\n      authValue,\n      {\n        ...options,\n        method: \"POST\",\n        body: stringify(body),\n      },\n      params\n    );\n  }\n\n  /**\n   * Makes a DELETE request, and return the JSON response.\n   * Will throw if the reponse is a non-2xx.\n   */\n  public async delete<T extends JsonObject>(\n    endpoint: URLSafeString,\n    authValue: AuthValue\n  ): Promise<T> {\n    return await this.#fetch<T>(endpoint, authValue, { method: \"DELETE\" });\n  }\n\n  /**\n   * Makes a PUT request for a Blob body, and return the JSON response.\n   * Will throw if the reponse is a non-2xx.\n   */\n  public async putBlob<T extends JsonObject>(\n    endpoint: URLSafeString,\n    authValue: AuthValue,\n    blob?: Blob,\n    params?: QueryParams,\n    options?: Omit<RequestInit, \"body\" | \"method\" | \"headers\">\n  ): Promise<T> {\n    return await this.#fetch<T>(\n      endpoint,\n      authValue,\n      {\n        ...options,\n        method: \"PUT\",\n        headers: {\n          \"Content-Type\": \"application/octet-stream\",\n        },\n        body: blob,\n      },\n      params\n    );\n  }\n}\n","/**\n * Helper function that can be used to implement exhaustive switch statements\n * with TypeScript. Example usage:\n *\n *    type Fruit = \"🍎\" | \"🍌\";\n *\n *    switch (fruit) {\n *      case \"🍎\":\n *      case \"🍌\":\n *        return doSomething();\n *\n *      default:\n *        return assertNever(fruit, \"Unknown fruit\");\n *    }\n *\n * If now the Fruit union is extended (i.e. add \"🍒\"), TypeScript will catch\n * this *statically*, rather than at runtime, and force you to handle the\n * 🍒 case.\n */\n// istanbul ignore next\nexport function assertNever(_value: never, errmsg: string): never {\n  throw new Error(errmsg);\n}\n\n/**\n * Asserts that a certain condition holds. If it does not hold, will throw\n * a runtime error in dev mode.\n *\n * In production, nothing is asserted and this acts as a no-op.\n */\nexport function assert(condition: boolean, errmsg: string): asserts condition {\n  if (process.env.NODE_ENV !== \"production\") {\n    // istanbul ignore if\n    if (!condition) {\n      const err = new Error(errmsg);\n      err.name = \"Assertion failure\";\n      throw err;\n    }\n  }\n}\n\n/**\n * Asserts that a given value is non-nullable. This is similar to TypeScript's\n * `!` operator, but will throw an error at runtime (dev-mode only) indicating\n * an incorrect assumption.\n *\n * Instead of:\n *\n *     foo!.bar\n *\n * Use:\n *\n *     nn(foo).bar\n *\n */\nexport function nn<T>(\n  value: T,\n  errmsg: string = \"Expected value to be non-nullable\"\n): NonNullable<T> {\n  assert(value !== null && value !== undefined, errmsg);\n  return value as NonNullable<T>;\n}\n","/**\n * A generic Finite State Machine (FSM) implementation.\n *\n * This is a generic implementation that is not Liveblocks specific. We could\n * put this in a separate NPM package if we wanted to make this more reusable.\n */\n\nimport type { EventSource, Observable } from \"./EventSource\";\nimport { makeEventSource } from \"./EventSource\";\n\n/**\n * Built-in event sent by .addTimedTransition().\n */\nexport type TimerEvent = { readonly type: \"TIMER\" };\n\n/**\n * Built-in events sent by .onEnterAsync().\n */\nexport type AsyncOKEvent<T> = {\n  readonly type: \"ASYNC_OK\";\n  readonly data: T;\n};\nexport type AsyncErrorEvent = {\n  readonly type: \"ASYNC_ERROR\";\n  readonly reason: unknown;\n};\n\nexport type BaseEvent = { readonly type: string };\nexport type BuiltinEvent = TimerEvent | AsyncOKEvent<unknown> | AsyncErrorEvent;\n\nexport type Patchable<TContext> = Readonly<TContext> & {\n  patch(patch: Partial<TContext>): void;\n};\n\nexport type CleanupFn<TContext> = (context: Patchable<TContext>) => void;\nexport type EnterFn<TContext> = (\n  context: Patchable<TContext>\n) => void | CleanupFn<TContext>;\n\nexport type TargetFn<\n  TContext extends object,\n  TEvent extends BaseEvent,\n  TState extends string,\n> = (\n  event: TEvent,\n  context: Readonly<TContext>\n) => TState | TargetObject<TContext, TEvent, TState> | null;\n\nexport type Effect<TContext, TEvent extends BaseEvent> = (\n  context: Patchable<TContext>,\n  event: TEvent\n) => void;\n\n/**\n * \"Expanded\" object form to specify a target state with.\n */\nexport type TargetObject<\n  TContext extends object,\n  TEvent extends BaseEvent,\n  TState extends string,\n> = {\n  target: TState;\n\n  /**\n   * Emit a side effect (other than assigning to the context) when this\n   * transition is taken.\n   */\n  effect: Effect<TContext, TEvent> | Effect<TContext, TEvent>[];\n};\n\nexport type Target<\n  TContext extends object,\n  TEvent extends BaseEvent,\n  TState extends string,\n> =\n  | TState // Static, e.g. 'complete'\n  | TargetObject<TContext, TEvent, TState>\n  | TargetFn<TContext, TEvent, TState>; // Dynamic, e.g. (context) => context.x ? 'complete' : 'other'\n\ntype Groups<T extends string> = T extends `${infer G}.${infer Rest}`\n  ? G | `${G}.${Groups<Rest>}`\n  : never;\nexport type Wildcard<T extends string> = \"*\" | `${Groups<T>}.*`;\n\nfunction distance(state1: string, state2: string): [number, number] {\n  if (state1 === state2) {\n    return [0, 0];\n  }\n\n  const chunks1 = state1.split(\".\");\n  const chunks2 = state2.split(\".\");\n  const minLen = Math.min(chunks1.length, chunks2.length);\n  let shared = 0;\n  for (; shared < minLen; shared++) {\n    if (chunks1[shared] !== chunks2[shared]) {\n      break;\n    }\n  }\n\n  const up = chunks1.length - shared;\n  const down = chunks2.length - shared;\n  return [up, down];\n}\n\nfunction patterns<TState extends string>(\n  targetState: TState,\n  levels: number\n): (Wildcard<TState> | TState)[] {\n  const parts = targetState.split(\".\");\n  if (levels < 1 || levels > parts.length + 1) {\n    throw new Error(\"Invalid number of levels\");\n  }\n\n  const result: (Wildcard<TState> | TState)[] = [];\n  if (levels > parts.length) {\n    result.push(\"*\");\n  }\n\n  for (let i = parts.length - levels + 1; i < parts.length; i++) {\n    const slice = parts.slice(0, i);\n    if (slice.length > 0) {\n      result.push((slice.join(\".\") + \".*\") as Wildcard<TState>);\n    }\n  }\n\n  result.push(targetState);\n\n  return result;\n}\n\nclass SafeContext<TContext extends object> {\n  #curr: Readonly<TContext>;\n\n  constructor(initialContext: TContext) {\n    this.#curr = initialContext;\n  }\n\n  get current(): Readonly<TContext> {\n    return this.#curr;\n  }\n\n  /**\n   * Call a callback function that allows patching of the context, by\n   * calling `context.patch()`. Patching is only allowed for the duration\n   * of this window.\n   */\n  allowPatching(callback: (context: Patchable<TContext>) => void): void {\n    // eslint-disable-next-line @typescript-eslint/no-this-alias\n    const self = this;\n    let allowed = true;\n\n    const patchableContext = {\n      ...this.#curr,\n      patch(patch: Partial<TContext>): void {\n        if (allowed) {\n          self.#curr = Object.assign({}, self.#curr, patch);\n\n          // Also patch the temporary mutable context helper itself, in case\n          // there are multiple calls in a succession that need\n          for (const pair of Object.entries(patch)) {\n            const [key, value] = pair as [\n              keyof TContext,\n              TContext[keyof TContext],\n            ];\n            if (key !== \"patch\") {\n              (this as TContext)[key] = value;\n            }\n          }\n        } else {\n          throw new Error(\"Can no longer patch stale context\");\n        }\n      },\n    };\n    callback(patchableContext);\n\n    // If ever the patch function is called after this temporary window,\n    // disallow it\n    allowed = false;\n    return;\n  }\n}\n\nenum RunningState {\n  NOT_STARTED_YET, // Machine can be set up during this phase\n  STARTED,\n  STOPPED,\n}\n\nlet nextId = 1;\n\nexport class FSM<\n  TContext extends object,\n  TEvent extends BaseEvent,\n  TState extends string,\n> {\n  public id: number;\n\n  // Indicates whether this state machine is still being configured, has\n  // started, or has terminated\n  #runningState: RunningState;\n\n  readonly #currentContext: SafeContext<TContext>;\n\n  #states: Set<TState>;\n  #currentStateOrNull: TState | null;\n\n  #allowedTransitions: Map<\n    TState,\n    Map<TEvent[\"type\"], TargetFn<TContext, TEvent, TState>>\n  >;\n\n  readonly #eventHub: {\n    readonly didReceiveEvent: EventSource<TEvent | BuiltinEvent>;\n    readonly willTransition: EventSource<{ from: TState; to: TState }>;\n    readonly didIgnoreEvent: EventSource<TEvent | BuiltinEvent>;\n    readonly willExitState: EventSource<TState>;\n    readonly didEnterState: EventSource<TState>;\n  };\n\n  public readonly events: {\n    readonly didReceiveEvent: Observable<TEvent | BuiltinEvent>;\n    readonly willTransition: Observable<{ from: TState; to: TState }>;\n    readonly didIgnoreEvent: Observable<TEvent | BuiltinEvent>;\n    readonly willExitState: Observable<TState>;\n    readonly didEnterState: Observable<TState>;\n  };\n\n  //\n  // The cleanup stack is a stack of (optional) callback functions that will\n  // be run when exiting the current state. If a state (or state group) does\n  // not have an exit handler, then the entry for that level may be\n  // `undefined`, but there will be an explicit entry in the stack for it.\n  //\n  // This will always be true:\n  //\n  //   cleanupStack.length == currentState.split('.').length + 1\n  //\n  // Each stack level represents a different state \"group\".\n  //\n  // For example, if you are in a state named `foo.bar.qux`, then the stack\n  // will contain the exit handler for `foo.bar.qux` (at the top), then\n  // `foo.bar.*`, then `foo.*`, and finally, `*`.\n  //\n  #cleanupStack: (CleanupFn<TContext> | null)[];\n\n  #enterFns: Map<TState | Wildcard<TState>, EnterFn<TContext>>;\n\n  // Used to provide better error messages\n  #knownEventTypes: Set<string>;\n\n  /**\n   * Returns the initial state, which is defined by the first call made to\n   * .addState().\n   */\n  get #initialState(): TState {\n    // Return the first state ever defined as the initial state\n    const result = this.#states.values()[Symbol.iterator]().next();\n    if (result.done) {\n      throw new Error(\"No states defined yet\");\n    } else {\n      return result.value;\n    }\n  }\n\n  public get currentState(): TState {\n    if (this.#currentStateOrNull === null) {\n      if (this.#runningState === RunningState.NOT_STARTED_YET) {\n        throw new Error(\"Not started yet\");\n      } else {\n        throw new Error(\"Already stopped\");\n      }\n    }\n    return this.#currentStateOrNull;\n  }\n\n  /**\n   * Starts the machine by entering the initial state.\n   */\n  public start(): this {\n    if (this.#runningState !== RunningState.NOT_STARTED_YET) {\n      throw new Error(\"State machine has already started\");\n    }\n\n    this.#runningState = RunningState.STARTED;\n    this.#currentStateOrNull = this.#initialState;\n    this.#enter(null);\n    return this;\n  }\n\n  /**\n   * Stops the state machine. Stopping the state machine will call exit\n   * handlers for the current state, but not enter a new state.\n   */\n  public stop(): void {\n    if (this.#runningState !== RunningState.STARTED) {\n      throw new Error(\"Cannot stop a state machine that hasn't started yet\");\n    }\n    this.#exit(null);\n    this.#runningState = RunningState.STOPPED;\n    this.#currentStateOrNull = null;\n  }\n\n  constructor(initialContext: Readonly<TContext>) {\n    this.id = nextId++;\n    this.#runningState = RunningState.NOT_STARTED_YET;\n    this.#currentStateOrNull = null;\n    this.#states = new Set();\n    this.#enterFns = new Map();\n    this.#cleanupStack = [];\n    this.#knownEventTypes = new Set();\n    this.#allowedTransitions = new Map();\n    this.#currentContext = new SafeContext(initialContext);\n    this.#eventHub = {\n      didReceiveEvent: makeEventSource(),\n      willTransition: makeEventSource(),\n      didIgnoreEvent: makeEventSource(),\n      willExitState: makeEventSource(),\n      didEnterState: makeEventSource(),\n    };\n    this.events = {\n      didReceiveEvent: this.#eventHub.didReceiveEvent.observable,\n      willTransition: this.#eventHub.willTransition.observable,\n      didIgnoreEvent: this.#eventHub.didIgnoreEvent.observable,\n      willExitState: this.#eventHub.willExitState.observable,\n      didEnterState: this.#eventHub.didEnterState.observable,\n    };\n  }\n\n  public get context(): Readonly<TContext> {\n    return this.#currentContext.current;\n  }\n\n  /**\n   * Define an explicit finite state in the state machine.\n   */\n  public addState(state: TState): this {\n    if (this.#runningState !== RunningState.NOT_STARTED_YET) {\n      throw new Error(\"Already started\");\n    }\n    this.#states.add(state);\n    return this;\n  }\n\n  public onEnter(\n    nameOrPattern: TState | Wildcard<TState>,\n    enterFn: EnterFn<TContext>\n  ): this {\n    if (this.#runningState !== RunningState.NOT_STARTED_YET) {\n      throw new Error(\"Already started\");\n    } else if (this.#enterFns.has(nameOrPattern)) {\n      throw new Error(\n        // TODO We _currently_ don't support multiple .onEnters() for the same\n        // state, but this is not a fundamental limitation. Just not\n        // implemented yet. If we wanted to, we could make this an array.\n        `enter/exit function for ${nameOrPattern} already exists`\n      );\n    }\n\n    this.#enterFns.set(nameOrPattern, enterFn);\n    return this;\n  }\n\n  /**\n   * Defines a promise-based state. When the state is entered, the promise is\n   * created. When the promise resolves, the machine will transition to the\n   * provided `onOK` target state. When the promise rejects, the machine will\n   * transition to the `onError` target state.\n   *\n   * Optionally, a `maxTimeout` can be set. If the timeout happens before the\n   * promise is settled, then the machine will also transition to the `onError`\n   * target state.\n   *\n   * @param stateOrPattern  The state name, or state group pattern name.\n   * @param promiseFn       The callback to be invoked when the state is entered.\n   * @param onOK            The state to transition to when the promise resolves.\n   * @param onError         The state to transition to when the promise\n   *                        rejects, or when the timeout happens before the\n   *                        promise has been settled.\n   * @param maxTimeout      Optional timeout in milliseconds.\n   *\n   * When the promise callback function is invoked, it's provided with an\n   * AbortSignal (2nd argument).\n   * If a state transition happens while the promise is pending (for example,\n   * an event, or a timeout happens), then an abort signal will be used to\n   * indicate this. Implementers can use this abort signal to terminate the\n   * in-flight promise, or ignore its results, etc.\n   */\n  public onEnterAsync<T>(\n    nameOrPattern: TState | Wildcard<TState>,\n    promiseFn: (context: Readonly<TContext>, signal: AbortSignal) => Promise<T>,\n    onOK: Target<TContext, AsyncOKEvent<T>, TState>,\n    onError: Target<TContext, AsyncErrorEvent, TState>,\n    maxTimeout?: number\n  ): this {\n    return this.onEnter(nameOrPattern, () => {\n      const abortController = new AbortController();\n      const signal = abortController.signal;\n\n      const timeoutId = maxTimeout\n        ? setTimeout(() => {\n            const reason = new Error(\"Timed out\");\n            this.#transition({ type: \"ASYNC_ERROR\", reason }, onError);\n          }, maxTimeout)\n        : undefined;\n\n      let done = false;\n      void promiseFn(this.#currentContext.current, signal).then(\n        // On OK\n        (data: T) => {\n          if (!signal.aborted) {\n            done = true;\n            this.#transition({ type: \"ASYNC_OK\", data }, onOK);\n          }\n        },\n\n        // On Error\n        (reason: unknown) => {\n          if (!signal.aborted) {\n            done = true;\n            this.#transition({ type: \"ASYNC_ERROR\", reason }, onError);\n          }\n        }\n      );\n\n      return () => {\n        clearTimeout(timeoutId);\n        if (!done) {\n          abortController.abort();\n        }\n      };\n    });\n  }\n\n  #getStatesMatching(nameOrPattern: TState | Wildcard<TState>): TState[] {\n    const matches: TState[] = [];\n\n    // We're trying to match a group pattern here, i.e. `foo.*` (which might\n    // match `foo.bar` and `foo.qux` states)\n    if (nameOrPattern === \"*\") {\n      for (const state of this.#states) {\n        matches.push(state);\n      }\n    } else if (nameOrPattern.endsWith(\".*\")) {\n      const prefix = nameOrPattern.slice(0, -1); // Strip only the \"*\", keep the \".\"\n      for (const state of this.#states) {\n        if (state.startsWith(prefix)) {\n          matches.push(state);\n        }\n      }\n    } else {\n      // Just a single, explicit state name\n      const name = nameOrPattern as TState;\n      if (this.#states.has(name)) {\n        matches.push(name);\n      }\n    }\n\n    if (matches.length === 0) {\n      throw new Error(`No states match ${JSON.stringify(nameOrPattern)}`);\n    }\n\n    return matches;\n  }\n\n  /**\n   * Define all allowed outgoing transitions for a state.\n   *\n   * The targets for each event can be defined as a function which returns the\n   * next state to transition to. These functions can look at the `event` or\n   * `context` params to conditionally decide which next state to transition\n   * to.\n   *\n   * If you set it to `null`, then the transition will be explicitly forbidden\n   * and throw an error. If you don't define a target for a transition, then\n   * such events will get ignored.\n   */\n  public addTransitions(\n    nameOrPattern: TState | Wildcard<TState>,\n    mapping: {\n      [E in TEvent as E[\"type\"]]?: Target<TContext, E, TState> | null;\n    }\n  ): this {\n    if (this.#runningState !== RunningState.NOT_STARTED_YET) {\n      throw new Error(\"Already started\");\n    }\n\n    for (const srcState of this.#getStatesMatching(nameOrPattern)) {\n      let map = this.#allowedTransitions.get(srcState);\n      if (map === undefined) {\n        map = new Map();\n        this.#allowedTransitions.set(srcState, map);\n      }\n\n      for (const [type, target_] of Object.entries(mapping)) {\n        if (map.has(type)) {\n          throw new Error(\n            `Trying to set transition \"${type}\" on \"${srcState}\" (via \"${nameOrPattern}\"), but a transition already exists there.`\n          );\n        }\n\n        const target = target_ as\n          | Target<TContext, TEvent, TState>\n          | null\n          | undefined;\n        this.#knownEventTypes.add(type);\n\n        if (target !== undefined) {\n          const targetFn = typeof target === \"function\" ? target : () => target;\n          map.set(type, targetFn);\n        }\n      }\n    }\n    return this;\n  }\n\n  /**\n   * Like `.addTransition()`, but takes an (anonymous) transition whenever the\n   * timer fires.\n   *\n   * @param stateOrPattern  The state name, or state group pattern name.\n   * @param after           Number of milliseconds after which to take the\n   *                        transition. If in the mean time, another transition\n   *                        is taken, the timer will get cancelled.\n   * @param target          The target state to go to.\n   */\n  public addTimedTransition(\n    stateOrPattern: TState | Wildcard<TState>,\n    after: number | ((context: Readonly<TContext>) => number),\n    target: Target<TContext, TimerEvent, TState>\n  ): this {\n    return this.onEnter(stateOrPattern, () => {\n      const ms =\n        typeof after === \"function\"\n          ? after(this.#currentContext.current)\n          : after;\n      const timeoutID = setTimeout(() => {\n        this.#transition({ type: \"TIMER\" }, target);\n      }, ms);\n\n      return () => {\n        clearTimeout(timeoutID);\n      };\n    });\n  }\n\n  #getTargetFn(\n    eventName: TEvent[\"type\"]\n  ): TargetFn<TContext, TEvent, TState> | undefined {\n    return this.#allowedTransitions.get(this.currentState)?.get(eventName);\n  }\n\n  /**\n   * Exits the current state, and executes any necessary cleanup functions.\n   * Call this before changing the current state to the next state.\n   *\n   * @param levels Defines how many \"levels\" of nesting will be\n   * exited. For example, if you transition from `foo.bar.qux` to\n   * `foo.bar.baz`, then the level is 1. But if you transition from\n   * `foo.bar.qux` to `bla.bla`, then the level is 3.\n   * If `null`, it will exit all levels.\n   */\n  #exit(levels: number | null) {\n    this.#eventHub.willExitState.notify(this.currentState);\n\n    this.#currentContext.allowPatching((patchableContext) => {\n      levels = levels ?? this.#cleanupStack.length;\n      for (let i = 0; i < levels; i++) {\n        this.#cleanupStack.pop()?.(patchableContext);\n      }\n    });\n  }\n\n  /**\n   * Enters the current state, and executes any necessary onEnter handlers.\n   * Call this directly _after_ setting the current state to the next state.\n   */\n  #enter(levels: number | null) {\n    const enterPatterns = patterns(\n      this.currentState,\n      levels ?? this.currentState.split(\".\").length + 1\n    );\n\n    this.#currentContext.allowPatching((patchableContext) => {\n      for (const pattern of enterPatterns) {\n        const enterFn = this.#enterFns.get(pattern);\n        const cleanupFn = enterFn?.(patchableContext);\n        if (typeof cleanupFn === \"function\") {\n          this.#cleanupStack.push(cleanupFn);\n        } else {\n          this.#cleanupStack.push(null);\n        }\n      }\n    });\n\n    this.#eventHub.didEnterState.notify(this.currentState);\n  }\n\n  /**\n   * Sends an event to the machine, which may cause an internal state\n   * transition to happen. When that happens, will trigger side effects.\n   */\n  public send(event: TEvent): void {\n    // Throw if the event is unknown, which may likely be a configuration error\n    if (!this.#knownEventTypes.has(event.type)) {\n      throw new Error(`Invalid event ${JSON.stringify(event.type)}`);\n    }\n\n    if (this.#runningState === RunningState.STOPPED) {\n      // Ignore all events sent to the machine after it has stopped. This is\n      // similar to how we ignore events sent to the machine after it\n      // transitioned to a phase in which the event won't be handled: it would\n      // also get ignored.\n      // However, if the machine _hasn't started yet_, we still let it throw an\n      // error, because then it's most likely a usage error.\n      return;\n    }\n\n    const targetFn = this.#getTargetFn(event.type);\n    if (targetFn !== undefined) {\n      return this.#transition(event, targetFn);\n    } else {\n      // Ignore the event otherwise\n      this.#eventHub.didIgnoreEvent.notify(event);\n    }\n  }\n\n  #transition<E extends TEvent | BuiltinEvent>(\n    event: E,\n    target: Target<TContext, E, TState>\n  ) {\n    this.#eventHub.didReceiveEvent.notify(event);\n\n    const oldState = this.currentState;\n\n    const targetFn = typeof target === \"function\" ? target : () => target;\n    const nextTarget = targetFn(event, this.#currentContext.current);\n    let nextState: TState;\n    let effects: Effect<TContext, E>[] | undefined = undefined;\n    if (nextTarget === null) {\n      // Do not transition\n      this.#eventHub.didIgnoreEvent.notify(event);\n      return;\n    }\n\n    if (typeof nextTarget === \"string\") {\n      nextState = nextTarget;\n    } else {\n      nextState = nextTarget.target;\n      effects = Array.isArray(nextTarget.effect)\n        ? nextTarget.effect\n        : [nextTarget.effect];\n    }\n\n    if (!this.#states.has(nextState)) {\n      throw new Error(`Invalid next state name: ${JSON.stringify(nextState)}`);\n    }\n\n    this.#eventHub.willTransition.notify({ from: oldState, to: nextState });\n\n    const [up, down] = distance(this.currentState, nextState);\n    if (up > 0) {\n      this.#exit(up);\n    }\n\n    this.#currentStateOrNull = nextState; // NOTE: Could stay the same, but... there could be an action to execute here\n    if (effects !== undefined) {\n      const effectsToRun = effects;\n      this.#currentContext.allowPatching((patchableContext) => {\n        for (const effect of effectsToRun) {\n          if (typeof effect === \"function\") {\n            // May mutate context\n            effect(patchableContext, event);\n          } else {\n            patchableContext.patch(effect);\n          }\n        }\n      });\n    }\n\n    if (down > 0) {\n      this.#enter(down);\n    }\n  }\n}\n\n/** @internal - For unit tests only */\nexport { distance, patterns };\n","import type { Json, JsonObject } from \"../lib/Json\";\nimport type { BaseUserMeta } from \"./BaseUserMeta\";\nimport type { Op } from \"./Op\";\nimport type { IdTuple, SerializedCrdt } from \"./SerializedCrdt\";\n\nexport enum ServerMsgCode {\n  // For Presence\n  UPDATE_PRESENCE = 100,\n  USER_JOINED = 101,\n  USER_LEFT = 102,\n  BROADCASTED_EVENT = 103,\n  ROOM_STATE = 104,\n\n  // For Storage\n  INITIAL_STORAGE_STATE = 200,\n  UPDATE_STORAGE = 201,\n  REJECT_STORAGE_OP = 299,\n\n  // For Yjs Docs\n  UPDATE_YDOC = 300,\n\n  // For Comments\n  THREAD_CREATED = 400,\n  THREAD_DELETED = 407,\n  THREAD_METADATA_UPDATED = 401,\n  THREAD_UPDATED = 408,\n  COMMENT_CREATED = 402,\n  COMMENT_EDITED = 403,\n  COMMENT_DELETED = 404,\n  COMMENT_REACTION_ADDED = 405,\n  COMMENT_REACTION_REMOVED = 406,\n}\n\n/**\n * Messages that can be sent from the server to the client.\n */\nexport type ServerMsg<\n  P extends JsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n> =\n  // For Presence\n  | UpdatePresenceServerMsg<P> // Broadcasted\n  | UserJoinServerMsg<U> // Broadcasted\n  | UserLeftServerMsg // Broadcasted\n  | BroadcastedEventServerMsg<E> // Broadcasted\n  | RoomStateServerMsg<U> // For a single client\n\n  // For Storage\n  | InitialDocumentStateServerMsg // For a single client\n  | UpdateStorageServerMsg // Broadcasted\n  | RejectedStorageOpServerMsg // For a single client\n  | YDocUpdateServerMsg // For receiving doc from backend\n\n  // Comments\n  | CommentsEventServerMsg;\n\nexport type CommentsEventServerMsg =\n  | ThreadCreatedEvent\n  | ThreadDeletedEvent\n  | ThreadMetadataUpdatedEvent\n  | ThreadUpdatedEvent\n  | CommentCreatedEvent\n  | CommentEditedEvent\n  | CommentDeletedEvent\n  | CommentReactionAdded\n  | CommentReactionRemoved;\n\ntype ThreadCreatedEvent = {\n  type: ServerMsgCode.THREAD_CREATED;\n  threadId: string;\n};\n\ntype ThreadDeletedEvent = {\n  type: ServerMsgCode.THREAD_DELETED;\n  threadId: string;\n};\n\ntype ThreadMetadataUpdatedEvent = {\n  type: ServerMsgCode.THREAD_METADATA_UPDATED;\n  threadId: string;\n};\n\ntype ThreadUpdatedEvent = {\n  type: ServerMsgCode.THREAD_UPDATED;\n  threadId: string;\n};\n\ntype CommentCreatedEvent = {\n  type: ServerMsgCode.COMMENT_CREATED;\n  threadId: string;\n  commentId: string;\n};\n\ntype CommentEditedEvent = {\n  type: ServerMsgCode.COMMENT_EDITED;\n  threadId: string;\n  commentId: string;\n};\n\ntype CommentDeletedEvent = {\n  type: ServerMsgCode.COMMENT_DELETED;\n  threadId: string;\n  commentId: string;\n};\n\ntype CommentReactionAdded = {\n  type: ServerMsgCode.COMMENT_REACTION_ADDED;\n  threadId: string;\n  commentId: string;\n  emoji: string;\n};\n\ntype CommentReactionRemoved = {\n  type: ServerMsgCode.COMMENT_REACTION_REMOVED;\n  threadId: string;\n  commentId: string;\n  emoji: string;\n};\n\n/**\n * Sent by the WebSocket server and broadcasted to all clients to announce that\n * a User updated their presence. For example, when a user moves their cursor.\n *\n * In most cases, the data payload will only include the fields from the\n * Presence that have been changed since the last announcement. However, after\n * a new user joins a room, a \"full presence\" will be announced so the newly\n * connected user will get each other's user full presence at least once. In\n * those cases, the `targetActor` field indicates the newly connected client,\n * so all other existing clients can ignore this broadcasted message.\n */\nexport type UpdatePresenceServerMsg<P extends JsonObject> =\n  //\n  // Full Presence™ message\n  //\n  | {\n      readonly type: ServerMsgCode.UPDATE_PRESENCE;\n      /**\n       * The User whose Presence has changed.\n       */\n      readonly actor: number;\n      /**\n       * When set, signifies that this is a Full Presence™ update, not a patch.\n       *\n       * The numeric value itself no longer has specific meaning. Historically,\n       * this field was intended so that clients could ignore these broadcasted\n       * full presence messages, but it turned out that getting a full presence\n       * \"keyframe\" from time to time was useful.\n       *\n       * So nowadays, the presence (pun intended) of this `targetActor` field\n       * is a backward-compatible way of expressing that the `data` contains\n       * all presence fields, and isn't a partial \"patch\".\n       */\n      readonly targetActor: number;\n      /**\n       * The partial or full Presence of a User. If the `targetActor` field is set,\n       * this will be the full Presence, otherwise it only contain the fields that\n       * have changed since the last broadcast.\n       */\n      readonly data: P;\n    }\n\n  //\n  // Partial Presence™ message\n  //\n  | {\n      readonly type: ServerMsgCode.UPDATE_PRESENCE;\n      /**\n       * The User whose Presence has changed.\n       */\n      readonly actor: number;\n      /**\n       * Not set for partial presence updates.\n       */\n      readonly targetActor?: undefined;\n      /**\n       * A partial Presence patch to apply to the User. It will only contain the\n       * fields that have changed since the last broadcast.\n       */\n      readonly data: Partial<P>;\n    };\n\n/**\n * Sent by the WebSocket server and broadcasted to all clients to announce that\n * a new User has joined the Room.\n */\nexport type UserJoinServerMsg<U extends BaseUserMeta> = {\n  readonly type: ServerMsgCode.USER_JOINED;\n  readonly actor: number;\n  /**\n   * The id of the User that has been set in the authentication endpoint.\n   * Useful to get additional information about the connected user.\n   */\n  readonly id: U[\"id\"];\n  /**\n   * Additional user information that has been set in the authentication\n   * endpoint.\n   */\n  readonly info: U[\"info\"];\n  /**\n   * Informs the client what (public) permissions this (other) User has.\n   */\n  readonly scopes: string[];\n};\n\n/**\n * Sent by the WebSocket server and broadcasted to all clients to announce that\n * a new User has left the Room.\n */\nexport type UserLeftServerMsg = {\n  readonly type: ServerMsgCode.USER_LEFT;\n  readonly actor: number;\n};\n\n/**\n * Sent by the WebSocket server when the ydoc is updated or when requested based on stateVector passed.\n * Contains a base64 encoded update\n */\nexport type YDocUpdateServerMsg = {\n  readonly type: ServerMsgCode.UPDATE_YDOC;\n  readonly update: string;\n  readonly isSync: boolean; // dropped after 1.2, we use presence of stateVector instead\n  readonly stateVector: string | null; // server's state vector, sent in response to fetch\n  readonly guid?: string; // an optional guid to identify which subdoc this update to\n  readonly v2?: boolean; // whether this is a v2 update\n};\n\n/**\n * Sent by the WebSocket server and broadcasted to all clients to announce that\n * a User broadcasted an Event to everyone in the Room.\n */\nexport type BroadcastedEventServerMsg<E extends Json> = {\n  readonly type: ServerMsgCode.BROADCASTED_EVENT;\n  /**\n   * The User who broadcast the Event. Absent when this event is broadcast from\n   * the REST API in the backend.\n   */\n  readonly actor: number;\n  /**\n   * The arbitrary payload of the Event. This can be any JSON value. Clients\n   * will have to manually verify/decode this event.\n   */\n  readonly event: E;\n};\n\n/**\n * Sent by the WebSocket server to a single client in response to the client\n * joining the Room, to provide the initial state of the Room. The payload\n * includes a list of all other Users that already are in the Room.\n */\nexport type RoomStateServerMsg<U extends BaseUserMeta> = {\n  readonly type: ServerMsgCode.ROOM_STATE;\n\n  /**\n   * Informs the client what their actor ID is going to be.\n   * @since v1.2 (WS API v7)\n   */\n  readonly actor: number;\n\n  /**\n   * Secure nonce for the current session.\n   * @since v1.2 (WS API v7)\n   */\n  readonly nonce: string;\n\n  /**\n   * Informs the client what permissions the current User (self) has.\n   * @since v1.2 (WS API v7)\n   */\n  readonly scopes: string[];\n\n  readonly users: {\n    readonly [otherActor: number]: U & { scopes: string[] };\n  };\n};\n\n/**\n * Sent by the WebSocket server to a single client in response to the client\n * joining the Room, to provide the initial Storage state of the Room. The\n * payload includes the entire Storage document.\n */\nexport type InitialDocumentStateServerMsg = {\n  readonly type: ServerMsgCode.INITIAL_STORAGE_STATE;\n  readonly items: IdTuple<SerializedCrdt>[];\n};\n\n/**\n * Sent by the WebSocket server and broadcasted to all clients to announce that\n * a change occurred in the Storage document.\n *\n * The payload of this message contains a list of Ops (aka incremental\n * mutations to make to the initially loaded document).\n */\nexport type UpdateStorageServerMsg = {\n  readonly type: ServerMsgCode.UPDATE_STORAGE;\n  readonly ops: Op[];\n};\n\n/**\n * Sent by the WebSocket server to the client to indicate that certain opIds\n * have been received but were rejected because they caused mutations that are\n * incompatible with the Room's schema.\n */\nexport type RejectedStorageOpServerMsg = {\n  readonly type: ServerMsgCode.REJECT_STORAGE_OP;\n  readonly opIds: string[];\n  readonly reason: string;\n};\n","export interface IWebSocketEvent {\n  type: string;\n}\n\nexport interface IWebSocketCloseEvent extends IWebSocketEvent {\n  readonly code: WebsocketCloseCodes;\n  readonly wasClean: boolean;\n  readonly reason: string;\n}\n\nexport interface IWebSocketMessageEvent extends IWebSocketEvent {\n  readonly data: string | Buffer | ArrayBuffer | readonly Buffer[];\n}\n\nexport interface IWebSocketInstance {\n  readonly CONNECTING: number; // 0\n  readonly OPEN: number; // 1\n  readonly CLOSING: number; // 2\n  readonly CLOSED: number; // 3\n\n  readonly readyState: number;\n\n  addEventListener(type: \"close\", listener: (this: IWebSocketInstance, ev: IWebSocketCloseEvent) => unknown): void; // prettier-ignore\n  addEventListener(type: \"message\", listener: (this: IWebSocketInstance, ev: IWebSocketMessageEvent) => unknown): void; // prettier-ignore\n  addEventListener(type: \"open\" | \"error\", listener: (this: IWebSocketInstance, ev: IWebSocketEvent) => unknown): void; // prettier-ignore\n\n  removeEventListener(type: \"close\", listener: (this: IWebSocketInstance, ev: IWebSocketCloseEvent) => unknown): void; // prettier-ignore\n  removeEventListener(type: \"message\", listener: (this: IWebSocketInstance, ev: IWebSocketMessageEvent) => unknown): void; // prettier-ignore\n  removeEventListener(type: \"open\" | \"error\", listener: (this: IWebSocketInstance, ev: IWebSocketEvent) => unknown): void; // prettier-ignore\n\n  close(): void;\n  send(data: string): void;\n}\n\n/**\n * Either the browser-based WebSocket API or Node.js' WebSocket API (from the\n * 'ws' package).\n *\n * This type defines the minimal WebSocket API that Liveblocks needs from\n * a WebSocket implementation, and is a minimal subset of the browser-based\n * WebSocket APIs and Node.js' WebSocket API so that both implementations are\n * assignable to this type.\n */\nexport interface IWebSocket {\n  new (address: string): IWebSocketInstance;\n}\n\n/**\n * The following ranges will be respected by the client:\n *\n *   10xx: client will reauthorize (just like 41xx)\n *   40xx: client will disconnect\n *   41xx: client will reauthorize\n *   42xx: client will retry without reauthorizing (currently not used)\n *\n */\nexport enum WebsocketCloseCodes {\n  /** Normal close of connection, the connection fulfilled its purpose. */\n  CLOSE_NORMAL = 1000,\n  /** Unexpected error happened with the network/infra level. In spirit akin to HTTP 503 */\n  CLOSE_ABNORMAL = 1006,\n  /** Unexpected error happened. In spirit akin to HTTP 500 */\n  UNEXPECTED_CONDITION = 1011,\n  /** Please back off for now, but try again in a few moments */\n  TRY_AGAIN_LATER = 1013,\n  /** Message wasn't understood, disconnect */\n  INVALID_MESSAGE_FORMAT = 4000,\n  /** Server refused to allow connection. Re-authorizing won't help. Disconnect. In spirit akin to HTTP 403 */\n  NOT_ALLOWED = 4001,\n  /** Unused */\n  MAX_NUMBER_OF_MESSAGES_PER_SECONDS = 4002,\n  /** Unused */\n  MAX_NUMBER_OF_CONCURRENT_CONNECTIONS = 4003,\n  /** Unused */\n  MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP = 4004,\n  /** Room is full, disconnect */\n  MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM = 4005,\n  /** The room's ID was updated, disconnect */\n  ROOM_ID_UPDATED = 4006,\n  /** The server kicked the connection from the room. */\n  KICKED = 4100,\n  /** The auth token is expired, reauthorize to get a fresh one. In spirit akin to HTTP 401 */\n  TOKEN_EXPIRED = 4109,\n  /** Disconnect immediately */\n  CLOSE_WITHOUT_RETRY = 4999,\n}\n\nexport function shouldDisconnect(code: WebsocketCloseCodes): boolean {\n  return (\n    code === WebsocketCloseCodes.CLOSE_WITHOUT_RETRY ||\n    ((code as number) >= 4000 && (code as number) < 4100)\n  );\n}\n\nexport function shouldReauth(code: WebsocketCloseCodes): boolean {\n  return (code as number) >= 4100 && (code as number) < 4200;\n}\n\nexport function shouldRetryWithoutReauth(code: WebsocketCloseCodes): boolean {\n  return (\n    code === WebsocketCloseCodes.TRY_AGAIN_LATER ||\n    ((code as number) >= 4200 && (code as number) < 4300)\n  );\n}\n","import { assertNever } from \"./lib/assert\";\nimport { controlledPromise } from \"./lib/controlledPromise\";\nimport type { Observable } from \"./lib/EventSource\";\nimport { makeBufferableEventSource, makeEventSource } from \"./lib/EventSource\";\nimport * as console from \"./lib/fancy-console\";\nimport type { BuiltinEvent, Patchable, Target } from \"./lib/fsm\";\nimport { FSM } from \"./lib/fsm\";\nimport type { Json } from \"./lib/Json\";\nimport { tryParseJson, withTimeout } from \"./lib/utils\";\nimport { ServerMsgCode } from \"./protocol/ServerMsg\";\nimport type {\n  IWebSocketCloseEvent,\n  IWebSocketEvent,\n  IWebSocketInstance,\n  IWebSocketMessageEvent,\n} from \"./types/IWebSocket\";\nimport {\n  shouldDisconnect,\n  shouldReauth,\n  shouldRetryWithoutReauth,\n  WebsocketCloseCodes,\n} from \"./types/IWebSocket\";\n\n/**\n * Returns a human-readable status indicating the current connection status of\n * a Room, as returned by `room.getStatus()`. Can be used to implement\n * a connection status badge.\n */\nexport type Status =\n  | \"initial\"\n  | \"connecting\"\n  | \"connected\"\n  | \"reconnecting\"\n  | \"disconnected\";\n\n/**\n * Whether or not the status is an \"idle\" state. Here, idle means that nothing\n * will happen until some action is taken. Unsurprisingly, these statuses match\n * the start and end states of the state machine.\n */\nexport function isIdle(status: Status): status is \"initial\" | \"disconnected\" {\n  return status === \"initial\" || status === \"disconnected\";\n}\n\n/**\n * Used to report about app-level reconnection issues.\n *\n * Normal (quick) reconnects won't be reported as a \"lost connection\". Instead,\n * the application will only get an event if the reconnection attempts by the\n * client are taking (much) longer than usual. Definitely a situation you want\n * to inform your users about, for example, by throwing a toast message on\n * screen, or show a \"trying to reconnect\" banner.\n */\nexport type LostConnectionEvent =\n  | \"lost\" // the client is trying to reconnect to Liveblocks, but it's taking (much) longer than usual\n  | \"restored\" // the client did reconnect after all\n  | \"failed\"; // the client was told to stop trying\n\n/**\n * Maps internal machine state to the public Status API.\n */\nfunction toNewConnectionStatus(machine: FSM<Context, Event, State>): Status {\n  const state = machine.currentState;\n  switch (state) {\n    case \"@ok.connected\":\n    case \"@ok.awaiting-pong\":\n      return \"connected\";\n\n    case \"@idle.initial\":\n      return \"initial\";\n\n    case \"@auth.busy\":\n    case \"@auth.backoff\":\n    case \"@connecting.busy\":\n    case \"@connecting.backoff\":\n    case \"@idle.zombie\":\n      return machine.context.successCount > 0 ? \"reconnecting\" : \"connecting\";\n\n    case \"@idle.failed\":\n      return \"disconnected\";\n\n    // istanbul ignore next\n    default:\n      return assertNever(state, \"Unknown state\");\n  }\n}\n\n/**\n * Events that can be sent to the machine externally.\n */\ntype Event =\n  // Public events that can be called on the connection manager\n  | { type: \"CONNECT\" } // e.g. when trying to enter a room\n  | { type: \"RECONNECT\" } // e.g. user asking for an explicit reconnect of the socket\n  | { type: \"DISCONNECT\" } // e.g. leaving the room\n  | { type: \"WINDOW_GOT_FOCUS\" } // e.g. user's browser tab is refocused\n  | { type: \"NAVIGATOR_ONLINE\" } // e.g. browser gets back online\n  | { type: \"NAVIGATOR_OFFLINE\" } // e.g. browser goes offline\n\n  // Events that the connection manager will internally deal with\n  | { type: \"PONG\" }\n  | { type: \"EXPLICIT_SOCKET_ERROR\"; event: IWebSocketEvent }\n  | { type: \"EXPLICIT_SOCKET_CLOSE\"; event: IWebSocketCloseEvent }\n\n  // Only used by the E2E testing app, to simulate a pong timeout :(\n  | { type: \"PONG_TIMEOUT\" };\n\ntype State =\n  | \"@idle.initial\"\n  | \"@idle.failed\"\n  | \"@idle.zombie\"\n  | \"@auth.busy\"\n  | \"@auth.backoff\"\n  | \"@connecting.busy\"\n  | \"@connecting.backoff\"\n  | \"@ok.connected\"\n  | \"@ok.awaiting-pong\";\n\n/**\n * Arbitrary record that will be used as the authentication \"authValue\". It's the\n * value that is returned by calling the authentication delegate, and will get\n * passed to the connection factory delegate. This value will be remembered by\n * the connection manager, but its value will not be interpreted, so it can be\n * any value (except null).\n */\nexport type BaseAuthResult = NonNullable<Json>;\n\ntype Context = {\n  /**\n   * Count the number of times the machine reaches an \"@ok.*\" state. Once the\n   * machine reaches idle state again, this count is reset to 0 again.\n   *\n   * This lets us distinguish:\n   * - If successCount = 0, then it's an initial \"connecting\" state.\n   * - If successCount > 0, then it's an \"reconnecting\" state.\n   */\n  successCount: number;\n\n  /**\n   * Will be populated with the last known auth authValue.\n   */\n  authValue: BaseAuthResult | null;\n\n  /**\n   * The current active WebSocket connection to the room. If this is not null\n   * on the context, then the socket has successfully been opened.\n   */\n  socket: IWebSocketInstance | null;\n\n  /**\n   * The current retry delay when automatically retrying. Will get bumped to\n   * the next \"tier\" every time a connection attempt fails. Reset every time\n   * a connection succeeded.\n   */\n  backoffDelay: number;\n};\n\nconst BACKOFF_DELAYS = [250, 500, 1_000, 2_000, 4_000, 8_000, 10_000] as const;\n\n// Resetting the delay happens upon success. We could reset to 0, but that\n// would risk no delay, which generally isn't wise. Instead, we'll reset it to\n// the lowest safe delay minus 1 millisecond. The reason is that every time\n// a retry happens, the retry delay will first be bumped to the next \"tier\".\nconst RESET_DELAY = BACKOFF_DELAYS[0] - 1;\n\n/**\n * Used to back off from WebSocket reconnection attempts after a known\n * Liveblocks issue, like \"room full\" or a \"rate limit\" error.\n */\nconst BACKOFF_DELAYS_SLOW = [2_000, 30_000, 60_000, 300_000] as const;\n\n/**\n * The client will send a PING to the server every 30 seconds, after which it\n * must receive a PONG back within the next 2 seconds. If that doesn't happen,\n * this is interpreted as an implicit connection loss event.\n */\nconst HEARTBEAT_INTERVAL = 30_000;\nconst PONG_TIMEOUT = 2_000;\n\n/**\n * Maximum amount of time that the authentication delegate take to return an\n * auth authValue, or else we consider authentication timed out.\n */\nconst AUTH_TIMEOUT = 10_000;\n\n/**\n * Maximum amount of time that the socket connect delegate may take to return\n * an opened WebSocket connection, or else we consider the attempt timed out.\n */\nconst SOCKET_CONNECT_TIMEOUT = 10_000;\n\n/**\n * Special error class that can be thrown during authentication to stop the\n * connection manager from retrying.\n */\nexport class StopRetrying extends Error {\n  constructor(reason: string) {\n    super(reason);\n  }\n}\n\nfunction nextBackoffDelay(\n  currentDelay: number,\n  delays: readonly number[]\n): number {\n  return (\n    delays.find((delay) => delay > currentDelay) ?? delays[delays.length - 1]\n  );\n}\n\nfunction increaseBackoffDelay(context: Patchable<Context>) {\n  context.patch({\n    backoffDelay: nextBackoffDelay(context.backoffDelay, BACKOFF_DELAYS),\n  });\n}\n\nfunction increaseBackoffDelayAggressively(context: Patchable<Context>) {\n  context.patch({\n    backoffDelay: nextBackoffDelay(context.backoffDelay, BACKOFF_DELAYS_SLOW),\n  });\n}\n\nfunction resetSuccessCount(context: Patchable<Context>) {\n  context.patch({ successCount: 0 });\n}\n\nenum LogLevel {\n  INFO,\n  WARN,\n  ERROR,\n}\n\n/**\n * Generic \"log\" effect. Use it in `effect` handlers of state transitions.\n */\nfunction log(level: LogLevel, message: string) {\n  const logger =\n    level === LogLevel.ERROR\n      ? console.error\n      : level === LogLevel.WARN\n        ? console.warn\n        : /* black hole */ () => {};\n  return () => {\n    logger(message);\n  };\n}\n\nfunction logPrematureErrorOrCloseEvent(e: IWebSocketEvent | Error) {\n  // Produce a useful log message\n  const conn = \"Connection to Liveblocks websocket server\";\n  return (ctx: Readonly<Context>) => {\n    if (e instanceof Error) {\n      console.warn(`${conn} could not be established. ${String(e)}`);\n    } else {\n      console.warn(\n        isCloseEvent(e)\n          ? `${conn} closed prematurely (code: ${e.code}). Retrying in ${ctx.backoffDelay}ms.`\n          : `${conn} could not be established.`\n      );\n    }\n  };\n}\n\nfunction logCloseEvent(event: IWebSocketCloseEvent) {\n  const details = [`code: ${event.code}`];\n  if (event.reason) {\n    details.push(`reason: ${event.reason}`);\n  }\n  return (ctx: Readonly<Context>) => {\n    console.warn(\n      `Connection to Liveblocks websocket server closed (${details.join(\", \")}). Retrying in ${ctx.backoffDelay}ms.`\n    );\n  };\n}\n\nconst logPermanentClose = log(\n  LogLevel.WARN,\n  \"Connection to WebSocket closed permanently. Won't retry.\"\n);\n\nfunction isCloseEvent(\n  error: IWebSocketEvent | Error\n): error is IWebSocketCloseEvent {\n  return !(error instanceof Error) && error.type === \"close\";\n}\n\nexport type Delegates<T extends BaseAuthResult> = {\n  authenticate: () => Promise<T>;\n  createSocket: (authValue: T) => IWebSocketInstance;\n  canZombie: () => boolean;\n};\n\n// istanbul ignore next\nfunction enableTracing(machine: FSM<Context, Event, State>) {\n  const start = new Date().getTime();\n\n  function log(...args: unknown[]) {\n    console.warn(\n      `${((new Date().getTime() - start) / 1000).toFixed(2)} [FSM #${\n        machine.id\n      }]`,\n      ...args\n    );\n  }\n  const unsubs = [\n    machine.events.didReceiveEvent.subscribe((e) => log(`Event ${e.type}`)),\n    machine.events.willTransition.subscribe(({ from, to }) =>\n      log(\"Transitioning\", from, \"→\", to)\n    ),\n    machine.events.didIgnoreEvent.subscribe((e) =>\n      log(\"Ignored event\", e.type, e, \"(current state won't handle it)\")\n    ),\n    // machine.events.willExitState.subscribe((s) => log(\"Exiting state\", s)),\n    // machine.events.didEnterState.subscribe((s) => log(\"Entering state\", s)),\n  ];\n  return () => {\n    for (const unsub of unsubs) {\n      unsub();\n    }\n  };\n}\n\nfunction defineConnectivityEvents(machine: FSM<Context, Event, State>) {\n  // Emitted whenever a new WebSocket connection attempt succeeds\n  const statusDidChange = makeEventSource<Status>();\n  const didConnect = makeEventSource<void>();\n  const didDisconnect = makeEventSource<void>();\n\n  let lastStatus: Status | null = null;\n\n  const unsubscribe = machine.events.didEnterState.subscribe(() => {\n    const currStatus = toNewConnectionStatus(machine);\n    if (currStatus !== lastStatus) {\n      statusDidChange.notify(currStatus);\n    }\n\n    if (lastStatus === \"connected\" && currStatus !== \"connected\") {\n      didDisconnect.notify();\n    } else if (lastStatus !== \"connected\" && currStatus === \"connected\") {\n      didConnect.notify();\n    }\n    lastStatus = currStatus;\n  });\n\n  return {\n    statusDidChange: statusDidChange.observable,\n    didConnect: didConnect.observable,\n    didDisconnect: didDisconnect.observable,\n    unsubscribe,\n  };\n}\n\nconst assign = (patch: Partial<Context>) => (ctx: Patchable<Context>) =>\n  ctx.patch(patch);\n\n/**\n * A ConnectionError is a partial data structure to help build a proper\n * LiveblocksError down the line.\n */\ntype ConnectionError = { message: string; code: number };\n\nfunction createConnectionStateMachine<T extends BaseAuthResult>(\n  delegates: Delegates<T>,\n  options: {\n    enableDebugLogging: boolean;\n    /** In protocol V7, the actor will no longer be available on the token.\n     * Instead, the `actor` will be sent to the client via a ROOM_STATE message\n     * over an established WebSocket connection. If this setting is set to\n     * `true`, the state machine will only jump to \"connected\" state _after_\n     * this message has been received. If this setting is `false`, the machine\n     * won't wait for the actor to be received, and instead jump to \"connected\"\n     * as soon as the WebSocket connection is established. */\n    waitForActorId: boolean;\n  }\n) {\n  // Create observable event sources, which this machine will call into when\n  // specific events happen\n  const onMessage = makeBufferableEventSource<IWebSocketMessageEvent>();\n  onMessage.pause(); // Pause all message delivery until status is OPEN\n\n  // Emitted whenever the server deliberately closes the connection for\n  // a specific Liveblocks reason\n  const onConnectionError = makeEventSource<ConnectionError>();\n\n  function fireErrorEvent(message: string, code: number) {\n    return () => {\n      onConnectionError.notify({ message, code });\n    };\n  }\n\n  const initialContext: Context & { authValue: T | null } = {\n    successCount: 0,\n    authValue: null,\n    socket: null,\n    backoffDelay: RESET_DELAY,\n  };\n\n  // The `machine` is the actual finite state machine instance that will\n  // maintain the WebSocket's connection\n  const machine = new FSM<Context, Event, State>(initialContext)\n    .addState(\"@idle.initial\")\n    .addState(\"@idle.failed\")\n    .addState(\"@idle.zombie\")\n    .addState(\"@auth.busy\")\n    .addState(\"@auth.backoff\")\n    .addState(\"@connecting.busy\")\n    .addState(\"@connecting.backoff\")\n    .addState(\"@ok.connected\")\n    .addState(\"@ok.awaiting-pong\");\n\n  //\n  // Configure events that can happen from anywhere\n  //\n  // It's always possible to explicitly get a .reconnect() or .disconnect()\n  // from the user.\n  //\n  machine.addTransitions(\"*\", {\n    RECONNECT: {\n      target: \"@auth.backoff\",\n      effect: [increaseBackoffDelay, resetSuccessCount],\n    },\n\n    DISCONNECT: \"@idle.initial\",\n  });\n\n  //\n  // Configure the @idle.* states\n  //\n  machine\n    .onEnter(\"@idle.*\", resetSuccessCount)\n\n    .addTransitions(\"@idle.*\", {\n      CONNECT: (_, ctx) =>\n        // If we still have a known authValue, try to reconnect to the socket directly,\n        // otherwise, try to obtain a new authValue\n        ctx.authValue !== null ? \"@connecting.busy\" : \"@auth.busy\",\n    });\n\n  //\n  // Configure the @auth.* states\n  //\n  machine\n    .addTransitions(\"@auth.backoff\", {\n      NAVIGATOR_ONLINE: {\n        target: \"@auth.busy\",\n        effect: assign({ backoffDelay: RESET_DELAY }),\n      },\n    })\n    .addTimedTransition(\n      \"@auth.backoff\",\n      (ctx) => ctx.backoffDelay,\n      \"@auth.busy\"\n    )\n\n    .onEnterAsync(\n      \"@auth.busy\",\n\n      () =>\n        withTimeout(\n          delegates.authenticate(),\n          AUTH_TIMEOUT,\n          \"Timed out during auth\"\n        ),\n\n      // On successful authentication\n      (okEvent) => ({\n        target: \"@connecting.busy\",\n        effect: assign({\n          authValue: okEvent.data,\n        }),\n      }),\n\n      // Auth failed\n      (failedEvent) => {\n        if (failedEvent.reason instanceof StopRetrying) {\n          return {\n            target: \"@idle.failed\",\n            effect: [\n              log(LogLevel.ERROR, failedEvent.reason.message),\n              fireErrorEvent(failedEvent.reason.message, -1),\n            ],\n          };\n        }\n\n        return {\n          target: \"@auth.backoff\",\n          effect: [\n            increaseBackoffDelay,\n            log(\n              LogLevel.ERROR,\n              `Authentication failed: ${\n                failedEvent.reason instanceof Error\n                  ? failedEvent.reason.message\n                  : String(failedEvent.reason)\n              }`\n            ),\n          ],\n        };\n      }\n    );\n\n  //\n  // Configure the @connecting.* states\n  //\n\n  // Function references\n  const onSocketError = (event: IWebSocketEvent) =>\n    machine.send({ type: \"EXPLICIT_SOCKET_ERROR\", event });\n\n  const onSocketClose = (event: IWebSocketCloseEvent) =>\n    machine.send({ type: \"EXPLICIT_SOCKET_CLOSE\", event });\n\n  const onSocketMessage = (event: IWebSocketMessageEvent) =>\n    event.data === \"pong\"\n      ? machine.send({ type: \"PONG\" })\n      : onMessage.notify(event);\n\n  function teardownSocket(socket: IWebSocketInstance | null) {\n    if (socket) {\n      socket.removeEventListener(\"error\", onSocketError);\n      socket.removeEventListener(\"close\", onSocketClose);\n      socket.removeEventListener(\"message\", onSocketMessage);\n      socket.close();\n    }\n  }\n\n  machine\n    .addTransitions(\"@connecting.backoff\", {\n      NAVIGATOR_ONLINE: {\n        target: \"@connecting.busy\",\n        effect: assign({ backoffDelay: RESET_DELAY }),\n      },\n    })\n    .addTimedTransition(\n      \"@connecting.backoff\",\n      (ctx) => ctx.backoffDelay,\n      \"@connecting.busy\"\n    )\n\n    .onEnterAsync(\n      \"@connecting.busy\",\n\n      //\n      // Use the \"createSocket\" delegate function (provided to the\n      // ManagedSocket) to create the actual WebSocket connection instance.\n      // Then, set up all the necessary event listeners, and wait for the\n      // \"open\" event to occur.\n      //\n      // When the \"open\" event happens, we're ready to transition to the\n      // OK state. This is done by resolving the Promise.\n      //\n      async (ctx, signal) => {\n        let capturedPrematureEvent: IWebSocketEvent | null = null;\n        let unconfirmedSocket: IWebSocketInstance | null = null;\n\n        const connect$ = new Promise<[IWebSocketInstance, () => void]>(\n          (resolve, rej) => {\n            // istanbul ignore next\n            if (ctx.authValue === null) {\n              throw new Error(\"No auth authValue\"); // This should never happen\n            }\n\n            const socket = delegates.createSocket(ctx.authValue as T);\n            unconfirmedSocket = socket;\n\n            function reject(event: IWebSocketEvent) {\n              capturedPrematureEvent = event;\n              socket.removeEventListener(\"message\", onSocketMessage);\n              rej(event);\n            }\n\n            const [actor$, didReceiveActor] = controlledPromise<void>();\n            if (!options.waitForActorId) {\n              // Mark the promise as \"resolved\" immediately, so we won't wait\n              // for a ROOM_STATE message to happen.\n              didReceiveActor();\n            }\n\n            /** Waits until actor is received (from the ROOM_STATE message) */\n            function waitForActorId(event: IWebSocketMessageEvent) {\n              const serverMsg = tryParseJson(event.data as string) as\n                | Record<string, Json>\n                | undefined;\n              if (serverMsg?.type === ServerMsgCode.ROOM_STATE) {\n                didReceiveActor();\n              }\n            }\n\n            //\n            // Part 1:\n            // The `error` and `close` event handlers marked (*) are installed\n            // here only temporarily, just to handle this promise-based state.\n            // When those get triggered, we reject this promise.\n            //\n            socket.addEventListener(\"message\", onSocketMessage);\n            if (options.waitForActorId) {\n              socket.addEventListener(\"message\", waitForActorId);\n            }\n            socket.addEventListener(\"error\", reject); // (*)\n            socket.addEventListener(\"close\", reject); // (*)\n            socket.addEventListener(\"open\", () => {\n              //\n              // Part 2:\n              // The \"open\" event just fired, so the server accepted our\n              // attempt to connect. We'll go on and resolve() our promise as\n              // a result.\n              //\n              // However, we cannot safely remove our error/close rejection\n              // handlers _just yet_. There is a small, unlikely-but-possible\n              // edge case: if (and only if) any close/error events are\n              // _already_ queued up in the event queue before this handler is\n              // invoked, then those will fire before our promise will be\n              // resolved.\n              //\n              // Scenario:\n              // - Event queue is empty, listeners are installed\n              // - Two events synchronously get scheduled in the event queue: [<open event>, <close event>]\n              // - The open handler is invoked (= this very callback)\n              // - Event queue now looks like: [<close event>]\n              // - We happily continue and resolve the promise\n              // - Event queue now looks like: [<close event>, <our resolved promise>]\n              // - Close event handler fires, but we already resolved promise! 😣\n              //\n              // This is what's called a \"premature\" event here, we'll deal\n              // with it in part 3.\n              //\n              socket.addEventListener(\"error\", onSocketError);\n              socket.addEventListener(\"close\", onSocketClose);\n              const unsub = () => {\n                socket.removeEventListener(\"error\", reject); // Remove (*)\n                socket.removeEventListener(\"close\", reject); // Remove (*)\n                socket.removeEventListener(\"message\", waitForActorId);\n              };\n\n              // Resolve the promise only once we received the actor ID from\n              // the server. This will act like a traffic light, going green\n              // only once the actor is received. If the machine is configured\n              // not to wait for the actor, the traffic light will already be\n              // green.\n              // All messages received in the mean time while waiting for the\n              // green light will be played back to the client after the\n              // transition to \"connected\".\n              void actor$.then(() => {\n                resolve([socket, unsub]);\n              });\n            });\n          }\n        );\n\n        return withTimeout(\n          connect$,\n          SOCKET_CONNECT_TIMEOUT,\n          \"Timed out during websocket connection\"\n        )\n          .then(\n            //\n            // Part 3:\n            // By now, our \"open\" event has fired, and the promise has been\n            // resolved. Two possible scenarios:\n            //\n            // 1. The happy path. Most likely.\n            // 2. Uh-oh. A premature close/error event has been observed. Let's\n            //    reject the promise after all.\n            //\n            // Any close/error event that will get scheduled after this point\n            // onwards, will be caught in the OK state, and dealt with\n            // accordingly.\n            //\n            ([socket, unsub]) => {\n              unsub();\n\n              if (signal.aborted) {\n                // Trigger cleanup logic in .catch() below. At this point, the\n                // promise is already cancelled, so none of the ok/err\n                // transitions will take place.\n                throw new Error(\"Aborted\");\n              }\n\n              if (capturedPrematureEvent) {\n                throw capturedPrematureEvent; // Take failure transition\n              }\n\n              return socket;\n            }\n          )\n          .catch((e) => {\n            teardownSocket(unconfirmedSocket);\n            throw e;\n          });\n      },\n\n      // Only transition to OK state after a successfully opened WebSocket connection\n      (okEvent) => ({\n        target: \"@ok.connected\",\n        effect: assign({\n          socket: okEvent.data,\n          backoffDelay: RESET_DELAY,\n        }),\n      }),\n\n      // If the WebSocket connection cannot be established\n      (failure) => {\n        const err = failure.reason as IWebSocketEvent | StopRetrying | Error;\n\n        // Stop retrying if this promise explicitly tells us so. This should,\n        // in the case of a WebSocket connection attempt only be the case if\n        // there is a configuration error.\n        if (err instanceof StopRetrying) {\n          return {\n            target: \"@idle.failed\",\n            effect: [\n              log(LogLevel.ERROR, err.message),\n              fireErrorEvent(err.message, -1),\n            ],\n          };\n        }\n\n        // If the server actively refuses the connection attempt, stop trying.\n        if (isCloseEvent(err)) {\n          // The default fall-through behavior is going to be reauthorizing\n          // with a back-off strategy. If we know the token was expired however\n          // we can reauthorize immediately (without back-off).\n          if (err.code === WebsocketCloseCodes.TOKEN_EXPIRED) {\n            return \"@auth.busy\";\n          }\n\n          if (shouldRetryWithoutReauth(err.code)) {\n            // Retry after backoff, but don't get a new token\n            return {\n              target: \"@connecting.backoff\",\n              effect: [\n                increaseBackoffDelayAggressively,\n                logPrematureErrorOrCloseEvent(err),\n              ],\n            };\n          }\n\n          // If the token was not allowed we can stop trying because getting\n          // another token for the same user won't help\n          if (shouldDisconnect(err.code)) {\n            return {\n              target: \"@idle.failed\",\n              effect: [\n                log(LogLevel.ERROR, err.reason),\n                fireErrorEvent(err.reason, err.code),\n              ],\n            };\n          }\n        }\n\n        // In all other (unknown) cases, always re-authenticate (but after a back-off)\n        return {\n          target: \"@auth.backoff\",\n          effect: [increaseBackoffDelay, logPrematureErrorOrCloseEvent(err)],\n        };\n      }\n    );\n\n  //\n  // Configure the @ok.* states\n  //\n  // Keeps a heartbeat alive with the server whenever in the @ok.* state group.\n  // 30 seconds after entering the \"@ok.connected\" state, it will emit\n  // a heartbeat, and awaits a PONG back that should arrive within 2 seconds.\n  // If this happens, then it transitions back to normal \"connected\" state, and\n  // the cycle repeats. If the PONG is not received timely, then we interpret\n  // it as an implicit connection loss, and transition to reconnect (throw away\n  // this socket, and open a new one).\n  //\n\n  const sendHeartbeat: Target<Context, Event | BuiltinEvent, State> = {\n    target: \"@ok.awaiting-pong\",\n    effect: (ctx) => {\n      ctx.socket?.send(\"ping\");\n    },\n  };\n\n  const maybeHeartbeat: Target<Context, Event | BuiltinEvent, State> = () => {\n    // If the browser tab isn't visible currently, ask the application if going\n    // zombie is fine\n    const doc = typeof document !== \"undefined\" ? document : undefined;\n    const canZombie =\n      doc?.visibilityState === \"hidden\" && delegates.canZombie();\n    return canZombie ? \"@idle.zombie\" : sendHeartbeat;\n  };\n\n  machine\n    .addTimedTransition(\"@ok.connected\", HEARTBEAT_INTERVAL, maybeHeartbeat)\n    .addTransitions(\"@ok.connected\", {\n      NAVIGATOR_OFFLINE: maybeHeartbeat, // Don't take the browser's word for it when it says it's offline. Do a ping/pong to make sure.\n      WINDOW_GOT_FOCUS: sendHeartbeat,\n    });\n\n  machine.addTransitions(\"@idle.zombie\", {\n    WINDOW_GOT_FOCUS: \"@connecting.backoff\", // When in zombie state, the client will try to wake up automatically when the window regains focus\n  });\n\n  machine\n    .onEnter(\"@ok.*\", (ctx) => {\n      ctx.patch({ successCount: ctx.successCount + 1 });\n\n      const timerID = setTimeout(\n        // On the next tick, start delivering all messages that have already\n        // been received, and continue synchronous delivery of all future\n        // incoming messages.\n        onMessage.unpause,\n        0\n      );\n\n      // ...but when *leaving* OK state, always tear down the old socket. It's\n      // no longer valid.\n      return (ctx) => {\n        teardownSocket(ctx.socket);\n        ctx.patch({ socket: null });\n        clearTimeout(timerID);\n        onMessage.pause();\n      };\n    })\n\n    .addTransitions(\"@ok.awaiting-pong\", { PONG: \"@ok.connected\" })\n    .addTimedTransition(\"@ok.awaiting-pong\", PONG_TIMEOUT, {\n      target: \"@connecting.busy\",\n      // Log implicit connection loss and drop the current open socket\n      effect: log(\n        LogLevel.WARN,\n        \"Received no pong from server, assume implicit connection loss.\"\n      ),\n    })\n\n    .addTransitions(\"@ok.*\", {\n      // When a socket receives an error, this can cause the closing of the\n      // socket, or not. So always check to see if the socket is still OPEN or\n      // not. When still OPEN, don't transition.\n      EXPLICIT_SOCKET_ERROR: (_, context) => {\n        if (context.socket?.readyState === 1 /* WebSocket.OPEN */) {\n          // TODO Do we need to forward this error to the client?\n          return null; /* Do not leave OK state, socket is still usable */\n        }\n\n        return {\n          target: \"@connecting.backoff\",\n          effect: increaseBackoffDelay,\n        };\n      },\n\n      EXPLICIT_SOCKET_CLOSE: (e) => {\n        // Server instructed us to stop retrying, so move to failed state\n        if (shouldDisconnect(e.event.code)) {\n          return {\n            target: \"@idle.failed\",\n            effect: [\n              logPermanentClose,\n              fireErrorEvent(e.event.reason, e.event.code),\n            ],\n          };\n        }\n\n        if (shouldReauth(e.event.code)) {\n          if (e.event.code === WebsocketCloseCodes.TOKEN_EXPIRED) {\n            // Token expiry is a special case, we can reauthorize immediately\n            // (without back-off)\n            return \"@auth.busy\";\n          } else {\n            return {\n              target: \"@auth.backoff\",\n              effect: [increaseBackoffDelay, logCloseEvent(e.event)],\n            };\n          }\n        }\n\n        if (shouldRetryWithoutReauth(e.event.code)) {\n          // If this is a custom Liveblocks server close reason, back off more\n          // aggressively, and emit a Liveblocks error event...\n          return {\n            target: \"@connecting.backoff\",\n            effect: [increaseBackoffDelayAggressively, logCloseEvent(e.event)],\n          };\n        }\n\n        // Consider any other close event a temporary network hiccup, and retry\n        // after a normal backoff delay\n        return {\n          target: \"@connecting.backoff\",\n          effect: [increaseBackoffDelay, logCloseEvent(e.event)],\n        };\n      },\n    });\n\n  // Lastly, register an event handler to listen for window-focus events as\n  // soon as the machine starts, and use it to send itself \"WINDOW_GOT_FOCUS\"\n  // events.\n  if (typeof document !== \"undefined\") {\n    const doc = typeof document !== \"undefined\" ? document : undefined;\n    const win = typeof window !== \"undefined\" ? window : undefined;\n    const root = win ?? doc;\n\n    machine.onEnter(\"*\", (ctx) => {\n      function onNetworkOffline() {\n        machine.send({ type: \"NAVIGATOR_OFFLINE\" });\n      }\n\n      function onNetworkBackOnline() {\n        machine.send({ type: \"NAVIGATOR_ONLINE\" });\n      }\n\n      function onVisibilityChange() {\n        if (doc?.visibilityState === \"visible\") {\n          machine.send({ type: \"WINDOW_GOT_FOCUS\" });\n        }\n      }\n\n      win?.addEventListener(\"online\", onNetworkBackOnline);\n      win?.addEventListener(\"offline\", onNetworkOffline);\n      root?.addEventListener(\"visibilitychange\", onVisibilityChange);\n      return () => {\n        root?.removeEventListener(\"visibilitychange\", onVisibilityChange);\n        win?.removeEventListener(\"online\", onNetworkBackOnline);\n        win?.removeEventListener(\"offline\", onNetworkOffline);\n\n        // Also tear down the old socket when stopping the machine, if there is one\n        teardownSocket(ctx.socket);\n      };\n    });\n  }\n\n  const cleanups = [];\n\n  const { statusDidChange, didConnect, didDisconnect, unsubscribe } =\n    defineConnectivityEvents(machine);\n  cleanups.push(unsubscribe);\n\n  // Install debug logging\n  // istanbul ignore next\n  if (options.enableDebugLogging) {\n    cleanups.push(enableTracing(machine));\n  }\n\n  // Start the machine\n  machine.start();\n\n  return {\n    machine,\n    cleanups,\n\n    // Observable events that will be emitted by this machine\n    events: {\n      statusDidChange,\n      didConnect,\n      didDisconnect,\n      onMessage: onMessage.observable,\n      onConnectionError: onConnectionError.observable,\n    },\n  };\n}\n\n/**\n * The ManagedSocket will set up a WebSocket connection to a room, and maintain\n * that connection over time.\n *\n * It's a light wrapper around the actual FSM that implements the logic,\n * exposing just a few safe actions and events that can be called or observed\n * from the outside.\n */\nexport class ManagedSocket<T extends BaseAuthResult> {\n  #machine: FSM<Context, Event, State>;\n  #cleanups: (() => void)[];\n\n  public readonly events: {\n    /**\n     * Emitted when the WebSocket connection goes in or out of \"connected\"\n     * state.\n     */\n    readonly statusDidChange: Observable<Status>;\n    /**\n     * Emitted when the WebSocket connection is first opened.\n     */\n    readonly didConnect: Observable<void>;\n    /**\n     * Emitted when the current WebSocket connection is lost and the socket\n     * becomes useless. A new WebSocket connection must be made after this to\n     * restore connectivity.\n     */\n    readonly didDisconnect: Observable<void>; // Deliberate close, a connection loss, etc.\n\n    /**\n     * Emitted for every incoming message from the currently active WebSocket\n     * connection.\n     */\n    readonly onMessage: Observable<IWebSocketMessageEvent>;\n\n    /**\n     * Emitted whenever a connection gets closed for a known error reason, e.g.\n     * max number of connections, max number of messages, etc.\n     */\n    readonly onConnectionError: Observable<ConnectionError>;\n  };\n\n  constructor(\n    delegates: Delegates<T>,\n    enableDebugLogging: boolean = false,\n    waitForActorId: boolean = true\n  ) {\n    const { machine, events, cleanups } = createConnectionStateMachine(\n      delegates,\n      { waitForActorId, enableDebugLogging }\n    );\n    this.#machine = machine;\n    this.events = events;\n    this.#cleanups = cleanups;\n  }\n\n  getStatus(): Status {\n    try {\n      return toNewConnectionStatus(this.#machine);\n    } catch {\n      return \"initial\";\n    }\n  }\n\n  /**\n   * Returns the current auth authValue.\n   */\n  get authValue(): T | null {\n    return this.#machine.context.authValue as T | null;\n  }\n\n  /**\n   * Call this method to try to connect to a WebSocket. This only has an effect\n   * if the machine is idle at the moment, otherwise this is a no-op.\n   */\n  public connect(): void {\n    this.#machine.send({ type: \"CONNECT\" });\n  }\n\n  /**\n   * If idle, will try to connect. Otherwise, it will attempt to reconnect to\n   * the socket, potentially obtaining a new authValue first, if needed.\n   */\n  public reconnect(): void {\n    this.#machine.send({ type: \"RECONNECT\" });\n  }\n\n  /**\n   * Call this method to disconnect from the current WebSocket. Is going to be\n   * a no-op if there is no active connection.\n   */\n  public disconnect(): void {\n    this.#machine.send({ type: \"DISCONNECT\" });\n  }\n\n  /**\n   * Call this to stop the machine and run necessary cleanup functions. After\n   * calling destroy(), you can no longer use this instance. Call this before\n   * letting the instance get garbage collected.\n   */\n  public destroy(): void {\n    this.#machine.stop();\n\n    let cleanup: (() => void) | undefined;\n    while ((cleanup = this.#cleanups.pop())) {\n      cleanup();\n    }\n  }\n\n  /**\n   * Safely send a message to the current WebSocket connection. Will emit a log\n   * message if this is somehow impossible.\n   */\n  public send(data: string): void {\n    const socket = this.#machine.context?.socket;\n    if (socket === null) {\n      console.warn(\"Cannot send: not connected yet\", data);\n    } else if (socket.readyState !== 1 /* WebSocket.OPEN */) {\n      console.warn(\"Cannot send: WebSocket no longer open\", data);\n    } else {\n      socket.send(data);\n    }\n  }\n\n  /**\n   * NOTE: Used by the E2E app only, to simulate explicit events.\n   * Not ideal to keep exposed :(\n   */\n  public _privateSendMachineEvent(event: Event): void {\n    this.#machine.send(event);\n  }\n}\n","/**\n * Use this symbol to brand an object property as internal.\n *\n * @example\n * Object.defineProperty(\n *   {\n *     public,\n *     [kInternal]: {\n *       private\n *     },\n *   },\n *   kInternal,\n *   {\n *     enumerable: false,\n *   }\n * );\n */\nexport const kInternal = Symbol();\n","import { isPlainObject } from \"./guards\";\n\nfunction shallowArray(xs: unknown[], ys: unknown[]): boolean {\n  if (xs.length !== ys.length) {\n    return false;\n  }\n\n  for (let i = 0; i < xs.length; i++) {\n    if (!Object.is(xs[i], ys[i])) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nfunction shallowObj(objA: unknown, objB: unknown): boolean {\n  // Only try to compare keys/values if these objects are both \"pojos\" (plain\n  // old JavaScript objects)\n  if (!isPlainObject(objA) || !isPlainObject(objB)) {\n    return false;\n  }\n\n  const keysA = Object.keys(objA);\n  if (keysA.length !== Object.keys(objB).length) {\n    return false;\n  }\n\n  return keysA.every(\n    (key) =>\n      Object.prototype.hasOwnProperty.call(objB, key) &&\n      Object.is(objA[key], objB[key])\n  );\n}\n\n/**\n * Shallowly compares two given values.\n *\n * - Two simple values are considered equal if they're strictly equal\n * - Two arrays are considered equal if their members are strictly equal\n * - Two objects are considered equal if their values are strictly equal\n *\n * Testing goes one level deep.\n */\nexport function shallow(a: unknown, b: unknown): boolean {\n  if (Object.is(a, b)) {\n    return true;\n  }\n\n  const isArrayA = Array.isArray(a);\n  const isArrayB = Array.isArray(b);\n  if (isArrayA || isArrayB) {\n    if (!isArrayA || !isArrayB) {\n      return false;\n    }\n\n    return shallowArray(a, b);\n  }\n\n  return shallowObj(a, b);\n}\n\n/**\n * Two-level deep shallow check.\n * Useful for checking equality of { isLoading: false, myData: [ ... ] } like\n * data structures, where you want to do a shallow comparison on the \"data\"\n * key.\n *\n * NOTE: Works on objects only, not on arrays!\n */\nexport function shallow2(a: unknown, b: unknown): boolean {\n  if (!isPlainObject(a) || !isPlainObject(b)) {\n    return shallow(a, b);\n  }\n\n  const keysA = Object.keys(a);\n  if (keysA.length !== Object.keys(b).length) {\n    return false;\n  }\n\n  return keysA.every(\n    (key) =>\n      Object.prototype.hasOwnProperty.call(b, key) && shallow(a[key], b[key])\n  );\n}\n","/**\n * Returns the insertion index for the given item.\n */\nfunction bisectRight<T>(arr: readonly T[], x: T, lt: (a: T, b: T) => boolean) {\n  let lo = 0;\n  let hi = arr.length;\n  while (lo < hi) {\n    const mid = lo + ((hi - lo) >> 1); // equiv of Math.floor((lo + hi) / 2)\n    if (lt(x, arr[mid])) {\n      hi = mid;\n    } else {\n      lo = mid + 1;\n    }\n  }\n  return lo;\n}\n\n/**\n * A datastructure to keep elements in ascending order, as defined by the \"less\n * than\" function you provide. The elements will be ordered according to\n * whatever you define as the \"less than\" for this element type, so that every\n * element is less than its successor in the list.\n *\n * const sorted = SortedList.from(\n *   [{ id: 4 }, { id: 1 }, { id: 9 }, { id: 4 }],\n *   (a, b) => a.id < b.id)\n * )\n * sorted.add({ id: 5 })\n * sorted.remove({ id: 4 })  // Assuming it's the same obj ref!\n *\n * Array.from(sorted)\n * [{ id: 1 }, { id: 4 }, { id: 5 }, { id: 9 }])\n */\nexport class SortedList<T> {\n  #data: T[];\n  #lt: (a: T, b: T) => boolean;\n\n  private constructor(alreadySortedList: T[], lt: (a: T, b: T) => boolean) {\n    this.#lt = lt;\n    this.#data = alreadySortedList;\n  }\n\n  public static with<T>(lt: (a: T, b: T) => boolean): SortedList<T> {\n    return SortedList.fromAlreadySorted([], lt);\n  }\n\n  public static from<T>(\n    arr: readonly T[],\n    lt: (a: T, b: T) => boolean\n  ): SortedList<T> {\n    const sorted = new SortedList([], lt);\n    for (const item of arr) {\n      sorted.add(item);\n    }\n    return sorted;\n  }\n\n  public static fromAlreadySorted<T>(\n    alreadySorted: T[],\n    lt: (a: T, b: T) => boolean\n  ): SortedList<T> {\n    return new SortedList(alreadySorted, lt);\n  }\n\n  /**\n   * Clones the sorted list to a new instance.\n   */\n  public clone(): SortedList<T> {\n    return new SortedList(this.#data.slice(), this.#lt);\n  }\n\n  /**\n   * Adds a new item to the sorted list, such that it remains sorted.\n   */\n  add(value: T): void {\n    const idx = bisectRight(this.#data, value, this.#lt);\n    this.#data.splice(idx, 0, value);\n  }\n\n  /**\n   * Removes all values from the sorted list, making it empty again.\n   * Returns whether the list was mutated or not.\n   */\n  clear(): boolean {\n    const hadData = this.#data.length > 0;\n    this.#data.length = 0;\n    return hadData;\n  }\n\n  /**\n   * Removes the first value matching the predicate.\n   * Returns whether the list was mutated or not.\n   */\n  removeBy(\n    predicate: (item: T) => boolean,\n    limit: number = Number.POSITIVE_INFINITY\n  ): boolean {\n    let deleted = 0;\n    for (let i = 0; i < this.#data.length; i++) {\n      if (predicate(this.#data[i])) {\n        this.#data.splice(i, 1);\n        deleted++;\n        if (deleted >= limit) {\n          break;\n        } else {\n          i--;\n        }\n      }\n    }\n    return deleted > 0;\n  }\n\n  /**\n   * Removes the given value from the sorted list, if it exists. The given\n   * value must be `===` to one of the list items. Only the first entry will be\n   * removed if the element exists in the sorted list multiple times.\n   *\n   * Returns whether the list was mutated or not.\n   */\n  remove(value: T): boolean {\n    const idx = this.#data.indexOf(value);\n    if (idx >= 0) {\n      this.#data.splice(idx, 1);\n      return true;\n    }\n    return false;\n  }\n\n  at(index: number): T | undefined {\n    return this.#data[index];\n  }\n\n  get length(): number {\n    return this.#data.length;\n  }\n\n  *filter(predicate: (value: T) => boolean): IterableIterator<T> {\n    for (const item of this.#data) {\n      if (predicate(item)) {\n        yield item;\n      }\n    }\n  }\n\n  // XXXX If we keep this, add unit tests. Or remove it.\n  *findAllRight(\n    predicate: (value: T, index: number) => unknown\n  ): IterableIterator<T> {\n    for (let i = this.#data.length - 1; i >= 0; i--) {\n      const item = this.#data[i];\n      if (predicate(item, i)) {\n        yield item;\n      }\n    }\n  }\n\n  [Symbol.iterator](): IterableIterator<T> {\n    return this.#data[Symbol.iterator]();\n  }\n\n  *iterReversed(): IterableIterator<T> {\n    for (let i = this.#data.length - 1; i >= 0; i--) {\n      yield this.#data[i];\n    }\n  }\n\n  /** Finds the leftmost item that matches the predicate. */\n  find(\n    predicate: (value: T, index: number) => unknown,\n    start?: number\n  ): T | undefined {\n    const idx = this.findIndex(predicate, start);\n    return idx > -1 ? this.#data.at(idx)! : undefined; // eslint-disable-line no-restricted-syntax\n  }\n\n  /** Finds the leftmost index that matches the predicate. */\n  findIndex(\n    predicate: (value: T, index: number) => unknown,\n    start = 0\n  ): number {\n    for (let i = Math.max(0, start); i < this.#data.length; i++) {\n      if (predicate(this.#data[i], i)) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  /** Finds the rightmost item that matches the predicate. */\n  findRight(\n    predicate: (value: T, index: number) => unknown,\n    start?: number\n  ): T | undefined {\n    const idx = this.findIndexRight(predicate, start);\n    return idx > -1 ? this.#data.at(idx)! : undefined; // eslint-disable-line no-restricted-syntax\n  }\n\n  /** Finds the rightmost index that matches the predicate. */\n  findIndexRight(\n    predicate: (value: T, index: number) => unknown,\n    start = this.#data.length - 1\n  ): number {\n    for (let i = Math.min(start, this.#data.length - 1); i >= 0; i--) {\n      if (predicate(this.#data[i], i)) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  get rawArray(): readonly T[] {\n    return this.#data;\n  }\n}\n","import { DefaultMap } from \"./DefaultMap\";\nimport { SortedList } from \"./SortedList\";\nimport { raise } from \"./utils\";\n\ntype PK = string;\n\n/**\n * A \"tree pool\" is a data structure that allows for easy insertion, deletion,\n * mutation, sorting, and accessing of an object pool of objects that have\n * tree-like relationships.\n *\n *   const pool = new TreePool<Simpson>(\n *     x => x.id,\n *     x => x.parent,\n *     (a, b) => a.name < b.name,\n *   );\n *\n * The first argument is a function that returns the primary key of an item.\n * The second argument is a function that returns the parent ID for an item (or null if its a root).\n * The third argument is a function that returns how to compare two items, to\n *   return queries in sorted order.\n *\n * To insert elements into the pool:\n *\n *   pool.upsert({ id: \"1\", name: \"Homer\" });\n *   pool.upsert({ id: \"2\", name: \"Marge\" });\n *   pool.upsert({ id: \"3\", name: \"Bart\", parent: \"2\" });\n *   pool.upsert({ id: \"4\", name: \"Lisa\", parent: \"2\" });\n *   pool.upsert({ id: \"5\", name: \"Maggie\", parent: \"2\" });\n *\n * To get all items in the pool:\n *\n *   // Items are sorted by the given comparison function, in this case\n *   // alphabetically, so: Bart, Homer, Lisa, Maggie, Marge\n *   Array.from(pool)  // [{ id: \"3\", name: \"Bart\", parent: \"2\" }, ...]\n *\n * To get all children:\n *\n *   // All kids are added as children of Marge\n *   pool.getChildren(\"1\")  // [] (Homer has no kids)\n *   pool.getChildren(\"2\")  // [Bart, Lisa, Maggie] (= alphabetically)\n *\n * To get all \"roots\":\n *   pool.getChildren(null)  // [Homer, Marge] (= alphabetically)\n *\n * To get all siblings:\n *   pool.getSiblings(\"3\")  // [Lisa, Maggie]\n *   pool.getSiblings(\"4\")  // [Bart, Maggie]\n *   pool.getSiblings(\"5\")  // [Bart, Lisa]\n *\n * A bit weird maybe, but Homer and Marge are siblings in this example:\n *   pool.getSiblings(\"1\")  // [Marge]\n *   pool.getSiblings(\"2\")  // [Homer]\n *\n * Changing data is no problem, as long as the primary key and parent key don't change:\n *   pool.upsert({ id: \"1\", name: \"Homer Simpson\" });\n *   pool.upsert({ id: \"3\", name: \"Bart, son of Marge\", parent: \"2\" });\n *\n * But... this will throw an error:\n *   pool.upsert({ id: \"3\", name: \"Bart, son of Homer\", parent: \"1\" });\n *   //                                                         ^^^\n *   //                                Cannot change parent ID. If you want to ever\n *   //                                do this, remove the entry, and recreate it!\n *\n * XXXX Idea for the API to iterate nodes in this tree in arbitrary ways:\n * Traversal can be done in all directions:\n *   pool.walk(\"4\", \"up\", { includeSelf: true })   // Iterates: Lisa, Marge\n *   pool.walk(\"4\", \"up\", { includeSelf: false })  // Iterates: Marge\n *   pool.walk(\"4\", \"left\")                        // Iterates: Bart    // Prev\n *   pool.walk(\"4\", \"right\")                       // Iterates: Maggie  // Next\n *   pool.walk(\"4\", \"depth-first\")\n *   pool.walk(\"4\", \"breadth-first\")\n *   pool.walk(\"4\", \"depth-first-reversed\")\n *   pool.walk(\"4\", \"breadth-first-reversed\")\n *\n */\nexport class TreePool<T> {\n  #_items: Map<PK, T>;\n  #_childrenOf: DefaultMap</* parent */ PK | null, /* children */ Set<PK>>;\n  #_sorted: SortedList<T>;\n\n  #_primaryKey: (item: T) => PK;\n  #_parentKeyFn: (item: T) => PK | null;\n  #_lt: (a: T, b: T) => boolean;\n\n  constructor(\n    primaryKey: (item: T) => PK,\n    parentKey: (item: T) => PK | null,\n    lt: (a: T, b: T) => boolean\n  ) {\n    this.#_primaryKey = primaryKey;\n    this.#_parentKeyFn = parentKey;\n    this.#_lt = lt;\n\n    this.#_items = new Map();\n    this.#_childrenOf = new DefaultMap(() => new Set());\n    this.#_sorted = SortedList.with(lt);\n  }\n\n  public get(id: PK): T | undefined {\n    return this.#_items.get(id);\n  }\n\n  public getOrThrow(id: PK): T {\n    return this.get(id) ?? raise(`Item with id ${id} not found`);\n  }\n\n  public get sorted(): SortedList<T> {\n    // XXXX While it's fine to expose this SortedList for efficiency, really we\n    // should be exposing it as a readonly value.\n    return this.#_sorted;\n  }\n\n  public getParentId(id: PK): PK | null {\n    const item = this.getOrThrow(id);\n    return this.#_parentKeyFn(item);\n  }\n\n  public getParent(id: PK): T | null {\n    const parentId = this.getParentId(id);\n    return parentId ? this.getOrThrow(parentId) : null;\n  }\n\n  public getChildren(id: PK | null): T[] {\n    const childIds = this.#_childrenOf.get(id);\n    if (!childIds) return [];\n\n    // XXXX Should we return a sorted list here? From previous note: Think about *storing* it as a sorted list here!\n    return Array.from(childIds).map(\n      (id) => this.#_items.get(id)! // eslint-disable-line no-restricted-syntax\n    );\n  }\n\n  public *walkUp(\n    id: PK,\n    predicate?: (item: T) => boolean\n    // options?: { includeSelf?: boolean },\n  ): IterableIterator<T> {\n    // const includeSelf = options?.includeSelf ?? true;\n    const includeSelf = true; // XXXX Generalize\n    let nodeId: PK | null = id;\n    do {\n      const item = this.getOrThrow(nodeId);\n      if (includeSelf || nodeId !== id) {\n        if (!predicate || predicate(item)) {\n          yield item;\n        }\n      }\n      nodeId = this.#_parentKeyFn(item);\n    } while (nodeId !== null);\n  }\n\n  // XXXX Generalize\n  public *walkLeft(\n    id: PK,\n    predicate?: (item: T) => boolean\n  ): IterableIterator<T> {\n    // XXXX Calling getSiblings is too inefficient, optimize later!\n    // XXXX But first make it work\n    const self = this.getOrThrow(id);\n    const siblings = SortedList.from(this.getSiblings(id), this.#_lt);\n    for (const sibling of siblings.iterReversed()) {\n      // Skip over all the \"right\" siblings\n      if (this.#_lt(self, sibling)) continue;\n\n      // If we get here, it's a \"left\" sibling\n      if (!predicate || predicate(sibling)) {\n        yield sibling;\n      }\n    }\n  }\n\n  // XXXX Generalize\n  public *walkRight(\n    id: PK,\n    predicate?: (item: T) => boolean\n  ): IterableIterator<T> {\n    // XXXX Calling getSiblings is too inefficient, optimize later!\n    // XXXX But first make it work\n    const self = this.getOrThrow(id);\n    const siblings = SortedList.from(this.getSiblings(id), this.#_lt);\n    for (const sibling of siblings) {\n      // Skip over all the \"left\" siblings\n      if (this.#_lt(sibling, self)) continue;\n\n      // If we get here, it's a \"right\" sibling\n      if (!predicate || predicate(sibling)) {\n        yield sibling;\n      }\n    }\n  }\n\n  // XXXX Generalize\n  public *walkDown(\n    id: PK,\n    predicate?: (item: T) => boolean\n    // _direction?: \"depth-first\",\n    // _reversed?: true\n    // | \"depth-first\"\n    // | \"breadth-first\"\n    // | \"breadth-first-rev\"\n    // options?: {\n    //   _direction: \"depth-first\";\n    //   _reversed: true;\n    //   // _includeSelf?: boolean;\n    // }\n  ): IterableIterator<T> {\n    const children = SortedList.from(this.getChildren(id), this.#_lt).rawArray;\n    for (let i = children.length - 1; i >= 0; i--) {\n      const child = children[i];\n      yield* this.walkDown(\n        this.#_primaryKey(child),\n        predicate\n        // \"depth-first\",\n        // true\n      );\n      if (!predicate || predicate(child)) {\n        yield child;\n      }\n    }\n\n    // if (options?.includeSelf) {\n    //   yield this.getOrThrow(id);\n    // }\n  }\n\n  /** Returns all siblings, not including the item itself. */\n  public getSiblings(id: PK): readonly T[] {\n    const self = this.getOrThrow(id);\n    const parentId = this.getParentId(id);\n    return this.getChildren(parentId).filter((item) => item !== self);\n  }\n\n  public [Symbol.iterator](): IterableIterator<T> {\n    return this.#_sorted[Symbol.iterator]();\n  }\n\n  public upsert(item: T): void {\n    const pk = this.#_primaryKey(item);\n    const existing = this.#_items.get(pk);\n    if (existing) {\n      // Allow upserts if the parent ID hasn't changed, otherwise, remove the\n      // entry and replace it with the new item\n      if (this.#_parentKeyFn(existing) !== this.#_parentKeyFn(item)) {\n        throw new Error(\n          \"Cannot upsert parent ID changes that change the tree structure. Remove the entry first, and recreate it\"\n        );\n      }\n\n      this.#_sorted.remove(existing);\n    }\n\n    this.#_items.set(pk, item);\n    this.#_sorted.add(item);\n\n    const parentId = this.#_parentKeyFn(item);\n    this.#_childrenOf.getOrCreate(parentId).add(pk);\n  }\n\n  public remove(pk: PK): boolean {\n    const item = this.#_items.get(pk);\n    if (!item) return false;\n\n    const childIds = this.#_childrenOf.get(pk);\n    if (childIds) {\n      throw new Error(\n        `Cannot remove item '${pk}' while it still has children. Remove children first.`\n      );\n    }\n\n    const parentId = this.#_parentKeyFn(item);\n    const siblings = this.#_childrenOf.get(parentId);\n    if (siblings) {\n      siblings.delete(pk);\n      if (siblings.size === 0) {\n        this.#_childrenOf.delete(parentId);\n      }\n    }\n\n    this.#_sorted.remove(item);\n    this.#_childrenOf.delete(pk);\n    this.#_items.delete(pk);\n    return true;\n  }\n\n  public clear(): boolean {\n    if (this.#_items.size === 0) return false;\n\n    this.#_childrenOf.clear();\n    this.#_items.clear();\n    this.#_sorted.clear();\n    return true;\n  }\n}\n","import { isPlainObject } from \"../lib/guards\";\nimport type { Json } from \"../lib/Json\";\nimport { b64decode, tryParseJson } from \"../lib/utils\";\nimport type { IUserInfo } from \"./BaseUserMeta\";\n\nexport enum Permission {\n  Read = \"room:read\",\n  Write = \"room:write\",\n  PresenceWrite = \"room:presence:write\",\n  CommentsWrite = \"comments:write\",\n  CommentsRead = \"comments:read\",\n}\n\nexport type LiveblocksPermissions = Record<string, Permission[]>;\n\nexport enum TokenKind {\n  SECRET_LEGACY = \"sec-legacy\",\n  ACCESS_TOKEN = \"acc\",\n  ID_TOKEN = \"id\",\n}\n\n/**\n * Infers from the given scopes whether the user can write the document (e.g.\n * Storage and/or YDoc).\n */\nexport function canWriteStorage(scopes: readonly string[]): boolean {\n  return scopes.includes(Permission.Write);\n}\n\nexport function canComment(scopes: readonly string[]): boolean {\n  return (\n    scopes.includes(Permission.CommentsWrite) ||\n    scopes.includes(Permission.Write)\n  );\n}\n\ntype JwtMeta = {\n  iat: number;\n  exp: number;\n};\n\n/**\n * Legacy Secret Token.\n */\nexport type LegacySecretToken = {\n  k: TokenKind.SECRET_LEGACY;\n  roomId: string;\n  scopes: string[];\n\n  // Extra payload as defined by the customer's own authorization\n  id?: string;\n  info?: IUserInfo;\n\n  // IMPORTANT: All other fields on the JWT token are deliberately treated as\n  // opaque, and not relied on by the client.\n  [other: string]: Json | undefined;\n} & JwtMeta;\n\n/**\n * New authorization Access Token.\n */\nexport type AccessToken = {\n  k: TokenKind.ACCESS_TOKEN;\n  pid: string; // project id\n  uid: string; // user id\n  perms: LiveblocksPermissions; // permissions\n  ui?: IUserInfo; // user info\n  ai?: boolean; // is AI Copilots enabled for this user?\n} & JwtMeta;\n\n/**\n * New authorization ID Token.\n */\nexport type IDToken = {\n  k: TokenKind.ID_TOKEN;\n  pid: string; // project id\n  uid: string; // user id\n  gids?: string[]; // group ids\n  ui?: IUserInfo; // user info\n  ai?: boolean; // is AI Copilots enabled for this user?\n} & JwtMeta;\n\nexport type AuthToken = AccessToken | IDToken | LegacySecretToken;\n\n// The \"rich\" token is data we obtain by parsing the JWT token and making all\n// metadata on it accessible. It's done right after hitting the backend, but\n// before the promise will get returned, so it's an inherent part of the\n// authentication step.\nexport type ParsedAuthToken = {\n  readonly raw: string; // The raw JWT value, unchanged\n  readonly parsed: AuthToken; // Rich data on the JWT value\n};\n\nfunction isValidAuthTokenPayload(\n  data: Json\n): data is AccessToken | IDToken | LegacySecretToken {\n  return (\n    isPlainObject(data) &&\n    (data.k === TokenKind.ACCESS_TOKEN ||\n      data.k === TokenKind.ID_TOKEN ||\n      data.k === TokenKind.SECRET_LEGACY)\n  );\n}\n\n/**\n * Parses a raw JWT token string, which allows reading the metadata/payload of\n * the token.\n *\n * NOTE: Doesn't do any validation, so always treat the metadata as other user\n * input: never trust these values for anything important.\n */\nexport function parseAuthToken(rawTokenString: string): ParsedAuthToken {\n  const tokenParts = rawTokenString.split(\".\");\n  if (tokenParts.length !== 3) {\n    throw new Error(\"Authentication error: invalid JWT token\");\n  }\n\n  const payload = tryParseJson(b64decode(tokenParts[1]));\n  if (!(payload && isValidAuthTokenPayload(payload))) {\n    throw new Error(\n      \"Authentication error: expected a valid token but did not get one. Hint: if you are using a callback, ensure the room is passed when creating the token. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientCallback\"\n    );\n  }\n\n  return {\n    raw: rawTokenString,\n    parsed: payload,\n  };\n}\n","///\n/// This module is shared with the back-end.\n/// All types in this file represent _public_ types, observable in the client\n/// or in our protocol.\n///\n\nimport type { JSONSchema7 } from \"json-schema\";\n\nimport { assertNever } from \"../lib/assert\";\nimport type { Json, JsonObject } from \"../lib/Json\";\nimport type { Relax } from \"../lib/Relax\";\nimport type { Resolve } from \"../lib/Resolve\";\nimport type { Brand } from \"../lib/utils\";\n\nexport type Cursor = Brand<string, \"Cursor\">;\nexport type ISODateString = Brand<string, \"ISODateString\">;\n\ntype ChatId = string;\n\n// --------------------------------------------------------------\n\nexport type MessageId = Brand<`ms_${string}`, \"MessageId\">;\nexport type CmdId = Brand<string, \"CmdId\">;\nexport type CopilotId = Brand<`co_${string}`, \"CopilotId\">;\n\n// A client WebSocket message is always a command to the server\nexport type ClientAiMsg = ClientCmd;\n\n// A server WebSocket message can be either a command response from the server,\n// or a server-initiated event\nexport type ServerAiMsg = ServerCmdResponse | ServerEvent;\n\n// Helper to create a pair of matching commands and responses\ntype DefineCmd<CmdName extends string, TRequest, TResponse> = [\n  Resolve<{ cmd: CmdName; cmdId: CmdId } & TRequest>,\n  Resolve<{ cmd: CmdName; cmdId: CmdId } & TResponse>,\n];\n\n// -------------------------------------------------------------------------------------------------\n// Commands are request/response pairs that are client-initiated\n// -------------------------------------------------------------------------------------------------\n\ntype CommandPair =\n  | GetChatsPair\n  | GetOrCreateChatPair\n  | DeleteChatPair\n  | GetMessageTreePair\n  | DeleteMessagePair\n  | ClearChatPair\n  | AskInChatPair\n  | AbortAiPair\n  | SetToolResultPair;\n\nexport type ClientCmd<T extends CommandPair = CommandPair> = T[0];\nexport type ServerCmdResponse<T extends CommandPair = CommandPair> = T[1];\n\nexport type GetChatsCmd = ClientCmd<GetChatsPair>;\nexport type GetOrCreateChatCmd = ClientCmd<GetOrCreateChatPair>;\nexport type DeleteChatCmd = ClientCmd<DeleteChatPair>;\nexport type GetMessageTreeCmd = ClientCmd<GetMessageTreePair>;\nexport type DeleteMessageCmd = ClientCmd<DeleteMessagePair>;\nexport type ClearChatCmd = ClientCmd<ClearChatPair>;\nexport type AskInChatCmd = ClientCmd<AskInChatPair>;\nexport type AbortAiCmd = ClientCmd<AbortAiPair>;\nexport type SetToolResultCmd = ClientCmd<SetToolResultPair>;\n\nexport type GetChatsResponse = ServerCmdResponse<GetChatsPair>;\nexport type GetOrCreateChatResponse = ServerCmdResponse<GetOrCreateChatPair>;\nexport type DeleteChatResponse = ServerCmdResponse<DeleteChatPair>;\nexport type GetMessageTreeResponse = ServerCmdResponse<GetMessageTreePair>;\nexport type DeleteMessageResponse = ServerCmdResponse<DeleteMessagePair>;\nexport type ClearChatResponse = ServerCmdResponse<ClearChatPair>;\nexport type AskInChatResponse = ServerCmdResponse<AskInChatPair>;\nexport type AbortAiResponse = ServerCmdResponse<AbortAiPair>;\nexport type SetToolResultResponse = ServerCmdResponse<SetToolResultPair>;\n\ntype GetChatsPair = DefineCmd<\n  \"get-chats\",\n  { cursor?: Cursor; pageSize?: number },\n  { chats: AiChat[]; nextCursor: Cursor | null }\n>;\n\nexport type CreateChatOptions = {\n  /** A human-friendly title for the chat. If not set, it will get auto-generated after the first response. */\n  title?: string;\n  /** Arbitrary metadata to record for this chat. This can be later used to filter the list of chats by metadata. */\n  metadata?: Record<string, string | string[]>;\n};\n\ntype GetOrCreateChatPair = DefineCmd<\n  \"get-or-create-chat\",\n  {\n    id: ChatId;\n    options?: CreateChatOptions;\n  },\n  { chat: AiChat }\n>;\n\ntype DeleteChatPair = DefineCmd<\n  \"delete-chat\",\n  { chatId: ChatId },\n  { chatId: ChatId }\n>;\n\ntype GetMessageTreePair = DefineCmd<\n  \"get-message-tree\",\n  { chatId: ChatId },\n  { chat: AiChat; messages: AiChatMessage[] }\n>;\n\ntype DeleteMessagePair = DefineCmd<\n  \"delete-message\",\n  { chatId: ChatId; messageId: MessageId },\n  { chatId: ChatId; messageId: MessageId }\n>;\n\ntype ClearChatPair = DefineCmd<\n  \"clear-chat\",\n  { chatId: ChatId },\n  { chatId: ChatId }\n>;\n\nexport type AiGenerationOptions = {\n  /**\n   * The Copilot ID to use for this request. If not provided, a built-in\n   * default Copilot will be used instead of one that you configured via the\n   * dashboard.\n   */\n  copilotId?: CopilotId;\n  stream?: boolean; // default = true\n  tools?: AiToolDescription[];\n  knowledge?: AiKnowledgeSource[];\n  timeout?: number; // in millis\n};\n\ntype AskInChatPair = DefineCmd<\n  \"ask-in-chat\",\n  {\n    chatId: ChatId;\n\n    /** The chat message to use as the source to create the assistant response. */\n    sourceMessage:\n      | // An existing message ID to reply to\n      MessageId\n      // Or a new (!) message ID to create (optimistically created on the client)\n      | {\n          id: MessageId;\n          parentMessageId: MessageId | null; // The existing message to use as parent\n          content: AiUserContentPart[];\n        };\n\n    /**\n     * The new (!) message ID to output the assistant response into. This ID\n     * should be a non-existing message ID, optimistically assigned by the\n     * client. The output message will be created as a child to the source\n     * message ID.\n     */\n    targetMessageId: MessageId;\n\n    generationOptions: AiGenerationOptions;\n  },\n  {\n    sourceMessage?: AiChatMessage; // If optimistically created\n    targetMessage: AiChatMessage;\n  }\n>;\n\ntype AbortAiPair = DefineCmd<\n  \"abort-ai\",\n  { messageId: MessageId },\n  { ok: true }\n>;\n\n// This is the type that users are supposed to return from the `execute()` method (or call respond() or confirm() with)\n// prettier-ignore\nexport type ToolResultResponse<R extends JsonObject = JsonObject> =\n  Relax<\n    (\n      | { data: R; description?: string; }\n      | { error: string }\n      | { cancel: true | /* reason */ string }\n    )\n  >;\n\nexport type NonEmptyString<T extends string> = T & { __nonEmpty: true };\n\n// This is the type that will get passed back into the `render()` method for further inspection\nexport type RenderableToolResultResponse<R extends JsonObject = JsonObject> =\n  Relax<\n    | { type: \"success\"; data: R }\n    | { type: \"error\"; error: NonEmptyString<string> }\n    | { type: \"cancelled\"; cancelled: true; reason?: string }\n  >;\n\ntype SetToolResultPair = DefineCmd<\n  \"set-tool-result\",\n  {\n    chatId: ChatId;\n    messageId: MessageId;\n    invocationId: string;\n    result: ToolResultResponse;\n    generationOptions: AiGenerationOptions;\n  },\n  { ok: true; message: AiChatMessage } | { ok: false }\n>;\n\n// -------------------------------------------------------------------------------------------------\n// Server-initiated events\n// -------------------------------------------------------------------------------------------------\n\nexport type ServerEvent =\n  | RebootedEvent\n  | CmdFailedEvent\n  | WarningServerEvent\n  | ErrorServerEvent\n  | SyncServerEvent\n  | DeltaServerEvent\n  | SettleServerEvent;\n\n// Sent from the server any time it woke up from hibernation. If this happens,\n// it means that any pending messages a client is still tracking are lost track\n// of. We emit this event to connected clients, so they can mark all of their\n// pending messages as failed.\nexport type RebootedEvent = {\n  event: \"rebooted\";\n};\n\nexport type CmdFailedEvent = {\n  event: \"cmd-failed\";\n  failedCmd: CommandPair[0][\"cmd\"];\n  failedCmdId: CmdId;\n  error: string;\n};\n\nexport type WarningServerEvent = { event: \"warning\"; message: string };\nexport type ErrorServerEvent = { event: \"error\"; error: string };\n\nexport type SyncServerEvent = {\n  event: \"sync\";\n\n  // Stuff to upsert\n  chats?: AiChat[];\n  messages?: AiChatMessage[];\n\n  // Stuff to delete\n  clear?: ChatId[]; // Chats to clear\n  \"-chats\"?: ChatId[]; // Chats to delete\n  \"-messages\"?: Pick<AiChatMessage, \"id\" | \"chatId\">[]; // Messages to delete\n};\n\n/**\n * A \"delta\" event is sent to append an incoming delta chunk to an assistant\n * message.\n */\nexport type DeltaServerEvent = {\n  event: \"delta\";\n  id: MessageId;\n  delta: AiAssistantDeltaUpdate;\n};\n\n/**\n * A \"settle\" event happens after 0 or more \"delta\" messages, and signifies the\n * end of a stream of updates to a pending assistant message. This event turns\n * a pending message into either a completed or failed assistant message.\n */\nexport type SettleServerEvent = {\n  event: \"settle\";\n  message:\n    | AiAwaitingToolAssistantMessage\n    | AiCompletedAssistantMessage\n    | AiFailedAssistantMessage;\n};\n\n// -------------------------------------------------------------------------------------------------\n// Shared data types\n// -------------------------------------------------------------------------------------------------\n\nexport type AiChat = {\n  id: ChatId;\n  title: string;\n  metadata: Record<string, string | string[]>;\n  createdAt: ISODateString;\n  lastMessageAt?: ISODateString; // Optional since some chats might have no messages\n  deletedAt?: ISODateString; // Optional for soft-deleted chats\n};\n\nexport type AiToolDescription = {\n  name: string;\n  description?: string;\n  parameters: JSONSchema7;\n};\n\nexport type AiToolInvocationPart<\n  A extends JsonObject = JsonObject,\n  R extends JsonObject = JsonObject,\n> = Relax<\n  | AiReceivingToolInvocationPart\n  | AiExecutingToolInvocationPart<A>\n  | AiExecutedToolInvocationPart<A, R>\n>;\n\nexport type AiReceivingToolInvocationPart = {\n  type: \"tool-invocation\";\n  stage: \"receiving\";\n  invocationId: string;\n  name: string;\n  partialArgs: Json;\n};\n\nexport type AiExecutingToolInvocationPart<A extends JsonObject = JsonObject> = {\n  type: \"tool-invocation\";\n  stage: \"executing\";\n  invocationId: string;\n  name: string;\n  args: A;\n};\n\nexport type AiExecutedToolInvocationPart<\n  A extends JsonObject = JsonObject,\n  R extends JsonObject = JsonObject,\n> = {\n  type: \"tool-invocation\";\n  stage: \"executed\";\n  invocationId: string;\n  name: string;\n  args: A;\n  result: RenderableToolResultResponse<R>;\n};\n\nexport type AiTextPart = {\n  type: \"text\";\n  text: string;\n};\n\nexport type AiTextDelta = {\n  type: \"text-delta\";\n  textDelta: string;\n};\n\n// NOTE: This chunk type is { type: 'reasoning', textDelta } in the Vercel AI\n// SDK, but I'm renaming it to reasoning-delta here, to distinguish it better\n// from the { type: \"reasoning\", text } part!\nexport type AiReasoningDelta = Relax<\n  | { type: \"reasoning-delta\"; textDelta: string }\n  | { type: \"reasoning-delta\"; signature: string }\n>;\n\nexport type AiReasoningPart = {\n  type: \"reasoning\";\n  text: string;\n};\n\nexport type AiUploadedImagePart = {\n  type: \"image\";\n  id: string;\n  name: string;\n  size: number;\n  mimeType: string;\n};\n\n// \"Parts\" are what make up the \"content\" of a message.\n// \"Content\" is always an \"array of parts\".\nexport type AiUserContentPart = AiTextPart | AiUploadedImagePart;\nexport type AiAssistantContentPart =\n  | AiReasoningPart\n  | AiTextPart\n  | AiToolInvocationPart;\n\nexport type AiAssistantDeltaUpdate =\n  | AiTextDelta // ...or a delta to append to the last sent part\n  | AiReasoningDelta // ...or a delta to append to the last sent part\n  | AiExecutingToolInvocationPart; // ...or a tool invocation chunk\n\nexport type AiUserMessage = {\n  id: MessageId;\n  chatId: ChatId;\n  parentId: MessageId | null;\n  role: \"user\";\n  content: AiUserContentPart[];\n  createdAt: ISODateString;\n  deletedAt?: ISODateString;\n  /** @internal */\n  _optimistic?: true; // Only set by clients, never by server\n};\n\nexport type AiAssistantMessage = Relax<\n  | AiGeneratingAssistantMessage\n  | AiAwaitingToolAssistantMessage\n  | AiCompletedAssistantMessage\n  | AiFailedAssistantMessage\n>;\n\nexport type AiGeneratingAssistantMessage = {\n  id: MessageId;\n  chatId: ChatId;\n  parentId: MessageId | null;\n  role: \"assistant\";\n  createdAt: ISODateString;\n  deletedAt?: ISODateString;\n\n  status: \"generating\";\n  contentSoFar: AiAssistantContentPart[];\n  /** @internal */\n  _optimistic?: true; // Only set by clients, never by server\n};\n\nexport type AiAwaitingToolAssistantMessage = {\n  id: MessageId;\n  chatId: ChatId;\n  parentId: MessageId | null;\n  role: \"assistant\";\n  createdAt: ISODateString;\n  deletedAt?: ISODateString;\n\n  status: \"awaiting-tool\";\n  contentSoFar: AiAssistantContentPart[];\n  /** @internal */\n  _optimistic?: true; // Only set by clients, never by server\n};\n\nexport type AiCompletedAssistantMessage = {\n  id: MessageId;\n  chatId: ChatId;\n  parentId: MessageId | null;\n  role: \"assistant\";\n  content: AiAssistantContentPart[];\n  createdAt: ISODateString;\n  deletedAt?: ISODateString;\n\n  status: \"completed\";\n  /** @internal */\n  _optimistic?: true; // Only set by clients, never by server\n};\n\nexport type AiFailedAssistantMessage = {\n  id: MessageId;\n  chatId: ChatId;\n  parentId: MessageId | null;\n  role: \"assistant\";\n  createdAt: ISODateString;\n  deletedAt?: ISODateString;\n\n  status: \"failed\";\n  contentSoFar: AiAssistantContentPart[];\n  errorReason: string;\n  /** @internal */\n  _optimistic?: true; // Only set by clients, never by server\n};\n\nexport type AiChatMessage = Relax<AiUserMessage | AiAssistantMessage>;\n\nexport type AiKnowledgeSource = {\n  description: string;\n  value: Json;\n};\n\n// --------------------------------------------------------------------------------------------------\n\nexport function appendDelta(\n  content: AiAssistantContentPart[],\n  delta: AiAssistantDeltaUpdate\n): void {\n  const lastPart = content[content.length - 1] as\n    | AiAssistantContentPart\n    | undefined;\n\n  // Otherwise, append a new part type to the array, which we can start\n  // writing into\n  switch (delta.type) {\n    case \"text-delta\":\n      if (lastPart?.type === \"text\") {\n        lastPart.text += delta.textDelta;\n      } else {\n        content.push({ type: \"text\", text: delta.textDelta });\n      }\n      break;\n\n    case \"reasoning-delta\":\n      if (lastPart?.type === \"reasoning\") {\n        lastPart.text += delta.textDelta;\n      } else {\n        content.push({\n          type: \"reasoning\",\n          text: delta.textDelta ?? \"\",\n        });\n      }\n      break;\n\n    case \"tool-invocation\":\n      content.push(delta);\n      break;\n\n    default:\n      return assertNever(delta, \"Unhandled case\");\n  }\n}\n","import { getBearerTokenFromAuthValue } from \"./api-client\";\nimport type { AuthValue } from \"./auth-manager\";\nimport type { Delegates, Status } from \"./connection\";\nimport { ManagedSocket, StopRetrying } from \"./connection\";\nimport { kInternal } from \"./internal\";\nimport { assertNever } from \"./lib/assert\";\nimport { Promise_withResolvers } from \"./lib/controlledPromise\";\nimport { DefaultMap } from \"./lib/DefaultMap\";\nimport * as console from \"./lib/fancy-console\";\nimport { isDefined } from \"./lib/guards\";\nimport type { JsonObject } from \"./lib/Json\";\nimport { nanoid } from \"./lib/nanoid\";\nimport type { Resolve } from \"./lib/Resolve\";\nimport { shallow, shallow2 } from \"./lib/shallow\";\nimport { batch, DerivedSignal, MutableSignal, Signal } from \"./lib/signals\";\nimport { SortedList } from \"./lib/SortedList\";\nimport { TreePool } from \"./lib/TreePool\";\nimport type { Brand, DistributiveOmit } from \"./lib/utils\";\nimport { raise, tryParseJson } from \"./lib/utils\";\nimport { TokenKind } from \"./protocol/AuthToken\";\nimport type {\n  DynamicSessionInfo,\n  OptionalTupleUnless,\n  Polyfills,\n  StaticSessionInfo,\n  TimeoutID,\n} from \"./room\";\nimport type {\n  AbortAiResponse,\n  AiAssistantDeltaUpdate,\n  AiChat,\n  AiChatMessage,\n  AiFailedAssistantMessage,\n  AiGeneratingAssistantMessage,\n  AiGenerationOptions,\n  AiKnowledgeSource,\n  AiToolDescription,\n  AiToolInvocationPart,\n  AiUserContentPart,\n  AiUserMessage,\n  AskInChatResponse,\n  ClearChatResponse,\n  ClientAiMsg,\n  CmdId,\n  CreateChatOptions,\n  Cursor,\n  DeleteChatResponse,\n  DeleteMessageResponse,\n  GetChatsResponse,\n  GetMessageTreeResponse,\n  GetOrCreateChatResponse,\n  ISODateString,\n  MessageId,\n  ServerAiMsg,\n  SetToolResultResponse,\n  ToolResultResponse,\n} from \"./types/ai\";\nimport { appendDelta } from \"./types/ai\";\nimport type { Awaitable } from \"./types/Awaitable\";\nimport type {\n  InferFromSchema,\n  JSONObjectSchema7,\n} from \"./types/InferFromSchema\";\nimport type {\n  IWebSocket,\n  IWebSocketInstance,\n  IWebSocketMessageEvent,\n} from \"./types/IWebSocket\";\nimport { PKG_VERSION } from \"./version\";\n\n// Server must respond to any command within 4 seconds. Note that this timeout\n// isn't related to the timeout for long-running AI tasks. If a long-running AI\n// task is started, the initial command response from the server is \"okay, I'll\n// keep you posted about this long-running task\". That okay is the response\n// which must happen within 4 seconds. In practice it should only take a few\n// milliseconds at most.\nconst DEFAULT_REQUEST_TIMEOUT = 4_000;\n\nexport type AiToolTypePack<\n  A extends JsonObject = JsonObject,\n  R extends JsonObject = JsonObject,\n> = {\n  A: A;\n  R: R;\n};\n\nexport type AskUserMessageInChatOptions = Omit<\n  AiGenerationOptions,\n  \"tools\" | \"knowledge\"\n>;\n\nexport type SetToolResultOptions = Omit<\n  AiGenerationOptions,\n  \"tools\" | \"knowledge\"\n>;\n\nexport type AiToolInvocationProps<\n  A extends JsonObject,\n  R extends JsonObject,\n> = Resolve<\n  DistributiveOmit<AiToolInvocationPart<A, R>, \"type\"> & {\n    respond: (\n      ...args: OptionalTupleUnless<R, [result: ToolResultResponse<R>]>\n    ) => void;\n\n    /**\n     * These are the inferred types for your tool call which you can pass down\n     * to UI components, like so:\n     *\n     *     <AiTool.Confirmation\n     *       types={types}\n     *       confirm={\n     *         // Now fully type-safe!\n     *         (args) => result\n     *       } />\n     *\n     * This will make your AiTool.Confirmation component aware of the types for\n     * `args` and `result`.\n     */\n    types: AiToolTypePack<A, R>;\n\n    // Private APIs\n    [kInternal]: {\n      execute: AiToolExecuteCallback<A, R> | undefined;\n    };\n  }\n>;\n\nexport type AiOpaqueToolInvocationProps = AiToolInvocationProps<\n  JsonObject,\n  JsonObject\n>;\n\nexport type AiToolExecuteContext = {\n  name: string;\n  invocationId: string;\n};\n\nexport type AiToolExecuteCallback<\n  A extends JsonObject,\n  R extends JsonObject,\n> = (\n  args: A,\n  context: AiToolExecuteContext\n) => Record<string, never> extends R\n  ? Awaitable<ToolResultResponse<R> | undefined | void>\n  : Awaitable<ToolResultResponse<R>>;\n\nexport type AiToolDefinition<\n  S extends JSONObjectSchema7,\n  A extends JsonObject,\n  R extends JsonObject,\n> = {\n  description?: string;\n  parameters: S;\n  execute?: AiToolExecuteCallback<A, R>;\n  render?: (props: AiToolInvocationProps<A, R>) => unknown;\n};\n\nexport type AiOpaqueToolDefinition = AiToolDefinition<\n  JSONObjectSchema7,\n  JsonObject,\n  JsonObject\n>;\n\n/**\n * Helper function to help infer the types of `args`, `render`, and `result`.\n * This function has no runtime implementation and is only needed to make it\n * possible for TypeScript to infer types.\n */\nexport function defineAiTool<R extends JsonObject>() {\n  return <const S extends JSONObjectSchema7>(\n    def: AiToolDefinition<\n      S,\n      InferFromSchema<S> extends JsonObject ? InferFromSchema<S> : JsonObject,\n      R\n    >\n  ): AiOpaqueToolDefinition => {\n    return def as AiOpaqueToolDefinition;\n  };\n}\n\ntype NavigationInfo = {\n  /**\n   * The message ID of the parent message, or null if there is no parent.\n   */\n  parent: MessageId | null;\n  /**\n   * The message ID of the left sibling message, or null if there is no left sibling.\n   */\n  prev: MessageId | null;\n  /**\n   * The message ID of the right sibling message, or null if there is no right sibling.\n   */\n  next: MessageId | null;\n};\n\nexport type WithNavigation<T> = T & { navigation: NavigationInfo };\n\ntype UiChatMessage = WithNavigation<AiChatMessage>;\n\ntype AiContext = {\n  staticSessionInfoSig: Signal<StaticSessionInfo | null>;\n  dynamicSessionInfoSig: Signal<DynamicSessionInfo | null>;\n  pendingCmds: Map<\n    CmdId,\n    {\n      resolve: (value: ServerAiMsg) => void;\n      reject: (reason: unknown) => void;\n    }\n  >;\n  chatsStore: ReturnType<typeof createStore_forUserAiChats>;\n  toolsStore: ReturnType<typeof createStore_forTools>;\n  messagesStore: ReturnType<typeof createStore_forChatMessages>;\n  knowledge: KnowledgeStack;\n};\n\ntype LayerKey = Brand<string, \"LayerKey\">;\n\nexport class KnowledgeStack {\n  #_layers: Set<LayerKey>;\n\n  #stack: DefaultMap<string, Map<LayerKey, AiKnowledgeSource | null>>;\n  //                 /                \\\n  //      knowledge key               \"layer\" key\n  //      (random, or optionally      (one entry per mounted component)\n  //       set by user)\n  #_cache: AiKnowledgeSource[] | undefined;\n\n  constructor() {\n    this.#_layers = new Set<LayerKey>();\n    this.#stack = new DefaultMap(\n      () => new Map<LayerKey, AiKnowledgeSource | null>()\n    );\n    this.#_cache = undefined;\n  }\n\n  // Typically a useId()\n  registerLayer(uniqueLayerId: string): LayerKey {\n    const layerKey = uniqueLayerId as LayerKey;\n    if (this.#_layers.has(layerKey))\n      raise(`Layer '${layerKey}' already exists, provide a unique layer id`);\n    this.#_layers.add(layerKey);\n    return layerKey;\n  }\n\n  deregisterLayer(layerKey: LayerKey): void {\n    this.#_layers.delete(layerKey);\n    let deleted = false;\n    for (const [key, knowledge] of this.#stack) {\n      if (knowledge.delete(layerKey)) {\n        deleted = true;\n      }\n      if (knowledge.size === 0)\n        // Just memory cleanup\n        this.#stack.delete(key);\n    }\n    if (deleted) {\n      this.invalidate();\n    }\n  }\n\n  get(): AiKnowledgeSource[] {\n    return (this.#_cache ??= this.#recompute());\n  }\n\n  invalidate(): void {\n    this.#_cache = undefined;\n  }\n\n  #recompute(): AiKnowledgeSource[] {\n    return Array.from(this.#stack.values()).flatMap((layer) =>\n      // Return only the last item (returns [] when empty)\n      Array.from(layer.values()).slice(-1).filter(isDefined)\n    );\n  }\n\n  updateKnowledge(\n    layerKey: LayerKey,\n    key: string,\n    data: AiKnowledgeSource | null\n  ): void {\n    if (!this.#_layers.has(layerKey)) raise(`Unknown layer key: ${layerKey}`);\n    this.#stack.getOrCreate(key).set(layerKey, data);\n    this.invalidate();\n  }\n}\n\nexport type GetOrCreateChatOptions = {\n  name: string;\n  metadata?: AiChat[\"metadata\"];\n};\n\nfunction now(): ISODateString {\n  return new Date().toISOString() as ISODateString;\n}\n\n// Symbol used to register tools globally. These tools are not scoped to\n// a particular chatId and made available to any AiChat instance.\nconst kWILDCARD = Symbol(\"*\");\n\nfunction createStore_forTools() {\n  const toolsByChatIdΣ = new DefaultMap(\n    (_chatId: string | typeof kWILDCARD) => {\n      return new DefaultMap((_name: string) => {\n        return new Signal<AiOpaqueToolDefinition | undefined>(undefined);\n      });\n    }\n  );\n\n  //\n  // TODO This administration is pretty ugly at the moment.\n  // Would be nice to have some kind of helper for constructing these\n  // structures. Maintaining them in all these different DefaultMaps is pretty\n  // getting pretty tricky. Ideas are very welcomed!\n  //\n  // Key here is: '[\"my-tool\",\"my-chat\"]' or just '[\"my-tool\"]' (for global tools)\n  //\n  const globalOrScopedToolΣ = new DefaultMap((nameAndChat: string) => {\n    const [name, chatId] = tryParseJson(nameAndChat) as [\n      string,\n      string | undefined,\n    ];\n    return DerivedSignal.from(() => {\n      return (\n        // A tool that's registered and scoped to a specific chat ID...\n        (chatId !== undefined\n          ? toolsByChatIdΣ.get(chatId)?.get(name)\n          : undefined\n        )?.get() ??\n        // ...or a globally registered tool\n        toolsByChatIdΣ.getOrCreate(kWILDCARD).get(name)?.get()\n      );\n    });\n  });\n\n  function getToolΣ(name: string, chatId?: string) {\n    const key = JSON.stringify(chatId !== undefined ? [name, chatId] : [name]);\n    return globalOrScopedToolΣ.getOrCreate(key);\n  }\n\n  function registerTool(\n    name: string,\n    tool: AiOpaqueToolDefinition,\n    chatId?: string\n  ) {\n    if (!tool.execute && !tool.render) {\n      throw new Error(\n        \"A tool definition must have an execute() function, a render() function, or both.\"\n      );\n    }\n\n    const key = chatId ?? kWILDCARD;\n    toolsByChatIdΣ.getOrCreate(key).getOrCreate(name).set(tool);\n\n    return () => unregisterTool(key, name);\n  }\n\n  function unregisterTool(chatId: string | typeof kWILDCARD, name: string) {\n    const tools = toolsByChatIdΣ.get(chatId);\n    if (tools === undefined) return;\n    const tool = tools.get(name);\n    if (tool === undefined) return;\n    tool.set(undefined);\n  }\n\n  function getToolDescriptions(chatId: string): AiToolDescription[] {\n    const globalToolsΣ = toolsByChatIdΣ.get(kWILDCARD);\n    const scopedToolsΣ = toolsByChatIdΣ.get(chatId);\n    return Array.from([\n      ...(globalToolsΣ?.entries() ?? []),\n      ...(scopedToolsΣ?.entries() ?? []),\n    ]).flatMap(([name, toolΣ]) => {\n      const tool = toolΣ.get();\n      return tool\n        ? [{ name, description: tool.description, parameters: tool.parameters }]\n        : [];\n    });\n  }\n\n  return {\n    getToolDescriptions,\n\n    getToolΣ,\n    registerTool,\n  };\n}\n\nfunction createStore_forChatMessages(\n  toolsStore: ReturnType<typeof createStore_forTools>,\n  setToolResultFn: (\n    chatId: string,\n    messageId: MessageId,\n    invocationId: string,\n    result: ToolResultResponse,\n    options?: SetToolResultOptions\n  ) => Promise<void>\n) {\n  // Keeps track of all message IDs that are originated from this client. We\n  // use this concept of \"ownership\" to determine which client instance is\n  // allowed to auto-execute tool invocations for this message.\n  const myMessages = new Set<MessageId>();\n\n  // Keeps track of any tool invocations that have been auto-executed by this\n  // client. Note that this can also record invocations that don't have an\n  // execute() function. In that case, we also handled it (by kicking off nothing).\n  const handledInvocations = new Set<string>();\n\n  // We maintain a Map with mutable signals. Each such signal contains\n  // a mutable automatically-sorted list of chat messages by chat ID.\n  const messagePoolByChatIdΣ = new DefaultMap(\n    (_chatId: string) =>\n      new MutableSignal(\n        new TreePool<AiChatMessage>(\n          (x) => x.id,\n          (x) => x.parentId,\n          (x, y) => x.createdAt < y.createdAt\n        )\n      )\n  );\n\n  // Separately from that, we track all _generating_ signals in a separate\n  // administration. Because generating messages are likely to receive\n  // many/frequent updates, updating them in a separate administration makes\n  // rendering streaming contents much more efficient than if we had to\n  // re-create and re-render the entire chat list on every such update.\n  const generatingMessagesΣ = new MutableSignal(\n    new Map<MessageId, AiGeneratingAssistantMessage>()\n  );\n\n  function createOptimistically(\n    chatId: string,\n    role: \"user\",\n    parentId: MessageId | null,\n    content: AiUserContentPart[]\n  ): MessageId;\n  function createOptimistically(\n    chatId: string,\n    role: \"assistant\",\n    parentId: MessageId | null\n  ): MessageId;\n  function createOptimistically(\n    chatId: string,\n    role: \"user\" | \"assistant\",\n    parentId: MessageId | null,\n    third?: AiUserContentPart[]\n  ) {\n    const id = `ms_${nanoid()}` as MessageId;\n    const createdAt = now();\n    if (role === \"user\") {\n      const content = third!; // eslint-disable-line\n      upsert({\n        id,\n        chatId,\n        role,\n        parentId,\n        createdAt,\n        content,\n        _optimistic: true,\n      } satisfies AiUserMessage);\n    } else {\n      upsert({\n        id,\n        chatId,\n        role,\n        parentId,\n        createdAt,\n        status: \"generating\",\n        contentSoFar: [],\n        _optimistic: true,\n      } satisfies AiGeneratingAssistantMessage);\n    }\n    return id;\n  }\n\n  function upsertMany(messages: AiChatMessage[]): void {\n    batch(() => {\n      for (const message of messages) {\n        upsert(message);\n      }\n    });\n  }\n\n  function remove(chatId: string, messageId: MessageId): void {\n    const chatMsgsΣ = messagePoolByChatIdΣ.get(chatId);\n    if (!chatMsgsΣ) return;\n\n    const existing = chatMsgsΣ.get().get(messageId);\n    if (!existing || existing.deletedAt) return;\n\n    if (existing.role === \"assistant\" && existing.status !== \"completed\") {\n      upsert({ ...existing, deletedAt: now(), contentSoFar: [] });\n    } else {\n      upsert({ ...existing, deletedAt: now(), content: [] });\n    }\n  }\n\n  function removeByChatId(chatId: string): void {\n    const chatMsgsΣ = messagePoolByChatIdΣ.get(chatId);\n    if (chatMsgsΣ === undefined) return;\n    chatMsgsΣ.mutate((pool) => pool.clear());\n  }\n\n  function upsert(message: AiChatMessage): void {\n    batch(() => {\n      const chatMsgsΣ = messagePoolByChatIdΣ.getOrCreate(message.chatId);\n      chatMsgsΣ.mutate((pool) => pool.upsert(message));\n\n      // If the message is a pending update, write it to the generating\n      // messages LUT. If not, remove it from there.\n      if (message.role === \"assistant\" && message.status === \"generating\") {\n        generatingMessagesΣ.mutate((lut) => {\n          lut.set(message.id, structuredClone(message));\n        });\n      } else {\n        generatingMessagesΣ.mutate((lut) => {\n          lut.delete(message.id);\n        });\n      }\n\n      //\n      // If this message has \"awaiting-tool\" status, it may be the client's\n      // move to trigger an action / call an execute function.\n      //\n      // We will automatically invoke execute()...\n      // - only if such function is provided by the user\n      // - at most once (which is why we track it in seenToolCallIds)\n      // - and only if the current client ID is the designated client ID\n      //\n      if (message.role === \"assistant\" && message.status === \"awaiting-tool\") {\n        if (myMessages.has(message.id)) {\n          for (const toolInvocation of message.contentSoFar.filter(\n            (part) =>\n              part.type === \"tool-invocation\" && part.stage === \"executing\"\n          )) {\n            if (!handledInvocations.has(toolInvocation.invocationId)) {\n              handledInvocations.add(toolInvocation.invocationId);\n            } else {\n              // Do nothing, we already kicked this one off\n              continue;\n            }\n\n            const executeFn = toolsStore\n              .getToolΣ(toolInvocation.name, message.chatId)\n              .get()?.execute;\n            if (executeFn) {\n              (async () => {\n                const result = await executeFn(toolInvocation.args, {\n                  name: toolInvocation.name,\n                  invocationId: toolInvocation.invocationId,\n                });\n                return await setToolResultFn(\n                  message.chatId,\n                  message.id,\n                  toolInvocation.invocationId,\n                  result ?? { data: {} }\n                  // TODO Pass in AiGenerationOptions here, or make the backend use the same options\n                );\n              })().catch((err) => {\n                console.error(\n                  `Error trying to respond to tool-call: ${String(err)} (in execute())`\n                );\n              });\n            }\n          }\n        }\n      } else {\n        myMessages.delete(message.id);\n      }\n    });\n  }\n\n  function addDelta(messageId: MessageId, delta: AiAssistantDeltaUpdate): void {\n    generatingMessagesΣ.mutate((lut) => {\n      const message = lut.get(messageId);\n      if (message === undefined) return false;\n\n      appendDelta(message.contentSoFar, delta);\n      lut.set(messageId, message);\n      return true;\n    });\n  }\n\n  function* iterGeneratingMessages() {\n    for (const chatMsgsΣ of messagePoolByChatIdΣ.values()) {\n      for (const m of chatMsgsΣ.get()) {\n        if (\n          m.role === \"assistant\" &&\n          m.status === \"generating\" &&\n          !m._optimistic\n        ) {\n          yield m;\n        }\n      }\n    }\n  }\n\n  function failAllPending(): void {\n    batch(() => {\n      generatingMessagesΣ.mutate((lut) => {\n        let deleted = false;\n        for (const [k, v] of lut) {\n          if (!v._optimistic) {\n            lut.delete(k);\n            deleted = true;\n          }\n        }\n        return deleted;\n      });\n\n      upsertMany(\n        Array.from(iterGeneratingMessages()).map(\n          (message) =>\n            ({\n              ...message,\n              status: \"failed\",\n              errorReason: \"Lost connection\",\n            }) as AiFailedAssistantMessage\n        )\n      );\n    });\n  }\n\n  function getMessageById(messageId: MessageId): AiChatMessage | undefined {\n    for (const messagesΣ of messagePoolByChatIdΣ.values()) {\n      const message = messagesΣ.get().get(messageId);\n      if (message) {\n        return message;\n      }\n    }\n    return undefined;\n  }\n\n  function first<T>(iterable: IterableIterator<T>): T | undefined {\n    const result = iterable.next();\n    return result.done ? undefined : result.value;\n  }\n\n  function selectBranch(\n    pool: TreePool<AiChatMessage>,\n    preferredBranch: MessageId | null\n  ): UiChatMessage[] {\n    function isAlive(message: AiChatMessage): boolean {\n      // This could be generalized by doing a walk(\n      //   { direction: 'down',\n      //     type: 'breadth-first',\n      //     includeSelf: true,\n      //     predicate: m => !m.deletedAt,\n      //   })\n\n      // If it's a non-deleted message, it's alive\n      if (!message.deletedAt) {\n        return true;\n      }\n      for (const _ of pool.walkDown(message.id, (m) => !m.deletedAt)) {\n        return true;\n      }\n      return false;\n    }\n\n    function selectSpine(leaf: AiChatMessage): UiChatMessage[] {\n      const spine = [];\n\n      let lastVisitedMessage: UiChatMessage | null = null;\n      for (const message of pool.walkUp(leaf.id)) {\n        const prev = first(pool.walkLeft(message.id, isAlive))?.id ?? null;\n        const next = first(pool.walkRight(message.id, isAlive))?.id ?? null;\n\n        // Remove deleted messages only if they don't have any non-deleted\n        // children, and also don't have a next/prev link, requiring the\n        // deleted node to have an on-screen presence.\n        if (!message.deletedAt || prev || next) {\n          const node: UiChatMessage = {\n            ...message,\n            navigation: { parent: null, prev, next },\n          };\n          // Set the parent of the last visited to the id of the current node.\n          if (lastVisitedMessage !== null) {\n            lastVisitedMessage.navigation.parent = node.id;\n          }\n          lastVisitedMessage = node;\n          spine.push(node);\n        }\n      }\n      return spine.reverse();\n    }\n\n    function fallback(): UiChatMessage[] {\n      const latest = pool.sorted.findRight((m) => !m.deletedAt);\n      return latest ? selectSpine(latest) : [];\n    }\n\n    if (preferredBranch === null) {\n      return fallback();\n    }\n\n    const message = pool.get(preferredBranch);\n    if (!message) {\n      return fallback();\n    }\n\n    // Find the first non-deleted grand child. If one doesn't exist, keep\n    // walking up the tree and repeat, until we find one.\n    for (const current of pool.walkUp(message.id)) {\n      // If a non-deleted grandchild exists, select it.\n      for (const desc of pool.walkDown(current.id, (m) => !m.deletedAt)) {\n        return selectSpine(desc);\n      }\n\n      // If the current node is not deleted, select it.\n      if (!current.deletedAt) {\n        return selectSpine(current);\n      }\n\n      // Otherwise, continue looping by walking up one level and repeating.\n    }\n\n    return fallback();\n  }\n\n  const immutableMessagesByBranch = new DefaultMap((chatId: string) => {\n    return new DefaultMap((branchId: MessageId | null) => {\n      const messagesΣ = DerivedSignal.from(() => {\n        const pool = messagePoolByChatIdΣ.getOrCreate(chatId).get();\n        return selectBranch(pool, branchId);\n      }, shallow2);\n\n      return DerivedSignal.from((): UiChatMessage[] => {\n        const generatingMessages = generatingMessagesΣ.get();\n        return messagesΣ.get().map((message) => {\n          if (message.role !== \"assistant\" || message.status !== \"generating\") {\n            return message;\n          }\n          const generatingMessage = generatingMessages.get(message.id);\n          if (generatingMessage === undefined) return message;\n          return {\n            ...message,\n            contentSoFar: generatingMessage.contentSoFar,\n          } satisfies AiGeneratingAssistantMessage;\n        });\n      }, shallow);\n    });\n  });\n\n  function getChatMessagesForBranchΣ(chatId: string, branch?: MessageId) {\n    return immutableMessagesByBranch\n      .getOrCreate(chatId)\n      .getOrCreate(branch || null);\n  }\n\n  return {\n    // Readers\n    getMessageById,\n    getChatMessagesForBranchΣ,\n\n    // Mutations\n    createOptimistically,\n    upsert,\n    upsertMany,\n    remove,\n    removeByChatId,\n    addDelta,\n    failAllPending,\n\n    markMine(messageId: MessageId) {\n      myMessages.add(messageId);\n    },\n  };\n}\n\nfunction createStore_forUserAiChats() {\n  // The foundation is the mutable signal, which is a simple Map (easy to make\n  // one-off updates to). But externally we expose a derived signal that\n  // produces a new lazy \"object\" copy of this map any time it changes. This\n  // plays better with React APIs.\n  const allChatsInclDeletedΣ = new MutableSignal(\n    SortedList.with<AiChat>((x, y) => y.createdAt < x.createdAt)\n  );\n  const nonDeletedChatsΣ = DerivedSignal.from(() =>\n    Array.from(allChatsInclDeletedΣ.get()).filter((c) => !c.deletedAt)\n  );\n\n  function upsertMany(chats: AiChat[]) {\n    allChatsInclDeletedΣ.mutate((list) => {\n      for (const chat of chats) {\n        list.removeBy((c) => c.id === chat.id, 1);\n        list.add(chat);\n      }\n    });\n  }\n\n  function upsert(chat: AiChat) {\n    upsertMany([chat]);\n  }\n\n  /**\n   * \"Just\" deleting a chat we already know about might break assumptions in\n   * clients that are currently displaying the chat on-screen. So instead,\n   * we'll re-render those so they can display the chat is deleted.\n   */\n  function markDeleted(chatId: string) {\n    allChatsInclDeletedΣ.mutate((list) => {\n      const chat = list.find((c) => c.id === chatId);\n      if (!chat) return false;\n\n      upsert({ ...chat, deletedAt: now() });\n      return undefined;\n    });\n  }\n\n  function getChatById(chatId: string) {\n    return Array.from(allChatsInclDeletedΣ.get()).find(\n      (chat) => chat.id === chatId\n    );\n  }\n\n  return {\n    chatsΣ: nonDeletedChatsΣ,\n    getChatById,\n\n    // Mutations\n    upsert,\n    upsertMany,\n    markDeleted,\n  };\n}\n\n/** @private This API will change, and is not considered stable. DO NOT RELY on it. */\nexport type Ai = {\n  [kInternal]: {\n    context: AiContext;\n  };\n  connectInitially: () => void;\n  // connect: () => void;\n  // reconnect: () => void;\n  disconnect: () => void;\n  getStatus: () => Status;\n\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  getChats: (options?: { cursor?: Cursor }) => Promise<GetChatsResponse>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  getOrCreateChat: (\n    /** A unique identifier for the chat. */\n    chatId: string,\n    options?: CreateChatOptions\n  ) => Promise<GetOrCreateChatResponse>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  deleteChat: (chatId: string) => Promise<DeleteChatResponse>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  getMessageTree: (chatId: string) => Promise<GetMessageTreeResponse>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  deleteMessage: (\n    chatId: string,\n    messageId: MessageId\n  ) => Promise<DeleteMessageResponse>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  clearChat: (chatId: string) => Promise<ClearChatResponse>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  askUserMessageInChat: (\n    chatId: string,\n    userMessage:\n      | MessageId\n      | {\n          id: MessageId;\n          parentMessageId: MessageId | null;\n          content: AiUserContentPart[];\n        },\n    targetMessageId: MessageId,\n    options?: AskUserMessageInChatOptions\n  ) => Promise<AskInChatResponse>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  abort: (messageId: MessageId) => Promise<AbortAiResponse>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  setToolResult: (\n    chatId: string,\n    messageId: MessageId,\n    invocationId: string,\n    result: ToolResultResponse,\n    options?: SetToolResultOptions\n  ) => Promise<void>;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  signals: {\n    chatsΣ: DerivedSignal<AiChat[]>;\n    getChatMessagesForBranchΣ(\n      chatId: string,\n      branch?: MessageId\n    ): DerivedSignal<UiChatMessage[]>;\n    getToolΣ(\n      name: string,\n      chatId?: string\n    ): DerivedSignal<AiOpaqueToolDefinition | undefined>;\n  };\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  getChatById: (chatId: string) => AiChat | undefined;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  registerKnowledgeLayer: (uniqueLayerId: string) => LayerKey;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  deregisterKnowledgeLayer: (layerKey: LayerKey) => void;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  updateKnowledge: (\n    layerKey: LayerKey,\n    data: AiKnowledgeSource,\n    key?: string\n  ) => void;\n  /** @private This API will change, and is not considered stable. DO NOT RELY on it. */\n  registerTool: (\n    name: string,\n    tool: AiOpaqueToolDefinition,\n    chatId?: string\n  ) => () => void;\n};\n\n/** @internal */\nexport type AiConfig = {\n  delegates: Delegates<AuthValue>;\n\n  userId?: string;\n  lostConnectionTimeout: number;\n  backgroundKeepAliveTimeout?: number;\n  polyfills?: Polyfills;\n\n  enableDebugLogging?: boolean;\n};\n\nexport function createAi(config: AiConfig): Ai {\n  const managedSocket: ManagedSocket<AuthValue> = new ManagedSocket(\n    config.delegates,\n    config.enableDebugLogging,\n    false // AI doesn't have actors (yet, but it will)\n  );\n\n  const chatsStore = createStore_forUserAiChats();\n  const toolsStore = createStore_forTools();\n  const messagesStore = createStore_forChatMessages(toolsStore, setToolResult);\n  const context: AiContext = {\n    staticSessionInfoSig: new Signal<StaticSessionInfo | null>(null),\n    dynamicSessionInfoSig: new Signal<DynamicSessionInfo | null>(null),\n    pendingCmds: new Map(),\n    chatsStore,\n    messagesStore,\n    toolsStore,\n    knowledge: new KnowledgeStack(),\n  };\n\n  let lastTokenKey: string | undefined;\n  function onStatusDidChange(_newStatus: Status) {\n    const authValue = managedSocket.authValue;\n    if (authValue !== null) {\n      const tokenKey = getBearerTokenFromAuthValue(authValue);\n\n      if (tokenKey !== lastTokenKey) {\n        lastTokenKey = tokenKey;\n\n        if (authValue.type === \"secret\") {\n          const token = authValue.token.parsed;\n          context.staticSessionInfoSig.set({\n            userId: token.k === TokenKind.SECRET_LEGACY ? token.id : token.uid,\n            userInfo:\n              token.k === TokenKind.SECRET_LEGACY ? token.info : token.ui,\n          });\n        } else {\n          context.staticSessionInfoSig.set({\n            userId: undefined,\n            userInfo: undefined,\n          });\n        }\n      }\n    }\n  }\n  let _connectionLossTimerId: TimeoutID | undefined;\n  let _hasLostConnection = false;\n\n  function handleConnectionLossEvent(newStatus: Status) {\n    if (newStatus === \"reconnecting\") {\n      _connectionLossTimerId = setTimeout(() => {\n        _hasLostConnection = true;\n      }, config.lostConnectionTimeout);\n    } else {\n      clearTimeout(_connectionLossTimerId);\n\n      if (_hasLostConnection) {\n        _hasLostConnection = false;\n      }\n    }\n  }\n\n  function onDidConnect() {\n    // NoOp for now, but we should maybe fetch messages or something?\n  }\n\n  function onDidDisconnect() {\n    console.warn(\"onDidDisconnect\");\n  }\n\n  function handleServerMessage(event: IWebSocketMessageEvent) {\n    if (typeof event.data !== \"string\")\n      // Ignore binary (non-string) WebSocket messages\n      return;\n\n    const msg = tryParseJson(event.data) as ServerAiMsg | undefined;\n    if (!msg)\n      // Ignore non-JSON messages\n      return;\n\n    // If the current msg carries a cmdId, check to see if it's a known one,\n    // and if it's still exists in our pendingRequest administration. If not,\n    // it may have timed out already, or it wasn't intended for us.\n    const cmdId =\n      \"cmdId\" in msg\n        ? msg.cmdId\n        : msg.event === \"cmd-failed\"\n          ? msg.failedCmdId\n          : undefined;\n    const pendingCmd = context.pendingCmds.get(cmdId!); // eslint-disable-line no-restricted-syntax\n\n    if (cmdId && !pendingCmd) {\n      console.warn(\"Ignoring unexpected command response. Already timed out, or not for us?\", msg); // prettier-ignore\n      return;\n    }\n\n    if (\"event\" in msg) {\n      switch (msg.event) {\n        case \"cmd-failed\":\n          pendingCmd?.reject(new Error(msg.error));\n          break;\n\n        case \"delta\": {\n          const { id, delta } = msg;\n          context.messagesStore.addDelta(id, delta);\n          break;\n        }\n\n        case \"settle\": {\n          context.messagesStore.upsert(msg.message);\n          break;\n        }\n\n        case \"warning\":\n          console.warn(msg.message);\n          break;\n\n        case \"error\":\n          console.error(msg.error);\n          break;\n\n        case \"rebooted\":\n          context.messagesStore.failAllPending();\n          break;\n\n        case \"sync\":\n          batch(() => {\n            // Delete any resources?\n            for (const m of msg[\"-messages\"] ?? []) {\n              context.messagesStore.remove(m.chatId, m.id);\n            }\n            for (const chatId of msg[\"-chats\"] ?? []) {\n              context.chatsStore.markDeleted(chatId);\n              context.messagesStore.removeByChatId(chatId);\n            }\n            for (const chatId of msg.clear ?? []) {\n              context.messagesStore.removeByChatId(chatId);\n            }\n\n            // Add any new resources?\n            if (msg.chats) {\n              context.chatsStore.upsertMany(msg.chats);\n            }\n            if (msg.messages) {\n              context.messagesStore.upsertMany(msg.messages);\n            }\n          });\n          break;\n\n        default:\n          return assertNever(msg, \"Unhandled case\");\n      }\n    } else {\n      switch (msg.cmd) {\n        case \"get-chats\":\n          context.chatsStore.upsertMany(msg.chats);\n          break;\n\n        case \"get-or-create-chat\":\n          context.chatsStore.upsert(msg.chat);\n          break;\n\n        case \"delete-chat\":\n          context.chatsStore.markDeleted(msg.chatId);\n          context.messagesStore.removeByChatId(msg.chatId);\n          break;\n\n        case \"get-message-tree\":\n          context.chatsStore.upsert(msg.chat);\n          context.messagesStore.upsertMany(msg.messages);\n          break;\n\n        case \"delete-message\":\n          context.messagesStore.remove(msg.chatId, msg.messageId);\n          break;\n\n        case \"clear-chat\":\n          context.messagesStore.removeByChatId(msg.chatId);\n          break;\n\n        case \"ask-in-chat\":\n          if (msg.sourceMessage) {\n            // This field will only be returned if the ask-in-chat command\n            // created a new source message\n            context.messagesStore.upsert(msg.sourceMessage);\n          }\n          context.messagesStore.upsert(msg.targetMessage);\n          break;\n\n        case \"abort-ai\":\n          // TODO Not handled yet\n          break;\n\n        case \"set-tool-result\":\n          if (msg.ok) {\n            context.messagesStore.upsert(msg.message);\n          }\n          break;\n\n        default:\n          return assertNever(msg, \"Unhandled case\");\n      }\n    }\n\n    // After handling the side-effects above, we can resolve the promise\n    pendingCmd?.resolve(msg);\n  }\n\n  managedSocket.events.onMessage.subscribe(handleServerMessage);\n  managedSocket.events.statusDidChange.subscribe(onStatusDidChange);\n  managedSocket.events.statusDidChange.subscribe(handleConnectionLossEvent);\n  managedSocket.events.didConnect.subscribe(onDidConnect);\n  managedSocket.events.didDisconnect.subscribe(onDidDisconnect);\n  managedSocket.events.onConnectionError.subscribe(({ message, code }) => {\n    //const type = \"AI_CONNECTION_ERROR\";\n    // const err = new LiveblocksError(message, { type, code });\n    if (process.env.NODE_ENV !== \"production\") {\n      console.error(\n        `Connection to websocket server closed. Reason: ${message} (code: ${code}).`\n      );\n    }\n  });\n\n  function connectInitially() {\n    if (managedSocket.getStatus() === \"initial\") {\n      managedSocket.connect();\n    }\n  }\n\n  async function sendClientMsgWithResponse<T extends ServerAiMsg>(\n    msg: DistributiveOmit<ClientAiMsg, \"cmdId\">\n  ): Promise<T> {\n    connectInitially();\n    if (managedSocket.getStatus() !== \"connected\") {\n      await managedSocket.events.didConnect.waitUntil();\n    }\n\n    const { promise, resolve, reject } = Promise_withResolvers<ServerAiMsg>();\n\n    // Automatically calls reject() when signal is aborted\n    const abortSignal = AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT);\n    abortSignal.addEventListener(\"abort\", () => reject(abortSignal.reason), {\n      once: true,\n    });\n\n    const cmdId = nanoid(7) as CmdId;\n    context.pendingCmds.set(cmdId, { resolve, reject });\n\n    sendClientMsg({ ...msg, cmdId });\n    return (\n      (promise as Promise<T>)\n        .finally(() => {\n          // Always cleanup\n          context.pendingCmds.delete(cmdId);\n        })\n        // Make sure these promises don't go uncaught (in contrast to the\n        // promise instance we return to the caller)\n        .catch((err: Error) => {\n          console.error(err.message);\n          throw err;\n        })\n    );\n  }\n\n  function sendClientMsg(msg: ClientAiMsg) {\n    managedSocket.send(\n      JSON.stringify({\n        ...msg,\n      })\n    );\n  }\n\n  function getChats(options: { cursor?: Cursor } = {}) {\n    return sendClientMsgWithResponse<GetChatsResponse>({\n      cmd: \"get-chats\",\n      cursor: options.cursor,\n    });\n  }\n\n  function getOrCreateChat(id: string, options?: CreateChatOptions) {\n    return sendClientMsgWithResponse<GetOrCreateChatResponse>({\n      cmd: \"get-or-create-chat\",\n      id,\n      options,\n    });\n  }\n\n  function getMessageTree(chatId: string) {\n    return sendClientMsgWithResponse<GetMessageTreeResponse>({\n      cmd: \"get-message-tree\",\n      chatId,\n    });\n  }\n\n  function registerKnowledgeLayer(uniqueLayerId: string): LayerKey {\n    return context.knowledge.registerLayer(uniqueLayerId);\n  }\n\n  function deregisterKnowledgeLayer(layerKey: LayerKey): void {\n    context.knowledge.deregisterLayer(layerKey);\n  }\n\n  function updateKnowledge(\n    layerKey: LayerKey,\n    data: AiKnowledgeSource,\n    key: string = nanoid()\n  ) {\n    context.knowledge.updateKnowledge(layerKey, key, data);\n  }\n\n  async function setToolResult(\n    chatId: string,\n    messageId: MessageId,\n    invocationId: string,\n    result: ToolResultResponse,\n    options?: SetToolResultOptions\n  ): Promise<void> {\n    const knowledge = context.knowledge.get();\n    const tools = context.toolsStore.getToolDescriptions(chatId);\n\n    const resp: SetToolResultResponse = await sendClientMsgWithResponse({\n      cmd: \"set-tool-result\",\n      chatId,\n      messageId,\n      invocationId,\n      result,\n      generationOptions: {\n        copilotId: options?.copilotId,\n        stream: options?.stream,\n        timeout: options?.timeout,\n\n        // Knowledge and tools aren't coming from the options, but retrieved\n        // from the global context\n        knowledge: knowledge.length > 0 ? knowledge : undefined,\n        tools: tools.length > 0 ? tools : undefined,\n      },\n    });\n    if (resp.ok) {\n      messagesStore.markMine(resp.message.id);\n    }\n  }\n\n  return Object.defineProperty(\n    {\n      [kInternal]: {\n        context,\n      },\n\n      connectInitially,\n      // reconnect: () => managedSocket.reconnect(),\n      disconnect: () => managedSocket.disconnect(),\n\n      getChats,\n      getOrCreateChat,\n\n      deleteChat: (chatId: string) => {\n        return sendClientMsgWithResponse({ cmd: \"delete-chat\", chatId });\n      },\n\n      getMessageTree,\n\n      deleteMessage: (chatId: string, messageId: MessageId) =>\n        sendClientMsgWithResponse({ cmd: \"delete-message\", chatId, messageId }),\n      clearChat: (chatId: string) =>\n        sendClientMsgWithResponse({ cmd: \"clear-chat\", chatId }),\n\n      askUserMessageInChat: async (\n        chatId: string,\n        userMessage:\n          | MessageId\n          | {\n              id: MessageId;\n              parentMessageId: MessageId | null;\n              content: AiUserContentPart[];\n            },\n        targetMessageId: MessageId,\n        options?: AskUserMessageInChatOptions\n      ): Promise<AskInChatResponse> => {\n        const knowledge = context.knowledge.get();\n        const tools = context.toolsStore.getToolDescriptions(chatId);\n\n        const resp: AskInChatResponse = await sendClientMsgWithResponse({\n          cmd: \"ask-in-chat\",\n          chatId,\n          sourceMessage: userMessage,\n          targetMessageId,\n          generationOptions: {\n            copilotId: options?.copilotId,\n            stream: options?.stream,\n            timeout: options?.timeout,\n\n            // Knowledge and tools aren't coming from the options, but retrieved\n            // from the global context\n            knowledge: knowledge.length > 0 ? knowledge : undefined,\n            tools: tools.length > 0 ? tools : undefined,\n          },\n        });\n        messagesStore.markMine(resp.targetMessage.id);\n        return resp;\n      },\n\n      abort: (messageId: MessageId) =>\n        sendClientMsgWithResponse({ cmd: \"abort-ai\", messageId }),\n\n      setToolResult,\n\n      getStatus: () => managedSocket.getStatus(),\n\n      signals: {\n        chatsΣ: context.chatsStore.chatsΣ,\n        getChatMessagesForBranchΣ:\n          context.messagesStore.getChatMessagesForBranchΣ,\n        getToolΣ: context.toolsStore.getToolΣ,\n      },\n\n      getChatById: context.chatsStore.getChatById,\n      registerKnowledgeLayer,\n      deregisterKnowledgeLayer,\n      updateKnowledge,\n\n      registerTool: context.toolsStore.registerTool,\n    } satisfies Ai,\n    kInternal,\n    { enumerable: false }\n  );\n}\n\nexport function makeCreateSocketDelegateForAi(\n  baseUrl: string,\n  WebSocketPolyfill?: IWebSocket\n) {\n  return (authValue: AuthValue): IWebSocketInstance => {\n    const ws: IWebSocket | undefined =\n      WebSocketPolyfill ??\n      (typeof WebSocket === \"undefined\" ? undefined : WebSocket);\n\n    if (ws === undefined) {\n      throw new StopRetrying(\n        \"To use Liveblocks client in a non-DOM environment, you need to provide a WebSocket polyfill.\"\n      );\n    }\n\n    const url = new URL(baseUrl);\n    url.protocol = url.protocol === \"http:\" ? \"ws\" : \"wss\";\n    url.pathname = \"/ai/v4\";\n    // TODO: don't allow public key to do this\n    if (authValue.type === \"secret\") {\n      url.searchParams.set(\"tok\", authValue.token.raw);\n    } else if (authValue.type === \"public\") {\n      throw new Error(\"Public key not supported with AI Copilots\");\n    } else {\n      return assertNever(authValue, \"Unhandled case\");\n    }\n    url.searchParams.set(\"version\", PKG_VERSION || \"dev\");\n    return new ws(url.toString());\n  };\n}\n","import { StopRetrying } from \"./connection\";\nimport { isPlainObject } from \"./lib/guards\";\nimport type { Json } from \"./lib/Json\";\nimport type { Relax } from \"./lib/Relax\";\nimport { stringifyOrLog as stringify } from \"./lib/stringify\";\nimport type {\n  Authentication,\n  CustomAuthenticationResult,\n} from \"./protocol/Authentication\";\nimport type { AuthToken, ParsedAuthToken } from \"./protocol/AuthToken\";\nimport { parseAuthToken, Permission, TokenKind } from \"./protocol/AuthToken\";\nimport type { Polyfills } from \"./room\";\n\nexport type AuthValue =\n  | { type: \"secret\"; token: ParsedAuthToken }\n  | { type: \"public\"; publicApiKey: string };\n\nexport type RequestedScope = \"room:read\" | \"comments:read\";\n\nexport type AuthManager = {\n  reset(): void;\n  getAuthValue(requestOptions: {\n    requestedScope: RequestedScope;\n    roomId?: string;\n  }): Promise<AuthValue>;\n};\n\ntype AuthEndpoint =\n  | string\n  | ((room?: string) => Promise<CustomAuthenticationResult>);\n\nexport type AuthenticationOptions = {\n  polyfills?: Polyfills;\n} & Relax<{ publicApiKey: string } | { authEndpoint: AuthEndpoint }>;\n\nexport function createAuthManager(\n  authOptions: AuthenticationOptions,\n  onAuthenticate?: (token: AuthToken) => void\n): AuthManager {\n  const authentication = prepareAuthentication(authOptions);\n\n  const seenTokens: Set<string> = new Set();\n\n  const tokens: ParsedAuthToken[] = [];\n  const expiryTimes: number[] = []; // Supposed to always contain the same number of elements as `tokens`\n\n  const requestPromises = new Map<string, Promise<ParsedAuthToken>>();\n\n  function reset() {\n    seenTokens.clear();\n    tokens.length = 0;\n    expiryTimes.length = 0;\n    requestPromises.clear();\n  }\n\n  function hasCorrespondingScopes(\n    requestedScope: RequestedScope,\n    scopes: Permission[]\n  ) {\n    if (requestedScope === \"comments:read\") {\n      return (\n        scopes.includes(Permission.CommentsRead) ||\n        scopes.includes(Permission.CommentsWrite) ||\n        scopes.includes(Permission.Read) ||\n        scopes.includes(Permission.Write)\n      );\n    } else if (requestedScope === \"room:read\") {\n      return (\n        scopes.includes(Permission.Read) || scopes.includes(Permission.Write)\n      );\n    }\n\n    return false;\n  }\n\n  function getCachedToken(requestOptions: {\n    requestedScope: RequestedScope;\n    roomId?: string;\n  }): ParsedAuthToken | undefined {\n    const now = Math.ceil(Date.now() / 1000);\n\n    for (let i = tokens.length - 1; i >= 0; i--) {\n      const token = tokens[i];\n      const expiresAt = expiryTimes[i];\n\n      // If this token is expired, remove it from cache, as if it never existed\n      // in the first place\n      if (expiresAt <= now) {\n        tokens.splice(i, 1);\n        expiryTimes.splice(i, 1);\n        continue;\n      }\n\n      if (token.parsed.k === TokenKind.ID_TOKEN) {\n        // When ID token method is used, only one token per user should be used and cached at the same time.\n        return token;\n      } else if (token.parsed.k === TokenKind.ACCESS_TOKEN) {\n        // In this version, we accept access tokens with zero permission when issuing token for resources outside a room.\n        if (\n          !requestOptions.roomId &&\n          Object.entries(token.parsed.perms).length === 0\n        ) {\n          return token;\n        }\n\n        for (const [resource, scopes] of Object.entries(token.parsed.perms)) {\n          // If the requester didn't pass a roomId,\n          // it means they need the token to access the user's resources (inbox notifications for example).\n          // We return any access token that contains a wildcard for the requested scope.\n          if (!requestOptions.roomId) {\n            if (\n              resource.includes(\"*\") &&\n              hasCorrespondingScopes(requestOptions.requestedScope, scopes)\n            ) {\n              return token;\n            }\n          } else if (\n            (resource.includes(\"*\") &&\n              requestOptions.roomId.startsWith(resource.replace(\"*\", \"\"))) ||\n            (requestOptions.roomId === resource &&\n              hasCorrespondingScopes(requestOptions.requestedScope, scopes))\n          ) {\n            return token;\n          }\n        }\n      }\n    }\n\n    return undefined;\n  }\n\n  async function makeAuthRequest(options: {\n    requestedScope: RequestedScope;\n    roomId?: string;\n  }): Promise<ParsedAuthToken> {\n    const fetcher =\n      authOptions.polyfills?.fetch ??\n      (typeof window === \"undefined\" ? undefined : window.fetch);\n\n    if (authentication.type === \"private\") {\n      if (fetcher === undefined) {\n        throw new StopRetrying(\n          \"To use Liveblocks client in a non-DOM environment with a url as auth endpoint, you need to provide a fetch polyfill.\"\n        );\n      }\n\n      const response = await fetchAuthEndpoint(fetcher, authentication.url, {\n        room: options.roomId,\n      });\n      const parsed = parseAuthToken(response.token);\n\n      if (seenTokens.has(parsed.raw)) {\n        throw new StopRetrying(\n          \"The same Liveblocks auth token was issued from the backend before. Caching Liveblocks tokens is not supported.\"\n        );\n      }\n\n      onAuthenticate?.(parsed.parsed);\n      return parsed;\n    }\n\n    if (authentication.type === \"custom\") {\n      const response = await authentication.callback(options.roomId);\n      if (response && typeof response === \"object\") {\n        if (typeof response.token === \"string\") {\n          const parsed = parseAuthToken(response.token);\n\n          onAuthenticate?.(parsed.parsed);\n          return parsed;\n        } else if (typeof response.error === \"string\") {\n          const reason = `Authentication failed: ${\n            \"reason\" in response && typeof response.reason === \"string\"\n              ? response.reason\n              : \"Forbidden\"\n          }`;\n\n          // istanbul ignore else\n          if (response.error === \"forbidden\") {\n            throw new StopRetrying(reason);\n          } else {\n            throw new Error(reason);\n          }\n        }\n      }\n\n      throw new Error(\n        'Your authentication callback function should return a token, but it did not. Hint: the return value should look like: { token: \"...\" }'\n      );\n    }\n\n    // istanbul ignore next\n    throw new Error(\n      \"Unexpected authentication type. Must be private or custom.\"\n    );\n  }\n\n  async function getAuthValue(requestOptions: {\n    requestedScope: RequestedScope;\n    roomId?: string;\n  }): Promise<AuthValue> {\n    if (authentication.type === \"public\") {\n      return { type: \"public\", publicApiKey: authentication.publicApiKey };\n    }\n\n    const cachedToken = getCachedToken(requestOptions);\n    if (cachedToken !== undefined) {\n      return { type: \"secret\", token: cachedToken };\n    }\n\n    let currentPromise;\n    if (requestOptions.roomId) {\n      currentPromise = requestPromises.get(requestOptions.roomId);\n      if (currentPromise === undefined) {\n        currentPromise = makeAuthRequest(requestOptions);\n        requestPromises.set(requestOptions.roomId, currentPromise);\n      }\n    } else {\n      currentPromise = requestPromises.get(\"liveblocks-user-token\");\n      if (currentPromise === undefined) {\n        currentPromise = makeAuthRequest(requestOptions);\n        requestPromises.set(\"liveblocks-user-token\", currentPromise);\n      }\n    }\n\n    try {\n      const token = await currentPromise;\n      // Translate \"server timestamps\" to \"local timestamps\" in case clocks aren't in sync\n      const BUFFER = 30; // Expire tokens 30 seconds sooner than they have to\n      const expiresAt =\n        Math.floor(Date.now() / 1000) +\n        (token.parsed.exp - token.parsed.iat) -\n        BUFFER;\n\n      seenTokens.add(token.raw);\n\n      // Legacy tokens should not get cached\n      if (token.parsed.k !== TokenKind.SECRET_LEGACY) {\n        tokens.push(token);\n        expiryTimes.push(expiresAt);\n      }\n\n      return { type: \"secret\", token };\n    } finally {\n      if (requestOptions.roomId) {\n        requestPromises.delete(requestOptions.roomId);\n      } else {\n        requestPromises.delete(\"liveblocks-user-token\");\n      }\n    }\n  }\n\n  return {\n    reset,\n    getAuthValue,\n  };\n}\n\nfunction prepareAuthentication(\n  authOptions: AuthenticationOptions\n): Authentication {\n  const { publicApiKey, authEndpoint } = authOptions;\n\n  if (authEndpoint !== undefined && publicApiKey !== undefined) {\n    throw new Error(\n      \"You cannot simultaneously use `publicApiKey` and `authEndpoint` options. Please pick one and leave the other option unspecified. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient\"\n    );\n  }\n\n  if (typeof publicApiKey === \"string\") {\n    if (publicApiKey.startsWith(\"sk_\")) {\n      throw new Error(\n        \"Invalid `publicApiKey` option. The value you passed is a secret key, which should not be used from the client. Please only ever pass a public key here. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientPublicKey\"\n      );\n    } else if (!publicApiKey.startsWith(\"pk_\")) {\n      throw new Error(\n        \"Invalid key. Please use the public key format: pk_<public key>. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientPublicKey\"\n      );\n    }\n    return {\n      type: \"public\",\n      publicApiKey,\n    };\n  }\n\n  if (typeof authEndpoint === \"string\") {\n    return {\n      type: \"private\",\n      url: authEndpoint,\n    };\n  } else if (typeof authEndpoint === \"function\") {\n    return {\n      type: \"custom\",\n      callback: authEndpoint,\n    };\n  } else if (authEndpoint !== undefined) {\n    throw new Error(\n      \"The `authEndpoint` option must be a string or a function. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientAuthEndpoint\"\n    );\n  }\n\n  throw new Error(\n    \"Invalid Liveblocks client options. Please provide either a `publicApiKey` or `authEndpoint` option. They cannot both be empty. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient\"\n  );\n}\n\nasync function fetchAuthEndpoint(\n  fetch: typeof window.fetch,\n  endpoint: string,\n  body: {\n    room?: string;\n  }\n): Promise<{ token: string }> {\n  const res = await fetch(endpoint, {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n    },\n    body: stringify(body),\n  });\n  if (!res.ok) {\n    const reason = `${\n      (await res.text()).trim() || \"reason not provided in auth response\"\n    } (${res.status} returned by POST ${endpoint})`;\n\n    if (res.status === 401 || res.status === 403) {\n      // Throw a special error instance, which the connection manager will\n      // recognize and understand that retrying will have no effect\n      throw new StopRetrying(`Unauthorized: ${reason}`);\n    } else {\n      throw new Error(`Failed to authenticate: ${reason}`);\n    }\n  }\n\n  let data: Json;\n  try {\n    data = await (res.json() as Promise<Json>);\n  } catch (er) {\n    throw new Error(\n      `Expected a JSON response when doing a POST request on \"${endpoint}\". ${String(\n        er\n      )}`\n    );\n  }\n\n  if (!isPlainObject(data) || typeof data.token !== \"string\") {\n    throw new Error(\n      `Expected a JSON response of the form \\`{ token: \"...\" }\\` when doing a POST request on \"${endpoint}\", but got ${stringify(\n        data\n      )}`\n    );\n  }\n  const { token } = data;\n  return { token };\n}\n","export const DEFAULT_BASE_URL = \"https://api.liveblocks.io\";\n","import { makeEventSource } from \"../lib/EventSource\";\nimport type * as DevTools from \"./protocol\";\n\ntype SendToPanelOptions = {\n  /**\n   * We'll only want to send messages from the client to the panel if the panel\n   * has shown interest in this. To allow message passing to the dev panel,\n   * call allowMessagePassing().\n   */\n  force: boolean;\n};\n\nlet _bridgeActive = false;\nexport function activateBridge(allowed: boolean): void {\n  _bridgeActive = allowed;\n}\n\nexport function sendToPanel(\n  message: DevTools.ClientToPanelMessage,\n  options?: SendToPanelOptions\n): void {\n  // DevTools communication only happens on the client side\n  // Define it as a no-op in production environments or when run outside of a browser context\n  if (process.env.NODE_ENV === \"production\" || typeof window === \"undefined\") {\n    return;\n  }\n\n  const fullMsg = {\n    ...message,\n    source: \"liveblocks-devtools-client\",\n  };\n\n  if (!(options?.force || _bridgeActive)) {\n    /*\n    // eslint-disable-next-line rulesdir/console-must-be-fancy\n    console.log(\n      \"%c[client → panel] %c%s\",\n      \"color: green\",\n      \"color: gray; font-weight: bold\",\n      fullMsg.msg,\n      \"[🚫 NOT sent!]\"\n    );\n    */\n    return;\n  }\n  /*\n  // eslint-disable-next-line rulesdir/console-must-be-fancy\n  console.log(\n    \"%c[client → panel] %c%s\",\n    \"color: green\",\n    \"color: green; font-weight: bold\",\n    fullMsg.msg,\n    fullMsg\n  );*/\n  window.postMessage(fullMsg, \"*\");\n}\n\nconst eventSource = makeEventSource<DevTools.FullPanelToClientMessage>();\n\n// Define it as a no-op in production environments or when run outside of a browser context\nif (process.env.NODE_ENV !== \"production\" && typeof window !== \"undefined\") {\n  window.addEventListener(\"message\", (event: MessageEvent<unknown>) => {\n    if (\n      event.source === window &&\n      (event.data as Record<string, unknown>)?.source ===\n        \"liveblocks-devtools-panel\"\n    ) {\n      // console.log(\n      //   \"%c[client ← panel] %c%s\",\n      //   \"color: purple\",\n      //   \"color: purple; font-weight: bold\",\n      //   (event.data as Record<string, unknown>).msg,\n      //   event.data\n      // );\n      eventSource.notify(event.data as DevTools.FullPanelToClientMessage);\n    } else {\n      // Message not for us\n    }\n  });\n}\n\nexport const onMessageFromPanel = eventSource.observable;\n","import { kInternal } from \"../internal\";\nimport type { Json, JsonObject } from \"../lib/Json\";\nimport type { BaseUserMeta } from \"../protocol/BaseUserMeta\";\nimport type { UpdateYDocClientMsg } from \"../protocol/ClientMsg\";\nimport type { YDocUpdateServerMsg } from \"../protocol/ServerMsg\";\nimport type { OpaqueRoom, RoomEventMessage } from \"../room\";\nimport { PKG_VERSION } from \"../version\";\nimport { activateBridge, onMessageFromPanel, sendToPanel } from \"./bridge\";\n\nconst VERSION = PKG_VERSION || \"dev\";\nlet _devtoolsSetupHasRun = false;\n\n/**\n * Sends a wake up message to the devtools panel, if any such panel exists, and\n * listens for the initial connect message, which would be the trigger to start\n * emitting updates.\n *\n * Must be called before linkDevTools() can be used.\n *\n * Will only run once, even when called multiple times.\n */\nexport function setupDevTools(getAllRooms: () => string[]): void {\n  // Define it as a no-op in production environments or when run outside of a browser context\n  if (process.env.NODE_ENV === \"production\" || typeof window === \"undefined\") {\n    return;\n  }\n\n  if (_devtoolsSetupHasRun) {\n    // This setup code should only happen the first time\n    return;\n  }\n\n  _devtoolsSetupHasRun = true;\n\n  onMessageFromPanel.subscribe((msg) => {\n    switch (msg.msg) {\n      // When a devtool panel sends an explicit \"connect\" message back to this\n      // live running client (in response to the \"wake-up-devtools\" message,\n      // or when the devtool panel is opened for the first time), it means that it's okay to\n      // start emitting messages.\n      // Before this explicit acknowledgement, any call to sendToPanel() will\n      // be a no-op.\n      case \"connect\": {\n        // Allows future sendToPanel() messages to go through\n        activateBridge(true);\n\n        // Emit an explicit \"room::available\" message for every known room at\n        // this point. These can be used by the devpanel to subscribe to such\n        // room's updates.\n        for (const roomId of getAllRooms()) {\n          sendToPanel({\n            msg: \"room::available\",\n            roomId,\n            clientVersion: VERSION,\n          });\n        }\n\n        break;\n      }\n\n      // TODO: Later on, we can support explicit disconnects, too\n      // case \"disconnect\": {\n      //   // Make sendToPanel() no-ops again\n      //   activateBridge(false);\n      //   break;\n      // }\n    }\n  });\n\n  // Send initial wake up message, in case the devtool panel is already open!\n  sendToPanel({ msg: \"wake-up-devtools\" }, { force: true });\n}\n\nconst unsubsByRoomId = new Map<string, (() => void)[]>();\n\nfunction stopSyncStream(roomId: string): void {\n  const unsubs = unsubsByRoomId.get(roomId) ?? [];\n  unsubsByRoomId.delete(roomId); // Pop it off\n\n  for (const unsub of unsubs) {\n    // Cancel all of the subscriptions to room updates that are synchronizing\n    // partial state to the devtools panel\n    unsub();\n  }\n}\n\n/**\n * Starts, or restarts, the stream of sync messages for the given room. A sync\n * stream consists of an initial \"full sync\" message, followed by many\n * \"partial\" messages that happen whenever part of the room changes.\n */\nfunction startSyncStream(room: OpaqueRoom): void {\n  stopSyncStream(room.id);\n\n  // Sync the room ID instantly, as soon as we know it\n  fullSync(room);\n\n  unsubsByRoomId.set(room.id, [\n    // When the connection status changes\n    room.events.status.subscribe(() => partialSyncConnection(room)),\n\n    // When storage initializes, send the update\n    room.events.storageDidLoad.subscribeOnce(() => partialSyncStorage(room)),\n\n    // Any time storage updates, send the new storage root\n    room.events.storageBatch.subscribe(() => partialSyncStorage(room)),\n\n    // Any time \"me\" or \"others\" updates, send the new values accordingly\n    room.events.self.subscribe(() => partialSyncMe(room)),\n    room.events.others.subscribe(() => partialSyncOthers(room)),\n\n    // Any time ydoc is updated, forward the update\n    room.events.ydoc.subscribe((update) => syncYdocUpdate(room, update)),\n\n    // Any time a custom room event is received, forward it\n    room.events.customEvent.subscribe((eventData) =>\n      forwardEvent(room, eventData)\n    ),\n  ]);\n}\n\nfunction syncYdocUpdate(\n  room: OpaqueRoom,\n  update: YDocUpdateServerMsg | UpdateYDocClientMsg\n) {\n  sendToPanel({\n    msg: \"room::sync::ydoc\",\n    roomId: room.id,\n    update,\n  });\n}\n\nconst loadedAt = Date.now();\nlet eventCounter = 0;\n\nfunction nextEventId() {\n  return `event-${loadedAt}-${eventCounter++}`;\n}\n\nfunction forwardEvent(\n  room: OpaqueRoom,\n  eventData: RoomEventMessage<JsonObject, BaseUserMeta, Json>\n) {\n  sendToPanel({\n    msg: \"room::events::custom-event\",\n    roomId: room.id,\n    event: {\n      type: \"CustomEvent\",\n      id: nextEventId(),\n      key: \"Event\",\n      connectionId: eventData.connectionId,\n      payload: eventData.event,\n    },\n  });\n}\n\nfunction partialSyncConnection(room: OpaqueRoom) {\n  sendToPanel({\n    msg: \"room::sync::partial\",\n    roomId: room.id,\n    status: room.getStatus(),\n  });\n}\n\nfunction partialSyncStorage(room: OpaqueRoom) {\n  const root = room.getStorageSnapshot();\n  if (root) {\n    sendToPanel({\n      msg: \"room::sync::partial\",\n      roomId: room.id,\n      storage: root.toTreeNode(\"root\").payload,\n    });\n  }\n}\n\nfunction partialSyncMe(room: OpaqueRoom) {\n  const me = room[kInternal].getSelf_forDevTools();\n  if (me) {\n    sendToPanel({\n      msg: \"room::sync::partial\",\n      roomId: room.id,\n      me,\n    });\n  }\n}\n\nfunction partialSyncOthers(room: OpaqueRoom) {\n  // Any time others updates, send the new storage root to the dev panel\n  const others = room[kInternal].getOthers_forDevTools();\n  if (others) {\n    sendToPanel({\n      msg: \"room::sync::partial\",\n      roomId: room.id,\n      others,\n    });\n  }\n}\n\nfunction fullSync(room: OpaqueRoom) {\n  const root = room.getStorageSnapshot();\n  const me = room[kInternal].getSelf_forDevTools();\n  const others = room[kInternal].getOthers_forDevTools();\n  // Because the room doesn't have access to the YJS doc, we must tell it to go get the full doc\n  // sending an empty vector will return the whole document and then devtools will be up to date\n  room.fetchYDoc(\"\");\n  sendToPanel({\n    msg: \"room::sync::full\",\n    roomId: room.id,\n    status: room.getStatus(),\n    storage: root?.toTreeNode(\"root\").payload ?? null,\n    me,\n    others,\n  });\n}\n\n// Currently registered \"channel\" listeners, waiting for \"room::subscribe\" or\n// \"room::unsubscribe\" messages coming from the devtools panel\nconst roomChannelListeners = new Map<string, () => void>();\n\nfunction stopRoomChannelListener(roomId: string) {\n  const listener = roomChannelListeners.get(roomId);\n  roomChannelListeners.delete(roomId);\n  if (listener) {\n    listener();\n  }\n}\n\n/**\n * Publicly announce to the devtool panel that a new room is available.\n */\nexport function linkDevTools(roomId: string, room: OpaqueRoom): void {\n  // Define it as a no-op in production environments or when run outside of a browser context\n  if (process.env.NODE_ENV === \"production\" || typeof window === \"undefined\") {\n    return;\n  }\n  sendToPanel({ msg: \"room::available\", roomId, clientVersion: VERSION });\n\n  // Before adding a new listener, stop all active listeners, so there is only\n  // ever going to be one listener per room \"channel\"\n  stopRoomChannelListener(roomId);\n  roomChannelListeners.set(\n    roomId,\n\n    // Returns the unsubscribe callback, that we store in the\n    // roomChannelListeners registry\n    onMessageFromPanel.subscribe((msg) => {\n      switch (msg.msg) {\n        // Sent by the devtool panel when it wants to receive the sync stream\n        // for a room\n        case \"room::subscribe\": {\n          // Only act on this message if it's intended for this room\n          if (msg.roomId === roomId) {\n            startSyncStream(room);\n          }\n          break;\n        }\n\n        case \"room::unsubscribe\": {\n          // Only act on this message if it's intended for this room\n          if (msg.roomId === roomId) {\n            stopSyncStream(roomId);\n          }\n          break;\n        }\n      }\n    })\n  );\n}\n\nexport function unlinkDevTools(roomId: string): void {\n  // Define it as a no-op in production environments or when run outside of a browser context\n  if (process.env.NODE_ENV === \"production\" || typeof window === \"undefined\") {\n    return;\n  }\n\n  // Immediately stop the sync stream of room updates to the dev panel\n  stopSyncStream(roomId);\n\n  stopRoomChannelListener(roomId);\n\n  // Inform dev panel that this room is no longer available\n  sendToPanel({\n    msg: \"room::unavailable\",\n    roomId,\n  });\n}\n","import type { DAD } from \"../globals/augmentation\";\nimport * as console from \"../lib/fancy-console\";\nimport { create, entries, keys, values } from \"../lib/utils\";\n\n/**\n * Pre-defined notification channels support list.\n */\nexport type NotificationChannel = \"email\" | \"slack\" | \"teams\" | \"webPush\";\n\n/**\n * `K` represents custom notification kinds\n * defined in the augmentation `ActivitiesData` (e.g `liveblocks.config.ts`).\n * It means the type `NotificationKind` will be shaped like:\n * thread | textMention | $customKind1 | $customKind2 | ...\n */\nexport type NotificationKind<K extends keyof DAD = keyof DAD> =\n  | \"thread\"\n  | \"textMention\"\n  | K;\n\n/**\n * A notification channel settings is a set of notification kinds.\n * One setting can have multiple kinds (+ augmentation)\n */\nexport type NotificationChannelSettings = {\n  [K in NotificationKind]: boolean;\n};\n\n/**\n * @private\n *\n * Base definition of notification settings.\n * Plain means it's a simple object coming from the remote backend.\n *\n * It's the raw settings object where somme channels cannot exists\n * because there are no notification kinds enabled on the dashboard.\n * And this object isn't yet proxied by the creator factory `createNotificationSettings`.\n */\nexport type NotificationSettingsPlain = {\n  [C in NotificationChannel]?: NotificationChannelSettings;\n};\n\n/**\n * @internal\n *\n * Symbol to branch plain value of notification settings\n * inside the NotificationSettings object.\n */\nconst kPlain = Symbol(\"notification-settings-plain\");\n\n/**\n * @internal\n * Proxied `NotificationSettingsPlain` object.\n */\ntype ProxiedNotificationSettings = NotificationSettingsPlain;\n\n/**\n * Notification settings.\n * One channel for one set of settings.\n */\nexport type NotificationSettings = {\n  [C in NotificationChannel]: NotificationChannelSettings | null;\n};\n\n/**\n * It creates a deep partial specific for `NotificationSettings`\n * to offer a nice DX when updating the settings (e.g not being forced to define every keys)\n * and at the same the some preserver the augmentation for custom kinds (e.g `liveblocks.config.ts`).\n */\ntype DeepPartialWithAugmentation<T> = T extends object\n  ? {\n      [P in keyof T]?: T[P] extends { [K in NotificationKind]: boolean }\n        ? Partial<T[P]> & { [K in keyof DAD]?: boolean }\n        : DeepPartialWithAugmentation<T[P]>;\n    }\n  : T;\n\n/**\n * Partial notification settings with augmentation preserved gracefully.\n * It means you can update the settings without being forced to define every keys.\n * Useful when implementing update functions.\n */\nexport type PartialNotificationSettings =\n  DeepPartialWithAugmentation<NotificationSettingsPlain>;\n\n/**\n * @private\n *\n * Creates a `NotificationSettings` object with the given initial plain settings.\n * It defines a getter for each channel to access the settings and returns `null` with an error log\n * in case the required channel isn't enabled in the dashboard.\n *\n * You can see this function as `Proxy` like around `NotificationSettingsPlain` type.\n * We can't predict what will be enabled on the dashboard or not, so it's important\n * provide a good DX to developers by returning `null` completed by an error log\n * when they try to access a channel that isn't enabled in the dashboard.\n */\nexport function createNotificationSettings(\n  plain: NotificationSettingsPlain\n): NotificationSettings {\n  const channels: NotificationChannel[] = [\n    \"email\",\n    \"slack\",\n    \"teams\",\n    \"webPush\",\n  ];\n  const descriptors: PropertyDescriptorMap &\n    ThisType<NotificationSettings & { [kPlain]: ProxiedNotificationSettings }> =\n    {\n      [kPlain]: {\n        value: plain,\n        enumerable: false,\n      },\n    };\n\n  for (const channel of channels) {\n    descriptors[channel] = {\n      enumerable: true,\n      /**\n       * In the TypeScript standard library definitions, the built-in interface for a property descriptor\n       * does not include a specialized type for the “this” context in the getter or setter functions.\n       * As a result, both the ⁠get and ⁠set methods implicitly have ⁠this: any.\n       * The reason is that property descriptors in JavaScript are used across various objects with\n       * no enforced shape for ⁠this. And so the standard library definitions have to remain as broad as possible\n       * to support any valid JavaScript usage (e.g `Object.defineProperty`).\n       *\n       * So we can safely tells that this getter is typed as `this: NotificationSettings` because we're\n       * creating a well known shaped object → `NotificationSettings`.\n       */\n      get(\n        this: NotificationSettings & {\n          [kPlain]: ProxiedNotificationSettings;\n        }\n      ): NotificationChannelSettings | null {\n        const value = this[kPlain][channel];\n        if (typeof value === \"undefined\") {\n          console.error(\n            `In order to use the '${channel}' channel, please set up your project first. For more information: https://liveblocks.io/docs/errors/enable-a-notification-channel`\n          );\n          return null;\n        }\n        return value;\n      },\n    };\n  }\n\n  return create<NotificationSettings>(null, descriptors);\n}\n\n/**\n * @private\n *\n * Patch a `NotificationSettings` object by applying notification kind updates\n * coming from a `PartialNotificationSettings` object.\n */\nexport function patchNotificationSettings(\n  existing: NotificationSettings,\n  patch: PartialNotificationSettings\n): NotificationSettings {\n  // Create a copy of the settings object to mutate\n  const outcoming = createNotificationSettings({\n    ...(\n      existing as NotificationSettings & {\n        [kPlain]: ProxiedNotificationSettings;\n      }\n    )[kPlain],\n  });\n\n  for (const channel of keys(patch)) {\n    const updates = patch[channel];\n    if (updates !== undefined) {\n      const kindUpdates = Object.fromEntries(\n        entries(updates).filter(([, value]) => value !== undefined)\n      ) as NotificationChannelSettings; // Fine to type cast here because we've filtered out undefined values\n\n      (\n        outcoming as NotificationSettings & {\n          [kPlain]: ProxiedNotificationSettings;\n        }\n      )[kPlain][channel] = {\n        ...(\n          outcoming as NotificationSettings & {\n            [kPlain]: ProxiedNotificationSettings;\n          }\n        )[kPlain][channel],\n        ...kindUpdates,\n      };\n    }\n  }\n\n  return outcoming;\n}\n\n/**\n *\n * Utility to check if a notification channel settings\n * is enabled for every notification kinds.\n *\n * Usage:\n * ```ts\n * const isEmailChannelEnabled = isNotificationChannelEnabled(settings.email);\n * ```\n */\nexport function isNotificationChannelEnabled(\n  settings: NotificationChannelSettings | null\n): boolean {\n  return settings !== null\n    ? values(settings).every((enabled) => enabled === true)\n    : false;\n}\n","/**\n * Positions, aka the Pos type, are efficient encodings of \"positions\" in\n * a list, using the following printable subset of the ASCII alphabet:\n *\n *    !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\n *   ^                                                                                             ^\n *   Lowest digit                                                                      Highest digit\n *\n * Each Pos is a sequence of characters from the above alphabet, conceptually\n * codifying a floating point number 0 < n < 1. For example, the string \"31007\"\n * would be used to represent the number 0.31007, except that this\n * representation uses base 96.\n *\n *   0 ≃ ' '  (lowest digit)\n *   1 ≃ '!'\n *   2 ≃ '\"'\n *   ...\n *   9 ≃ '~'  (highest digit)\n *\n * So think:\n *   '!'    ≃ 0.1\n *   '\"'    ≃ 0.2\n *   '!\"~'  ≃ 0.129\n *\n * Three rules:\n * - All \"characters\" in the string should be valid digits (from the above\n *   alphabet)\n * - The value 0.0 is not a valid Pos value\n * - A Pos cannot have trailing \"zeroes\"\n *\n * This representation has the following benefits:\n *\n * 1. It's always possible to get a number that lies before, after, or between\n *    two arbitrary Pos values.\n * 2. Pos values can be compared using normal string comparison.\n *\n * Some examples:\n * - '!'  < '\"'   (like how .1  < .2)\n * - '!'  < '~'   (like how .1  < .9)\n * - '!!' < '!~'  (like how .11 < .19)\n * - '~!' < '~~'  (like how .91 < .99)\n * - '~'  < '~!'  (like how .9  < .91)\n * - '!!' < '!O'  (like how .1  < .5)\n * - '!O' < '!~'  (like how .5  < .9)\n *\n */\nimport type { Brand } from \"./utils\";\n\n/**\n * A valid/verified \"position\" string. These values are used as \"parentKey\"s by\n * LiveList children, and define their relative ordering.\n */\nexport type Pos = Brand<string, \"Pos\">;\n\nconst MIN_CODE = 32; // ASCII code of the lowest alphabet char (e.g. ' ')\nconst MAX_CODE = 126; // ASCII code of the highest alphabet char (e.g. '~')\n\nconst NUM_DIGITS = MAX_CODE - MIN_CODE + 1;\n\nconst ZERO: string = nthDigit(0); // \" \"\n\n/**\n * The \"first\" canonical position.\n * In an equivalent decimal number system, think of this as the value .1.\n */\nconst ONE: Pos = nthDigit(1); // \"!\"\n\nconst ZERO_NINE = (ZERO + nthDigit(-1)) as Pos;\n\n/**\n * Returns the Pos value for the nth digit in the alphabet.\n * Value must be between 0 and 94.\n *\n * Just used to generate some static data, and for usage in test cases.\n */\nfunction nthDigit(n: 0): string; // \"0\" is a legal _digit_, but not a legal Pos value\nfunction nthDigit(n: number): Pos;\nfunction nthDigit(n: number): Pos {\n  const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);\n  if (code < MIN_CODE || code > MAX_CODE) {\n    throw new Error(`Invalid n value: ${n}`);\n  }\n  return String.fromCharCode(code) as Pos;\n}\n\n/**\n * Given two positions, returns the position value that lies in the middle.\n * When given only a high bound, computes the canonical position \"before\" it.\n * When given only a low bound, computes the canonical position \"after\" it.\n * When given no bounds at all, returns the \"first\" canonical position.\n */\nfunction makePosition(x?: Pos, y?: Pos): Pos {\n  if (x !== undefined && y !== undefined) {\n    return between(x, y);\n  } else if (x !== undefined) {\n    return after(x);\n  } else if (y !== undefined) {\n    return before(y);\n  } else {\n    return ONE;\n  }\n}\n\n/**\n * Given any position value, computes the canonical position \"before\" it.\n *\n * The equivalent in a decimal number system would be:\n *   before(.1)     // .09\n *   before(.11)    // .1\n *   before(.111)   // .1\n *   before(.2)     // .1\n *   before(.23101) // .2\n *   before(.3)     // .2\n *   ...\n *   before(.8)     // .7\n *   before(.9)     // .8\n *   before(.91)    // .9\n *   before(.92)    // .9\n *   before(.93)    // .9\n *   ...\n *   before(.98)    // .9\n *   before(.99)    // .9\n *\n * Note:\n *   before(.01)    // .009\n *   before(.001)   // .0009\n *   before(.002)   // .001\n *   before(.00283) // .002\n *\n */\nfunction before(pos: Pos): Pos {\n  const lastIndex = pos.length - 1;\n  for (let i = 0; i <= lastIndex; i++) {\n    const code = pos.charCodeAt(i);\n\n    // Scan away all leading zeros, if there are any\n    if (code <= MIN_CODE) {\n      continue;\n    }\n\n    //\n    // Now, i points to the first non-zero digit\n    //\n    // Two options:\n    // 1. It's the last digit.\n    //    a. If it's a 1, it's on the edge. Replace with \"09\".\n    //    b. Otherwise, just lower it.\n    // 2. It's not the last digit, so we can just chop off the remainder.\n    //\n    if (i === lastIndex) {\n      if (code === MIN_CODE + 1) {\n        return (pos.substring(0, i) + ZERO_NINE) as Pos;\n      } else {\n        return (pos.substring(0, i) + String.fromCharCode(code - 1)) as Pos;\n      }\n    } else {\n      return pos.substring(0, i + 1) as Pos;\n    }\n  }\n\n  // If we end up here, it means the input consisted of only zeroes, which is\n  // invalid, so return the canonical first value as a best effort\n  return ONE;\n}\n\n/**\n * Given any position value, computes the canonical position \"after\" it.\n *\n * The equivalent in a decimal number system would be:\n *   after(.001)  // .1\n *   after(.1)    // .2\n *   after(.101)  // .2\n *   after(.2)    // .3\n *   after(.3)    // .4\n *   ...\n *   after(.8)    // .9\n *   after(.9)    // .91\n *   after(.91)   // .92\n *   after(.9123) // .92\n *   ...\n *   after(.98)   // .99\n *   after(.99)   // .991\n *   after(.9999) // .99991\n *\n */\nfunction after(pos: Pos): Pos {\n  for (let i = 0; i <= pos.length - 1; i++) {\n    const code = pos.charCodeAt(i);\n\n    // Scan away all leading \"nines\", if there are any\n    if (code >= MAX_CODE) {\n      continue;\n    }\n\n    // Now, i points to the first non-\"nine\" digit\n    return (pos.substring(0, i) + String.fromCharCode(code + 1)) as Pos;\n  }\n\n  // If we end up here, it means the input consisted of only \"nines\", means we\n  // can just append a ONE digit.\n  return (pos + ONE) as Pos;\n}\n\n/**\n * Given two positions, returns the position value that lies in the middle.\n *\n * Think:\n *   between('!', '%')  // '#'    (like how between(.1, .5) would be .3)\n *   between('!', '\"')  // '!O'   (like how between(.1, .2) would be .15)\n *\n *   between(.1, .3)      // .2\n *   between(.1, .4)      // also .2\n *   between(.1, .5)      // .3\n *   between(.11, .21)    // .15\n *   between(.1,  .1003)  // .1001\n *   between(.11, .12)    // .115\n *   between(.09, .1)     // .095\n *   between(.19, .21)    // .195\n *\n */\nfunction between(lo: Pos, hi: Pos): Pos {\n  if (lo < hi) {\n    return _between(lo, hi);\n  } else if (lo > hi) {\n    return _between(hi, lo);\n  } else {\n    throw new Error(\"Cannot compute value between two equal positions\");\n  }\n}\n\n/**\n * Like between(), but guaranteed that lo < hi.\n */\nfunction _between(lo: Pos, hi: Pos | \"\"): Pos {\n  let index = 0;\n\n  const loLen = lo.length;\n  const hiLen = hi.length;\n  while (true) {\n    const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;\n    const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;\n\n    if (loCode === hiCode) {\n      index++;\n      continue;\n    }\n\n    // Difference of only 1 means we'll have to settle this in the next digit\n    if (hiCode - loCode === 1) {\n      const size = index + 1;\n      let prefix = lo.substring(0, size);\n      if (prefix.length < size) {\n        prefix += ZERO.repeat(size - prefix.length);\n      }\n      const suffix = lo.substring(size) as Pos;\n      const nines = \"\"; // Will get interpreted like .999999…\n      return (prefix + _between(suffix, nines)) as Pos;\n    } else {\n      // Difference of more than 1 means we take the \"middle\" between these digits\n      return (takeN(lo, index) +\n        String.fromCharCode((hiCode + loCode) >> 1)) as Pos;\n    }\n  }\n}\n\nfunction takeN(pos: string, n: number): string {\n  return n < pos.length\n    ? pos.substring(0, n)\n    : pos + ZERO.repeat(n - pos.length);\n}\n\nconst MIN_NON_ZERO_CODE = MIN_CODE + 1;\n\n/**\n * Checks whether a given string is a valid Pos value. There are three rules:\n *\n *   - The string must not be the empty string\n *   - The string must not have any trailing \"zeroes\" (trailing \" \")\n *   - All characters in the string must be from our alphabet\n *\n */\nfunction isPos(str: string): str is Pos {\n  // May not be empty string\n  if (str === \"\") {\n    return false;\n  }\n\n  // Last digit may not be a \"0\" (no trailing zeroes)\n  const lastIdx = str.length - 1;\n  const last = str.charCodeAt(lastIdx);\n  if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {\n    return false;\n  }\n\n  for (let i = 0; i < lastIdx; i++) {\n    const code = str.charCodeAt(i);\n    if (code < MIN_CODE || code > MAX_CODE) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nfunction convertToPos(str: string): Pos {\n  const codes: number[] = [];\n\n  // All chars in the string must be in the min-max range\n  for (let i = 0; i < str.length; i++) {\n    const code = str.charCodeAt(i);\n\n    // Clamp to min-max range\n    codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);\n  }\n\n  // Strip all trailing zeros\n  while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {\n    codes.length--;\n  }\n\n  return codes.length > 0\n    ? (String.fromCharCode(...codes) as Pos)\n    : // Edge case: the str was a 0-only string, which is invalid. Default back to .1\n      ONE;\n}\n\n/**\n * Checks that a str is a valid Pos, and converts it to the nearest valid one\n * if not.\n */\nfunction asPos(str: string): Pos {\n  // Calling convertToPos(str) would suffice here, but since this is a hot code\n  // path, we prefer to just check, which is a lot faster.\n  return isPos(str) ? str : convertToPos(str);\n}\n\nexport { asPos, makePosition };\n\n// For use in unit tests only\nexport {\n  after as __after,\n  before as __before,\n  between as __between,\n  isPos as __isPos,\n  nthDigit as __nthDigit,\n  NUM_DIGITS as __NUM_DIGITS,\n};\n","import type { Json, JsonObject } from \"../lib/Json\";\n\nexport enum OpCode {\n  INIT = 0,\n  SET_PARENT_KEY = 1,\n  CREATE_LIST = 2,\n  UPDATE_OBJECT = 3,\n  CREATE_OBJECT = 4,\n  DELETE_CRDT = 5,\n  DELETE_OBJECT_KEY = 6,\n  CREATE_MAP = 7,\n  CREATE_REGISTER = 8,\n}\n\n/**\n * These operations are the payload for {@link UpdateStorageServerMsg} messages\n * only.\n */\nexport type Op =\n  | AckOp\n  | CreateOp\n  | UpdateObjectOp\n  | DeleteCrdtOp\n  | SetParentKeyOp // Only for lists!\n  | DeleteObjectKeyOp;\n\nexport type CreateOp =\n  | CreateObjectOp\n  | CreateRegisterOp\n  | CreateMapOp\n  | CreateListOp;\n\nexport type UpdateObjectOp = {\n  readonly opId?: string;\n  readonly id: string;\n  readonly type: OpCode.UPDATE_OBJECT;\n  readonly data: Partial<JsonObject>;\n};\n\nexport type CreateObjectOp = {\n  readonly opId?: string;\n  readonly id: string;\n  readonly intent?: \"set\";\n  readonly deletedId?: string;\n  readonly type: OpCode.CREATE_OBJECT;\n  readonly parentId: string;\n  readonly parentKey: string;\n  readonly data: JsonObject;\n};\n\nexport type CreateListOp = {\n  readonly opId?: string;\n  readonly id: string;\n  readonly intent?: \"set\";\n  readonly deletedId?: string;\n  readonly type: OpCode.CREATE_LIST;\n  readonly parentId: string;\n  readonly parentKey: string;\n};\n\nexport type CreateMapOp = {\n  readonly opId?: string;\n  readonly id: string;\n  readonly intent?: \"set\";\n  readonly deletedId?: string;\n  readonly type: OpCode.CREATE_MAP;\n  readonly parentId: string;\n  readonly parentKey: string;\n};\n\nexport type CreateRegisterOp = {\n  readonly opId?: string;\n  readonly id: string;\n  readonly intent?: \"set\";\n  readonly deletedId?: string;\n  readonly type: OpCode.CREATE_REGISTER;\n  readonly parentId: string;\n  readonly parentKey: string;\n  readonly data: Json;\n};\n\nexport type DeleteCrdtOp = {\n  readonly opId?: string;\n  readonly id: string;\n  readonly type: OpCode.DELETE_CRDT;\n};\n\n//\n// HACK:\n// Disguised as a \"DeleteCrdtOp\" for a nonexisting node \"ACK\", this Op that the\n// server may return to senders is effectively sent as a backward-compatible\n// way to trigger an acknowledgement for Ops that were seen by the server, but\n// deliberately ignored.\n//\nexport type AckOp = {\n  readonly type: OpCode.DELETE_CRDT; // Not a typo!\n  readonly id: \"ACK\";\n  readonly opId: string;\n};\n\n/**\n * Create an Op that can be used as an acknowledgement for the given opId, to\n * send back to the originating client in cases where the server decided to\n * ignore the Op and not forward it.\n *\n * Why?\n * It's important for the client to receive an acknowledgement for this, so\n * that it can correctly update its own unacknowledged Ops administration.\n * Otherwise it could get in \"synchronizing\" state indefinitely.\n *\n * CLEVER HACK\n * Introducing a new Op type for this would not be backward-compatible as\n * receiving such Op would crash old clients :(\n * So the clever backward-compatible hack pulled here is that we codify the\n * acknowledgement as a \"deletion Op\" for the non-existing node id \"ACK\". In\n * old clients such Op is accepted, but will effectively be a no-op as that\n * node does not exist, but as a side-effect the Op will get acknowledged.\n */\nexport function ackOp(opId: string): AckOp {\n  return {\n    type: OpCode.DELETE_CRDT,\n    id: \"ACK\", // (H)ACK\n    opId,\n  };\n}\n\nexport function isAckOp(op: Op): op is AckOp {\n  return op.type === OpCode.DELETE_CRDT && op.id === \"ACK\";\n}\n\nexport type SetParentKeyOp = {\n  readonly opId?: string;\n  readonly id: string;\n  readonly type: OpCode.SET_PARENT_KEY;\n  readonly parentKey: string;\n};\n\nexport type DeleteObjectKeyOp = {\n  readonly opId?: string;\n  readonly id: string;\n  readonly type: OpCode.DELETE_OBJECT_KEY;\n  readonly key: string;\n};\n","import { assertNever } from \"../lib/assert\";\nimport type { Pos } from \"../lib/position\";\nimport { asPos } from \"../lib/position\";\nimport type { CreateOp, Op } from \"../protocol/Op\";\nimport { OpCode } from \"../protocol/Op\";\nimport type { SerializedCrdt } from \"../protocol/SerializedCrdt\";\nimport type * as DevTools from \"../types/DevToolsTreeNode\";\nimport type { Immutable } from \"../types/Immutable\";\nimport type { LiveNode, Lson } from \"./Lson\";\nimport type { StorageUpdate } from \"./StorageUpdates\";\n\nexport type ApplyResult =\n  | { reverse: Op[]; modified: StorageUpdate }\n  | { modified: false };\n\n/**\n * The managed pool is a namespace registry (i.e. a context) that \"owns\" all\n * the individual live nodes, ensuring each one has a unique ID, and holding on\n * to live nodes before and after they are inter-connected.\n */\nexport interface ManagedPool {\n  readonly roomId: string;\n  readonly nodes: ReadonlyMap<string, LiveNode>;\n  readonly generateId: () => string;\n  readonly generateOpId: () => string;\n\n  readonly getNode: (id: string) => LiveNode | undefined;\n  readonly addNode: (id: string, node: LiveNode) => void;\n  readonly deleteNode: (id: string) => void;\n\n  /**\n   * Dispatching has three responsibilities:\n   * - Sends serialized ops to the WebSocket servers\n   * - Add reverse operations to the undo/redo stack\n   * - Notify room subscribers with updates (in-client, no networking)\n   */\n  dispatch: (\n    ops: Op[],\n    reverseOps: Op[],\n    storageUpdates: Map<string, StorageUpdate>\n  ) => void;\n\n  /**\n   * Ensures storage can be written to else throws an error.\n   * This is used to prevent writing to storage when the user does not have\n   * permission to do so.\n   * @throws {Error} if storage is not writable\n   * @returns {void}\n   */\n  assertStorageIsWritable: () => void;\n}\n\nexport type CreateManagedPoolOptions = {\n  /**\n   * Returns the current connection ID. This is used to generate unique\n   * prefixes for nodes created by this client. This number is allowed to\n   * change over time (for example, when the client reconnects).\n   */\n  getCurrentConnectionId(): number;\n\n  /**\n   * Will get invoked when any Live structure calls .dispatch() on the pool.\n   */\n  onDispatch?: (\n    ops: Op[],\n    reverse: Op[],\n    storageUpdates: Map<string, StorageUpdate>\n  ) => void;\n\n  /**\n   * Will get invoked when any Live structure calls .assertStorageIsWritable()\n   * on the pool. Defaults to true when not provided. Return false if you want\n   * to prevent writes to the pool locally early, because you know they won't\n   * have an effect upstream.\n   */\n  isStorageWritable?: () => boolean;\n};\n\n/**\n * @private Private API, never use this API directly.\n */\nexport function createManagedPool(\n  roomId: string,\n  options: CreateManagedPoolOptions\n): ManagedPool {\n  const {\n    getCurrentConnectionId,\n    onDispatch,\n    isStorageWritable = () => true,\n  } = options;\n\n  let clock = 0;\n  let opClock = 0;\n  const nodes = new Map<string, LiveNode>();\n\n  return {\n    roomId,\n    nodes,\n\n    getNode: (id: string) => nodes.get(id),\n    addNode: (id: string, node: LiveNode) => void nodes.set(id, node),\n    deleteNode: (id: string) => void nodes.delete(id),\n\n    generateId: () => `${getCurrentConnectionId()}:${clock++}`,\n    generateOpId: () => `${getCurrentConnectionId()}:${opClock++}`,\n\n    dispatch(\n      ops: Op[],\n      reverse: Op[],\n      storageUpdates: Map<string, StorageUpdate>\n    ) {\n      onDispatch?.(ops, reverse, storageUpdates);\n    },\n\n    assertStorageIsWritable: () => {\n      if (!isStorageWritable()) {\n        throw new Error(\n          \"Cannot write to storage with a read only user, please ensure the user has write permissions\"\n        );\n      }\n    },\n  };\n}\n\nexport enum OpSource {\n  UNDOREDO_RECONNECT,\n  REMOTE,\n  ACK,\n}\n\n// TODO Temporary helper to help convert from AbstractCrdt -> LiveNode, only\n// needed for within this module. The reason is that AbstractCrdt is an\n// _abstract_ type, and in our LiveNode union we exhaustively include all\n// concrete types.\n// TODO Remove me later, if we inline the abstract base methods in the concrete\n// classes.\nfunction crdtAsLiveNode(\n  value: AbstractCrdt // eslint-disable-line no-restricted-syntax\n): LiveNode {\n  return value as LiveNode;\n}\n\ntype HasParent = {\n  readonly type: \"HasParent\";\n  readonly node: LiveNode;\n  readonly key: string;\n\n  // Typically the same as `key`, but checked to be a valid Pos value (needed\n  // when used as item in a LiveList)\n  readonly pos: Pos;\n};\n\ntype NoParent = {\n  readonly type: \"NoParent\";\n};\n\ntype Orphaned = {\n  readonly type: \"Orphaned\";\n  readonly oldKey: string;\n\n  // Typically the same as `key`, but checked to be a valid Pos value (needed\n  // when used as item in a LiveList)\n  readonly oldPos: Pos;\n};\n\nfunction HasParent(\n  node: LiveNode,\n  key: string,\n  pos: Pos = asPos(key)\n): HasParent {\n  return Object.freeze({ type: \"HasParent\", node, key, pos });\n}\n\nconst NoParent: NoParent = Object.freeze({ type: \"NoParent\" });\n\nfunction Orphaned(oldKey: string, oldPos: Pos = asPos(oldKey)): Orphaned {\n  return Object.freeze({ type: \"Orphaned\", oldKey, oldPos });\n}\n\n/**\n * Represents the possible states of the parent field pointers.\n */\ntype ParentInfo =\n  // Both the parent node and the parent key are set. This is a normal child.\n  | HasParent\n\n  // Neither are set. This is either the root node (if attached to a document),\n  // or it's a dangling node that hasn't been attached yet.\n  | NoParent\n\n  // -------------------------------------------------------------------------\n  // TODO Refactor this state away!\n  // -------------------------------------------------------------------------\n  // Tricky case! This state is used after the node is detached from its\n  // parent, but we still need to retain the parent key that it was originally\n  // attached under. For example we rely on this to derive the reverse Op to\n  // add. We should be able to get rid of this case by structuring the code\n  // differently!\n  | Orphaned;\n\nexport abstract class AbstractCrdt {\n  //                  ^^^^^^^^^^^^ TODO: Make this an interface\n  #pool?: ManagedPool;\n  #id?: string;\n\n  #parent: ParentInfo = NoParent;\n\n  /** @internal */\n  _getParentKeyOrThrow(): string {\n    switch (this.parent.type) {\n      case \"HasParent\":\n        return this.parent.key;\n\n      case \"NoParent\":\n        throw new Error(\"Parent key is missing\");\n\n      case \"Orphaned\":\n        return this.parent.oldKey;\n\n      default:\n        return assertNever(this.parent, \"Unknown state\");\n    }\n  }\n\n  /** @internal */\n  get _parentPos(): Pos {\n    switch (this.parent.type) {\n      case \"HasParent\":\n        return this.parent.pos;\n\n      case \"NoParent\":\n        throw new Error(\"Parent key is missing\");\n\n      case \"Orphaned\":\n        return this.parent.oldPos;\n\n      default:\n        return assertNever(this.parent, \"Unknown state\");\n    }\n  }\n\n  /** @internal */\n  protected get _pool(): ManagedPool | undefined {\n    return this.#pool;\n  }\n\n  get roomId(): string | null {\n    return this.#pool ? this.#pool.roomId : null;\n  }\n\n  /** @internal */\n  get _id(): string | undefined {\n    return this.#id;\n  }\n\n  /** @internal */\n  get parent(): ParentInfo {\n    return this.#parent;\n  }\n\n  /** @internal */\n  get _parentKey(): string | null {\n    switch (this.parent.type) {\n      case \"HasParent\":\n        return this.parent.key;\n\n      case \"NoParent\":\n        return null;\n\n      case \"Orphaned\":\n        return this.parent.oldKey;\n\n      default:\n        return assertNever(this.parent, \"Unknown state\");\n    }\n  }\n\n  /** @internal */\n  _apply(op: Op, _isLocal: boolean): ApplyResult {\n    switch (op.type) {\n      case OpCode.DELETE_CRDT: {\n        if (this.parent.type === \"HasParent\") {\n          return this.parent.node._detachChild(crdtAsLiveNode(this));\n        }\n\n        return { modified: false };\n      }\n    }\n\n    return { modified: false };\n  }\n\n  /** @internal */\n  _setParentLink(newParentNode: LiveNode, newParentKey: string): void {\n    switch (this.parent.type) {\n      case \"HasParent\":\n        if (this.parent.node !== newParentNode) {\n          throw new Error(\"Cannot set parent: node already has a parent\");\n        } else {\n          // Ignore\n          this.#parent = HasParent(newParentNode, newParentKey);\n          return;\n        }\n\n      case \"Orphaned\":\n      case \"NoParent\": {\n        this.#parent = HasParent(newParentNode, newParentKey);\n        return;\n      }\n\n      default:\n        return assertNever(this.parent, \"Unknown state\");\n    }\n  }\n\n  /** @internal */\n  _attach(id: string, pool: ManagedPool): void {\n    if (this.#id || this.#pool) {\n      throw new Error(\"Cannot attach node: already attached\");\n    }\n\n    pool.addNode(id, crdtAsLiveNode(this));\n\n    this.#id = id;\n    this.#pool = pool;\n  }\n\n  /** @internal */\n  abstract _attachChild(op: CreateOp, source: OpSource): ApplyResult;\n\n  /** @internal */\n  _detach(): void {\n    if (this.#pool && this.#id) {\n      this.#pool.deleteNode(this.#id);\n    }\n\n    switch (this.parent.type) {\n      case \"HasParent\": {\n        this.#parent = Orphaned(this.parent.key, this.parent.pos);\n        break;\n      }\n\n      case \"NoParent\": {\n        this.#parent = NoParent;\n        break;\n      }\n\n      case \"Orphaned\": {\n        // No change needed\n        break;\n      }\n\n      default:\n        assertNever(this.parent, \"Unknown state\");\n    }\n\n    this.#pool = undefined;\n  }\n\n  /** @internal */\n  abstract _detachChild(crdt: LiveNode): ApplyResult;\n\n  /** @internal */\n  abstract _toOps(\n    parentId: string,\n    parentKey: string,\n    pool?: ManagedPool\n  ): CreateOp[];\n\n  /** @internal */\n  abstract _serialize(): SerializedCrdt;\n\n  /** This caches the result of the last .toImmutable() call for this Live node. */\n  #cachedImmutable?: Immutable;\n\n  #cachedTreeNodeKey?: string | number;\n  /** This caches the result of the last .toTreeNode() call for this Live node. */\n  #cachedTreeNode?: DevTools.LsonTreeNode;\n\n  /**\n   * @internal\n   *\n   * Clear the Immutable cache, so that the next call to `.toImmutable()` will\n   * recompute the equivalent Immutable value again.  Call this after every\n   * mutation to the Live node.\n   */\n  invalidate(): void {\n    if (\n      this.#cachedImmutable !== undefined ||\n      this.#cachedTreeNode !== undefined\n    ) {\n      this.#cachedImmutable = undefined;\n      this.#cachedTreeNode = undefined;\n\n      if (this.parent.type === \"HasParent\") {\n        this.parent.node.invalidate();\n      }\n    }\n  }\n\n  /** @internal */\n  abstract _toTreeNode(key: string): DevTools.LsonTreeNode;\n\n  /**\n   * @internal\n   *\n   * Return an snapshot of this Live tree for use in DevTools.\n   */\n  toTreeNode(key: string): DevTools.LsonTreeNode {\n    if (this.#cachedTreeNode === undefined || this.#cachedTreeNodeKey !== key) {\n      this.#cachedTreeNodeKey = key;\n      this.#cachedTreeNode = this._toTreeNode(key);\n    }\n\n    // Return cached version\n    return this.#cachedTreeNode;\n  }\n\n  /** @internal */\n  abstract _toImmutable(): Immutable;\n\n  /**\n   * Return an immutable snapshot of this Live node and its children.\n   */\n  toImmutable(): Immutable {\n    if (this.#cachedImmutable === undefined) {\n      this.#cachedImmutable = this._toImmutable();\n    }\n\n    // Return cached version\n    return this.#cachedImmutable;\n  }\n\n  /**\n   * Returns a deep clone of the current LiveStructure, suitable for insertion\n   * in the tree elsewhere.\n   */\n  abstract clone(): Lson;\n}\n","import type { Json, JsonObject } from \"../lib/Json\";\n\nexport type IdTuple<T> = [id: string, value: T];\n\nexport enum CrdtType {\n  OBJECT = 0,\n  LIST = 1,\n  MAP = 2,\n  REGISTER = 3,\n}\n\nexport type SerializedCrdt = SerializedRootObject | SerializedChild;\n\nexport type SerializedChild =\n  | SerializedObject\n  | SerializedList\n  | SerializedMap\n  | SerializedRegister;\n\nexport type SerializedRootObject = {\n  readonly type: CrdtType.OBJECT;\n  readonly data: JsonObject;\n\n  // Root objects don't have a parent relationship\n  readonly parentId?: never;\n  readonly parentKey?: never;\n};\n\nexport type SerializedObject = {\n  readonly type: CrdtType.OBJECT;\n  readonly parentId: string;\n  readonly parentKey: string;\n  readonly data: JsonObject;\n};\n\nexport type SerializedList = {\n  readonly type: CrdtType.LIST;\n  readonly parentId: string;\n  readonly parentKey: string;\n};\n\nexport type SerializedMap = {\n  readonly type: CrdtType.MAP;\n  readonly parentId: string;\n  readonly parentKey: string;\n};\n\nexport type SerializedRegister = {\n  readonly type: CrdtType.REGISTER;\n  readonly parentId: string;\n  readonly parentKey: string;\n  readonly data: Json;\n};\n\nexport function isRootCrdt(crdt: SerializedCrdt): crdt is SerializedRootObject {\n  return crdt.type === CrdtType.OBJECT && !isChildCrdt(crdt);\n}\n\nexport function isChildCrdt(crdt: SerializedCrdt): crdt is SerializedChild {\n  return crdt.parentId !== undefined && crdt.parentKey !== undefined;\n}\n","import type { LiveNode } from \"../crdts/Lson\";\nimport { nn } from \"../lib/assert\";\nimport type { Json } from \"../lib/Json\";\nimport { nanoid } from \"../lib/nanoid\";\nimport { deepClone } from \"../lib/utils\";\nimport type { CreateOp, CreateRegisterOp, Op } from \"../protocol/Op\";\nimport { OpCode } from \"../protocol/Op\";\nimport type { IdTuple, SerializedRegister } from \"../protocol/SerializedCrdt\";\nimport { CrdtType } from \"../protocol/SerializedCrdt\";\nimport type * as DevTools from \"../types/DevToolsTreeNode\";\nimport type { Immutable } from \"../types/Immutable\";\nimport type { ParentToChildNodeMap } from \"../types/NodeMap\";\nimport type { ApplyResult, ManagedPool } from \"./AbstractCrdt\";\nimport { AbstractCrdt } from \"./AbstractCrdt\";\n\n/**\n * INTERNAL\n */\nexport class LiveRegister<TValue extends Json> extends AbstractCrdt {\n  #data: TValue;\n\n  constructor(data: TValue) {\n    super();\n    this.#data = data;\n  }\n\n  get data(): TValue {\n    return this.#data;\n  }\n\n  /** @internal */\n  static _deserialize(\n    [id, item]: IdTuple<SerializedRegister>,\n    _parentToChildren: ParentToChildNodeMap,\n    pool: ManagedPool\n  ): LiveRegister<Json> {\n    const register = new LiveRegister(item.data);\n    register._attach(id, pool);\n    return register;\n  }\n\n  /** @internal */\n  _toOps(\n    parentId: string,\n    parentKey: string,\n    pool?: ManagedPool\n  ): CreateRegisterOp[] {\n    if (this._id === undefined) {\n      throw new Error(\n        \"Cannot serialize register if parentId or parentKey is undefined\"\n      );\n    }\n\n    return [\n      {\n        type: OpCode.CREATE_REGISTER,\n        opId: pool?.generateOpId(),\n        id: this._id,\n        parentId,\n        parentKey,\n        data: this.data,\n      },\n    ];\n  }\n\n  /** @internal */\n  _serialize(): SerializedRegister {\n    if (this.parent.type !== \"HasParent\") {\n      throw new Error(\"Cannot serialize LiveRegister if parent is missing\");\n    }\n\n    return {\n      type: CrdtType.REGISTER,\n      parentId: nn(this.parent.node._id, \"Parent node expected to have ID\"),\n      parentKey: this.parent.key,\n      data: this.data,\n    };\n  }\n\n  /** @internal */\n  _attachChild(_op: CreateOp): ApplyResult {\n    throw new Error(\"Method not implemented.\");\n  }\n\n  /** @internal */\n  _detachChild(_crdt: LiveNode): ApplyResult {\n    throw new Error(\"Method not implemented.\");\n  }\n\n  /** @internal */\n  _apply(op: Op, isLocal: boolean): ApplyResult {\n    return super._apply(op, isLocal);\n  }\n\n  /** @internal */\n  _toTreeNode(key: string): DevTools.LsonTreeNode {\n    return {\n      type: \"Json\",\n      id: this._id ?? nanoid(),\n      key,\n      payload: this.#data,\n    };\n  }\n\n  /** @internal */\n  _toImmutable(): Immutable {\n    return this.#data;\n  }\n\n  clone(): TValue {\n    return deepClone(this.data);\n  }\n}\n","import { nn } from \"../lib/assert\";\nimport { nanoid } from \"../lib/nanoid\";\nimport type { Pos } from \"../lib/position\";\nimport { asPos, makePosition } from \"../lib/position\";\nimport type { CreateListOp, CreateOp, Op } from \"../protocol/Op\";\nimport { OpCode } from \"../protocol/Op\";\nimport type { IdTuple, SerializedList } from \"../protocol/SerializedCrdt\";\nimport { CrdtType } from \"../protocol/SerializedCrdt\";\nimport type * as DevTools from \"../types/DevToolsTreeNode\";\nimport type { ParentToChildNodeMap } from \"../types/NodeMap\";\nimport type { ApplyResult, ManagedPool } from \"./AbstractCrdt\";\nimport { AbstractCrdt, OpSource } from \"./AbstractCrdt\";\nimport {\n  creationOpToLiveNode,\n  deserialize,\n  liveNodeToLson,\n  lsonToLiveNode,\n} from \"./liveblocks-helpers\";\nimport { LiveRegister } from \"./LiveRegister\";\nimport type { LiveNode, Lson } from \"./Lson\";\nimport type { ToImmutable } from \"./utils\";\n\nexport type LiveListUpdateDelta =\n  | { type: \"insert\"; index: number; item: Lson }\n  | { type: \"delete\"; index: number; deletedItem: Lson }\n  | { type: \"move\"; index: number; previousIndex: number; item: Lson }\n  | { type: \"set\"; index: number; item: Lson };\n\n/**\n * A LiveList notification that is sent in-client to any subscribers whenever\n * one or more of the items inside the LiveList instance have changed.\n */\nexport type LiveListUpdates<TItem extends Lson> = {\n  type: \"LiveList\";\n  node: LiveList<TItem>;\n  updates: LiveListUpdateDelta[];\n};\n\nfunction compareNodePosition(itemA: LiveNode, itemB: LiveNode) {\n  const posA = itemA._parentPos;\n  const posB = itemB._parentPos;\n  return posA === posB ? 0 : posA < posB ? -1 : 1;\n}\n\n/**\n * The LiveList class represents an ordered collection of items that is synchronized across clients.\n */\nexport class LiveList<TItem extends Lson> extends AbstractCrdt {\n  // TODO: Naive array at first, find a better data structure. Maybe an Order statistics tree?\n  #items: LiveNode[];\n  #implicitlyDeletedItems: WeakSet<LiveNode>;\n  #unacknowledgedSets: Map<string, string>;\n\n  constructor(items: TItem[]) {\n    super();\n    this.#items = [];\n    this.#implicitlyDeletedItems = new WeakSet();\n    this.#unacknowledgedSets = new Map();\n\n    let position = undefined;\n    for (const item of items) {\n      const newPosition = makePosition(position);\n      const node = lsonToLiveNode(item);\n      node._setParentLink(this, newPosition);\n      this.#items.push(node);\n      position = newPosition;\n    }\n  }\n\n  /** @internal */\n  static _deserialize(\n    [id]: IdTuple<SerializedList>,\n    parentToChildren: ParentToChildNodeMap,\n    pool: ManagedPool\n  ): LiveList<Lson> {\n    const list = new LiveList([]);\n    list._attach(id, pool);\n\n    const children = parentToChildren.get(id);\n    if (children === undefined) {\n      return list;\n    }\n\n    for (const [id, crdt] of children) {\n      const child = deserialize([id, crdt], parentToChildren, pool);\n\n      child._setParentLink(list, crdt.parentKey);\n      list._insertAndSort(child);\n    }\n\n    return list;\n  }\n\n  /**\n   * @internal\n   * This function assumes that the resulting ops will be sent to the server if they have an 'opId'\n   * so we mutate _unacknowledgedSets to avoid potential flickering\n   * https://github.com/liveblocks/liveblocks/pull/1177\n   *\n   * This is quite unintuitive and should disappear as soon as\n   * we introduce an explicit LiveList.Set operation\n   */\n  _toOps(parentId: string, parentKey: string, pool?: ManagedPool): CreateOp[] {\n    if (this._id === undefined) {\n      throw new Error(\"Cannot serialize item is not attached\");\n    }\n\n    const ops: CreateOp[] = [];\n    const op: CreateListOp = {\n      id: this._id,\n      opId: pool?.generateOpId(),\n      type: OpCode.CREATE_LIST,\n      parentId,\n      parentKey,\n    };\n\n    ops.push(op);\n\n    for (const item of this.#items) {\n      const parentKey = item._getParentKeyOrThrow();\n      const childOps = HACK_addIntentAndDeletedIdToOperation(\n        item._toOps(this._id, parentKey, pool),\n        undefined\n      );\n      const childOpId = childOps[0].opId;\n      if (childOpId !== undefined) {\n        this.#unacknowledgedSets.set(parentKey, childOpId);\n      }\n      ops.push(...childOps);\n    }\n\n    return ops;\n  }\n\n  /**\n   * @internal\n   *\n   * Adds a new item into the sorted list, in the correct position.\n   */\n  _insertAndSort(item: LiveNode): void {\n    this.#items.push(item);\n    this._sortItems();\n  }\n\n  /** @internal */\n  _sortItems(): void {\n    this.#items.sort(compareNodePosition);\n    this.invalidate();\n  }\n\n  /** @internal */\n  _indexOfPosition(position: string): number {\n    return this.#items.findIndex(\n      (item) => item._getParentKeyOrThrow() === position\n    );\n  }\n\n  /** @internal */\n  _attach(id: string, pool: ManagedPool): void {\n    super._attach(id, pool);\n\n    for (const item of this.#items) {\n      item._attach(pool.generateId(), pool);\n    }\n  }\n\n  /** @internal */\n  _detach(): void {\n    super._detach();\n\n    for (const item of this.#items) {\n      item._detach();\n    }\n  }\n\n  #applySetRemote(op: CreateOp): ApplyResult {\n    if (this._pool === undefined) {\n      throw new Error(\"Can't attach child if managed pool is not present\");\n    }\n\n    const { id, parentKey: key } = op;\n    const child = creationOpToLiveNode(op);\n    child._attach(id, this._pool);\n    child._setParentLink(this, key);\n\n    const deletedId = op.deletedId;\n\n    const indexOfItemWithSamePosition = this._indexOfPosition(key);\n\n    // If there is already an item at this position\n    if (indexOfItemWithSamePosition !== -1) {\n      const itemWithSamePosition = this.#items[indexOfItemWithSamePosition];\n\n      // No conflict, the item that is being replaced is the same that was deleted on the sender\n      if (itemWithSamePosition._id === deletedId) {\n        itemWithSamePosition._detach();\n\n        // Replace the existing item with the newly created item without sorting the list\n        this.#items[indexOfItemWithSamePosition] = child;\n\n        return {\n          modified: makeUpdate(this, [\n            setDelta(indexOfItemWithSamePosition, child),\n          ]),\n          reverse: [],\n        };\n      } else {\n        // item at position to be replaced is different from server, so we put in a cache\n        // This scenario can happen if an other item has been put at this position\n        // while getting the acknowledgement of the set (move, insert or set)\n        this.#implicitlyDeletedItems.add(itemWithSamePosition);\n\n        // Replace the existing item with the newly created item without sorting the list\n        this.#items[indexOfItemWithSamePosition] = child;\n\n        const delta: LiveListUpdateDelta[] = [\n          setDelta(indexOfItemWithSamePosition, child),\n        ];\n\n        // Even if we implicitly delete the item at the set position\n        // We still need to delete the item that was orginaly deleted by the set\n        const deleteDelta = this.#detachItemAssociatedToSetOperation(\n          op.deletedId\n        );\n\n        if (deleteDelta) {\n          delta.push(deleteDelta);\n        }\n\n        return {\n          modified: makeUpdate(this, delta),\n          reverse: [],\n        };\n      }\n    } else {\n      // Item at position to be replaced doesn't exist\n      const updates: LiveListUpdateDelta[] = [];\n      const deleteDelta = this.#detachItemAssociatedToSetOperation(\n        op.deletedId\n      );\n      if (deleteDelta) {\n        updates.push(deleteDelta);\n      }\n\n      this._insertAndSort(child);\n\n      updates.push(insertDelta(this._indexOfPosition(key), child));\n\n      return {\n        reverse: [],\n        modified: makeUpdate(this, updates),\n      };\n    }\n  }\n\n  #applySetAck(op: CreateOp): ApplyResult {\n    if (this._pool === undefined) {\n      throw new Error(\"Can't attach child if managed pool is not present\");\n    }\n\n    const delta: LiveListUpdateDelta[] = [];\n\n    // Deleted item can be re-inserted by remote undo/redo\n    const deletedDelta = this.#detachItemAssociatedToSetOperation(op.deletedId);\n    if (deletedDelta) {\n      delta.push(deletedDelta);\n    }\n\n    const unacknowledgedOpId = this.#unacknowledgedSets.get(op.parentKey);\n\n    if (unacknowledgedOpId !== undefined) {\n      if (unacknowledgedOpId !== op.opId) {\n        return delta.length === 0\n          ? { modified: false }\n          : { modified: makeUpdate(this, delta), reverse: [] };\n      } else {\n        this.#unacknowledgedSets.delete(op.parentKey);\n      }\n    }\n\n    const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);\n\n    const existingItem = this.#items.find((item) => item._id === op.id);\n\n    // If item already exists...\n    if (existingItem !== undefined) {\n      // ...and if it's at the right position\n      if (existingItem._parentKey === op.parentKey) {\n        // ... do nothing\n        return {\n          modified: delta.length > 0 ? makeUpdate(this, delta) : false,\n          reverse: [],\n        };\n      }\n\n      // Item exists but not at the right position (local move after set)\n      if (indexOfItemWithSamePosition !== -1) {\n        this.#implicitlyDeletedItems.add(\n          this.#items[indexOfItemWithSamePosition]\n        );\n        const [prevNode] = this.#items.splice(indexOfItemWithSamePosition, 1);\n        delta.push(deleteDelta(indexOfItemWithSamePosition, prevNode));\n      }\n\n      const prevIndex = this.#items.indexOf(existingItem);\n\n      existingItem._setParentLink(this, op.parentKey);\n      this._sortItems();\n\n      const newIndex = this.#items.indexOf(existingItem);\n      if (newIndex !== prevIndex) {\n        delta.push(moveDelta(prevIndex, newIndex, existingItem));\n      }\n\n      return {\n        modified: delta.length > 0 ? makeUpdate(this, delta) : false,\n        reverse: [],\n      };\n    } else {\n      // Item associated to the set ack does not exist either deleted localy or via remote undo/redo\n      const orphan = this._pool.getNode(op.id);\n      if (orphan && this.#implicitlyDeletedItems.has(orphan)) {\n        // Reattach orphan at the new position\n        orphan._setParentLink(this, op.parentKey);\n        // And delete it from the orphan cache\n        this.#implicitlyDeletedItems.delete(orphan);\n\n        this._insertAndSort(orphan);\n\n        const recreatedItemIndex = this.#items.indexOf(orphan);\n\n        return {\n          modified: makeUpdate(this, [\n            // If there is an item at this position, update is a set, else it's an insert\n            indexOfItemWithSamePosition === -1\n              ? insertDelta(recreatedItemIndex, orphan)\n              : setDelta(recreatedItemIndex, orphan),\n            ...delta,\n          ]),\n          reverse: [],\n        };\n      } else {\n        if (indexOfItemWithSamePosition !== -1) {\n          this.#items.splice(indexOfItemWithSamePosition, 1);\n        }\n\n        const { newItem, newIndex } = this.#createAttachItemAndSort(\n          op,\n          op.parentKey\n        );\n\n        return {\n          modified: makeUpdate(this, [\n            // If there is an item at this position, update is a set, else it's an insert\n            indexOfItemWithSamePosition === -1\n              ? insertDelta(newIndex, newItem)\n              : setDelta(newIndex, newItem),\n            ...delta,\n          ]),\n          reverse: [],\n        };\n      }\n    }\n  }\n\n  /**\n   * Returns the update delta of the deletion or null\n   */\n  #detachItemAssociatedToSetOperation(\n    deletedId?: string\n  ): LiveListUpdateDelta | null {\n    if (deletedId === undefined || this._pool === undefined) {\n      return null;\n    }\n\n    const deletedItem = this._pool.getNode(deletedId);\n    if (deletedItem === undefined) {\n      return null;\n    }\n\n    const result = this._detachChild(deletedItem);\n    if (result.modified === false) {\n      return null;\n    }\n\n    return result.modified.updates[0];\n  }\n\n  #applyRemoteInsert(op: CreateOp): ApplyResult {\n    if (this._pool === undefined) {\n      throw new Error(\"Can't attach child if managed pool is not present\");\n    }\n\n    const key = asPos(op.parentKey);\n\n    const existingItemIndex = this._indexOfPosition(key);\n\n    if (existingItemIndex !== -1) {\n      // If change is remote => assign a temporary position to existing child until we get the fix from the backend\n      this.#shiftItemPosition(existingItemIndex, key);\n    }\n\n    const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);\n\n    // TODO: add move update?\n    return {\n      modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),\n      reverse: [],\n    };\n  }\n\n  #applyInsertAck(op: CreateOp): ApplyResult {\n    const existingItem = this.#items.find((item) => item._id === op.id);\n    const key = asPos(op.parentKey);\n\n    const itemIndexAtPosition = this._indexOfPosition(key);\n\n    if (existingItem) {\n      if (existingItem._parentKey === key) {\n        // Normal case, no modification\n        return {\n          modified: false,\n        };\n      } else {\n        const oldPositionIndex = this.#items.indexOf(existingItem);\n        if (itemIndexAtPosition !== -1) {\n          this.#shiftItemPosition(itemIndexAtPosition, key);\n        }\n\n        existingItem._setParentLink(this, key);\n        this._sortItems();\n\n        const newIndex = this._indexOfPosition(key);\n\n        if (newIndex === oldPositionIndex) {\n          return { modified: false };\n        }\n\n        return {\n          modified: makeUpdate(this, [\n            moveDelta(oldPositionIndex, newIndex, existingItem),\n          ]),\n          reverse: [],\n        };\n      }\n    } else {\n      const orphan = nn(this._pool).getNode(op.id);\n      if (orphan && this.#implicitlyDeletedItems.has(orphan)) {\n        // Implicit delete after set\n        orphan._setParentLink(this, key);\n        this.#implicitlyDeletedItems.delete(orphan);\n\n        this._insertAndSort(orphan);\n\n        const newIndex = this._indexOfPosition(key);\n\n        return {\n          modified: makeUpdate(this, [insertDelta(newIndex, orphan)]),\n          reverse: [],\n        };\n      } else {\n        if (itemIndexAtPosition !== -1) {\n          this.#shiftItemPosition(itemIndexAtPosition, key);\n        }\n\n        const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);\n\n        return {\n          modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),\n          reverse: [],\n        };\n      }\n    }\n  }\n\n  #applyInsertUndoRedo(op: CreateOp): ApplyResult {\n    const { id, parentKey: key } = op;\n    const child = creationOpToLiveNode(op);\n\n    if (this._pool?.getNode(id) !== undefined) {\n      return { modified: false };\n    }\n\n    child._attach(id, nn(this._pool));\n    child._setParentLink(this, key);\n\n    const existingItemIndex = this._indexOfPosition(key);\n\n    let newKey = key;\n\n    if (existingItemIndex !== -1) {\n      const before = this.#items[existingItemIndex]?._parentPos;\n      const after = this.#items[existingItemIndex + 1]?._parentPos;\n\n      newKey = makePosition(before, after);\n      child._setParentLink(this, newKey);\n    }\n\n    this._insertAndSort(child);\n\n    const newIndex = this._indexOfPosition(newKey);\n\n    return {\n      modified: makeUpdate(this, [insertDelta(newIndex, child)]),\n      reverse: [{ type: OpCode.DELETE_CRDT, id }],\n    };\n  }\n\n  #applySetUndoRedo(op: CreateOp): ApplyResult {\n    const { id, parentKey: key } = op;\n    const child = creationOpToLiveNode(op);\n\n    if (this._pool?.getNode(id) !== undefined) {\n      return { modified: false };\n    }\n\n    this.#unacknowledgedSets.set(key, nn(op.opId));\n\n    const indexOfItemWithSameKey = this._indexOfPosition(key);\n\n    child._attach(id, nn(this._pool));\n    child._setParentLink(this, key);\n\n    const newKey = key;\n\n    // If there is already an item at this position\n    if (indexOfItemWithSameKey !== -1) {\n      // TODO: Should we add this item to implictly deleted item?\n      const existingItem = this.#items[indexOfItemWithSameKey];\n      existingItem._detach();\n\n      this.#items[indexOfItemWithSameKey] = child;\n\n      const reverse = HACK_addIntentAndDeletedIdToOperation(\n        existingItem._toOps(nn(this._id), key, this._pool),\n        op.id\n      );\n\n      const delta = [setDelta(indexOfItemWithSameKey, child)];\n      const deletedDelta = this.#detachItemAssociatedToSetOperation(\n        op.deletedId\n      );\n      if (deletedDelta) {\n        delta.push(deletedDelta);\n      }\n\n      return {\n        modified: makeUpdate(this, delta),\n        reverse,\n      };\n    } else {\n      this._insertAndSort(child);\n\n      // TODO: Use delta\n      this.#detachItemAssociatedToSetOperation(op.deletedId);\n\n      const newIndex = this._indexOfPosition(newKey);\n\n      return {\n        reverse: [{ type: OpCode.DELETE_CRDT, id }],\n        modified: makeUpdate(this, [insertDelta(newIndex, child)]),\n      };\n    }\n  }\n\n  /** @internal */\n  _attachChild(op: CreateOp, source: OpSource): ApplyResult {\n    if (this._pool === undefined) {\n      throw new Error(\"Can't attach child if managed pool is not present\");\n    }\n\n    let result: ApplyResult;\n\n    if (op.intent === \"set\") {\n      if (source === OpSource.REMOTE) {\n        result = this.#applySetRemote(op);\n      } else if (source === OpSource.ACK) {\n        result = this.#applySetAck(op);\n      } else {\n        result = this.#applySetUndoRedo(op);\n      }\n    } else {\n      if (source === OpSource.REMOTE) {\n        result = this.#applyRemoteInsert(op);\n      } else if (source === OpSource.ACK) {\n        result = this.#applyInsertAck(op);\n      } else {\n        result = this.#applyInsertUndoRedo(op);\n      }\n    }\n\n    if (result.modified !== false) {\n      this.invalidate();\n    }\n\n    return result;\n  }\n\n  /** @internal */\n  _detachChild(\n    child: LiveNode\n  ): { reverse: Op[]; modified: LiveListUpdates<TItem> } | { modified: false } {\n    if (child) {\n      const parentKey = nn(child._parentKey);\n      const reverse = child._toOps(nn(this._id), parentKey, this._pool);\n\n      const indexToDelete = this.#items.indexOf(child);\n\n      if (indexToDelete === -1) {\n        return {\n          modified: false,\n        };\n      }\n\n      const [previousNode] = this.#items.splice(indexToDelete, 1);\n      this.invalidate();\n\n      child._detach();\n\n      return {\n        modified: makeUpdate(this, [deleteDelta(indexToDelete, previousNode)]),\n        reverse,\n      };\n    }\n\n    return { modified: false };\n  }\n\n  #applySetChildKeyRemote(newKey: Pos, child: LiveNode): ApplyResult {\n    if (this.#implicitlyDeletedItems.has(child)) {\n      this.#implicitlyDeletedItems.delete(child);\n\n      child._setParentLink(this, newKey);\n      this._insertAndSort(child);\n\n      const newIndex = this.#items.indexOf(child);\n\n      // TODO: Shift existing item?\n\n      return {\n        modified: makeUpdate(this, [insertDelta(newIndex, child)]),\n        reverse: [],\n      };\n    }\n\n    const previousKey = child._parentKey;\n\n    if (newKey === previousKey) {\n      return {\n        modified: false,\n      };\n    }\n\n    // TODO: should we look at orphan\n    const existingItemIndex = this._indexOfPosition(newKey);\n\n    // Normal case\n    if (existingItemIndex === -1) {\n      const previousIndex = this.#items.indexOf(child);\n      child._setParentLink(this, newKey);\n      this._sortItems();\n      const newIndex = this.#items.indexOf(child);\n\n      if (newIndex === previousIndex) {\n        return {\n          modified: false,\n        };\n      }\n\n      return {\n        modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),\n        reverse: [],\n      };\n    } else {\n      this.#items[existingItemIndex]._setParentLink(\n        this,\n        makePosition(newKey, this.#items[existingItemIndex + 1]?._parentPos)\n      );\n\n      const previousIndex = this.#items.indexOf(child);\n      child._setParentLink(this, newKey);\n      this._sortItems();\n      const newIndex = this.#items.indexOf(child);\n\n      if (newIndex === previousIndex) {\n        return {\n          modified: false,\n        };\n      }\n\n      return {\n        modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),\n        reverse: [],\n      };\n    }\n  }\n\n  #applySetChildKeyAck(newKey: Pos, child: LiveNode): ApplyResult {\n    const previousKey = nn(child._parentKey);\n\n    if (this.#implicitlyDeletedItems.has(child)) {\n      const existingItemIndex = this._indexOfPosition(newKey);\n\n      this.#implicitlyDeletedItems.delete(child);\n\n      if (existingItemIndex !== -1) {\n        this.#items[existingItemIndex]._setParentLink(\n          this,\n          makePosition(newKey, this.#items[existingItemIndex + 1]?._parentPos)\n        );\n      }\n\n      child._setParentLink(this, newKey);\n      this._insertAndSort(child);\n\n      // TODO\n      return {\n        modified: false,\n      };\n    } else {\n      if (newKey === previousKey) {\n        return {\n          modified: false,\n        };\n      }\n\n      // At this point, it means that the item has been moved before receiving the ack\n      // so we replace it at the right position\n\n      const previousIndex = this.#items.indexOf(child);\n\n      const existingItemIndex = this._indexOfPosition(newKey);\n\n      if (existingItemIndex !== -1) {\n        this.#items[existingItemIndex]._setParentLink(\n          this,\n          makePosition(newKey, this.#items[existingItemIndex + 1]?._parentPos)\n        );\n      }\n\n      child._setParentLink(this, newKey);\n      this._sortItems();\n\n      const newIndex = this.#items.indexOf(child);\n\n      if (previousIndex === newIndex) {\n        // parentKey changed but final position in the list didn't\n        return {\n          modified: false,\n        };\n      } else {\n        return {\n          modified: makeUpdate(this, [\n            moveDelta(previousIndex, newIndex, child),\n          ]),\n          reverse: [],\n        };\n      }\n    }\n  }\n\n  #applySetChildKeyUndoRedo(newKey: Pos, child: LiveNode): ApplyResult {\n    const previousKey = nn(child._parentKey);\n\n    const previousIndex = this.#items.indexOf(child);\n    const existingItemIndex = this._indexOfPosition(newKey);\n\n    // Assign a temporary position until we get the fix from the backend\n    if (existingItemIndex !== -1) {\n      this.#items[existingItemIndex]._setParentLink(\n        this,\n        makePosition(newKey, this.#items[existingItemIndex + 1]?._parentPos)\n      );\n    }\n\n    child._setParentLink(this, newKey);\n\n    this._sortItems();\n\n    const newIndex = this.#items.indexOf(child);\n\n    if (previousIndex === newIndex) {\n      return {\n        modified: false,\n      };\n    }\n\n    return {\n      modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),\n      reverse: [\n        {\n          type: OpCode.SET_PARENT_KEY,\n          id: nn(child._id),\n          parentKey: previousKey,\n        },\n      ],\n    };\n  }\n\n  /** @internal */\n  _setChildKey(newKey: Pos, child: LiveNode, source: OpSource): ApplyResult {\n    if (source === OpSource.REMOTE) {\n      return this.#applySetChildKeyRemote(newKey, child);\n    } else if (source === OpSource.ACK) {\n      return this.#applySetChildKeyAck(newKey, child);\n    } else {\n      return this.#applySetChildKeyUndoRedo(newKey, child);\n    }\n  }\n\n  /** @internal */\n  _apply(op: Op, isLocal: boolean): ApplyResult {\n    return super._apply(op, isLocal);\n  }\n\n  /** @internal */\n  _serialize(): SerializedList {\n    if (this.parent.type !== \"HasParent\") {\n      throw new Error(\"Cannot serialize LiveList if parent is missing\");\n    }\n\n    return {\n      type: CrdtType.LIST,\n      parentId: nn(this.parent.node._id, \"Parent node expected to have ID\"),\n      parentKey: this.parent.key,\n    };\n  }\n\n  /**\n   * Returns the number of elements.\n   */\n  get length(): number {\n    return this.#items.length;\n  }\n\n  /**\n   * Adds one element to the end of the LiveList.\n   * @param element The element to add to the end of the LiveList.\n   */\n  push(element: TItem): void {\n    this._pool?.assertStorageIsWritable();\n    return this.insert(element, this.length);\n  }\n\n  /**\n   * Inserts one element at a specified index.\n   * @param element The element to insert.\n   * @param index The index at which you want to insert the element.\n   */\n  insert(element: TItem, index: number): void {\n    this._pool?.assertStorageIsWritable();\n    if (index < 0 || index > this.#items.length) {\n      throw new Error(\n        `Cannot insert list item at index \"\u001d${index}\". index should be between 0 and ${this.#items.length}`\n      );\n    }\n\n    const before = this.#items[index - 1]\n      ? this.#items[index - 1]._parentPos\n      : undefined;\n    const after = this.#items[index]\n      ? this.#items[index]._parentPos\n      : undefined;\n\n    const position = makePosition(before, after);\n\n    const value = lsonToLiveNode(element);\n    value._setParentLink(this, position);\n\n    this._insertAndSort(value);\n\n    if (this._pool && this._id) {\n      const id = this._pool.generateId();\n      value._attach(id, this._pool);\n\n      this._pool.dispatch(\n        value._toOps(this._id, position, this._pool),\n        [{ type: OpCode.DELETE_CRDT, id }],\n        new Map<string, LiveListUpdates<TItem>>([\n          [this._id, makeUpdate(this, [insertDelta(index, value)])],\n        ])\n      );\n    }\n  }\n\n  /**\n   * Move one element from one index to another.\n   * @param index The index of the element to move\n   * @param targetIndex The index where the element should be after moving.\n   */\n  move(index: number, targetIndex: number): void {\n    this._pool?.assertStorageIsWritable();\n    if (targetIndex < 0) {\n      throw new Error(\"targetIndex cannot be less than 0\");\n    }\n\n    if (targetIndex >= this.#items.length) {\n      throw new Error(\n        \"targetIndex cannot be greater or equal than the list length\"\n      );\n    }\n\n    if (index < 0) {\n      throw new Error(\"index cannot be less than 0\");\n    }\n\n    if (index >= this.#items.length) {\n      throw new Error(\"index cannot be greater or equal than the list length\");\n    }\n\n    let beforePosition = null;\n    let afterPosition = null;\n\n    if (index < targetIndex) {\n      afterPosition =\n        targetIndex === this.#items.length - 1\n          ? undefined\n          : this.#items[targetIndex + 1]._parentPos;\n      beforePosition = this.#items[targetIndex]._parentPos;\n    } else {\n      afterPosition = this.#items[targetIndex]._parentPos;\n      beforePosition =\n        targetIndex === 0 ? undefined : this.#items[targetIndex - 1]._parentPos;\n    }\n\n    const position = makePosition(beforePosition, afterPosition);\n\n    const item = this.#items[index];\n    const previousPosition = item._getParentKeyOrThrow();\n    item._setParentLink(this, position);\n    this._sortItems();\n\n    if (this._pool && this._id) {\n      const storageUpdates = new Map<string, LiveListUpdates<TItem>>([\n        [this._id, makeUpdate(this, [moveDelta(index, targetIndex, item)])],\n      ]);\n\n      this._pool.dispatch(\n        [\n          {\n            type: OpCode.SET_PARENT_KEY,\n            id: nn(item._id),\n            opId: this._pool.generateOpId(),\n            parentKey: position,\n          },\n        ],\n        [\n          {\n            type: OpCode.SET_PARENT_KEY,\n            id: nn(item._id),\n            parentKey: previousPosition,\n          },\n        ],\n        storageUpdates\n      );\n    }\n  }\n\n  /**\n   * Deletes an element at the specified index\n   * @param index The index of the element to delete\n   */\n  delete(index: number): void {\n    this._pool?.assertStorageIsWritable();\n    if (index < 0 || index >= this.#items.length) {\n      throw new Error(\n        `Cannot delete list item at index \"${index}\". index should be between 0 and ${\n          this.#items.length - 1\n        }`\n      );\n    }\n\n    const item = this.#items[index];\n    item._detach();\n    const [prev] = this.#items.splice(index, 1);\n    this.invalidate();\n\n    if (this._pool) {\n      const childRecordId = item._id;\n      if (childRecordId) {\n        const storageUpdates = new Map<string, LiveListUpdates<TItem>>();\n        storageUpdates.set(\n          nn(this._id),\n          makeUpdate(this, [deleteDelta(index, prev)])\n        );\n\n        this._pool.dispatch(\n          [\n            {\n              id: childRecordId,\n              opId: this._pool.generateOpId(),\n              type: OpCode.DELETE_CRDT,\n            },\n          ],\n          item._toOps(nn(this._id), item._getParentKeyOrThrow()),\n          storageUpdates\n        );\n      }\n    }\n  }\n\n  clear(): void {\n    this._pool?.assertStorageIsWritable();\n    if (this._pool) {\n      const ops: Op[] = [];\n      const reverseOps: Op[] = [];\n\n      const updateDelta: LiveListUpdateDelta[] = [];\n\n      for (const item of this.#items) {\n        item._detach();\n        const childId = item._id;\n        if (childId) {\n          ops.push({\n            type: OpCode.DELETE_CRDT,\n            id: childId,\n            opId: this._pool.generateOpId(),\n          });\n          reverseOps.push(\n            ...item._toOps(nn(this._id), item._getParentKeyOrThrow())\n          );\n\n          // Index is always 0 because updates are applied one after another\n          // when applied on an immutable state\n          updateDelta.push(deleteDelta(0, item));\n        }\n      }\n\n      this.#items = [];\n      this.invalidate();\n\n      const storageUpdates = new Map<string, LiveListUpdates<TItem>>();\n      storageUpdates.set(nn(this._id), makeUpdate(this, updateDelta));\n\n      this._pool.dispatch(ops, reverseOps, storageUpdates);\n    } else {\n      for (const item of this.#items) {\n        item._detach();\n      }\n      this.#items = [];\n      this.invalidate();\n    }\n  }\n\n  set(index: number, item: TItem): void {\n    this._pool?.assertStorageIsWritable();\n    if (index < 0 || index >= this.#items.length) {\n      throw new Error(\n        `Cannot set list item at index \"\u001d${index}\". index should be between 0 and ${\n          this.#items.length - 1\n        }`\n      );\n    }\n\n    const existingItem = this.#items[index];\n    const position = existingItem._getParentKeyOrThrow();\n\n    const existingId = existingItem._id;\n    existingItem._detach();\n\n    const value = lsonToLiveNode(item);\n    value._setParentLink(this, position);\n    this.#items[index] = value;\n    this.invalidate();\n\n    if (this._pool && this._id) {\n      const id = this._pool.generateId();\n      value._attach(id, this._pool);\n\n      const storageUpdates = new Map<string, LiveListUpdates<TItem>>();\n      storageUpdates.set(this._id, makeUpdate(this, [setDelta(index, value)]));\n\n      const ops = HACK_addIntentAndDeletedIdToOperation(\n        value._toOps(this._id, position, this._pool),\n        existingId\n      );\n      this.#unacknowledgedSets.set(position, nn(ops[0].opId));\n      const reverseOps = HACK_addIntentAndDeletedIdToOperation(\n        existingItem._toOps(this._id, position, undefined),\n        id\n      );\n\n      this._pool.dispatch(ops, reverseOps, storageUpdates);\n    }\n  }\n\n  /**\n   * Returns an Array of all the elements in the LiveList.\n   */\n  toArray(): TItem[] {\n    return this.#items.map(\n      (entry) => liveNodeToLson(entry) as TItem\n      //                               ^^^^^^^^\n      //                               FIXME! This isn't safe.\n    );\n  }\n\n  /**\n   * Tests whether all elements pass the test implemented by the provided function.\n   * @param predicate Function to test for each element, taking two arguments (the element and its index).\n   * @returns true if the predicate function returns a truthy value for every element. Otherwise, false.\n   */\n  every(predicate: (value: TItem, index: number) => unknown): boolean {\n    return this.toArray().every(predicate);\n  }\n\n  /**\n   * Creates an array with all elements that pass the test implemented by the provided function.\n   * @param predicate Function to test each element of the LiveList. Return a value that coerces to true to keep the element, or to false otherwise.\n   * @returns An array with the elements that pass the test.\n   */\n  filter(predicate: (value: TItem, index: number) => unknown): TItem[] {\n    return this.toArray().filter(predicate);\n  }\n\n  /**\n   * Returns the first element that satisfies the provided testing function.\n   * @param predicate Function to execute on each value.\n   * @returns The value of the first element in the LiveList that satisfies the provided testing function. Otherwise, undefined is returned.\n   */\n  find(predicate: (value: TItem, index: number) => unknown): TItem | undefined {\n    return this.toArray().find(predicate);\n  }\n\n  /**\n   * Returns the index of the first element in the LiveList that satisfies the provided testing function.\n   * @param predicate Function to execute on each value until the function returns true, indicating that the satisfying element was found.\n   * @returns The index of the first element in the LiveList that passes the test. Otherwise, -1.\n   */\n  findIndex(predicate: (value: TItem, index: number) => unknown): number {\n    return this.toArray().findIndex(predicate);\n  }\n\n  /**\n   * Executes a provided function once for each element.\n   * @param callbackfn Function to execute on each element.\n   */\n  forEach(callbackfn: (value: TItem, index: number) => void): void {\n    return this.toArray().forEach(callbackfn);\n  }\n\n  /**\n   * Get the element at the specified index.\n   * @param index The index on the element to get.\n   * @returns The element at the specified index or undefined.\n   */\n  get(index: number): TItem | undefined {\n    if (index < 0 || index >= this.#items.length) {\n      return undefined;\n    }\n\n    return liveNodeToLson(this.#items[index]) as TItem | undefined;\n    //                                           ^^^^^^^^^^^^^^^^^\n    //                                           FIXME! This isn't safe.\n  }\n\n  /**\n   * Returns the first index at which a given element can be found in the LiveList, or -1 if it is not present.\n   * @param searchElement Element to locate.\n   * @param fromIndex The index to start the search at.\n   * @returns The first index of the element in the LiveList; -1 if not found.\n   */\n  indexOf(searchElement: TItem, fromIndex?: number): number {\n    return this.toArray().indexOf(searchElement, fromIndex);\n  }\n\n  /**\n   * Returns the last index at which a given element can be found in the LiveList, or -1 if it is not present. The LiveLsit is searched backwards, starting at fromIndex.\n   * @param searchElement Element to locate.\n   * @param fromIndex The index at which to start searching backwards.\n   * @returns\n   */\n  lastIndexOf(searchElement: TItem, fromIndex?: number): number {\n    return this.toArray().lastIndexOf(searchElement, fromIndex);\n  }\n\n  /**\n   * Creates an array populated with the results of calling a provided function on every element.\n   * @param callback Function that is called for every element.\n   * @returns An array with each element being the result of the callback function.\n   */\n  map<U>(callback: (value: TItem, index: number) => U): U[] {\n    return this.#items.map((entry, i) =>\n      callback(\n        liveNodeToLson(entry) as TItem,\n        //                    ^^^^^^^^\n        //                    FIXME! This isn't safe.\n        i\n      )\n    );\n  }\n\n  /**\n   * Tests whether at least one element in the LiveList passes the test implemented by the provided function.\n   * @param predicate Function to test for each element.\n   * @returns true if the callback function returns a truthy value for at least one element. Otherwise, false.\n   */\n  some(predicate: (value: TItem, index: number) => unknown): boolean {\n    return this.toArray().some(predicate);\n  }\n\n  [Symbol.iterator](): IterableIterator<TItem> {\n    return new LiveListIterator(this.#items);\n  }\n\n  #createAttachItemAndSort(\n    op: CreateOp,\n    key: string\n  ): {\n    newItem: LiveNode;\n    newIndex: number;\n  } {\n    const newItem = creationOpToLiveNode(op);\n\n    newItem._attach(op.id, nn(this._pool));\n    newItem._setParentLink(this, key);\n\n    this._insertAndSort(newItem);\n\n    const newIndex = this._indexOfPosition(key);\n\n    return { newItem, newIndex };\n  }\n\n  #shiftItemPosition(index: number, key: Pos) {\n    const shiftedPosition = makePosition(\n      key,\n      this.#items.length > index + 1\n        ? this.#items[index + 1]?._parentPos\n        : undefined\n    );\n\n    this.#items[index]._setParentLink(this, shiftedPosition);\n  }\n\n  /** @internal */\n  _toTreeNode(key: string): DevTools.LsonTreeNode {\n    return {\n      type: \"LiveList\",\n      id: this._id ?? nanoid(),\n      key,\n      payload: this.#items.map((item, index) =>\n        item.toTreeNode(index.toString())\n      ),\n    };\n  }\n\n  toImmutable(): readonly ToImmutable<TItem>[] {\n    // Don't implement actual toJson logic in here. Implement it in ._toImmutable()\n    // instead. This helper merely exists to help TypeScript infer better\n    // return types.\n    return super.toImmutable() as readonly ToImmutable<TItem>[];\n  }\n\n  /** @internal */\n  _toImmutable(): readonly ToImmutable<TItem>[] {\n    const result = this.#items.map((node) => node.toImmutable());\n    return (\n      process.env.NODE_ENV === \"production\" ? result : Object.freeze(result)\n    ) as readonly ToImmutable<TItem>[];\n  }\n\n  clone(): LiveList<TItem> {\n    return new LiveList(this.#items.map((item) => item.clone() as TItem));\n  }\n}\n\nclass LiveListIterator<T extends Lson> implements IterableIterator<T> {\n  #innerIterator: IterableIterator<LiveNode>;\n\n  constructor(items: Array<LiveNode>) {\n    this.#innerIterator = items[Symbol.iterator]();\n  }\n\n  [Symbol.iterator](): IterableIterator<T> {\n    return this;\n  }\n\n  next(): IteratorResult<T> {\n    const result = this.#innerIterator.next();\n\n    if (result.done) {\n      return {\n        done: true,\n        value: undefined,\n      };\n    }\n\n    const value = liveNodeToLson(result.value) as T;\n    //                                         ^^^^\n    //                                         FIXME! This isn't safe.\n    return { value };\n  }\n}\n\nfunction makeUpdate<TItem extends Lson>(\n  liveList: LiveList<TItem>,\n  deltaUpdates: LiveListUpdateDelta[]\n): LiveListUpdates<TItem> {\n  return {\n    node: liveList,\n    type: \"LiveList\",\n    updates: deltaUpdates,\n  };\n}\n\nfunction setDelta(index: number, item: LiveNode): LiveListUpdateDelta {\n  return {\n    index,\n    type: \"set\",\n    item: item instanceof LiveRegister ? item.data : item,\n  };\n}\n\nfunction deleteDelta(\n  index: number,\n  deletedNode: LiveNode\n): LiveListUpdateDelta {\n  return {\n    type: \"delete\",\n    index,\n    deletedItem:\n      deletedNode instanceof LiveRegister ? deletedNode.data : deletedNode,\n  };\n}\n\nfunction insertDelta(index: number, item: LiveNode): LiveListUpdateDelta {\n  return {\n    index,\n    type: \"insert\",\n    item: item instanceof LiveRegister ? item.data : item,\n  };\n}\n\nfunction moveDelta(\n  previousIndex: number,\n  index: number,\n  item: LiveNode\n): LiveListUpdateDelta {\n  return {\n    type: \"move\",\n    index,\n    item: item instanceof LiveRegister ? item.data : item,\n    previousIndex,\n  };\n}\n\n/**\n * This function is only temporary.\n * As soon as we refactor the operations structure,\n * serializing a LiveStructure should not know anything about intent\n */\nfunction HACK_addIntentAndDeletedIdToOperation(\n  ops: CreateOp[],\n  deletedId: string | undefined\n): CreateOp[] {\n  return ops.map((op, index) => {\n    if (index === 0) {\n      // NOTE: Only patch the first Op here\n      const firstOp = op;\n      return {\n        ...firstOp,\n        intent: \"set\",\n        deletedId,\n      };\n    } else {\n      return op;\n    }\n  });\n}\n","import { nn } from \"../lib/assert\";\nimport { freeze } from \"../lib/freeze\";\nimport { nanoid } from \"../lib/nanoid\";\nimport type { CreateMapOp, CreateOp, Op } from \"../protocol/Op\";\nimport { OpCode } from \"../protocol/Op\";\nimport type { IdTuple, SerializedMap } from \"../protocol/SerializedCrdt\";\nimport { CrdtType } from \"../protocol/SerializedCrdt\";\nimport type * as DevTools from \"../types/DevToolsTreeNode\";\nimport type { ParentToChildNodeMap } from \"../types/NodeMap\";\nimport type { ApplyResult, ManagedPool } from \"./AbstractCrdt\";\nimport { AbstractCrdt, OpSource } from \"./AbstractCrdt\";\nimport {\n  creationOpToLiveNode,\n  deserialize,\n  isLiveNode,\n  liveNodeToLson,\n  lsonToLiveNode,\n} from \"./liveblocks-helpers\";\nimport type { LiveNode, Lson } from \"./Lson\";\nimport type { UpdateDelta } from \"./UpdateDelta\";\nimport type { ToImmutable } from \"./utils\";\n\n/**\n * A LiveMap notification that is sent in-client to any subscribers whenever\n * one or more of the values inside the LiveMap instance have changed.\n */\nexport type LiveMapUpdates<TKey extends string, TValue extends Lson> = {\n  type: \"LiveMap\";\n  node: LiveMap<TKey, TValue>;\n  updates: { [key: string]: UpdateDelta };\n  //               ^^^^^^\n  //               FIXME: `string` is not specific enough here. See if we can\n  //               improve this type to match TKey!\n};\n\n/**\n * The LiveMap class is similar to a JavaScript Map that is synchronized on all clients.\n * Keys should be a string, and values should be serializable to JSON.\n * If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.\n */\nexport class LiveMap<\n  TKey extends string,\n  TValue extends Lson,\n> extends AbstractCrdt {\n  #map: Map<TKey, LiveNode>;\n  #unacknowledgedSet: Map<TKey, string>;\n\n  constructor(entries?: readonly (readonly [TKey, TValue])[] | undefined) {\n    super();\n    this.#unacknowledgedSet = new Map<TKey, string>();\n\n    if (entries) {\n      const mappedEntries: [TKey, LiveNode][] = [];\n      for (const [key, value] of entries) {\n        const node = lsonToLiveNode(value);\n        node._setParentLink(this, key);\n        mappedEntries.push([key, node]);\n      }\n      this.#map = new Map(mappedEntries);\n    } else {\n      this.#map = new Map();\n    }\n  }\n\n  /** @internal */\n  _toOps(parentId: string, parentKey: string, pool?: ManagedPool): CreateOp[] {\n    if (this._id === undefined) {\n      throw new Error(\"Cannot serialize item is not attached\");\n    }\n\n    const ops: CreateOp[] = [];\n    const op: CreateMapOp = {\n      id: this._id,\n      opId: pool?.generateOpId(),\n      type: OpCode.CREATE_MAP,\n      parentId,\n      parentKey,\n    };\n\n    ops.push(op);\n\n    for (const [key, value] of this.#map) {\n      ops.push(...value._toOps(this._id, key, pool));\n    }\n\n    return ops;\n  }\n\n  /** @internal */\n  static _deserialize(\n    [id, _item]: IdTuple<SerializedMap>,\n    parentToChildren: ParentToChildNodeMap,\n    pool: ManagedPool\n  ): LiveMap<string, Lson> {\n    const map = new LiveMap();\n    map._attach(id, pool);\n\n    const children = parentToChildren.get(id);\n    if (children === undefined) {\n      return map;\n    }\n\n    for (const [id, crdt] of children) {\n      const child = deserialize([id, crdt], parentToChildren, pool);\n      child._setParentLink(map, crdt.parentKey);\n      map.#map.set(crdt.parentKey, child);\n      map.invalidate();\n    }\n\n    return map;\n  }\n\n  /** @internal */\n  _attach(id: string, pool: ManagedPool): void {\n    super._attach(id, pool);\n\n    for (const [_key, value] of this.#map) {\n      if (isLiveNode(value)) {\n        value._attach(pool.generateId(), pool);\n      }\n    }\n  }\n\n  /** @internal */\n  _attachChild(op: CreateOp, source: OpSource): ApplyResult {\n    if (this._pool === undefined) {\n      throw new Error(\"Can't attach child if managed pool is not present\");\n    }\n\n    const { id, parentKey, opId } = op;\n\n    const key = parentKey as TKey;\n    //                    ^^^^^^^ TODO: Fix me!\n\n    const child = creationOpToLiveNode(op);\n\n    if (this._pool.getNode(id) !== undefined) {\n      return { modified: false };\n    }\n\n    if (source === OpSource.ACK) {\n      const lastUpdateOpId = this.#unacknowledgedSet.get(key);\n      if (lastUpdateOpId === opId) {\n        // Acknowlegment from local operation\n        this.#unacknowledgedSet.delete(key);\n        return { modified: false };\n      } else if (lastUpdateOpId !== undefined) {\n        // Another local set has overriden the value, so we do nothing\n        return { modified: false };\n      }\n    } else if (source === OpSource.REMOTE) {\n      // If a remote operation set an item,\n      // delete the unacknowledgedSet associated to the key\n      // to make sure any future ack can override it\n      this.#unacknowledgedSet.delete(key);\n    }\n\n    const previousValue = this.#map.get(key);\n    let reverse: Op[];\n    if (previousValue) {\n      const thisId = nn(this._id);\n      reverse = previousValue._toOps(thisId, key);\n      previousValue._detach();\n    } else {\n      reverse = [{ type: OpCode.DELETE_CRDT, id }];\n    }\n\n    child._setParentLink(this, key);\n    child._attach(id, this._pool);\n    this.#map.set(key, child);\n    this.invalidate();\n\n    return {\n      modified: {\n        node: this,\n        type: \"LiveMap\",\n        updates: { [key]: { type: \"update\" } },\n      },\n      reverse,\n    };\n  }\n\n  /** @internal */\n  _detach(): void {\n    super._detach();\n\n    for (const item of this.#map.values()) {\n      item._detach();\n    }\n  }\n\n  /** @internal */\n  _detachChild(child: LiveNode): ApplyResult {\n    const id = nn(this._id);\n    const parentKey = nn(child._parentKey);\n    const reverse = child._toOps(id, parentKey, this._pool);\n\n    for (const [key, value] of this.#map) {\n      if (value === child) {\n        this.#map.delete(key);\n        this.invalidate();\n      }\n    }\n\n    child._detach();\n\n    const storageUpdate: LiveMapUpdates<TKey, TValue> = {\n      node: this,\n      type: \"LiveMap\",\n      updates: { [parentKey]: { type: \"delete\" } },\n    };\n\n    return { modified: storageUpdate, reverse };\n  }\n\n  /** @internal */\n  _serialize(): SerializedMap {\n    if (this.parent.type !== \"HasParent\") {\n      throw new Error(\"Cannot serialize LiveMap if parent is missing\");\n    }\n\n    return {\n      type: CrdtType.MAP,\n      parentId: nn(this.parent.node._id, \"Parent node expected to have ID\"),\n      parentKey: this.parent.key,\n    };\n  }\n\n  /**\n   * Returns a specified element from the LiveMap.\n   * @param key The key of the element to return.\n   * @returns The element associated with the specified key, or undefined if the key can't be found in the LiveMap.\n   */\n  get(key: TKey): TValue | undefined {\n    const value = this.#map.get(key);\n    if (value === undefined) {\n      return undefined;\n    }\n    return liveNodeToLson(value) as TValue | undefined;\n    //                           ^^^^^^^^^^^^^^^^^^^^^\n    //                           FIXME! This isn't safe.\n  }\n\n  /**\n   * Adds or updates an element with a specified key and a value.\n   * @param key The key of the element to add. Should be a string.\n   * @param value The value of the element to add. Should be serializable to JSON.\n   */\n  set(key: TKey, value: TValue): void {\n    this._pool?.assertStorageIsWritable();\n    const oldValue = this.#map.get(key);\n\n    if (oldValue) {\n      oldValue._detach();\n    }\n\n    const item = lsonToLiveNode(value);\n    item._setParentLink(this, key);\n\n    this.#map.set(key, item);\n    this.invalidate();\n\n    if (this._pool && this._id) {\n      const id = this._pool.generateId();\n      item._attach(id, this._pool);\n\n      const storageUpdates = new Map<string, LiveMapUpdates<TKey, TValue>>();\n      storageUpdates.set(this._id, {\n        node: this,\n        type: \"LiveMap\",\n        updates: { [key]: { type: \"update\" } },\n      });\n\n      const ops = item._toOps(this._id, key, this._pool);\n\n      this.#unacknowledgedSet.set(key, nn(ops[0].opId));\n\n      this._pool.dispatch(\n        item._toOps(this._id, key, this._pool),\n        oldValue\n          ? oldValue._toOps(this._id, key)\n          : [{ type: OpCode.DELETE_CRDT, id }],\n        storageUpdates\n      );\n    }\n  }\n\n  /**\n   * Returns the number of elements in the LiveMap.\n   */\n  get size(): number {\n    return this.#map.size;\n  }\n\n  /**\n   * Returns a boolean indicating whether an element with the specified key exists or not.\n   * @param key The key of the element to test for presence.\n   */\n  has(key: TKey): boolean {\n    return this.#map.has(key);\n  }\n\n  /**\n   * Removes the specified element by key.\n   * @param key The key of the element to remove.\n   * @returns true if an element existed and has been removed, or false if the element does not exist.\n   */\n  delete(key: TKey): boolean {\n    this._pool?.assertStorageIsWritable();\n    const item = this.#map.get(key);\n\n    if (item === undefined) {\n      return false;\n    }\n\n    item._detach();\n    this.#map.delete(key);\n    this.invalidate();\n\n    if (this._pool && item._id) {\n      const thisId = nn(this._id);\n      const storageUpdates = new Map<string, LiveMapUpdates<TKey, TValue>>();\n      storageUpdates.set(thisId, {\n        node: this,\n        type: \"LiveMap\",\n        updates: { [key]: { type: \"delete\" } },\n      });\n      this._pool.dispatch(\n        [\n          {\n            type: OpCode.DELETE_CRDT,\n            id: item._id,\n            opId: this._pool.generateOpId(),\n          },\n        ],\n        item._toOps(thisId, key),\n        storageUpdates\n      );\n    }\n\n    return true;\n  }\n\n  /**\n   * Returns a new Iterator object that contains the [key, value] pairs for each element.\n   */\n  entries(): IterableIterator<[TKey, TValue]> {\n    const innerIterator = this.#map.entries();\n\n    return {\n      [Symbol.iterator]() {\n        return this;\n      },\n      next() {\n        const iteratorValue = innerIterator.next();\n\n        if (iteratorValue.done) {\n          return {\n            done: true,\n            value: undefined,\n          };\n        }\n\n        const entry = iteratorValue.value;\n\n        const key = entry[0];\n        const value = liveNodeToLson(iteratorValue.value[1]) as TValue;\n        //                                                   ^^^^^^^^^\n        //                                                   FIXME! This isn't safe.\n        return {\n          value: [key, value],\n        };\n      },\n    };\n  }\n\n  /**\n   * Same function object as the initial value of the entries method.\n   */\n  [Symbol.iterator](): IterableIterator<[TKey, TValue]> {\n    return this.entries();\n  }\n\n  /**\n   * Returns a new Iterator object that contains the keys for each element.\n   */\n  keys(): IterableIterator<TKey> {\n    return this.#map.keys();\n  }\n\n  /**\n   * Returns a new Iterator object that contains the values for each element.\n   */\n  values(): IterableIterator<TValue> {\n    const innerIterator = this.#map.values();\n\n    return {\n      [Symbol.iterator]() {\n        return this;\n      },\n      next() {\n        const iteratorValue = innerIterator.next();\n\n        if (iteratorValue.done) {\n          return {\n            done: true,\n            value: undefined,\n          };\n        }\n\n        const value = liveNodeToLson(iteratorValue.value) as TValue;\n        //                                                ^^^^^^^^^\n        //                                                FIXME! This isn't safe.\n\n        return { value };\n      },\n    };\n  }\n\n  /**\n   * Executes a provided function once per each key/value pair in the Map object, in insertion order.\n   * @param callback Function to execute for each entry in the map.\n   */\n  forEach(\n    callback: (value: TValue, key: TKey, map: LiveMap<TKey, TValue>) => void\n  ): void {\n    for (const entry of this) {\n      callback(entry[1], entry[0], this);\n    }\n  }\n\n  /** @internal */\n  _toTreeNode(key: string): DevTools.LsonTreeNode {\n    return {\n      type: \"LiveMap\",\n      id: this._id ?? nanoid(),\n      key,\n      payload: Array.from(this.#map.entries()).map(([key, val]) =>\n        val.toTreeNode(key)\n      ),\n    };\n  }\n\n  toImmutable(): ReadonlyMap<TKey, ToImmutable<TValue>> {\n    // Don't implement actual toImmutable logic in here. Implement it in\n    // ._toImmutable() instead. This helper merely exists to help TypeScript\n    // infer better return types.\n    return super.toImmutable() as ReadonlyMap<TKey, ToImmutable<TValue>>;\n  }\n\n  /** @internal */\n  _toImmutable(): ReadonlyMap<TKey, ToImmutable<TValue>> {\n    const result: Map<TKey, ToImmutable<TValue>> = new Map();\n    for (const [key, value] of this.#map) {\n      result.set(key, value.toImmutable() as ToImmutable<TValue>);\n    }\n    return freeze(result);\n  }\n\n  clone(): LiveMap<TKey, TValue> {\n    return new LiveMap(\n      Array.from(this.#map).map(([key, node]) => [key, node.clone() as TValue])\n    );\n  }\n}\n","import type { LiveNode, Lson, LsonObject } from \"../crdts/Lson\";\nimport { nn } from \"../lib/assert\";\nimport type { JsonObject } from \"../lib/Json\";\nimport { nanoid } from \"../lib/nanoid\";\nimport type { RemoveUndefinedValues } from \"../lib/utils\";\nimport { compactObject, deepClone } from \"../lib/utils\";\nimport type {\n  CreateObjectOp,\n  CreateOp,\n  DeleteObjectKeyOp,\n  Op,\n  UpdateObjectOp,\n} from \"../protocol/Op\";\nimport { OpCode } from \"../protocol/Op\";\nimport type {\n  IdTuple,\n  SerializedChild,\n  SerializedCrdt,\n  SerializedObject,\n  SerializedRootObject,\n} from \"../protocol/SerializedCrdt\";\nimport { CrdtType, isRootCrdt } from \"../protocol/SerializedCrdt\";\nimport type * as DevTools from \"../types/DevToolsTreeNode\";\nimport type { ParentToChildNodeMap } from \"../types/NodeMap\";\nimport type { ApplyResult, ManagedPool } from \"./AbstractCrdt\";\nimport { AbstractCrdt, OpSource } from \"./AbstractCrdt\";\nimport {\n  creationOpToLson,\n  deserializeToLson,\n  isLiveNode,\n  isLiveStructure,\n} from \"./liveblocks-helpers\";\nimport type { UpdateDelta } from \"./UpdateDelta\";\nimport type { ToImmutable } from \"./utils\";\n\nexport type LiveObjectUpdateDelta<O extends { [key: string]: unknown }> = {\n  [K in keyof O]?: UpdateDelta | undefined;\n};\n\n/**\n * A LiveObject notification that is sent in-client to any subscribers whenever\n * one or more of the entries inside the LiveObject instance have changed.\n */\nexport type LiveObjectUpdates<TData extends LsonObject> = {\n  type: \"LiveObject\";\n  node: LiveObject<TData>;\n  updates: LiveObjectUpdateDelta<TData>;\n};\n\n/**\n * The LiveObject class is similar to a JavaScript object that is synchronized on all clients.\n * Keys should be a string, and values should be serializable to JSON.\n * If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.\n */\nexport class LiveObject<O extends LsonObject> extends AbstractCrdt {\n  #map: Map<string, Lson>;\n  #propToLastUpdate: Map<string, string>;\n\n  static #buildRootAndParentToChildren(\n    items: IdTuple<SerializedCrdt>[]\n  ): [IdTuple<SerializedRootObject>, ParentToChildNodeMap] {\n    const parentToChildren: ParentToChildNodeMap = new Map();\n    let root: IdTuple<SerializedRootObject> | null = null;\n\n    for (const [id, crdt] of items) {\n      if (isRootCrdt(crdt)) {\n        root = [id, crdt];\n      } else {\n        const tuple: IdTuple<SerializedChild> = [id, crdt];\n        const children = parentToChildren.get(crdt.parentId);\n        if (children !== undefined) {\n          children.push(tuple);\n        } else {\n          parentToChildren.set(crdt.parentId, [tuple]);\n        }\n      }\n    }\n\n    if (root === null) {\n      throw new Error(\"Root can't be null\");\n    }\n\n    return [root, parentToChildren];\n  }\n\n  /** @private Do not use this API directly */\n  static _fromItems<O extends LsonObject>(\n    items: IdTuple<SerializedCrdt>[],\n    pool: ManagedPool\n  ): LiveObject<O> {\n    const [root, parentToChildren] =\n      LiveObject.#buildRootAndParentToChildren(items);\n    return LiveObject._deserialize(\n      root,\n      parentToChildren,\n      pool\n    ) as LiveObject<O>;\n  }\n\n  constructor(obj: O = {} as O) {\n    super();\n\n    this.#propToLastUpdate = new Map<string, string>();\n\n    const o: RemoveUndefinedValues<LsonObject> = compactObject(obj);\n    for (const key of Object.keys(o)) {\n      const value = o[key];\n      if (isLiveNode(value)) {\n        value._setParentLink(this, key);\n      }\n    }\n\n    this.#map = new Map(Object.entries(o));\n  }\n\n  /** @internal */\n  _toOps(parentId: string, parentKey: string, pool?: ManagedPool): CreateOp[] {\n    if (this._id === undefined) {\n      throw new Error(\"Cannot serialize item is not attached\");\n    }\n\n    const opId = pool?.generateOpId();\n\n    const ops: CreateOp[] = [];\n    const op: CreateObjectOp = {\n      type: OpCode.CREATE_OBJECT,\n      id: this._id,\n      opId,\n      parentId,\n      parentKey,\n      data: {},\n    };\n\n    ops.push(op);\n\n    for (const [key, value] of this.#map) {\n      if (isLiveNode(value)) {\n        ops.push(...value._toOps(this._id, key, pool));\n      } else {\n        op.data[key] = value;\n      }\n    }\n\n    return ops;\n  }\n\n  /** @internal */\n  static _deserialize(\n    [id, item]: IdTuple<SerializedObject | SerializedRootObject>,\n    parentToChildren: ParentToChildNodeMap,\n    pool: ManagedPool\n  ): LiveObject<LsonObject> {\n    const liveObj = new LiveObject(item.data);\n    liveObj._attach(id, pool);\n    return this._deserializeChildren(liveObj, parentToChildren, pool);\n  }\n\n  /** @internal */\n  static _deserializeChildren(\n    liveObj: LiveObject<JsonObject>,\n    parentToChildren: ParentToChildNodeMap,\n    pool: ManagedPool\n  ): LiveObject<LsonObject> {\n    const children = parentToChildren.get(nn(liveObj._id));\n    if (children === undefined) {\n      return liveObj;\n    }\n\n    for (const [id, crdt] of children) {\n      const child = deserializeToLson([id, crdt], parentToChildren, pool);\n      if (isLiveStructure(child)) {\n        child._setParentLink(liveObj, crdt.parentKey);\n      }\n      liveObj.#map.set(crdt.parentKey, child);\n      liveObj.invalidate();\n    }\n\n    return liveObj;\n  }\n\n  /** @internal */\n  _attach(id: string, pool: ManagedPool): void {\n    super._attach(id, pool);\n\n    for (const [_key, value] of this.#map) {\n      if (isLiveNode(value)) {\n        value._attach(pool.generateId(), pool);\n      }\n    }\n  }\n\n  /** @internal */\n  _attachChild(op: CreateOp, source: OpSource): ApplyResult {\n    if (this._pool === undefined) {\n      throw new Error(\"Can't attach child if managed pool is not present\");\n    }\n\n    const { id, opId, parentKey: key } = op;\n    const child = creationOpToLson(op);\n\n    if (this._pool.getNode(id) !== undefined) {\n      if (this.#propToLastUpdate.get(key) === opId) {\n        // Acknowlegment from local operation\n        this.#propToLastUpdate.delete(key);\n      }\n\n      return { modified: false };\n    }\n\n    if (source === OpSource.UNDOREDO_RECONNECT) {\n      this.#propToLastUpdate.set(key, nn(opId));\n    } else if (this.#propToLastUpdate.get(key) === undefined) {\n      // Remote operation with no local change => apply operation\n    } else if (this.#propToLastUpdate.get(key) === opId) {\n      // Acknowlegment from local operation\n      this.#propToLastUpdate.delete(key);\n      return { modified: false };\n    } else {\n      // Conflict, ignore remote operation\n      return { modified: false };\n    }\n\n    const thisId = nn(this._id);\n    const previousValue = this.#map.get(key);\n    let reverse: Op[];\n    if (isLiveNode(previousValue)) {\n      reverse = previousValue._toOps(thisId, key);\n      previousValue._detach();\n    } else if (previousValue === undefined) {\n      reverse = [{ type: OpCode.DELETE_OBJECT_KEY, id: thisId, key }];\n    } else {\n      reverse = [\n        {\n          type: OpCode.UPDATE_OBJECT,\n          id: thisId,\n          data: { [key]: previousValue },\n        },\n      ];\n    }\n\n    this.#map.set(key, child);\n    this.invalidate();\n\n    if (isLiveStructure(child)) {\n      child._setParentLink(this, key);\n      child._attach(id, this._pool);\n    }\n\n    return {\n      reverse,\n      modified: {\n        node: this,\n        type: \"LiveObject\",\n        updates: { [key]: { type: \"update\" } },\n      },\n    };\n  }\n\n  /** @internal */\n  _detachChild(child: LiveNode): ApplyResult {\n    if (child) {\n      const id = nn(this._id);\n      const parentKey = nn(child._parentKey);\n      const reverse = child._toOps(id, parentKey, this._pool);\n\n      for (const [key, value] of this.#map) {\n        if (value === child) {\n          this.#map.delete(key);\n          this.invalidate();\n        }\n      }\n\n      child._detach();\n\n      const storageUpdate: LiveObjectUpdates<O> = {\n        node: this,\n        type: \"LiveObject\",\n        updates: {\n          [parentKey]: { type: \"delete\" },\n        } as { [K in keyof O]: UpdateDelta },\n      };\n\n      return { modified: storageUpdate, reverse };\n    }\n\n    return { modified: false };\n  }\n\n  /** @internal */\n  _detach(): void {\n    super._detach();\n\n    for (const value of this.#map.values()) {\n      if (isLiveNode(value)) {\n        value._detach();\n      }\n    }\n  }\n\n  /** @internal */\n  _apply(op: Op, isLocal: boolean): ApplyResult {\n    if (op.type === OpCode.UPDATE_OBJECT) {\n      return this.#applyUpdate(op, isLocal);\n    } else if (op.type === OpCode.DELETE_OBJECT_KEY) {\n      return this.#applyDeleteObjectKey(op, isLocal);\n    }\n\n    return super._apply(op, isLocal);\n  }\n\n  /** @internal */\n  _serialize(): SerializedObject | SerializedRootObject {\n    const data: JsonObject = {};\n\n    // Add only the static Json data fields into the objects\n    for (const [key, value] of this.#map) {\n      if (!isLiveNode(value)) {\n        data[key] = value;\n      }\n    }\n\n    if (this.parent.type === \"HasParent\" && this.parent.node._id) {\n      return {\n        type: CrdtType.OBJECT,\n        parentId: this.parent.node._id,\n        parentKey: this.parent.key,\n        data,\n      };\n    } else {\n      // Root object has no parent ID/key\n      return {\n        type: CrdtType.OBJECT,\n        data,\n      };\n    }\n  }\n\n  #applyUpdate(op: UpdateObjectOp, isLocal: boolean): ApplyResult {\n    let isModified = false;\n    const id = nn(this._id);\n    const reverse: Op[] = [];\n    const reverseUpdate: UpdateObjectOp = {\n      type: OpCode.UPDATE_OBJECT,\n      id,\n      data: {},\n    };\n\n    for (const key in op.data as Partial<O>) {\n      const oldValue = this.#map.get(key);\n      if (isLiveNode(oldValue)) {\n        reverse.push(...oldValue._toOps(id, key));\n        oldValue._detach();\n      } else if (oldValue !== undefined) {\n        reverseUpdate.data[key] = oldValue;\n      } else if (oldValue === undefined) {\n        reverse.push({ type: OpCode.DELETE_OBJECT_KEY, id, key });\n      }\n    }\n\n    const updateDelta: LiveObjectUpdateDelta<O> = {};\n    for (const key in op.data as Partial<O>) {\n      const value = op.data[key];\n      if (value === undefined) {\n        continue;\n      }\n\n      if (isLocal) {\n        this.#propToLastUpdate.set(key, nn(op.opId));\n      } else if (this.#propToLastUpdate.get(key) === undefined) {\n        // Not modified localy so we apply update\n        isModified = true;\n      } else if (this.#propToLastUpdate.get(key) === op.opId) {\n        // Acknowlegment from local operation\n        this.#propToLastUpdate.delete(key);\n        continue;\n      } else {\n        // Conflict, ignore remote operation\n        continue;\n      }\n\n      const oldValue = this.#map.get(key);\n\n      if (isLiveNode(oldValue)) {\n        oldValue._detach();\n      }\n\n      isModified = true;\n      updateDelta[key] = { type: \"update\" };\n      this.#map.set(key, value);\n      this.invalidate();\n    }\n\n    if (Object.keys(reverseUpdate.data).length !== 0) {\n      reverse.unshift(reverseUpdate);\n    }\n\n    return isModified\n      ? {\n          modified: {\n            node: this,\n            type: \"LiveObject\",\n            updates: updateDelta,\n          },\n          reverse,\n        }\n      : { modified: false };\n  }\n\n  #applyDeleteObjectKey(op: DeleteObjectKeyOp, isLocal: boolean): ApplyResult {\n    const key = op.key;\n\n    // If property does not exist, exit without notifying\n    if (this.#map.has(key) === false) {\n      return { modified: false };\n    }\n\n    // If a local operation exists on the same key and we receive a remote\n    // one prevent flickering by not applying delete op.\n    if (!isLocal && this.#propToLastUpdate.get(key) !== undefined) {\n      return { modified: false };\n    }\n\n    const oldValue = this.#map.get(key);\n\n    const id = nn(this._id);\n    let reverse: Op[] = [];\n    if (isLiveNode(oldValue)) {\n      reverse = oldValue._toOps(id, op.key);\n      oldValue._detach();\n    } else if (oldValue !== undefined) {\n      reverse = [\n        {\n          type: OpCode.UPDATE_OBJECT,\n          id,\n          data: { [key]: oldValue },\n        },\n      ];\n    }\n\n    this.#map.delete(key);\n    this.invalidate();\n    return {\n      modified: {\n        node: this,\n        type: \"LiveObject\",\n        updates: { [op.key]: { type: \"delete\" } },\n      },\n      reverse,\n    };\n  }\n\n  /**\n   * Transform the LiveObject into a javascript object\n   */\n  toObject(): O {\n    return Object.fromEntries(this.#map) as O;\n  }\n\n  /**\n   * Adds or updates a property with a specified key and a value.\n   * @param key The key of the property to add\n   * @param value The value of the property to add\n   */\n  set<TKey extends keyof O>(key: TKey, value: O[TKey]): void {\n    // TODO: Find out why typescript complains\n    this._pool?.assertStorageIsWritable();\n    this.update({ [key]: value } as unknown as Partial<O>);\n  }\n\n  /**\n   * Returns a specified property from the LiveObject.\n   * @param key The key of the property to get\n   */\n  get<TKey extends keyof O>(key: TKey): O[TKey] {\n    return this.#map.get(key as string) as O[TKey];\n  }\n\n  /**\n   * Deletes a key from the LiveObject\n   * @param key The key of the property to delete\n   */\n  delete(key: keyof O): void {\n    this._pool?.assertStorageIsWritable();\n    const keyAsString = key as string;\n    const oldValue = this.#map.get(keyAsString);\n\n    if (oldValue === undefined) {\n      return;\n    }\n\n    if (this._pool === undefined || this._id === undefined) {\n      if (isLiveNode(oldValue)) {\n        oldValue._detach();\n      }\n      this.#map.delete(keyAsString);\n      this.invalidate();\n      return;\n    }\n\n    let reverse: Op[];\n\n    if (isLiveNode(oldValue)) {\n      oldValue._detach();\n      reverse = oldValue._toOps(this._id, keyAsString);\n    } else {\n      reverse = [\n        {\n          type: OpCode.UPDATE_OBJECT,\n          data: { [keyAsString]: oldValue },\n          id: this._id,\n        },\n      ];\n    }\n\n    this.#map.delete(keyAsString);\n    this.invalidate();\n\n    const storageUpdates = new Map<string, LiveObjectUpdates<O>>();\n    storageUpdates.set(this._id, {\n      node: this,\n      type: \"LiveObject\",\n      updates: { [key]: { type: \"delete\" } } as {\n        [K in keyof O]: UpdateDelta;\n      },\n    });\n\n    this._pool.dispatch(\n      [\n        {\n          type: OpCode.DELETE_OBJECT_KEY,\n          key: keyAsString,\n          id: this._id,\n          opId: this._pool.generateOpId(),\n        },\n      ],\n      reverse,\n      storageUpdates\n    );\n  }\n\n  /**\n   * Adds or updates multiple properties at once with an object.\n   * @param patch The object used to overrides properties\n   */\n  update(patch: Partial<O>): void {\n    this._pool?.assertStorageIsWritable();\n    if (this._pool === undefined || this._id === undefined) {\n      for (const key in patch) {\n        const newValue = patch[key];\n        if (newValue === undefined) {\n          continue;\n        }\n\n        const oldValue = this.#map.get(key);\n        if (isLiveNode(oldValue)) {\n          oldValue._detach();\n        }\n\n        if (isLiveNode(newValue)) {\n          newValue._setParentLink(this, key);\n        }\n\n        this.#map.set(key, newValue);\n        this.invalidate();\n      }\n\n      return;\n    }\n\n    const ops: Op[] = [];\n    const reverseOps: Op[] = [];\n\n    const opId = this._pool.generateOpId();\n    const updatedProps: JsonObject = {};\n\n    const reverseUpdateOp: UpdateObjectOp = {\n      id: this._id,\n      type: OpCode.UPDATE_OBJECT,\n      data: {},\n    };\n\n    const updateDelta: LiveObjectUpdateDelta<O> = {};\n\n    for (const key in patch) {\n      const newValue: Lson | undefined = patch[key];\n      if (newValue === undefined) {\n        continue;\n      }\n\n      const oldValue = this.#map.get(key);\n\n      if (isLiveNode(oldValue)) {\n        reverseOps.push(...oldValue._toOps(this._id, key));\n        oldValue._detach();\n      } else if (oldValue === undefined) {\n        reverseOps.push({ type: OpCode.DELETE_OBJECT_KEY, id: this._id, key });\n      } else {\n        reverseUpdateOp.data[key] = oldValue;\n      }\n\n      if (isLiveNode(newValue)) {\n        newValue._setParentLink(this, key);\n        newValue._attach(this._pool.generateId(), this._pool);\n        const newAttachChildOps = newValue._toOps(this._id, key, this._pool);\n\n        const createCrdtOp = newAttachChildOps.find(\n          (op: Op & { parentId?: string }) => op.parentId === this._id\n        );\n        if (createCrdtOp) {\n          this.#propToLastUpdate.set(key, nn(createCrdtOp.opId));\n        }\n\n        ops.push(...newAttachChildOps);\n      } else {\n        updatedProps[key] = newValue;\n        this.#propToLastUpdate.set(key, opId);\n      }\n\n      this.#map.set(key, newValue);\n      this.invalidate();\n      updateDelta[key] = { type: \"update\" };\n    }\n\n    if (Object.keys(reverseUpdateOp.data).length !== 0) {\n      reverseOps.unshift(reverseUpdateOp);\n    }\n\n    if (Object.keys(updatedProps).length !== 0) {\n      ops.unshift({\n        opId,\n        id: this._id,\n        type: OpCode.UPDATE_OBJECT,\n        data: updatedProps,\n      });\n    }\n\n    const storageUpdates = new Map<string, LiveObjectUpdates<O>>();\n    storageUpdates.set(this._id, {\n      node: this,\n      type: \"LiveObject\",\n      updates: updateDelta,\n    });\n    this._pool.dispatch(ops, reverseOps, storageUpdates);\n  }\n\n  toImmutable(): ToImmutable<O> {\n    // Don't implement actual toImmutable logic in here. Implement it in\n    // ._toImmutable() instead. This helper merely exists to help TypeScript\n    // infer better return types.\n    return super.toImmutable() as ToImmutable<O>;\n  }\n\n  /** @internal */\n  toTreeNode(key: string): DevTools.LiveTreeNode<\"LiveObject\"> {\n    // Don't implement actual toTreeNode logic in here. Implement it in\n    // ._toTreeNode() instead. This helper merely exists to help TypeScript\n    // infer better return types.\n    return super.toTreeNode(key) as DevTools.LiveTreeNode<\"LiveObject\">;\n  }\n\n  /** @internal */\n  _toTreeNode(key: string): DevTools.LsonTreeNode {\n    const nodeId = this._id ?? nanoid();\n    return {\n      type: \"LiveObject\",\n      id: nodeId,\n      key,\n      payload: Array.from(this.#map.entries()).map(([key, value]) =>\n        isLiveNode(value)\n          ? value.toTreeNode(key)\n          : { type: \"Json\", id: `${nodeId}:${key}`, key, payload: value }\n      ),\n    };\n  }\n\n  /** @internal */\n  _toImmutable(): ToImmutable<O> {\n    const result: { [key: string]: unknown } = {};\n    for (const [key, val] of this.#map) {\n      result[key] = isLiveStructure(val) ? val.toImmutable() : val;\n    }\n    return (\n      process.env.NODE_ENV === \"production\" ? result : Object.freeze(result)\n    ) as ToImmutable<O>;\n  }\n\n  clone(): LiveObject<O> {\n    return new LiveObject(\n      Object.fromEntries(\n        Array.from(this.#map).map(([key, value]) => [\n          key,\n          isLiveStructure(value) ? value.clone() : deepClone(value),\n        ])\n      ) as O\n    );\n  }\n}\n","import { assertNever, nn } from \"../lib/assert\";\nimport { isPlainObject } from \"../lib/guards\";\nimport type { Json } from \"../lib/Json\";\nimport { stringifyOrLog as stringify } from \"../lib/stringify\";\nimport { deepClone, entries } from \"../lib/utils\";\nimport type { CreateOp, Op } from \"../protocol/Op\";\nimport { OpCode } from \"../protocol/Op\";\nimport type { IdTuple, SerializedCrdt } from \"../protocol/SerializedCrdt\";\nimport { CrdtType } from \"../protocol/SerializedCrdt\";\nimport type { NodeMap, ParentToChildNodeMap } from \"../types/NodeMap\";\nimport type { ManagedPool } from \"./AbstractCrdt\";\nimport { LiveList, type LiveListUpdates } from \"./LiveList\";\nimport { LiveMap, type LiveMapUpdates } from \"./LiveMap\";\nimport { LiveObject, type LiveObjectUpdates } from \"./LiveObject\";\nimport { LiveRegister } from \"./LiveRegister\";\nimport type { LiveNode, LiveStructure, Lson, LsonObject } from \"./Lson\";\nimport type { StorageUpdate } from \"./StorageUpdates\";\n\nexport function creationOpToLiveNode(op: CreateOp): LiveNode {\n  return lsonToLiveNode(creationOpToLson(op));\n}\n\nexport function creationOpToLson(op: CreateOp): Lson {\n  switch (op.type) {\n    case OpCode.CREATE_REGISTER:\n      return op.data;\n    case OpCode.CREATE_OBJECT:\n      return new LiveObject(op.data);\n    case OpCode.CREATE_MAP:\n      return new LiveMap();\n    case OpCode.CREATE_LIST:\n      return new LiveList([]);\n    default:\n      return assertNever(op, \"Unknown creation Op\");\n  }\n}\n\nexport function isSameNodeOrChildOf(node: LiveNode, parent: LiveNode): boolean {\n  if (node === parent) {\n    return true;\n  }\n  if (node.parent.type === \"HasParent\") {\n    return isSameNodeOrChildOf(node.parent.node, parent);\n  }\n  return false;\n}\n\nexport function deserialize(\n  [id, crdt]: IdTuple<SerializedCrdt>,\n  parentToChildren: ParentToChildNodeMap,\n  pool: ManagedPool\n): LiveNode {\n  switch (crdt.type) {\n    case CrdtType.OBJECT: {\n      return LiveObject._deserialize([id, crdt], parentToChildren, pool);\n    }\n    case CrdtType.LIST: {\n      return LiveList._deserialize([id, crdt], parentToChildren, pool);\n    }\n    case CrdtType.MAP: {\n      return LiveMap._deserialize([id, crdt], parentToChildren, pool);\n    }\n    case CrdtType.REGISTER: {\n      return LiveRegister._deserialize([id, crdt], parentToChildren, pool);\n    }\n    default: {\n      throw new Error(\"Unexpected CRDT type\");\n    }\n  }\n}\n\nexport function deserializeToLson(\n  [id, crdt]: IdTuple<SerializedCrdt>,\n  parentToChildren: ParentToChildNodeMap,\n  pool: ManagedPool\n): Lson {\n  switch (crdt.type) {\n    case CrdtType.OBJECT: {\n      return LiveObject._deserialize([id, crdt], parentToChildren, pool);\n    }\n    case CrdtType.LIST: {\n      return LiveList._deserialize([id, crdt], parentToChildren, pool);\n    }\n    case CrdtType.MAP: {\n      return LiveMap._deserialize([id, crdt], parentToChildren, pool);\n    }\n    case CrdtType.REGISTER: {\n      return crdt.data;\n    }\n    default: {\n      throw new Error(\"Unexpected CRDT type\");\n    }\n  }\n}\n\nexport function isLiveStructure(value: unknown): value is LiveStructure {\n  return isLiveList(value) || isLiveMap(value) || isLiveObject(value);\n}\n\nexport function isLiveNode(value: unknown): value is LiveNode {\n  return isLiveStructure(value) || isLiveRegister(value);\n}\n\nexport function isLiveList(value: unknown): value is LiveList<Lson> {\n  return value instanceof LiveList;\n}\n\nexport function isLiveMap(value: unknown): value is LiveMap<string, Lson> {\n  return value instanceof LiveMap;\n}\n\nexport function isLiveObject(value: unknown): value is LiveObject<LsonObject> {\n  return value instanceof LiveObject;\n}\n\nexport function isLiveRegister(value: unknown): value is LiveRegister<Json> {\n  return value instanceof LiveRegister;\n}\n\nexport function cloneLson<L extends Lson | undefined>(value: L): L {\n  return value === undefined\n    ? (undefined as L)\n    : isLiveStructure(value)\n      ? (value.clone() as L)\n      : (deepClone(value) as L);\n}\n\nexport function liveNodeToLson(obj: LiveNode): Lson {\n  if (obj instanceof LiveRegister) {\n    return obj.data;\n  } else if (\n    obj instanceof LiveList ||\n    obj instanceof LiveMap ||\n    obj instanceof LiveObject\n  ) {\n    return obj;\n  } else {\n    return assertNever(obj, \"Unknown AbstractCrdt\");\n  }\n}\n\nexport function lsonToLiveNode(value: Lson): LiveNode {\n  if (\n    value instanceof LiveObject ||\n    value instanceof LiveMap ||\n    value instanceof LiveList\n  ) {\n    return value;\n  } else {\n    return new LiveRegister(value);\n  }\n}\n\nexport function getTreesDiffOperations(\n  currentItems: NodeMap,\n  newItems: NodeMap\n): Op[] {\n  const ops: Op[] = [];\n\n  currentItems.forEach((_, id) => {\n    if (!newItems.get(id)) {\n      // Delete crdt\n      ops.push({\n        type: OpCode.DELETE_CRDT,\n        id,\n      });\n    }\n  });\n\n  newItems.forEach((crdt, id) => {\n    const currentCrdt = currentItems.get(id);\n    if (currentCrdt) {\n      if (crdt.type === CrdtType.OBJECT) {\n        if (\n          currentCrdt.type !== CrdtType.OBJECT ||\n          stringify(crdt.data) !== stringify(currentCrdt.data)\n        ) {\n          ops.push({\n            type: OpCode.UPDATE_OBJECT,\n            id,\n            data: crdt.data,\n          });\n        }\n      }\n      if (crdt.parentKey !== currentCrdt.parentKey) {\n        ops.push({\n          type: OpCode.SET_PARENT_KEY,\n          id,\n          parentKey: nn(crdt.parentKey, \"Parent key must not be missing\"),\n        });\n      }\n    } else {\n      // new Crdt\n      switch (crdt.type) {\n        case CrdtType.REGISTER:\n          ops.push({\n            type: OpCode.CREATE_REGISTER,\n            id,\n            parentId: crdt.parentId,\n            parentKey: crdt.parentKey,\n            data: crdt.data,\n          });\n          break;\n        case CrdtType.LIST:\n          ops.push({\n            type: OpCode.CREATE_LIST,\n            id,\n            parentId: crdt.parentId,\n            parentKey: crdt.parentKey,\n          });\n          break;\n        case CrdtType.OBJECT:\n          if (crdt.parentId === undefined || crdt.parentKey === undefined) {\n            throw new Error(\n              \"Internal error. Cannot serialize storage root into an operation\"\n            );\n          }\n          ops.push({\n            type: OpCode.CREATE_OBJECT,\n            id,\n            parentId: crdt.parentId,\n            parentKey: crdt.parentKey,\n            data: crdt.data,\n          });\n          break;\n        case CrdtType.MAP:\n          ops.push({\n            type: OpCode.CREATE_MAP,\n            id,\n            parentId: crdt.parentId,\n            parentKey: crdt.parentKey,\n          });\n          break;\n      }\n    }\n  });\n\n  return ops;\n}\n\nfunction mergeObjectStorageUpdates<A extends LsonObject, B extends LsonObject>(\n  first: LiveObjectUpdates<A>,\n  second: LiveObjectUpdates<B>\n): LiveObjectUpdates<B> {\n  const updates = first.updates as (typeof second)[\"updates\"];\n  for (const [key, value] of entries(second.updates)) {\n    updates[key] = value;\n  }\n  return {\n    ...second,\n    updates,\n  };\n}\n\nfunction mergeMapStorageUpdates<K2 extends string, V2 extends Lson>(\n  first: LiveMapUpdates<string, Lson>,\n  second: LiveMapUpdates<K2, V2>\n): LiveMapUpdates<K2, V2> {\n  const updates = first.updates;\n  for (const [key, value] of entries(second.updates)) {\n    updates[key] = value;\n  }\n  return {\n    ...second,\n    updates,\n  };\n}\n\nfunction mergeListStorageUpdates<T extends Lson>(\n  first: LiveListUpdates<Lson>,\n  second: LiveListUpdates<T>\n): LiveListUpdates<T> {\n  const updates = first.updates;\n  return {\n    ...second,\n    updates: updates.concat(second.updates),\n  };\n}\n\nexport function mergeStorageUpdates(\n  first: StorageUpdate | undefined,\n  second: StorageUpdate\n): StorageUpdate {\n  if (first === undefined) {\n    return second;\n  }\n\n  if (first.type === \"LiveObject\" && second.type === \"LiveObject\") {\n    return mergeObjectStorageUpdates(first, second);\n  } else if (first.type === \"LiveMap\" && second.type === \"LiveMap\") {\n    return mergeMapStorageUpdates(first, second);\n  } else if (first.type === \"LiveList\" && second.type === \"LiveList\") {\n    return mergeListStorageUpdates(first, second);\n  } else {\n    /* Mismatching merge types. Throw an error here? */\n  }\n\n  return second;\n}\n\nfunction isPlain(\n  value: unknown\n): value is\n  | undefined\n  | null\n  | string\n  | boolean\n  | number\n  | unknown[]\n  | { [key: string]: unknown } {\n  const type = typeof value;\n  return (\n    value === undefined ||\n    value === null ||\n    type === \"string\" ||\n    type === \"boolean\" ||\n    type === \"number\" ||\n    Array.isArray(value) ||\n    isPlainObject(value)\n  );\n}\n\nexport function findNonSerializableValue(\n  value: unknown,\n  path: string = \"\"\n): { path: string; value: unknown } | false {\n  if (!isPlain) {\n    return {\n      path: path || \"root\",\n      value,\n    };\n  }\n\n  if (typeof value !== \"object\" || value === null) {\n    return false;\n  }\n\n  for (const [key, nestedValue] of Object.entries(value)) {\n    const nestedPath = path ? path + \".\" + key : key;\n\n    if (!isPlain(nestedValue)) {\n      return {\n        path: nestedPath,\n        value: nestedValue,\n      };\n    }\n\n    if (typeof nestedValue === \"object\") {\n      const nonSerializableNestedValue = findNonSerializableValue(\n        nestedValue,\n        nestedPath\n      );\n\n      if (nonSerializableNestedValue) {\n        return nonSerializableNestedValue;\n      }\n    }\n  }\n\n  return false;\n}\n","export function captureStackTrace(\n  msg: string,\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  traceRoot?: Function\n): string | undefined {\n  // Hack: Normally browsers will add the name of the error before the stack trace (default Error).\n  // To customize this, we set the name of the error to the message we want to display.\n  const errorLike: { name: string; stack?: string } = { name: msg };\n\n  // Error.captureStackTrace is non-standard and only available in certain browsers/runtimes.\n  if (typeof Error.captureStackTrace !== \"function\") {\n    return undefined;\n  }\n\n  Error.captureStackTrace(errorLike, traceRoot);\n  return errorLike.stack;\n}\n","import { raise } from \"./utils\";\n\n/**\n * A Deque (= Double Ended Queue) is like a stack, but where elements can be\n * efficiently pushed or popped from either side.\n *\n * The following calls are equivalent with arrays (but insertions are O(n)\n * instead of O(n^2)):\n *\n * - deque.push(1)              ⇔  array.push(1)\n * - deque.push([1, 2, 3])      ⇔  array.push(1, 2, 3)\n * - deque.push(many)           ⇔  array.push(...many)\n * - deque.pop()                ⇔  array.pop()\n *\n * - deque.pushLeft(1)          ⇔  array.unshift(1)\n * - deque.pushLeft([1, 2, 3])  ⇔  array.unshift(1, 2, 3)\n * - deque.pushLeft(many)       ⇔  array.unshift(...many)\n * - deque.popLeft()            ⇔  array.shift()\n *\n */\nexport class Deque<T> {\n  readonly #data: Record<number, T>;\n  #front: number;\n  #back: number;\n  #size: number;\n\n  constructor() {\n    this.#data = {};\n    this.#front = 0; // Inclusive\n    this.#back = 1; // Exclusive\n    this.#size = 0;\n  }\n\n  get length(): number {\n    return this.#size;\n  }\n\n  *[Symbol.iterator](): IterableIterator<T> {\n    const size = this.#size;\n    const front = this.#front;\n    for (let i = 0; i < size; i++) {\n      yield this.#data[front + i];\n    }\n  }\n\n  push(value: T | readonly T[]): void {\n    const values: readonly T[] = Array.isArray(value)\n      ? value\n      : ([value] as readonly T[]);\n    if (this.#back > Number.MAX_SAFE_INTEGER - values.length - 1)\n      raise(\"Deque full\");\n    for (const value of values) {\n      this.#data[this.#back++ - 1] = value;\n    }\n    this.#size += values.length;\n  }\n\n  pop(): T | undefined {\n    if (this.#size < 1) return undefined;\n\n    this.#back--;\n    const value = this.#data[this.#back - 1];\n    delete this.#data[this.#back - 1];\n    this.#size--;\n    return value;\n  }\n\n  pushLeft(value: T | readonly T[]): void {\n    const values: readonly T[] = Array.isArray(value)\n      ? value\n      : ([value] as readonly T[]);\n    if (this.#front < Number.MIN_SAFE_INTEGER + values.length)\n      raise(\"Deque full\");\n    for (let i = values.length - 1; i >= 0; i--) {\n      this.#data[--this.#front] = values[i];\n    }\n    this.#size += values.length;\n  }\n\n  popLeft(): T | undefined {\n    if (this.#size < 1) return undefined;\n\n    const value = this.#data[this.#front];\n    delete this.#data[this.#front];\n    this.#front++;\n    this.#size--;\n    return value;\n  }\n}\n","/**\n * Represents an indefinitely deep arbitrary JSON data structure. There are\n * four types that make up the Json family:\n *\n * - Json         any legal JSON value\n * - JsonScalar   any legal JSON leaf value (no lists or objects)\n * - JsonArray    a JSON value whose outer type is an array\n * - JsonObject   a JSON value whose outer type is an object\n *\n */\nexport type Json = JsonScalar | JsonArray | JsonObject;\nexport type JsonScalar = string | number | boolean | null;\nexport type JsonArray = Json[];\n/**\n * Any valid JSON object.\n */\nexport type JsonObject = { [key: string]: Json | undefined };\n\nexport function isJsonScalar(data: Json): data is JsonScalar {\n  return (\n    data === null ||\n    typeof data === \"string\" ||\n    typeof data === \"number\" ||\n    typeof data === \"boolean\"\n  );\n}\n\nexport function isJsonArray(data: Json): data is JsonArray {\n  return Array.isArray(data);\n}\n\nexport function isJsonObject(data: Json): data is JsonObject {\n  return !isJsonScalar(data) && !isJsonArray(data);\n}\n","import type { Json, JsonObject } from \"../lib/Json\";\nimport type { Op } from \"./Op\";\n\nexport enum ClientMsgCode {\n  // For Presence\n  UPDATE_PRESENCE = 100,\n  BROADCAST_EVENT = 103,\n\n  // For Storage\n  FETCH_STORAGE = 200,\n  UPDATE_STORAGE = 201,\n\n  // For Yjs support\n  FETCH_YDOC = 300,\n  UPDATE_YDOC = 301,\n}\n\n/**\n * Messages that can be sent from the client to the server.\n */\nexport type ClientMsg<P extends JsonObject, E extends Json> =\n  // For Presence\n  | BroadcastEventClientMsg<E>\n  | UpdatePresenceClientMsg<P>\n\n  // For Storage\n  | UpdateStorageClientMsg\n  | FetchStorageClientMsg\n\n  // For Yjs support\n  | FetchYDocClientMsg\n  | UpdateYDocClientMsg;\n\nexport type BroadcastEventClientMsg<E extends Json> = {\n  type: ClientMsgCode.BROADCAST_EVENT;\n  event: E;\n};\n\nexport type UpdatePresenceClientMsg<P extends JsonObject> =\n  //\n  // Full Presence™ message\n  //\n  | {\n      readonly type: ClientMsgCode.UPDATE_PRESENCE;\n      /**\n       * Set this to any number to signify that this is a Full Presence™\n       * update, not a patch.\n       *\n       * The numeric value itself no longer has specific meaning. Historically,\n       * this field was intended so that clients could ignore these broadcasted\n       * full presence messages, but it turned out that getting a full presence\n       * \"keyframe\" from time to time was useful.\n       *\n       * So nowadays, the presence (pun intended) of this `targetActor` field\n       * is a backward-compatible way of expressing that the `data` contains\n       * all presence fields, and isn't a partial \"patch\".\n       */\n      readonly targetActor: number;\n      readonly data: P;\n    }\n\n  //\n  // Partial Presence™ message\n  //\n  | {\n      readonly type: ClientMsgCode.UPDATE_PRESENCE;\n      /**\n       * Absence of the `targetActor` field signifies that this is a Partial\n       * Presence™ \"patch\".\n       */\n      readonly targetActor?: undefined;\n      readonly data: Partial<P>;\n    };\n\nexport type UpdateStorageClientMsg = {\n  readonly type: ClientMsgCode.UPDATE_STORAGE;\n  readonly ops: Op[];\n};\n\nexport type FetchStorageClientMsg = {\n  readonly type: ClientMsgCode.FETCH_STORAGE;\n};\n\nexport type FetchYDocClientMsg = {\n  readonly type: ClientMsgCode.FETCH_YDOC;\n  readonly vector: string; // base64 encoded stateVector a from yjs doc\n  readonly guid?: string; // an optional guid to identify a subdoc\n  readonly v2?: boolean; // if it's a v2 update\n};\n\nexport type UpdateYDocClientMsg = {\n  readonly type: ClientMsgCode.UPDATE_YDOC;\n  readonly update: string; // base64 encoded update from a yjs doc\n  readonly guid?: string; // an optional guid to identify a subdoc\n  readonly v2?: boolean; // if it's a v2 update\n};\n","import { freeze } from \"../lib/freeze\";\nimport type { JsonObject } from \"../lib/Json\";\nimport { DerivedSignal, merge, MutableSignal } from \"../lib/signals\";\nimport { compact, compactObject } from \"../lib/utils\";\nimport { canComment, canWriteStorage } from \"../protocol/AuthToken\";\nimport type { BaseUserMeta } from \"../protocol/BaseUserMeta\";\nimport type { User } from \"../types/User\";\n\ntype Connection<U extends BaseUserMeta> = {\n  readonly connectionId: number;\n  readonly scopes: string[];\n  readonly id: U[\"id\"];\n  readonly info: U[\"info\"];\n};\n\nfunction makeUser<P extends JsonObject, U extends BaseUserMeta>(\n  conn: Connection<U>,\n  presence: P\n): User<P, U> {\n  const { connectionId, id, info } = conn;\n  const canWrite = canWriteStorage(conn.scopes);\n  return freeze(\n    compactObject({\n      connectionId,\n      id,\n      info,\n      canWrite,\n      canComment: canComment(conn.scopes),\n      isReadOnly: !canWrite, // Deprecated, kept for backward-compatibility\n      presence,\n    })\n  );\n}\n\nexport class ManagedOthers<P extends JsonObject, U extends BaseUserMeta> {\n  // Track mutable state internally, but signal to the outside when the\n  // observable derived state changes only\n  readonly #internal: MutableSignal<{\n    connections: Map</* connectionId */ number, Connection<U>>;\n    presences: Map</* connectionId */ number, P>;\n  }>;\n  readonly #userCache: Map</* connectionId */ number, User<P, U>>;\n\n  // The \"clean\" signal that is exposed to the outside world\n  public readonly signal: DerivedSignal<readonly User<P, U>[]>;\n\n  constructor() {\n    this.#internal = new MutableSignal({\n      connections: new Map</* connectionId */ number, Connection<U>>(),\n      presences: new Map</* connectionId */ number, P>(),\n    });\n\n    this.signal = DerivedSignal.from(\n      this.#internal,\n      (_ignore): readonly User<P, U>[] =>\n        compact(\n          Array.from(this.#internal.get().presences.keys()).map(\n            (connectionId) => this.getUser(Number(connectionId))\n          )\n        )\n    );\n\n    // Others\n    this.#userCache = new Map();\n  }\n\n  // Shorthand for .signal.get()\n  get(): readonly User<P, U>[] {\n    return this.signal.get();\n  }\n\n  public connectionIds(): IterableIterator<number> {\n    return this.#internal.get().connections.keys();\n  }\n\n  clearOthers(): void {\n    this.#internal.mutate((state) => {\n      state.connections.clear();\n      state.presences.clear();\n      this.#userCache.clear();\n    });\n  }\n\n  #_getUser(connectionId: number): User<P, U> | undefined {\n    const state = this.#internal.get();\n    const conn = state.connections.get(connectionId);\n    const presence = state.presences.get(connectionId);\n    if (conn !== undefined && presence !== undefined) {\n      return makeUser(conn, presence);\n    }\n    return undefined;\n  }\n\n  getUser(connectionId: number): User<P, U> | undefined {\n    const cachedUser = this.#userCache.get(connectionId);\n    if (cachedUser) {\n      return cachedUser;\n    }\n\n    const computedUser = this.#_getUser(connectionId);\n    if (computedUser) {\n      this.#userCache.set(connectionId, computedUser);\n      return computedUser;\n    }\n\n    return undefined;\n  }\n\n  #invalidateUser(connectionId: number): void {\n    this.#userCache.delete(connectionId);\n  }\n\n  /**\n   * Records a known connection. This records the connection ID and the\n   * associated metadata.\n   */\n  setConnection(\n    connectionId: number,\n    metaUserId: U[\"id\"],\n    metaUserInfo: U[\"info\"],\n    scopes: string[]\n  ): void {\n    this.#internal.mutate((state) => {\n      state.connections.set(\n        connectionId,\n        freeze({\n          connectionId,\n          id: metaUserId,\n          info: metaUserInfo,\n          scopes,\n        })\n      );\n      if (!state.presences.has(connectionId)) {\n        return false;\n      }\n      return this.#invalidateUser(connectionId);\n    });\n  }\n\n  /**\n   * Removes a known connectionId. Removes both the connection's metadata and\n   * the presence information.\n   */\n  removeConnection(connectionId: number): void {\n    this.#internal.mutate((state) => {\n      state.connections.delete(connectionId);\n      state.presences.delete(connectionId);\n      this.#invalidateUser(connectionId);\n    });\n  }\n\n  /**\n   * Stores a new user from a full presence update. If the user already exists,\n   * its known presence data is overwritten.\n   */\n  setOther(connectionId: number, presence: P): void {\n    this.#internal.mutate((state) => {\n      state.presences.set(connectionId, freeze(compactObject(presence)));\n      if (!state.connections.has(connectionId)) {\n        return false;\n      }\n      return this.#invalidateUser(connectionId);\n    });\n  }\n\n  /**\n   * Patches the presence data for an existing \"other\". If we don't know the\n   * initial presence data for this user yet, discard this patch and await the\n   * full .setOther() call first.\n   */\n  patchOther(connectionId: number, patch: Partial<P>): void {\n    this.#internal.mutate((state) => {\n      const oldPresence = state.presences.get(connectionId);\n      if (oldPresence === undefined) {\n        return false;\n      }\n\n      const newPresence = merge(oldPresence, patch);\n      if (oldPresence === newPresence) {\n        return false;\n      }\n\n      state.presences.set(connectionId, freeze(newPresence));\n      return this.#invalidateUser(connectionId);\n    });\n  }\n}\n","import { assertNever } from \"../lib/assert\";\nimport type { Relax } from \"../lib/Relax\";\nimport type { BaseMetadata, CommentBody } from \"../protocol/Comments\";\nimport type { Patchable } from \"./Patchable\";\n\n// All possible error originating from using Presence, Storage, or Yjs\n\ntype AiConnectionErrorContext = {\n  type: \"AI_CONNECTION_ERROR\";\n  code: -1 | 4001 | (number & {}); // eslint-disable-line @typescript-eslint/ban-types\n};\n\ntype RoomConnectionErrorContext = {\n  type: \"ROOM_CONNECTION_ERROR\";\n  code: -1 | 4001 | 4005 | 4006 | (number & {}); // eslint-disable-line @typescript-eslint/ban-types\n  roomId: string;\n};\n\n// All possible errors originating from using Comments or Notifications\ntype CommentsOrNotificationsErrorContext =\n  | {\n      type: \"CREATE_THREAD_ERROR\";\n      roomId: string;\n      threadId: string;\n      commentId: string;\n      body: CommentBody;\n      metadata: BaseMetadata;\n    }\n  | {\n      type: \"DELETE_THREAD_ERROR\";\n      roomId: string;\n      threadId: string;\n    }\n  | {\n      type: \"EDIT_THREAD_METADATA_ERROR\";\n      roomId: string;\n      threadId: string;\n      metadata: Patchable<BaseMetadata>;\n    }\n  | {\n      type:\n        | \"MARK_THREAD_AS_RESOLVED_ERROR\"\n        | \"MARK_THREAD_AS_UNRESOLVED_ERROR\"\n        | \"SUBSCRIBE_TO_THREAD_ERROR\"\n        | \"UNSUBSCRIBE_FROM_THREAD_ERROR\";\n      roomId: string;\n      threadId: string;\n    }\n  | {\n      type: \"CREATE_COMMENT_ERROR\" | \"EDIT_COMMENT_ERROR\";\n      roomId: string;\n      threadId: string;\n      commentId: string;\n      body: CommentBody;\n    }\n  | {\n      type: \"DELETE_COMMENT_ERROR\";\n      roomId: string;\n      threadId: string;\n      commentId: string;\n    }\n  | {\n      type: \"ADD_REACTION_ERROR\" | \"REMOVE_REACTION_ERROR\";\n      roomId: string;\n      threadId: string;\n      commentId: string;\n      emoji: string;\n    }\n  | {\n      type: \"MARK_INBOX_NOTIFICATION_AS_READ_ERROR\";\n      inboxNotificationId: string;\n      roomId?: string;\n    }\n  | {\n      type: \"DELETE_INBOX_NOTIFICATION_ERROR\";\n      inboxNotificationId: string;\n    }\n  | {\n      type:\n        | \"MARK_ALL_INBOX_NOTIFICATIONS_AS_READ_ERROR\"\n        | \"DELETE_ALL_INBOX_NOTIFICATIONS_ERROR\";\n    }\n  | {\n      type: \"UPDATE_ROOM_SUBSCRIPTION_SETTINGS_ERROR\";\n      roomId: string;\n    }\n  | {\n      type: \"UPDATE_NOTIFICATION_SETTINGS_ERROR\";\n    };\n\nexport type LiveblocksErrorContext = Relax<\n  | RoomConnectionErrorContext // from Presence, Storage, or Yjs\n  | CommentsOrNotificationsErrorContext // from Comments or Notifications or UserNotificationSettings\n  | AiConnectionErrorContext // from AI\n>;\n\nexport class LiveblocksError extends Error {\n  public readonly context: LiveblocksErrorContext;\n\n  constructor(message: string, context: LiveblocksErrorContext, cause?: Error) {\n    super(message, { cause });\n    this.context = context;\n    this.name = \"LiveblocksError\";\n  }\n\n  /** Convenience accessor for error.context.roomId (if available) */\n  get roomId(): LiveblocksErrorContext[\"roomId\"] {\n    return this.context.roomId;\n  }\n\n  /** @internal Use `context.code` instead, to enable type narrowing */\n  get code(): LiveblocksErrorContext[\"code\"] {\n    return this.context.code;\n  }\n\n  /**\n   * Creates a LiveblocksError from a generic error, by attaching Liveblocks\n   * contextual information like room ID, thread ID, etc.\n   */\n  static from(context: LiveblocksErrorContext, cause?: Error): LiveblocksError {\n    return new LiveblocksError(\n      defaultMessageFromContext(context),\n      context,\n      cause\n    );\n  }\n}\n\n/**\n * Return a default, human-friendly error message for each possible error.\n */\nfunction defaultMessageFromContext(context: LiveblocksErrorContext): string {\n  // prettier-ignore\n  switch (context.type) {\n      case \"ROOM_CONNECTION_ERROR\": {\n        switch (context.code) {\n          case 4001: return \"Not allowed to connect to the room\";\n          case 4005: return \"Room is already full\";\n          case 4006: return \"Kicked out of the room, because the room ID changed\";\n          default:   return \"Could not connect to the room\";\n        }\n      }\n\n    case \"AI_CONNECTION_ERROR\": {\n      switch (context.code) {\n        case 4001: return \"Not allowed to connect to ai\";\n        default:   return \"Could not connect to the room\";\n      }\n    }\n\n    case \"CREATE_THREAD_ERROR\": return \"Could not create new thread\";\n    case \"DELETE_THREAD_ERROR\": return \"Could not delete thread\";\n    case \"EDIT_THREAD_METADATA_ERROR\": return \"Could not edit thread metadata\";\n    case \"MARK_THREAD_AS_RESOLVED_ERROR\": return \"Could not mark thread as resolved\";\n    case \"MARK_THREAD_AS_UNRESOLVED_ERROR\": return \"Could not mark thread as unresolved\";\n    case \"SUBSCRIBE_TO_THREAD_ERROR\": return \"Could not subscribe to thread\";\n    case \"UNSUBSCRIBE_FROM_THREAD_ERROR\": return \"Could not unsubscribe from thread\";\n    case \"CREATE_COMMENT_ERROR\": return \"Could not create new comment\";\n    case \"EDIT_COMMENT_ERROR\": return \"Could not edit comment\";\n    case \"DELETE_COMMENT_ERROR\": return \"Could not delete comment\";\n    case \"ADD_REACTION_ERROR\": return \"Could not add reaction\";\n    case \"REMOVE_REACTION_ERROR\": return \"Could not remove reaction\";\n    case \"MARK_INBOX_NOTIFICATION_AS_READ_ERROR\": return \"Could not mark inbox notification as read\";\n    case \"DELETE_INBOX_NOTIFICATION_ERROR\": return \"Could not delete inbox notification\";\n    case \"MARK_ALL_INBOX_NOTIFICATIONS_AS_READ_ERROR\": return \"Could not mark all inbox notifications as read\";\n    case \"DELETE_ALL_INBOX_NOTIFICATIONS_ERROR\": return \"Could not delete all inbox notifications\";\n    case \"UPDATE_ROOM_SUBSCRIPTION_SETTINGS_ERROR\": return \"Could not update room subscription settings\";\n    case \"UPDATE_NOTIFICATION_SETTINGS_ERROR\": return \"Could not update notification settings\";\n\n    default:\n      return assertNever(context, \"Unhandled case\");\n  }\n}\n","import { getBearerTokenFromAuthValue, type RoomHttpApi } from \"./api-client\";\nimport type { AuthManager, AuthValue } from \"./auth-manager\";\nimport type { InternalSyncStatus } from \"./client\";\nimport type { Delegates, LostConnectionEvent, Status } from \"./connection\";\nimport { ManagedSocket, StopRetrying } from \"./connection\";\nimport type { ApplyResult, ManagedPool } from \"./crdts/AbstractCrdt\";\nimport { createManagedPool, OpSource } from \"./crdts/AbstractCrdt\";\nimport {\n  cloneLson,\n  getTreesDiffOperations,\n  isLiveList,\n  isLiveNode,\n  isSameNodeOrChildOf,\n  mergeStorageUpdates,\n} from \"./crdts/liveblocks-helpers\";\nimport { LiveObject } from \"./crdts/LiveObject\";\nimport type { LiveStructure, LsonObject } from \"./crdts/Lson\";\nimport type { StorageCallback, StorageUpdate } from \"./crdts/StorageUpdates\";\nimport type { DE, DM, DP, DS, DU } from \"./globals/augmentation\";\nimport { kInternal } from \"./internal\";\nimport { assertNever, nn } from \"./lib/assert\";\nimport type { BatchStore } from \"./lib/batch\";\nimport { Promise_withResolvers } from \"./lib/controlledPromise\";\nimport { createCommentAttachmentId } from \"./lib/createIds\";\nimport { captureStackTrace } from \"./lib/debug\";\nimport { Deque } from \"./lib/Deque\";\nimport type { Callback, EventSource, Observable } from \"./lib/EventSource\";\nimport { makeEventSource } from \"./lib/EventSource\";\nimport * as console from \"./lib/fancy-console\";\nimport type { Json, JsonObject } from \"./lib/Json\";\nimport { isJsonArray, isJsonObject } from \"./lib/Json\";\nimport { asPos } from \"./lib/position\";\nimport { DerivedSignal, PatchableSignal, Signal } from \"./lib/signals\";\nimport { stringifyOrLog as stringify } from \"./lib/stringify\";\nimport {\n  compact,\n  deepClone,\n  memoizeOnSuccess,\n  raise,\n  tryParseJson,\n} from \"./lib/utils\";\nimport type {\n  ContextualPromptContext,\n  ContextualPromptResponse,\n} from \"./protocol/Ai\";\nimport type { Permission } from \"./protocol/AuthToken\";\nimport { canComment, canWriteStorage, TokenKind } from \"./protocol/AuthToken\";\nimport type { BaseUserMeta, IUserInfo } from \"./protocol/BaseUserMeta\";\nimport type {\n  ClientMsg,\n  UpdateStorageClientMsg,\n  UpdateYDocClientMsg,\n} from \"./protocol/ClientMsg\";\nimport { ClientMsgCode } from \"./protocol/ClientMsg\";\nimport type {\n  BaseMetadata,\n  CommentAttachment,\n  CommentBody,\n  CommentData,\n  CommentLocalAttachment,\n  CommentUserReaction,\n  QueryMetadata,\n  ThreadData,\n  ThreadDeleteInfo,\n} from \"./protocol/Comments\";\nimport type {\n  InboxNotificationData,\n  InboxNotificationDeleteInfo,\n} from \"./protocol/InboxNotifications\";\nimport type { Op } from \"./protocol/Op\";\nimport { isAckOp, OpCode } from \"./protocol/Op\";\nimport type { RoomSubscriptionSettings } from \"./protocol/RoomSubscriptionSettings\";\nimport type { IdTuple, SerializedCrdt } from \"./protocol/SerializedCrdt\";\nimport type {\n  CommentsEventServerMsg,\n  InitialDocumentStateServerMsg,\n  RoomStateServerMsg,\n  ServerMsg,\n  UpdatePresenceServerMsg,\n  UserJoinServerMsg,\n  UserLeftServerMsg,\n  YDocUpdateServerMsg,\n} from \"./protocol/ServerMsg\";\nimport { ServerMsgCode } from \"./protocol/ServerMsg\";\nimport type {\n  SubscriptionData,\n  SubscriptionDeleteInfo,\n} from \"./protocol/Subscriptions\";\nimport type { HistoryVersion } from \"./protocol/VersionHistory\";\nimport { ManagedOthers } from \"./refs/ManagedOthers\";\nimport type * as DevTools from \"./types/DevToolsTreeNode\";\nimport type {\n  IWebSocket,\n  IWebSocketCloseEvent,\n  IWebSocketInstance,\n  IWebSocketMessageEvent,\n} from \"./types/IWebSocket\";\nimport { LiveblocksError } from \"./types/LiveblocksError\";\nimport type { NodeMap } from \"./types/NodeMap\";\nimport type {\n  InternalOthersEvent,\n  OthersEvent,\n  TextEditorType,\n} from \"./types/Others\";\nimport type { Patchable } from \"./types/Patchable\";\nimport type { User } from \"./types/User\";\nimport { PKG_VERSION } from \"./version\";\n\nexport type TimeoutID = ReturnType<typeof setTimeout>;\n\n//\n// NOTE:\n// This type looks an awful lot like InternalOthersEvent, but don't change this\n// type definition or DRY this up!\n// The type LegacyOthersEvent is used in the signature of some public APIs, and\n// as such should remain backward compatible.\n//\ntype LegacyOthersEvent<P extends JsonObject, U extends BaseUserMeta> =\n  | { type: \"leave\"; user: User<P, U> }\n  | { type: \"enter\"; user: User<P, U> }\n  | {\n      type: \"update\";\n      user: User<P, U>;\n      updates: Partial<P>;\n    }\n  | { type: \"reset\" };\n\ntype LegacyOthersEventCallback<P extends JsonObject, U extends BaseUserMeta> = (\n  others: readonly User<P, U>[],\n  event: LegacyOthersEvent<P, U>\n) => void;\n\nexport type RoomEventMessage<\n  P extends JsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n> = {\n  /**\n   * The connection ID of the client that sent the event.\n   * If this message was broadcast from the server (via the REST API), then\n   * this value will be -1.\n   */\n  connectionId: number;\n  /**\n   * The User (from the others list) that sent the event.\n   * If this message was broadcast from the server (via the REST API), then\n   * this value will be null.\n   */\n  user: User<P, U> | null;\n  event: E;\n};\n\nexport type SyncStatus =\n  /* Liveblocks is in the process of writing changes */\n  | \"synchronizing\"\n  /* Liveblocks has persisted all pending changes */\n  | \"synchronized\";\n\nexport type StorageStatus =\n  /* The storage is not loaded and has not been requested. */\n  | \"not-loaded\"\n  /* The storage is loading from Liveblocks servers */\n  | \"loading\"\n  /* Some storage modifications has not been acknowledged yet by the server */\n  | \"synchronizing\"\n  /* The storage is sync with Liveblocks servers */\n  | \"synchronized\";\n\ntype RoomEventCallbackMap<\n  P extends JsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n> = {\n  status: Callback<Status>; // New/recommended API\n  \"lost-connection\": Callback<LostConnectionEvent>;\n  event: Callback<RoomEventMessage<P, U, E>>;\n  \"my-presence\": Callback<P>;\n  //\n  // NOTE: LegacyOthersEventCallback is the only one not taking a Callback<T>\n  // shape, since this API historically has taken _two_ callback arguments\n  // instead of just one.\n  others: LegacyOthersEventCallback<P, U>;\n  error: Callback<Error>;\n  history: Callback<HistoryEvent>;\n  \"storage-status\": Callback<StorageStatus>;\n  comments: Callback<CommentsEventServerMsg>;\n};\n\nexport interface History {\n  /**\n   * Undoes the last operation executed by the current client.\n   * It does not impact operations made by other clients.\n   *\n   * @example\n   * room.updatePresence({ selectedId: \"xx\" }, { addToHistory: true });\n   * room.updatePresence({ selectedId: \"yy\" }, { addToHistory: true });\n   * room.history.undo();\n   * // room.getPresence() equals { selectedId: \"xx\" }\n   */\n  undo: () => void;\n\n  /**\n   * Redoes the last operation executed by the current client.\n   * It does not impact operations made by other clients.\n   *\n   * @example\n   * room.updatePresence({ selectedId: \"xx\" }, { addToHistory: true });\n   * room.updatePresence({ selectedId: \"yy\" }, { addToHistory: true });\n   * room.history.undo();\n   * // room.getPresence() equals { selectedId: \"xx\" }\n   * room.history.redo();\n   * // room.getPresence() equals { selectedId: \"yy\" }\n   */\n  redo: () => void;\n\n  /**\n   * Returns whether there are any operations to undo.\n   *\n   * @example\n   * room.updatePresence({ selectedId: \"xx\" }, { addToHistory: true });\n   * // room.history.canUndo() is true\n   * room.history.undo();\n   * // room.history.canUndo() is false\n   */\n  canUndo: () => boolean;\n\n  /**\n   * Returns whether there are any operations to redo.\n   *\n   * @example\n   * room.updatePresence({ selectedId: \"xx\" }, { addToHistory: true });\n   * room.history.undo();\n   * // room.history.canRedo() is true\n   * room.history.redo();\n   * // room.history.canRedo() is false\n   */\n  canRedo: () => boolean;\n\n  /**\n   * Clears the undo and redo stacks. This operation cannot be undone ;)\n   */\n  clear: () => void;\n\n  /**\n   * All future modifications made on the Room will be merged together to create a single history item until resume is called.\n   *\n   * @example\n   * room.updatePresence({ cursor: { x: 0, y: 0 } }, { addToHistory: true });\n   * room.history.pause();\n   * room.updatePresence({ cursor: { x: 1, y: 1 } }, { addToHistory: true });\n   * room.updatePresence({ cursor: { x: 2, y: 2 } }, { addToHistory: true });\n   * room.history.resume();\n   * room.history.undo();\n   * // room.getPresence() equals { cursor: { x: 0, y: 0 } }\n   */\n  pause: () => void;\n\n  /**\n   * Resumes history. Modifications made on the Room are not merged into a single history item anymore.\n   *\n   * @example\n   * room.updatePresence({ cursor: { x: 0, y: 0 } }, { addToHistory: true });\n   * room.history.pause();\n   * room.updatePresence({ cursor: { x: 1, y: 1 } }, { addToHistory: true });\n   * room.updatePresence({ cursor: { x: 2, y: 2 } }, { addToHistory: true });\n   * room.history.resume();\n   * room.history.undo();\n   * // room.getPresence() equals { cursor: { x: 0, y: 0 } }\n   */\n  resume: () => void;\n}\n\nexport type HistoryEvent = {\n  canUndo: boolean;\n  canRedo: boolean;\n};\n\nexport type RoomEventName = Extract<\n  keyof RoomEventCallbackMap<never, never, never>,\n  string\n>;\n\nexport type RoomEventCallbackFor<\n  K extends RoomEventName,\n  P extends JsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n> = RoomEventCallbackMap<P, U, E>[K];\n\nexport type RoomEventCallback = RoomEventCallbackFor<\n  RoomEventName,\n  JsonObject,\n  BaseUserMeta,\n  Json\n>;\n\nexport type BroadcastOptions = {\n  /**\n   * Whether or not event is queued if the connection is currently closed.\n   *\n   * ❗ We are not sure if we want to support this option in the future so it might be deprecated to be replaced by something else\n   */\n  shouldQueueEventIfNotReady: boolean;\n};\n\ntype SubscribeFn<\n  P extends JsonObject,\n  _TStorage extends LsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n> = {\n  /**\n   * Subscribes to changes made on any Live structure. Returns an unsubscribe function.\n   *\n   * @internal This legacy API works, but was never documented publicly.\n   */\n  (callback: StorageCallback): () => void;\n\n  /**\n   * Subscribe to the current user presence updates.\n   *\n   * @param listener the callback that is called every time the current user presence is updated with {@link Room.updatePresence}.\n   *\n   * @returns Unsubscribe function.\n   *\n   * @example\n   * room.subscribe(\"my-presence\", (presence) => {\n   *   // Do something\n   * });\n   */\n  (type: \"my-presence\", listener: Callback<P>): () => void;\n\n  /**\n   * Subscribe to the other users updates.\n   *\n   * @param listener the callback that is called when a user enters or leaves the room or when a user update its presence.\n   *\n   * @returns Unsubscribe function.\n   *\n   * @example\n   * room.subscribe(\"others\", (others) => {\n   *   // Do something\n   * });\n   *\n   */\n  (type: \"others\", listener: LegacyOthersEventCallback<P, U>): () => void;\n\n  /**\n   * Subscribe to events broadcasted by {@link Room.broadcastEvent}\n   *\n   * @param listener the callback that is called when a user calls {@link Room.broadcastEvent}\n   *\n   * @returns Unsubscribe function.\n   *\n   * @example\n   * room.subscribe(\"event\", ({ event, connectionId }) => {\n   *   // Do something\n   * });\n   *\n   */\n  (type: \"event\", listener: Callback<RoomEventMessage<P, U, E>>): () => void;\n\n  /**\n   * Subscribe to errors thrown in the room.\n   *\n   * @returns Unsubscribe function.\n   *\n   */\n  (type: \"error\", listener: Callback<LiveblocksError>): () => void;\n\n  /**\n   * Subscribe to connection status updates. The callback will be called any\n   * time the status changes.\n   *\n   * @returns Unsubscribe function.\n   *\n   */\n  (type: \"status\", listener: Callback<Status>): () => void;\n\n  /**\n   * Subscribe to the exceptional event where reconnecting to the Liveblocks\n   * servers is taking longer than usual. This typically is a sign of a client\n   * that has lost internet connectivity.\n   *\n   * This isn't problematic (because the Liveblocks client is still trying to\n   * reconnect), but it's typically a good idea to inform users about it if\n   * the connection takes too long to recover.\n   */\n  (\n    type: \"lost-connection\",\n    listener: Callback<LostConnectionEvent>\n  ): () => void;\n\n  /**\n   * Subscribes to changes made on a Live structure. Returns an unsubscribe function.\n   *\n   * @param callback The callback this called when the Live structure changes.\n   *\n   * @returns Unsubscribe function.\n   *\n   * @example\n   * const liveMap = new LiveMap();  // Could also be LiveList or LiveObject\n   * const unsubscribe = room.subscribe(liveMap, (liveMap) => { });\n   * unsubscribe();\n   */\n  <L extends LiveStructure>(\n    liveStructure: L,\n    callback: (node: L) => void\n  ): () => void;\n\n  /**\n   * Subscribes to changes made on a Live structure and all the nested data\n   * structures. Returns an unsubscribe function. In a future version, we\n   * will also expose what exactly changed in the Live structure.\n   *\n   * @param callback The callback this called when the Live structure, or any\n   * of its nested values, changes.\n   *\n   * @returns Unsubscribe function.\n   *\n   * @example\n   * const liveMap = new LiveMap();  // Could also be LiveList or LiveObject\n   * const unsubscribe = room.subscribe(liveMap, (updates) => { }, { isDeep: true });\n   * unsubscribe();\n   */\n  <L extends LiveStructure>(\n    liveStructure: L,\n    callback: StorageCallback,\n    options: { isDeep: true }\n  ): () => void;\n\n  /**\n   * Subscribe to the current user's history changes.\n   *\n   * @returns Unsubscribe function.\n   *\n   * @example\n   * room.subscribe(\"history\", ({ canUndo, canRedo }) => {\n   *   // Do something\n   * });\n   */\n  (type: \"history\", listener: Callback<HistoryEvent>): () => void;\n\n  /**\n   * Subscribe to storage status changes.\n   *\n   * @returns Unsubscribe function.\n   *\n   * @example\n   * room.subscribe(\"storage-status\", (status) => {\n   *   switch(status) {\n   *      case \"not-loaded\":\n   *        break;\n   *      case \"loading\":\n   *        break;\n   *      case \"synchronizing\":\n   *        break;\n   *      case \"synchronized\":\n   *        break;\n   *      default:\n   *        break;\n   *   }\n   * });\n   */\n  (type: \"storage-status\", listener: Callback<StorageStatus>): () => void;\n\n  (type: \"comments\", listener: Callback<CommentsEventServerMsg>): () => void;\n};\n\nexport type GetThreadsOptions<M extends BaseMetadata> = {\n  cursor?: string;\n  query?: {\n    resolved?: boolean;\n    metadata?: Partial<QueryMetadata<M>>;\n  };\n};\n\nexport type GetThreadsSinceOptions = {\n  since: Date;\n  signal?: AbortSignal;\n};\n\nexport type UploadAttachmentOptions = {\n  signal?: AbortSignal;\n};\n\ntype ListTextVersionsSinceOptions = {\n  since: Date;\n  signal?: AbortSignal;\n};\n\ntype GetSubscriptionSettingsOptions = {\n  signal?: AbortSignal;\n};\n\n/**\n * @private Widest-possible Room type, matching _any_ Room instance. Note that\n * this type is different from `Room`-without-type-arguments. That represents\n * a Room instance using globally augmented types only, which is narrower.\n */\nexport type OpaqueRoom = Room<\n  JsonObject,\n  LsonObject,\n  BaseUserMeta,\n  Json,\n  BaseMetadata\n>;\n\nexport type Room<\n  P extends JsonObject = DP,\n  S extends LsonObject = DS,\n  U extends BaseUserMeta = DU,\n  E extends Json = DE,\n  M extends BaseMetadata = DM,\n> = {\n  /**\n   * @private\n   *\n   * Private methods and variables used in the core internals, but as a user\n   * of Liveblocks, NEVER USE ANY OF THESE DIRECTLY, because bad things\n   * will probably happen if you do.\n   */\n  readonly [kInternal]: PrivateRoomApi;\n\n  /**\n   * The id of the room.\n   */\n  readonly id: string;\n\n  /**\n   * Return the current connection status for this room. Can be used to display\n   * a status badge for your Liveblocks connection.\n   */\n  getStatus(): Status;\n  readonly subscribe: SubscribeFn<P, S, U, E>;\n\n  /**\n   * Room's history contains functions that let you undo and redo operation made on by the current client on the presence and storage.\n   */\n  readonly history: History;\n\n  /**\n   * Gets the current user.\n   * Returns null if not it is not yet connected to the room.\n   *\n   * @example\n   * const user = room.getSelf();\n   */\n  getSelf(): User<P, U> | null;\n\n  /**\n   * Gets the presence of the current user.\n   *\n   * @example\n   * const presence = room.getPresence();\n   */\n  getPresence(): P;\n\n  /**\n   * Gets all the other users in the room.\n   *\n   * @example\n   * const others = room.getOthers();\n   */\n  getOthers(): readonly User<P, U>[];\n\n  /**\n   * Updates the presence of the current user. Only pass the properties you want to update. No need to send the full presence.\n   * @param patch A partial object that contains the properties you want to update.\n   * @param options Optional object to configure the behavior of updatePresence.\n   *\n   * @example\n   * room.updatePresence({ x: 0 });\n   * room.updatePresence({ y: 0 });\n   *\n   * const presence = room.getPresence();\n   * // presence is equivalent to { x: 0, y: 0 }\n   */\n  updatePresence(\n    patch: Partial<P>,\n    options?: {\n      /**\n       * Whether or not the presence should have an impact on the undo/redo history.\n       */\n      addToHistory: boolean;\n    }\n  ): void;\n\n  /**\n   * Sends Yjs document updates to Liveblocks server.\n   *\n   * @param {string} data the doc update to send to the server, base64 encoded uint8array\n   */\n  updateYDoc(data: string, guid?: string, isV2?: boolean): void;\n\n  /**\n   * Sends a request for the current document from liveblocks server\n   */\n  fetchYDoc(stateVector: string, guid?: string, isV2?: boolean): void;\n\n  /**\n   * Broadcasts an event to other users in the room. Event broadcasted to the room can be listened with {@link Room.subscribe}(\"event\").\n   * @param {any} event the event to broadcast. Should be serializable to JSON\n   *\n   * @example\n   * // On client A\n   * room.broadcastEvent({ type: \"EMOJI\", emoji: \"🔥\" });\n   *\n   * // On client B\n   * room.subscribe(\"event\", ({ event }) => {\n   *   if(event.type === \"EMOJI\") {\n   *     // Do something\n   *   }\n   * });\n   */\n  broadcastEvent(event: E, options?: BroadcastOptions): void;\n\n  /**\n   * Get the room's storage asynchronously.\n   * The storage's root is a {@link LiveObject}.\n   *\n   * @example\n   * const { root } = await room.getStorage();\n   */\n  getStorage(): Promise<{\n    root: LiveObject<S>;\n  }>;\n\n  /**\n   * Get the room's storage synchronously.\n   * The storage's root is a {@link LiveObject}.\n   *\n   * @example\n   * const root = room.getStorageSnapshot();\n   */\n  getStorageSnapshot(): LiveObject<S> | null;\n\n  /**\n   * All possible room events, subscribable from a single place.\n   *\n   * @private These event sources are private for now, but will become public\n   * once they're stable.\n   */\n  readonly events: {\n    readonly status: Observable<Status>;\n    readonly lostConnection: Observable<LostConnectionEvent>;\n\n    readonly customEvent: Observable<RoomEventMessage<P, U, E>>; // prettier-ignore\n    readonly self: Observable<User<P, U>>;\n    readonly myPresence: Observable<P>;\n    readonly others: Observable<OthersEvent<P, U>>;\n    readonly storageBatch: Observable<StorageUpdate[]>;\n    readonly history: Observable<HistoryEvent>;\n\n    /**\n     * Subscribe to the storage loaded event. Will fire any time a full Storage\n     * copy is downloaded. (This happens after the initial connect, and on\n     * every reconnect.)\n     */\n    readonly storageDidLoad: Observable<void>;\n\n    readonly storageStatus: Observable<StorageStatus>;\n    readonly ydoc: Observable<YDocUpdateServerMsg | UpdateYDocClientMsg>;\n    readonly comments: Observable<CommentsEventServerMsg>;\n\n    /**\n     * Called right before the room is destroyed. The event cannot be used to\n     * prevent the room from being destroyed, only to be informed that this is\n     * imminent.\n     */\n    readonly roomWillDestroy: Observable<void>;\n  };\n\n  /**\n   * Batches modifications made during the given function.\n   * All the modifications are sent to other clients in a single message.\n   * All the subscribers are called only after the batch is over.\n   * All the modifications are merged in a single history item (undo/redo).\n   *\n   * @example\n   * const { root } = await room.getStorage();\n   * room.batch(() => {\n   *   root.set(\"x\", 0);\n   *   room.updatePresence({ cursor: { x: 100, y: 100 }});\n   * });\n   */\n  batch<T>(fn: () => T): T;\n\n  /**\n   * Get the storage status.\n   *\n   * - `not-loaded`: Initial state when entering the room.\n   * - `loading`: Once the storage has been requested via room.getStorage().\n   * - `synchronizing`: When some local updates have not been acknowledged by Liveblocks servers.\n   * - `synchronized`: Storage is in sync with Liveblocks servers.\n   */\n  getStorageStatus(): StorageStatus;\n\n  isPresenceReady(): boolean;\n  isStorageReady(): boolean;\n\n  /**\n   * Returns a Promise that resolves as soon as Presence is available, which\n   * happens shortly after the WebSocket connection has been established. Once\n   * this happens, `self` and `others` are known and available to use. After\n   * awaiting this promise, `.isPresenceReady()` will be guaranteed to be true.\n   * Even when calling this function multiple times, it's guaranteed to return\n   * the same Promise instance.\n   */\n  waitUntilPresenceReady(): Promise<void>;\n\n  /**\n   * Returns a Promise that resolves as soon as Storage has been loaded and\n   * available. After awaiting this promise, `.isStorageReady()` will be\n   * guaranteed to be true. Even when calling this function multiple times,\n   * it's guaranteed to return the same Promise instance.\n   */\n  waitUntilStorageReady(): Promise<void>;\n\n  /**\n   * Start an attempt to connect the room (aka \"enter\" it). Calling\n   * `.connect()` only has an effect if the room is still in its idle initial\n   * state, or the room was explicitly disconnected, or reconnection attempts\n   * were stopped (for example, because the user isn't authorized to enter the\n   * room). Will be a no-op otherwise.\n   */\n  connect(): void;\n\n  /**\n   * Disconnect the room's connection to the Liveblocks server, if any. Puts\n   * the room back into an idle state. It will not do anything until either\n   * `.connect()` or `.reconnect()` is called.\n   *\n   * Only use this API if you wish to connect the room again at a later time.\n   * If you want to disconnect the room because you no longer need it, call\n   * `.destroy()` instead.\n   */\n  disconnect(): void;\n\n  /**\n   * @internal (for now)\n   *\n   * Disconnect the room's connection to the Liveblocks server, if any. Runs\n   * cleanup functions. The room instance can no longer be used to (re)connect.\n   */\n  destroy(): void;\n\n  /**\n   * Reconnect the room to the Liveblocks server by re-establishing a fresh\n   * connection. If the room is not connected yet, initiate it.\n   */\n  reconnect(): void;\n\n  /**\n   * Returns the threads within the current room and their associated inbox notifications.\n   * It also returns the request date that can be used for subsequent polling.\n   *\n   * @example\n   * const {\n   *   threads,\n   *   inboxNotifications,\n   *   subscriptions,\n   *   requestedAt\n   * } = await room.getThreads({ query: { resolved: false }});\n   */\n  getThreads(options?: GetThreadsOptions<M>): Promise<{\n    threads: ThreadData<M>[];\n    inboxNotifications: InboxNotificationData[];\n    subscriptions: SubscriptionData[];\n    requestedAt: Date;\n    nextCursor: string | null;\n    permissionHints: Record<string, Permission[]>;\n  }>;\n\n  /**\n   * Returns the updated and deleted threads and their associated inbox notifications and subscriptions since the requested date.\n   *\n   * @example\n   * const result = await room.getThreads();\n   * // ... //\n   * await room.getThreadsSince({ since: result.requestedAt });\n   */\n  getThreadsSince(options: GetThreadsSinceOptions): Promise<{\n    threads: {\n      updated: ThreadData<M>[];\n      deleted: ThreadDeleteInfo[];\n    };\n    inboxNotifications: {\n      updated: InboxNotificationData[];\n      deleted: InboxNotificationDeleteInfo[];\n    };\n    subscriptions: {\n      updated: SubscriptionData[];\n      deleted: SubscriptionDeleteInfo[];\n    };\n    requestedAt: Date;\n    permissionHints: Record<string, Permission[]>;\n  }>;\n\n  /**\n   * Returns a thread and the associated inbox notification and subscription if it exists.\n   *\n   * @example\n   * const { thread, inboxNotification, subscription } = await room.getThread(\"th_xxx\");\n   */\n  getThread(threadId: string): Promise<{\n    thread?: ThreadData<M>;\n    inboxNotification?: InboxNotificationData;\n    subscription?: SubscriptionData;\n  }>;\n\n  /**\n   * Creates a thread.\n   *\n   * @example\n   * const thread = await room.createThread({\n   *   body: {\n   *     version: 1,\n   *     content: [{ type: \"paragraph\", children: [{ text: \"Hello\" }] }],\n   *   },\n   * })\n   */\n  createThread(options: {\n    threadId?: string;\n    commentId?: string;\n    metadata: M | undefined;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }): Promise<ThreadData<M>>;\n\n  /**\n   * Deletes a thread.\n   *\n   * @example\n   * await room.deleteThread(\"th_xxx\");\n   */\n  deleteThread(threadId: string): Promise<void>;\n\n  /**\n   * Edits a thread's metadata.\n   * To delete an existing metadata property, set its value to `null`.\n   *\n   * @example\n   * await room.editThreadMetadata({ threadId: \"th_xxx\", metadata: { x: 100, y: 100 } })\n   */\n  editThreadMetadata(options: {\n    metadata: Patchable<M>;\n    threadId: string;\n  }): Promise<M>;\n\n  /**\n   * Marks a thread as resolved.\n   *\n   * @example\n   * await room.markThreadAsResolved(\"th_xxx\");\n   */\n  markThreadAsResolved(threadId: string): Promise<void>;\n\n  /**\n   * Marks a thread as unresolved.\n   *\n   * @example\n   * await room.markThreadAsUnresolved(\"th_xxx\");\n   */\n  markThreadAsUnresolved(threadId: string): Promise<void>;\n\n  /**\n   * Subscribes the user to a thread.\n   *\n   * @example\n   * await room.subscribeToThread(\"th_xxx\");\n   */\n  subscribeToThread(threadId: string): Promise<SubscriptionData>;\n\n  /**\n   * Unsubscribes the user from a thread.\n   *\n   * @example\n   * await room.unsubscribeFromThread(\"th_xxx\");\n   */\n  unsubscribeFromThread(threadId: string): Promise<void>;\n\n  /**\n   * Creates a comment.\n   *\n   * @example\n   * await room.createComment({\n   *   threadId: \"th_xxx\",\n   *   body: {\n   *     version: 1,\n   *     content: [{ type: \"paragraph\", children: [{ text: \"Hello\" }] }],\n   *   },\n   * });\n   */\n  createComment(options: {\n    threadId: string;\n    commentId?: string;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }): Promise<CommentData>;\n\n  /**\n   * Edits a comment.\n   *\n   * @example\n   * await room.editComment({\n   *   threadId: \"th_xxx\",\n   *   commentId: \"cm_xxx\"\n   *   body: {\n   *     version: 1,\n   *     content: [{ type: \"paragraph\", children: [{ text: \"Hello\" }] }],\n   *   },\n   * });\n   */\n  editComment(options: {\n    threadId: string;\n    commentId: string;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }): Promise<CommentData>;\n\n  /**\n   * Deletes a comment.\n   * If it is the last non-deleted comment, the thread also gets deleted.\n   *\n   * @example\n   * await room.deleteComment({\n   *   threadId: \"th_xxx\",\n   *   commentId: \"cm_xxx\"\n   * });\n   */\n  deleteComment(options: {\n    threadId: string;\n    commentId: string;\n  }): Promise<void>;\n\n  /**\n   * Adds a reaction from a comment for the current user.\n   *\n   * @example\n   * await room.addReaction({ threadId: \"th_xxx\", commentId: \"cm_xxx\", emoji: \"👍\" })\n   */\n  addReaction(options: {\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }): Promise<CommentUserReaction>;\n\n  /**\n   * Removes a reaction from a comment.\n   *\n   * @example\n   * await room.removeReaction({ threadId: \"th_xxx\", commentId: \"cm_xxx\", emoji: \"👍\" })\n   */\n  removeReaction(options: {\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }): Promise<void>;\n\n  /**\n   * Creates a local attachment from a file.\n   *\n   * @example\n   * room.prepareAttachment(file);\n   */\n  prepareAttachment(file: File): CommentLocalAttachment;\n\n  /**\n   * Uploads a local attachment.\n   *\n   * @example\n   * const attachment = room.prepareAttachment(file);\n   * await room.uploadAttachment(attachment);\n   */\n  uploadAttachment(\n    attachment: CommentLocalAttachment,\n    options?: UploadAttachmentOptions\n  ): Promise<CommentAttachment>;\n\n  /**\n   * Returns a presigned URL for an attachment by its ID.\n   *\n   * @example\n   * await room.getAttachmentUrl(\"at_xxx\");\n   */\n  getAttachmentUrl(attachmentId: string): Promise<string>;\n\n  /**\n   * Gets the user's subscription settings for the current room.\n   *\n   * @example\n   * const settings = await room.getSubscriptionSettings();\n   */\n  getSubscriptionSettings(\n    options?: GetSubscriptionSettingsOptions\n  ): Promise<RoomSubscriptionSettings>;\n\n  /**\n   * Updates the user's subscription settings for the current room.\n   *\n   * @example\n   * await room.updateSubscriptionSettings({ threads: \"replies_and_mentions\" });\n   */\n  updateSubscriptionSettings(\n    settings: Partial<RoomSubscriptionSettings>\n  ): Promise<RoomSubscriptionSettings>;\n\n  /**\n   * @private\n   *\n   * Internal use only. Signature might change in the future.\n   */\n  markInboxNotificationAsRead(notificationId: string): Promise<void>;\n};\n\nexport type YjsSyncStatus = \"loading\" | \"synchronizing\" | \"synchronized\";\n\n/**\n * Interface that @liveblocks/yjs must respect.\n * This interface type is declare in @liveblocks/core, so we don't have to\n * depend on `yjs`. It's only used to determine the API contract between\n * @liveblocks/core and @liveblocks/yjs.\n */\nexport interface IYjsProvider {\n  synced: boolean;\n  getStatus: () => YjsSyncStatus;\n  on(event: \"sync\", listener: (synced: boolean) => void): void;\n  on(event: \"status\", listener: (status: YjsSyncStatus) => void): void;\n  off(event: \"sync\", listener: (synced: boolean) => void): void;\n  off(event: \"status\", listener: (status: YjsSyncStatus) => void): void;\n}\n\n/**\n * A \"Sync Source\" can be a Storage document, a Yjs document, Comments,\n * Notifications, etc.\n * The Client keeps a registry of all active sync sources, and will use it to\n * determine the global \"sync status\" for Liveblocks.\n */\nexport interface SyncSource {\n  setSyncStatus(status: InternalSyncStatus): void;\n  destroy(): void;\n}\n\n/**\n * @private\n *\n * Private methods to directly control the underlying state machine for this\n * room. Used in the core internals and for unit testing, but as a user of\n * Liveblocks, NEVER USE ANY OF THESE METHODS DIRECTLY, because bad things\n * will probably happen if you do.\n */\nexport type PrivateRoomApi = {\n  // For introspection in unit tests only\n  presenceBuffer: Json | undefined;\n  undoStack: readonly (readonly Readonly<HistoryOp<JsonObject>>[])[];\n  nodeCount: number;\n\n  // Get/set the associated Yjs provider on this room\n  getYjsProvider(): IYjsProvider | undefined;\n  setYjsProvider(provider: IYjsProvider | undefined): void;\n  yjsProviderDidChange: Observable<void>;\n\n  // For DevTools support (Liveblocks browser extension)\n  getSelf_forDevTools(): DevTools.UserTreeNode | null;\n  getOthers_forDevTools(): readonly DevTools.UserTreeNode[];\n\n  // For reporting editor metadata\n  reportTextEditor(editor: TextEditorType, rootKey: string): Promise<void>;\n\n  createTextMention(userId: string, mentionId: string): Promise<void>;\n  deleteTextMention(mentionId: string): Promise<void>;\n  listTextVersions(): Promise<{\n    versions: HistoryVersion[];\n    requestedAt: Date;\n  }>;\n  listTextVersionsSince(options: ListTextVersionsSinceOptions): Promise<{\n    versions: HistoryVersion[];\n    requestedAt: Date;\n  }>;\n\n  getTextVersion(versionId: string): Promise<Response>;\n  createTextVersion(): Promise<void>;\n\n  executeContextualPrompt(options: {\n    prompt: string;\n    context: ContextualPromptContext;\n    previous?: {\n      prompt: string;\n      response: ContextualPromptResponse;\n    };\n    signal: AbortSignal;\n  }): Promise<string>;\n\n  // NOTE: These are only used in our e2e test app!\n  simulate: {\n    explicitClose(event: IWebSocketCloseEvent): void;\n    rawSend(data: string): void;\n  };\n\n  attachmentUrlsStore: BatchStore<string, string>;\n};\n\n//\n// The maximum message size on websockets is 1MB. If a message larger than this\n// threshold is attempted to be sent, the strategy picked via the\n// `largeMessageStrategy` option will be used.\n//\n// In practice, we'll set threshold to slightly less than 1 MB.\nconst MAX_SOCKET_MESSAGE_SIZE = 1024 * 1024 - 512;\n\nfunction makeIdFactory(connectionId: number): IdFactory {\n  let count = 0;\n  return () => `${connectionId}:${count++}`;\n}\n\ntype HistoryOp<P extends JsonObject> =\n  | Op\n  | {\n      readonly type: \"presence\";\n      readonly data: P;\n    };\n\ntype IdFactory = () => string;\n\nexport type StaticSessionInfo = {\n  readonly userId?: string;\n  readonly userInfo?: IUserInfo;\n};\n\nexport type DynamicSessionInfo = {\n  readonly actor: number;\n  readonly nonce: string;\n  readonly scopes: string[];\n};\n\ntype RoomState<\n  P extends JsonObject,\n  S extends LsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n> = {\n  /**\n   * All pending changes that yet need to be synced.\n   */\n  buffer: {\n    flushTimerID: TimeoutID | undefined;\n\n    // When the last flush happened. Together with config.throttleDelay, this\n    // will control whether the next flush will be sent out immediately, or if\n    // a flush will get scheduled for a few milliseconds into the future.\n    readonly lastFlushedAt: number;\n\n    // Queued-up \"my presence\" updates to be flushed at the earliest convenience\n    presenceUpdates:\n      | { type: \"partial\"; data: Partial<P> }\n      | { type: \"full\"; data: P }\n      | null;\n    messages: ClientMsg<P, E>[];\n    storageOperations: Op[];\n  };\n\n  //\n  // The \"self\" User takes assembly of three sources-of-truth:\n  // - The JWT token provides the userId and userInfo metadata (static)\n  // - The server, in its initial ROOM_STATE message, will provide the actor ID\n  //   and the scopes (dynamic)\n  // - The presence is provided by the client's initialPresence configuration (presence)\n  //\n  readonly staticSessionInfoSig: Signal<StaticSessionInfo | null>;\n  readonly dynamicSessionInfoSig: Signal<DynamicSessionInfo | null>;\n  readonly myPresence: PatchableSignal<P>;\n  readonly others: ManagedOthers<P, U>;\n\n  idFactory: IdFactory | null;\n  initialStorage: S;\n\n  yjsProvider: IYjsProvider | undefined;\n  readonly yjsProviderDidChange: EventSource<void>;\n\n  pool: ManagedPool;\n  root: LiveObject<S> | undefined;\n\n  readonly undoStack: HistoryOp<P>[][];\n  readonly redoStack: HistoryOp<P>[][];\n\n  /**\n   * When history is paused, all operations will get queued up here. When\n   * history is resumed, these operations get \"committed\" to the undo stack.\n   */\n  pausedHistory: null | Deque<HistoryOp<P>>;\n\n  /**\n   * Place to collect all mutations during a batch. Ops will be sent over the\n   * wire after the batch is ended.\n   */\n  activeBatch: {\n    ops: Op[];\n    reverseOps: Deque<HistoryOp<P>>;\n    updates: {\n      others: [];\n      presence: boolean;\n      storageUpdates: Map<string, StorageUpdate>;\n    };\n  } | null;\n\n  // A registry of yet-unacknowledged Ops. These Ops have already been\n  // submitted to the server, but have not yet been acknowledged.\n  readonly unacknowledgedOps: Map<string, Op>;\n\n  // Stack traces of all pending Ops. Used for debugging in non-production builds\n  readonly opStackTraces?: Map<string, string>;\n};\n\nexport type Polyfills = {\n  atob?: (data: string) => string;\n  fetch?: typeof fetch;\n  WebSocket?: IWebSocket;\n};\n\n/**\n * Makes all tuple positions optional.\n * Example, turns:\n *   [foo: string; bar: number]\n * into:\n *   [foo?: string; bar?: number]\n */\ntype OptionalTuple<T extends any[]> = { [K in keyof T]?: T[K] };\n\n/**\n * Returns Partial<T> if all fields on C are optional, T otherwise.\n */\nexport type PartialUnless<C, T> =\n  Record<string, never> extends C\n    ? Partial<T>\n    : // Extra test. We'll want to treat \"never\" as if the empty object is\n      // assignable to it, because otherwise it will not\n      [C] extends [never]\n      ? Partial<T>\n      : T;\n\n/**\n * Returns OptionalTupleUnless<T> if all fields on C are optional, T otherwise.\n */\nexport type OptionalTupleUnless<C, T extends any[]> =\n  Record<string, never> extends C\n    ? OptionalTuple<T>\n    : // Extra test. We'll want to treat \"never\" as if the empty object is\n      // assignable to it, because otherwise it will not\n      [C] extends [never]\n      ? OptionalTuple<T>\n      : T;\n\nexport type RoomDelegates = Omit<Delegates<AuthValue>, \"canZombie\">;\n\nexport type LargeMessageStrategy =\n  | \"default\"\n  | \"split\"\n  | \"experimental-fallback-to-http\";\n\n/** @internal */\nexport type RoomConfig<M extends BaseMetadata> = {\n  delegates: RoomDelegates;\n\n  roomId: string;\n  throttleDelay: number;\n  lostConnectionTimeout: number;\n  backgroundKeepAliveTimeout?: number;\n  largeMessageStrategy?: LargeMessageStrategy;\n\n  unstable_streamData?: boolean;\n\n  polyfills?: Polyfills;\n\n  roomHttpClient: RoomHttpApi<M>;\n\n  baseUrl: string;\n  enableDebugLogging?: boolean;\n\n  // We would not have to pass this complicated factory/callback functions to\n  // the createRoom() function if we would simply pass the Client instance to\n  // the Room instance, so it can directly call this back on the Client.\n  createSyncSource: () => SyncSource;\n  errorEventSource: EventSource<LiveblocksError>;\n};\n\nfunction userToTreeNode(\n  key: string,\n  user: User<JsonObject, BaseUserMeta>\n): DevTools.UserTreeNode {\n  return {\n    type: \"User\",\n    id: `${user.connectionId}`,\n    key,\n    payload: {\n      connectionId: user.connectionId,\n      id: user.id,\n      info: user.info,\n      presence: user.presence,\n      isReadOnly: !user.canWrite,\n    },\n  };\n}\n\n/**\n * Returns a ref to access if, and if so, how long the current tab is in the\n * background and an unsubscribe function.\n *\n * The `inBackgroundSince` value will either be a JS timestamp indicating the\n * moment the tab was put into the background, or `null` in case the tab isn't\n * currently in the background. In non-DOM environments, this will always\n * return `null`.\n */\nfunction installBackgroundTabSpy(): [\n  inBackgroundSince: { readonly current: number | null },\n  unsub: () => void,\n] {\n  const doc = typeof document !== \"undefined\" ? document : undefined;\n  const inBackgroundSince: { current: number | null } = { current: null };\n\n  function onVisibilityChange() {\n    if (doc?.visibilityState === \"hidden\") {\n      inBackgroundSince.current = inBackgroundSince.current ?? Date.now();\n    } else {\n      inBackgroundSince.current = null;\n    }\n  }\n\n  doc?.addEventListener(\"visibilitychange\", onVisibilityChange);\n  const unsub = () => {\n    doc?.removeEventListener(\"visibilitychange\", onVisibilityChange);\n  };\n\n  return [inBackgroundSince, unsub];\n}\n\n/**\n * @internal\n * Initializes a new Room, and returns its public API.\n */\nexport function createRoom<\n  P extends JsonObject,\n  S extends LsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n  M extends BaseMetadata,\n>(\n  options: { initialPresence: P; initialStorage: S },\n  config: RoomConfig<M>\n): Room<P, S, U, E, M> {\n  const roomId = config.roomId;\n  const initialPresence = options.initialPresence; // ?? {};\n  const initialStorage = options.initialStorage; // ?? {};\n\n  const httpClient = config.roomHttpClient;\n\n  const [inBackgroundSince, uninstallBgTabSpy] = installBackgroundTabSpy();\n\n  // Create a delegate pair for (a specific) Live Room socket connection(s)\n  const delegates = {\n    ...config.delegates,\n\n    // A connection is allowed to go into \"zombie state\" only if all of the\n    // following conditions apply:\n    //\n    // - The `backgroundKeepAliveTimeout` client option is configured\n    // - The browser window has been in the background for at least\n    //   `backgroundKeepAliveTimeout` milliseconds\n    // - There are no pending changes\n    //\n    canZombie() {\n      return (\n        config.backgroundKeepAliveTimeout !== undefined &&\n        inBackgroundSince.current !== null &&\n        Date.now() >\n          inBackgroundSince.current + config.backgroundKeepAliveTimeout &&\n        getStorageStatus() !== \"synchronizing\"\n      );\n    },\n  };\n\n  const managedSocket: ManagedSocket<AuthValue> = new ManagedSocket(\n    delegates,\n    config.enableDebugLogging\n  );\n\n  // The room's internal stateful context\n  const context: RoomState<P, S, U, E> = {\n    buffer: {\n      flushTimerID: undefined,\n      lastFlushedAt: 0,\n      presenceUpdates:\n        // Queue up the initial presence message as a Full Presence™ update\n        {\n          type: \"full\",\n          data: initialPresence,\n        },\n      messages: [],\n      storageOperations: [],\n    },\n\n    staticSessionInfoSig: new Signal<StaticSessionInfo | null>(null),\n    dynamicSessionInfoSig: new Signal<DynamicSessionInfo | null>(null),\n    myPresence: new PatchableSignal(initialPresence),\n    others: new ManagedOthers<P, U>(),\n\n    initialStorage,\n    idFactory: null,\n\n    // The Yjs provider associated to this room\n    yjsProvider: undefined,\n    yjsProviderDidChange: makeEventSource(),\n\n    // Storage\n    pool: createManagedPool(roomId, {\n      getCurrentConnectionId,\n      onDispatch,\n      isStorageWritable,\n    }),\n    root: undefined,\n\n    undoStack: [],\n    redoStack: [],\n    pausedHistory: null,\n\n    activeBatch: null,\n    unacknowledgedOps: new Map<string, Op>(),\n\n    // Debug\n    opStackTraces:\n      process.env.NODE_ENV !== \"production\"\n        ? new Map<string, string>()\n        : undefined,\n  };\n\n  let lastTokenKey: string | undefined;\n  function onStatusDidChange(newStatus: Status) {\n    const authValue = managedSocket.authValue;\n    if (authValue !== null) {\n      const tokenKey = getBearerTokenFromAuthValue(authValue);\n\n      if (tokenKey !== lastTokenKey) {\n        lastTokenKey = tokenKey;\n\n        if (authValue.type === \"secret\") {\n          const token = authValue.token.parsed;\n          context.staticSessionInfoSig.set({\n            userId: token.k === TokenKind.SECRET_LEGACY ? token.id : token.uid,\n            userInfo:\n              token.k === TokenKind.SECRET_LEGACY ? token.info : token.ui,\n          });\n        } else {\n          context.staticSessionInfoSig.set({\n            userId: undefined,\n            userInfo: undefined,\n          });\n        }\n      }\n    }\n\n    // Forward to the outside world\n    eventHub.status.notify(newStatus);\n    notifySelfChanged();\n  }\n\n  let _connectionLossTimerId: TimeoutID | undefined;\n  let _hasLostConnection = false;\n\n  function handleConnectionLossEvent(newStatus: Status) {\n    if (newStatus === \"reconnecting\") {\n      _connectionLossTimerId = setTimeout(() => {\n        eventHub.lostConnection.notify(\"lost\");\n        _hasLostConnection = true;\n\n        // Clear the others\n        context.others.clearOthers();\n        notify({ others: [{ type: \"reset\" }] });\n      }, config.lostConnectionTimeout);\n    } else {\n      clearTimeout(_connectionLossTimerId);\n\n      if (_hasLostConnection) {\n        if (newStatus === \"disconnected\") {\n          eventHub.lostConnection.notify(\"failed\");\n        } else {\n          // Typically the case when going back to \"connected\", but really take\n          // *any* other state change as a recovery sign\n          eventHub.lostConnection.notify(\"restored\");\n        }\n\n        _hasLostConnection = false;\n      }\n    }\n  }\n\n  function onDidConnect() {\n    // Re-broadcast the full user presence as soon as we (re)connect\n    context.buffer.presenceUpdates = {\n      type: \"full\",\n      data:\n        // Because context.me.current is a readonly object, we'll have to\n        // make a copy here. Otherwise, type errors happen later when\n        // \"patching\" my presence.\n        { ...context.myPresence.get() },\n    };\n\n    // NOTE: There was a flush here before, but I don't think it's really\n    // needed anymore. We're now combining this flush with the one below, to\n    // combine them in a single batch.\n    // tryFlushing();\n\n    // If a storage fetch has ever been initiated, we assume the client is\n    // interested in storage, so we will refresh it after a reconnection.\n    if (_getStorage$ !== null) {\n      refreshStorage({ flush: false });\n    }\n    flushNowOrSoon();\n  }\n\n  function onDidDisconnect() {\n    clearTimeout(context.buffer.flushTimerID);\n  }\n\n  // Register events handlers for events coming from the socket\n  // We never have to unsubscribe, because the Room and the Connection Manager\n  // will have the same life-time.\n  managedSocket.events.onMessage.subscribe(handleServerMessage);\n  managedSocket.events.statusDidChange.subscribe(onStatusDidChange);\n  managedSocket.events.statusDidChange.subscribe(handleConnectionLossEvent);\n  managedSocket.events.didConnect.subscribe(onDidConnect);\n  managedSocket.events.didDisconnect.subscribe(onDidDisconnect);\n  managedSocket.events.onConnectionError.subscribe(({ message, code }) => {\n    const type = \"ROOM_CONNECTION_ERROR\";\n    const err = new LiveblocksError(message, { type, code, roomId });\n    const didNotify = config.errorEventSource.notify(err);\n    if (!didNotify) {\n      if (process.env.NODE_ENV !== \"production\") {\n        console.error(\n          `Connection to websocket server closed. Reason: ${message} (code: ${code}).`\n        );\n      }\n    }\n  });\n\n  function onDispatch(\n    ops: Op[],\n    reverse: Op[],\n    storageUpdates: Map<string, StorageUpdate>\n  ): void {\n    if (process.env.NODE_ENV !== \"production\") {\n      const stackTrace = captureStackTrace(\"Storage mutation\", onDispatch);\n      if (stackTrace) {\n        for (const op of ops) {\n          if (op.opId) {\n            nn(context.opStackTraces).set(op.opId, stackTrace);\n          }\n        }\n      }\n    }\n\n    if (context.activeBatch) {\n      for (const op of ops) {\n        context.activeBatch.ops.push(op);\n      }\n      for (const [key, value] of storageUpdates) {\n        context.activeBatch.updates.storageUpdates.set(\n          key,\n          mergeStorageUpdates(\n            context.activeBatch.updates.storageUpdates.get(key),\n            value\n          )\n        );\n      }\n      context.activeBatch.reverseOps.pushLeft(reverse);\n    } else {\n      addToUndoStack(reverse);\n      context.redoStack.length = 0;\n      dispatchOps(ops);\n      notify({ storageUpdates });\n    }\n  }\n\n  function isStorageWritable(): boolean {\n    const scopes = context.dynamicSessionInfoSig.get()?.scopes;\n    // If we aren't connected yet, assume we can write\n    return scopes !== undefined ? canWriteStorage(scopes) : true;\n  }\n\n  const eventHub = {\n    status: makeEventSource<Status>(), // New/recommended API\n    lostConnection: makeEventSource<LostConnectionEvent>(),\n\n    customEvent: makeEventSource<RoomEventMessage<P, U, E>>(),\n    self: makeEventSource<User<P, U>>(),\n    myPresence: makeEventSource<P>(),\n    others: makeEventSource<OthersEvent<P, U>>(),\n    storageBatch: makeEventSource<StorageUpdate[]>(),\n    history: makeEventSource<HistoryEvent>(),\n    storageDidLoad: makeEventSource<void>(),\n    storageStatus: makeEventSource<StorageStatus>(),\n    ydoc: makeEventSource<YDocUpdateServerMsg | UpdateYDocClientMsg>(),\n\n    comments: makeEventSource<CommentsEventServerMsg>(),\n    roomWillDestroy: makeEventSource<void>(),\n  };\n\n  async function createTextMention(userId: string, mentionId: string) {\n    return httpClient.createTextMention({ roomId, userId, mentionId });\n  }\n\n  async function deleteTextMention(mentionId: string) {\n    return httpClient.deleteTextMention({ roomId, mentionId });\n  }\n\n  async function reportTextEditor(type: TextEditorType, rootKey: string) {\n    await httpClient.reportTextEditor({ roomId, type, rootKey });\n  }\n\n  async function listTextVersions() {\n    return httpClient.listTextVersions({ roomId });\n  }\n\n  async function listTextVersionsSince(options: ListTextVersionsSinceOptions) {\n    return httpClient.listTextVersionsSince({\n      roomId,\n      since: options.since,\n      signal: options.signal,\n    });\n  }\n\n  async function getTextVersion(versionId: string) {\n    return httpClient.getTextVersion({ roomId, versionId });\n  }\n\n  async function createTextVersion() {\n    return httpClient.createTextVersion({ roomId });\n  }\n\n  async function executeContextualPrompt(options: {\n    prompt: string;\n    context: ContextualPromptContext;\n    previous?: {\n      prompt: string;\n      response: ContextualPromptResponse;\n    };\n    signal: AbortSignal;\n  }) {\n    return httpClient.executeContextualPrompt({\n      roomId,\n      ...options,\n    });\n  }\n\n  /**\n   * Split a single large UPDATE_STORAGE message into smaller chunks, by\n   * splitting the ops list recursively in half.\n   */\n  function* chunkOps(msg: UpdateStorageClientMsg): IterableIterator<string> {\n    const { ops, ...rest } = msg;\n    if (ops.length < 2) {\n      throw new Error(\"Cannot split ops into smaller chunks\");\n    }\n\n    const mid = Math.floor(ops.length / 2);\n    const firstHalf = ops.slice(0, mid);\n    const secondHalf = ops.slice(mid);\n\n    for (const halfOps of [firstHalf, secondHalf]) {\n      const half: UpdateStorageClientMsg = { ops: halfOps, ...rest };\n      const text = stringify([half]);\n      if (!isTooBigForWebSocket(text)) {\n        yield text;\n      } else {\n        yield* chunkOps(half);\n      }\n    }\n  }\n\n  /**\n   * Split the message array in half (two chunks), and try to send each chunk\n   * separately. If the chunk is still too big, repeat the process. If a chunk\n   * can no longer be split up (i.e. is 1 message), then error.\n   */\n  function* chunkMessages(\n    messages: ClientMsg<P, E>[]\n  ): IterableIterator<string> {\n    if (messages.length < 2) {\n      if (messages[0].type === ClientMsgCode.UPDATE_STORAGE) {\n        yield* chunkOps(messages[0]);\n        return;\n      } else {\n        throw new Error(\n          \"Cannot split into chunks smaller than the allowed message size\"\n        );\n      }\n    }\n\n    const mid = Math.floor(messages.length / 2);\n    const firstHalf = messages.slice(0, mid);\n    const secondHalf = messages.slice(mid);\n\n    for (const half of [firstHalf, secondHalf]) {\n      const text = stringify(half);\n      if (!isTooBigForWebSocket(text)) {\n        yield text;\n      } else {\n        yield* chunkMessages(half);\n      }\n    }\n  }\n\n  function isTooBigForWebSocket(text: string): boolean {\n    // The theoretical worst case is that each character in the string is\n    // a 4-byte UTF-8 character. String.prototype.length is an O(1) operation,\n    // so we can spare ourselves the TextEncoder() measurement overhead with\n    // this heuristic.\n    if (text.length * 4 < MAX_SOCKET_MESSAGE_SIZE) {\n      return false;\n    }\n\n    // Otherwise we need to measure to be sure\n    return new TextEncoder().encode(text).length >= MAX_SOCKET_MESSAGE_SIZE;\n  }\n\n  function sendMessages(messages: ClientMsg<P, E>[]) {\n    const strategy = config.largeMessageStrategy ?? \"default\";\n\n    const text = stringify(messages);\n    if (!isTooBigForWebSocket(text)) {\n      return managedSocket.send(text); // Happy path\n    }\n\n    // If message is too big for WebSockets, we need to follow a strategy\n    switch (strategy) {\n      case \"default\": {\n        console.error(\"Message is too large for websockets, not sending. Configure largeMessageStrategy option to deal with this.\"); // prettier-ignore\n        // Don't send the message\n        return;\n      }\n\n      case \"split\": {\n        console.warn(\"Message is too large for websockets, splitting into smaller chunks\"); // prettier-ignore\n        for (const chunk of chunkMessages(messages)) {\n          managedSocket.send(chunk);\n        }\n        return;\n      }\n\n      case \"experimental-fallback-to-http\": {\n        console.warn(\"Message is too large for websockets, so sending over HTTP instead\"); // prettier-ignore\n        const nonce =\n          context.dynamicSessionInfoSig.get()?.nonce ??\n          raise(\"Session is not authorized to send message over HTTP\");\n\n        void httpClient\n          .sendMessages<P, E>({ roomId, nonce, messages })\n          .then((resp) => {\n            if (!resp.ok && resp.status === 403) {\n              managedSocket.reconnect();\n            }\n          });\n        return;\n      }\n    }\n  }\n\n  const self = DerivedSignal.from(\n    context.staticSessionInfoSig,\n    context.dynamicSessionInfoSig,\n    context.myPresence,\n    (staticSession, dynamicSession, myPresence): User<P, U> | null => {\n      if (staticSession === null || dynamicSession === null) {\n        return null;\n      } else {\n        const canWrite = canWriteStorage(dynamicSession.scopes);\n        return {\n          connectionId: dynamicSession.actor,\n          id: staticSession.userId,\n          info: staticSession.userInfo,\n          presence: myPresence,\n          canWrite,\n          canComment: canComment(dynamicSession.scopes),\n        };\n      }\n    }\n  );\n\n  let _lastSelf: Readonly<User<P, U>> | undefined;\n  function notifySelfChanged() {\n    const currSelf = self.get();\n    if (currSelf !== null && currSelf !== _lastSelf) {\n      eventHub.self.notify(currSelf);\n      _lastSelf = currSelf;\n    }\n  }\n\n  // For use in DevTools\n  const selfAsTreeNode = DerivedSignal.from(self, (me) =>\n    me !== null ? userToTreeNode(\"Me\", me) : null\n  );\n\n  function createOrUpdateRootFromMessage(\n    message: InitialDocumentStateServerMsg\n  ) {\n    if (message.items.length === 0) {\n      throw new Error(\"Internal error: cannot load storage without items\");\n    }\n\n    if (context.root !== undefined) {\n      updateRoot(message.items);\n    } else {\n      context.root = LiveObject._fromItems<S>(message.items, context.pool);\n    }\n\n    const canWrite = self.get()?.canWrite ?? true;\n\n    // Populate missing top-level keys using `initialStorage`\n    const stackSizeBefore = context.undoStack.length;\n    for (const key in context.initialStorage) {\n      if (context.root.get(key) === undefined) {\n        if (canWrite) {\n          context.root.set(key, cloneLson(context.initialStorage[key]));\n        } else {\n          console.warn(\n            `Attempted to populate missing storage key '${key}', but current user has no write access`\n          );\n        }\n      }\n    }\n\n    // Initial storage is populated using normal \"set\" operations in the loop\n    // above, those updates can end up in the undo stack, so let's prune it.\n    context.undoStack.length = stackSizeBefore;\n  }\n\n  function updateRoot(items: IdTuple<SerializedCrdt>[]) {\n    if (context.root === undefined) {\n      return;\n    }\n\n    const currentItems: NodeMap = new Map();\n    for (const [id, node] of context.pool.nodes) {\n      currentItems.set(id, node._serialize());\n    }\n\n    // Get operations that represent the diff between 2 states.\n    const ops = getTreesDiffOperations(currentItems, new Map(items));\n\n    const result = applyOps(ops, false);\n\n    notify(result.updates);\n  }\n\n  function _addToRealUndoStack(historyOps: HistoryOp<P>[]) {\n    // If undo stack is too large, we remove the older item\n    if (context.undoStack.length >= 50) {\n      context.undoStack.shift();\n    }\n\n    context.undoStack.push(historyOps);\n    onHistoryChange();\n  }\n\n  function addToUndoStack(historyOps: HistoryOp<P>[]) {\n    if (context.pausedHistory !== null) {\n      context.pausedHistory.pushLeft(historyOps);\n    } else {\n      _addToRealUndoStack(historyOps);\n    }\n  }\n\n  type NotifyUpdates = {\n    storageUpdates?: Map<string, StorageUpdate>;\n    presence?: boolean;\n    others?: InternalOthersEvent<P, U>[];\n  };\n\n  function notify(updates: NotifyUpdates) {\n    const storageUpdates = updates.storageUpdates;\n    const othersUpdates = updates.others;\n\n    if (othersUpdates !== undefined && othersUpdates.length > 0) {\n      const others = context.others.get();\n      for (const event of othersUpdates) {\n        eventHub.others.notify({ ...event, others });\n      }\n    }\n\n    if (updates.presence ?? false) {\n      notifySelfChanged();\n      eventHub.myPresence.notify(context.myPresence.get());\n    }\n\n    if (storageUpdates !== undefined && storageUpdates.size > 0) {\n      const updates = Array.from(storageUpdates.values());\n      eventHub.storageBatch.notify(updates);\n    }\n    notifyStorageStatus();\n  }\n\n  function getCurrentConnectionId() {\n    const info = context.dynamicSessionInfoSig.get();\n    if (info) {\n      return info.actor;\n    }\n\n    throw new Error(\n      \"Internal. Tried to get connection id but connection was never open\"\n    );\n  }\n\n  function applyOps<O extends HistoryOp<P>>(\n    rawOps: readonly O[],\n    isLocal: boolean\n  ): {\n    // Input Ops can get opIds assigned during application.\n    ops: O[];\n    reverse: O[];\n    updates: {\n      storageUpdates: Map<string, StorageUpdate>;\n      presence: boolean;\n    };\n  } {\n    const output = {\n      reverse: new Deque<O>(),\n      storageUpdates: new Map<string, StorageUpdate>(),\n      presence: false,\n    };\n\n    const createdNodeIds = new Set<string>();\n\n    // Ops applied after undo/redo won't have opIds assigned, yet. Let's do\n    // that right now first.\n    const ops = rawOps.map((op) => {\n      if (op.type !== \"presence\" && !op.opId) {\n        return { ...op, opId: context.pool.generateOpId() };\n      } else {\n        return op;\n      }\n    });\n\n    for (const op of ops) {\n      if (op.type === \"presence\") {\n        const reverse = {\n          type: \"presence\" as const,\n          data: {} as P,\n        };\n\n        for (const key in op.data) {\n          reverse.data[key] = context.myPresence.get()[key];\n        }\n\n        context.myPresence.patch(op.data);\n\n        if (context.buffer.presenceUpdates === null) {\n          context.buffer.presenceUpdates = { type: \"partial\", data: op.data };\n        } else {\n          // Merge the new fields with whatever is already queued up (doesn't\n          // matter whether its a partial or full update)\n          for (const key in op.data) {\n            context.buffer.presenceUpdates.data[key] = op.data[key];\n          }\n        }\n\n        output.reverse.pushLeft(reverse as O);\n        output.presence = true;\n      } else {\n        let source: OpSource;\n\n        if (isLocal) {\n          source = OpSource.UNDOREDO_RECONNECT;\n        } else {\n          const opId = nn(op.opId);\n          if (process.env.NODE_ENV !== \"production\") {\n            nn(context.opStackTraces).delete(opId);\n          }\n\n          const deleted = context.unacknowledgedOps.delete(opId);\n          source = deleted ? OpSource.ACK : OpSource.REMOTE;\n        }\n\n        const applyOpResult = applyOp(op, source);\n        if (applyOpResult.modified) {\n          const nodeId = applyOpResult.modified.node._id;\n\n          // If the modified node is not the root (undefined) and was created in the same batch, we don't want to notify\n          // storage updates for the children.\n          if (!(nodeId && createdNodeIds.has(nodeId))) {\n            output.storageUpdates.set(\n              nn(applyOpResult.modified.node._id),\n              mergeStorageUpdates(\n                output.storageUpdates.get(nn(applyOpResult.modified.node._id)),\n                applyOpResult.modified\n              )\n            );\n            output.reverse.pushLeft(applyOpResult.reverse as O[]);\n          }\n\n          if (\n            op.type === OpCode.CREATE_LIST ||\n            op.type === OpCode.CREATE_MAP ||\n            op.type === OpCode.CREATE_OBJECT\n          ) {\n            createdNodeIds.add(nn(op.id));\n          }\n        }\n      }\n    }\n\n    return {\n      ops,\n      reverse: Array.from(output.reverse),\n      updates: {\n        storageUpdates: output.storageUpdates,\n        presence: output.presence,\n      },\n    };\n  }\n\n  function applyOp(op: Op, source: OpSource): ApplyResult {\n    // Explicit case to handle incoming \"AckOp\"s, which are supposed to be\n    // no-ops.\n    if (isAckOp(op)) {\n      return { modified: false };\n    }\n\n    switch (op.type) {\n      case OpCode.DELETE_OBJECT_KEY:\n      case OpCode.UPDATE_OBJECT:\n      case OpCode.DELETE_CRDT: {\n        const node = context.pool.nodes.get(op.id);\n        if (node === undefined) {\n          return { modified: false };\n        }\n\n        return node._apply(op, source === OpSource.UNDOREDO_RECONNECT);\n      }\n\n      case OpCode.SET_PARENT_KEY: {\n        const node = context.pool.nodes.get(op.id);\n        if (node === undefined) {\n          return { modified: false };\n        }\n\n        if (node.parent.type === \"HasParent\" && isLiveList(node.parent.node)) {\n          return node.parent.node._setChildKey(\n            asPos(op.parentKey),\n            node,\n            source\n          );\n        }\n        return { modified: false };\n      }\n      case OpCode.CREATE_OBJECT:\n      case OpCode.CREATE_LIST:\n      case OpCode.CREATE_MAP:\n      case OpCode.CREATE_REGISTER: {\n        if (op.parentId === undefined) {\n          return { modified: false };\n        }\n\n        const parentNode = context.pool.nodes.get(op.parentId);\n        if (parentNode === undefined) {\n          return { modified: false };\n        }\n\n        return parentNode._attachChild(op, source);\n      }\n    }\n  }\n\n  function updatePresence(\n    patch: Partial<P>,\n    options?: { addToHistory: boolean }\n  ) {\n    const oldValues = {} as P;\n\n    if (context.buffer.presenceUpdates === null) {\n      // try {\n      context.buffer.presenceUpdates = {\n        type: \"partial\",\n        data: {},\n      };\n      // } catch (err) {\n      //   window.console.log({ context, patch, err });\n      //   throw err;\n      // }\n    }\n\n    for (const key in patch) {\n      type K = typeof key;\n      const overrideValue: P[K] | undefined = patch[key];\n      if (overrideValue === undefined) {\n        continue;\n      }\n      context.buffer.presenceUpdates.data[key] = overrideValue;\n      oldValues[key] = context.myPresence.get()[key];\n    }\n\n    context.myPresence.patch(patch);\n\n    if (context.activeBatch) {\n      if (options?.addToHistory) {\n        context.activeBatch.reverseOps.pushLeft({\n          type: \"presence\",\n          data: oldValues,\n        });\n      }\n      context.activeBatch.updates.presence = true;\n    } else {\n      flushNowOrSoon();\n      if (options?.addToHistory) {\n        addToUndoStack([{ type: \"presence\", data: oldValues }]);\n      }\n      notify({ presence: true });\n    }\n  }\n\n  function onUpdatePresenceMessage(\n    message: UpdatePresenceServerMsg<P>\n  ): InternalOthersEvent<P, U> | undefined {\n    if (message.targetActor !== undefined) {\n      // The incoming message is a full presence update. We are obliged to\n      // handle it if `targetActor` matches our own connection ID, but we can\n      // use the opportunity to effectively reset the known presence as\n      // a \"keyframe\" update, while we have free access to it.\n      const oldUser = context.others.getUser(message.actor);\n      context.others.setOther(message.actor, message.data);\n\n      const newUser = context.others.getUser(message.actor);\n      if (oldUser === undefined && newUser !== undefined) {\n        // The user just became \"visible\" due to this update, so fire the\n        // \"enter\" event\n        return { type: \"enter\", user: newUser };\n      }\n    } else {\n      // The incoming message is a partial presence update\n      context.others.patchOther(message.actor, message.data), message;\n    }\n\n    const user = context.others.getUser(message.actor);\n    if (user) {\n      return {\n        type: \"update\",\n        updates: message.data,\n        user,\n      };\n    } else {\n      return undefined;\n    }\n  }\n\n  function onUserLeftMessage(\n    message: UserLeftServerMsg\n  ): InternalOthersEvent<P, U> | null {\n    const user = context.others.getUser(message.actor);\n    if (user) {\n      context.others.removeConnection(message.actor);\n      return { type: \"leave\", user };\n    }\n    return null;\n  }\n\n  function onRoomStateMessage(\n    message: RoomStateServerMsg<U>\n  ): InternalOthersEvent<P, U> {\n    // The server will inform the client about its assigned actor ID and scopes\n    context.dynamicSessionInfoSig.set({\n      actor: message.actor,\n      nonce: message.nonce,\n      scopes: message.scopes,\n    });\n    context.idFactory = makeIdFactory(message.actor);\n    notifySelfChanged();\n\n    for (const connectionId of context.others.connectionIds()) {\n      const user = message.users[connectionId];\n      if (user === undefined) {\n        context.others.removeConnection(connectionId);\n      }\n    }\n\n    for (const key in message.users) {\n      const user = message.users[key];\n      const connectionId = Number(key);\n      context.others.setConnection(\n        connectionId,\n        user.id,\n        user.info,\n        user.scopes\n      );\n    }\n\n    // NOTE: We could be notifying the \"others\" event here, but the reality is\n    // that ROOM_STATE is often the first message to be received from the\n    // server, and it won't contain all the information needed to update the\n    // other views yet. Instead, we'll let the others' presences trickle in,\n    // and notify each time that happens.\n    return { type: \"reset\" };\n  }\n\n  function canUndo() { return context.undoStack.length > 0; } // prettier-ignore\n  function canRedo() { return context.redoStack.length > 0; } // prettier-ignore\n  function onHistoryChange() {\n    eventHub.history.notify({ canUndo: canUndo(), canRedo: canRedo() });\n  }\n\n  function onUserJoinedMessage(\n    message: UserJoinServerMsg<U>\n  ): InternalOthersEvent<P, U> | undefined {\n    context.others.setConnection(\n      message.actor,\n      message.id,\n      message.info,\n      message.scopes\n    );\n    // Send current presence to new user\n    // TODO: Consider storing it on the backend\n    context.buffer.messages.push({\n      type: ClientMsgCode.UPDATE_PRESENCE,\n      data: context.myPresence.get(),\n      targetActor: message.actor,\n    });\n    flushNowOrSoon();\n\n    // We recorded the connection, but we won't make the new user visible\n    // unless we also know their initial presence data at this point.\n    const user = context.others.getUser(message.actor);\n    return user ? { type: \"enter\", user } : undefined;\n  }\n\n  function parseServerMessage(data: Json): ServerMsg<P, U, E> | null {\n    if (!isJsonObject(data)) {\n      return null;\n    }\n\n    return data as ServerMsg<P, U, E>;\n    //             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FIXME: Properly validate incoming external data instead!\n  }\n\n  function parseServerMessages(text: string): ServerMsg<P, U, E>[] | null {\n    const data: Json | undefined = tryParseJson(text);\n    if (data === undefined) {\n      return null;\n    } else if (isJsonArray(data)) {\n      return compact(data.map((item) => parseServerMessage(item)));\n    } else {\n      return compact([parseServerMessage(data)]);\n    }\n  }\n\n  function applyAndSendOps(offlineOps: Map<string, Op>) {\n    if (offlineOps.size === 0) {\n      return;\n    }\n\n    const messages: ClientMsg<P, E>[] = [];\n\n    const inOps = Array.from(offlineOps.values());\n\n    const result = applyOps(inOps, true);\n\n    messages.push({\n      type: ClientMsgCode.UPDATE_STORAGE,\n      ops: result.ops,\n    });\n\n    notify(result.updates);\n\n    sendMessages(messages);\n  }\n\n  /**\n   * Handles a message received on the WebSocket. Will never be a \"pong\". The\n   * \"pong\" is handled at the connection manager level.\n   */\n  function handleServerMessage(event: IWebSocketMessageEvent) {\n    if (typeof event.data !== \"string\") {\n      // istanbul ignore next: Unknown incoming message\n      return;\n    }\n\n    const messages = parseServerMessages(event.data);\n    if (messages === null || messages.length === 0) {\n      // istanbul ignore next: Unknown incoming message\n      return;\n    }\n\n    const updates = {\n      storageUpdates: new Map<string, StorageUpdate>(),\n      others: [] as InternalOthersEvent<P, U>[],\n    };\n\n    for (const message of messages) {\n      switch (message.type) {\n        case ServerMsgCode.USER_JOINED: {\n          const userJoinedUpdate = onUserJoinedMessage(message);\n          if (userJoinedUpdate) {\n            updates.others.push(userJoinedUpdate);\n          }\n          break;\n        }\n\n        case ServerMsgCode.UPDATE_PRESENCE: {\n          const othersPresenceUpdate = onUpdatePresenceMessage(message);\n          if (othersPresenceUpdate) {\n            updates.others.push(othersPresenceUpdate);\n          }\n          break;\n        }\n\n        case ServerMsgCode.BROADCASTED_EVENT: {\n          const others = context.others.get();\n          eventHub.customEvent.notify({\n            connectionId: message.actor,\n            user:\n              message.actor < 0\n                ? null\n                : (others.find((u) => u.connectionId === message.actor) ??\n                  null),\n            event: message.event,\n          });\n          break;\n        }\n\n        case ServerMsgCode.USER_LEFT: {\n          const event = onUserLeftMessage(message);\n          if (event) {\n            updates.others.push(event);\n          }\n          break;\n        }\n\n        case ServerMsgCode.UPDATE_YDOC: {\n          eventHub.ydoc.notify(message);\n          break;\n        }\n\n        case ServerMsgCode.ROOM_STATE: {\n          updates.others.push(onRoomStateMessage(message));\n          break;\n        }\n\n        case ServerMsgCode.INITIAL_STORAGE_STATE: {\n          // createOrUpdateRootFromMessage function could add ops to offlineOperations.\n          // Client shouldn't resend these ops as part of the offline ops sending after reconnect.\n          processInitialStorage(message);\n          break;\n        }\n        // Write event\n        case ServerMsgCode.UPDATE_STORAGE: {\n          const applyResult = applyOps(message.ops, false);\n          for (const [key, value] of applyResult.updates.storageUpdates) {\n            updates.storageUpdates.set(\n              key,\n              mergeStorageUpdates(updates.storageUpdates.get(key), value)\n            );\n          }\n          break;\n        }\n\n        // Receiving a RejectedOps message in the client means that the server is no\n        // longer in sync with the client. Trying to synchronize the client again by\n        // rolling back particular Ops may be hard/impossible. It's fine to not try and\n        // accept the out-of-sync reality and throw an error. We look at this kind of bug\n        // as a developer-owned bug. In production, these errors are not expected to happen.\n        case ServerMsgCode.REJECT_STORAGE_OP: {\n          console.errorWithTitle(\n            \"Storage mutation rejection error\",\n            message.reason\n          );\n\n          if (process.env.NODE_ENV !== \"production\") {\n            const traces: Set<string> = new Set();\n            for (const opId of message.opIds) {\n              const trace = context.opStackTraces?.get(opId);\n              if (trace) {\n                traces.add(trace);\n              }\n            }\n\n            if (traces.size > 0) {\n              console.warnWithTitle(\n                \"The following function calls caused the rejected storage mutations:\",\n                `\\n\\n${Array.from(traces).join(\"\\n\\n\")}`\n              );\n            }\n\n            throw new Error(\n              `Storage mutations rejected by server: ${message.reason}`\n            );\n          }\n\n          break;\n        }\n\n        case ServerMsgCode.THREAD_CREATED:\n        case ServerMsgCode.THREAD_DELETED:\n        case ServerMsgCode.THREAD_METADATA_UPDATED:\n        case ServerMsgCode.THREAD_UPDATED:\n        case ServerMsgCode.COMMENT_REACTION_ADDED:\n        case ServerMsgCode.COMMENT_REACTION_REMOVED:\n        case ServerMsgCode.COMMENT_CREATED:\n        case ServerMsgCode.COMMENT_EDITED:\n        case ServerMsgCode.COMMENT_DELETED: {\n          eventHub.comments.notify(message);\n          break;\n        }\n      }\n    }\n\n    notify(updates);\n  }\n\n  function flushNowOrSoon() {\n    const storageOps = context.buffer.storageOperations;\n    if (storageOps.length > 0) {\n      for (const op of storageOps) {\n        context.unacknowledgedOps.set(nn(op.opId), op);\n      }\n      notifyStorageStatus();\n    }\n\n    if (managedSocket.getStatus() !== \"connected\") {\n      context.buffer.storageOperations = [];\n      return;\n    }\n\n    const now = Date.now();\n    const elapsedMillis = now - context.buffer.lastFlushedAt;\n\n    if (elapsedMillis >= config.throttleDelay) {\n      // Flush the buffer right now\n      const messagesToFlush = serializeBuffer();\n      if (messagesToFlush.length === 0) {\n        return;\n      }\n\n      sendMessages(messagesToFlush);\n      context.buffer = {\n        flushTimerID: undefined,\n        lastFlushedAt: now,\n        messages: [],\n        storageOperations: [],\n        presenceUpdates: null,\n      };\n    } else {\n      // Or schedule the flush a few millis into the future\n      clearTimeout(context.buffer.flushTimerID);\n      context.buffer.flushTimerID = setTimeout(\n        flushNowOrSoon,\n        config.throttleDelay - elapsedMillis\n      );\n    }\n  }\n\n  /**\n   * Returns a list of ClientMsgs to flush to the network, computed from all\n   * pending changes in the buffer. Has no side effects.\n   */\n  function serializeBuffer() {\n    const messages: ClientMsg<P, E>[] = [];\n    if (context.buffer.presenceUpdates) {\n      messages.push(\n        context.buffer.presenceUpdates.type === \"full\"\n          ? {\n              type: ClientMsgCode.UPDATE_PRESENCE,\n              // Populating the `targetActor` field turns this message into\n              // a Full Presence™ update message (not a patch), which will get\n              // interpreted by other clients as such.\n              targetActor: -1,\n              data: context.buffer.presenceUpdates.data,\n            }\n          : {\n              type: ClientMsgCode.UPDATE_PRESENCE,\n              data: context.buffer.presenceUpdates.data,\n            }\n      );\n    }\n    for (const event of context.buffer.messages) {\n      messages.push(event);\n    }\n    if (context.buffer.storageOperations.length > 0) {\n      messages.push({\n        type: ClientMsgCode.UPDATE_STORAGE,\n        ops: context.buffer.storageOperations,\n      });\n    }\n    return messages;\n  }\n\n  function updateYDoc(update: string, guid?: string, isV2?: boolean) {\n    const clientMsg: UpdateYDocClientMsg = {\n      type: ClientMsgCode.UPDATE_YDOC,\n      update,\n      guid,\n      v2: isV2,\n    };\n    context.buffer.messages.push(clientMsg);\n    eventHub.ydoc.notify(clientMsg);\n    flushNowOrSoon();\n  }\n\n  function broadcastEvent(\n    event: E,\n    options: BroadcastOptions = {\n      shouldQueueEventIfNotReady: false,\n    }\n  ) {\n    if (\n      managedSocket.getStatus() !== \"connected\" &&\n      !options.shouldQueueEventIfNotReady\n    ) {\n      return;\n    }\n\n    context.buffer.messages.push({\n      type: ClientMsgCode.BROADCAST_EVENT,\n      event,\n    });\n    flushNowOrSoon();\n  }\n\n  function dispatchOps(ops: Op[]) {\n    const { storageOperations } = context.buffer;\n    for (const op of ops) {\n      storageOperations.push(op);\n    }\n    flushNowOrSoon();\n  }\n\n  let _getStorage$: Promise<void> | null = null;\n  let _resolveStoragePromise: (() => void) | null = null;\n\n  function processInitialStorage(message: InitialDocumentStateServerMsg) {\n    const unacknowledgedOps = new Map(context.unacknowledgedOps);\n    createOrUpdateRootFromMessage(message);\n    applyAndSendOps(unacknowledgedOps);\n    _resolveStoragePromise?.();\n    notifyStorageStatus();\n    eventHub.storageDidLoad.notify();\n  }\n\n  async function streamStorage() {\n    // TODO: Handle potential race conditions where the room get disconnected while the request is pending\n    if (!managedSocket.authValue) return;\n    const items = await httpClient.streamStorage({ roomId });\n    processInitialStorage({ type: ServerMsgCode.INITIAL_STORAGE_STATE, items });\n  }\n\n  function refreshStorage(options: { flush: boolean }) {\n    const messages = context.buffer.messages;\n    if (config.unstable_streamData) {\n      // instead of sending a fetch message over WS, stream over HTTP\n      void streamStorage();\n    } else if (\n      !messages.some((msg) => msg.type === ClientMsgCode.FETCH_STORAGE)\n    ) {\n      // Only add the fetch message to the outgoing message queue if it isn't\n      // already there\n      messages.push({ type: ClientMsgCode.FETCH_STORAGE });\n    }\n\n    if (options.flush) {\n      flushNowOrSoon();\n    }\n  }\n\n  function startLoadingStorage(): Promise<void> {\n    if (_getStorage$ === null) {\n      refreshStorage({ flush: true });\n      _getStorage$ = new Promise((resolve) => {\n        _resolveStoragePromise = resolve;\n      });\n      notifyStorageStatus();\n    }\n    return _getStorage$;\n  }\n\n  /**\n   * Closely related to .getStorage(), but synchronously. Will be `null`\n   * initially. When requested for the first time, will kick off the loading of\n   * Storage if it hasn't happened yet.\n   *\n   * Once Storage is loaded, will return a stable reference to the storage\n   * root.\n   */\n  function getStorageSnapshot(): LiveObject<S> | null {\n    const root = context.root;\n    if (root !== undefined) {\n      // Done loading\n      return root;\n    } else {\n      // Not done loading, kick off the loading (will not do anything if already kicked off)\n      void startLoadingStorage();\n      return null;\n    }\n  }\n\n  async function getStorage(): Promise<{\n    root: LiveObject<S>;\n  }> {\n    if (context.root !== undefined) {\n      // Store has already loaded, so we can resolve it directly\n      return Promise.resolve({\n        root: context.root,\n      });\n    }\n\n    await startLoadingStorage();\n    return {\n      root: nn(context.root) as LiveObject<S>,\n    };\n  }\n\n  function fetchYDoc(vector: string, guid?: string, isV2?: boolean): void {\n    // don't allow multiple fetches in the same buffer with the same vector\n    // dev tools may also call with a different vector (if its opened later), and that's okay\n    // because the updates will be ignored by the provider\n    if (\n      !context.buffer.messages.find((m) => {\n        return (\n          m.type === ClientMsgCode.FETCH_YDOC &&\n          m.vector === vector &&\n          m.guid === guid &&\n          m.v2 === isV2\n        );\n      })\n    ) {\n      context.buffer.messages.push({\n        type: ClientMsgCode.FETCH_YDOC,\n        vector,\n        guid,\n        v2: isV2,\n      });\n    }\n\n    flushNowOrSoon();\n  }\n\n  function undo() {\n    if (context.activeBatch) {\n      throw new Error(\"undo is not allowed during a batch\");\n    }\n    const historyOps = context.undoStack.pop();\n    if (historyOps === undefined) {\n      return;\n    }\n\n    context.pausedHistory = null;\n    const result = applyOps(historyOps, true);\n\n    notify(result.updates);\n    context.redoStack.push(result.reverse);\n    onHistoryChange();\n\n    for (const op of result.ops) {\n      if (op.type !== \"presence\") {\n        context.buffer.storageOperations.push(op);\n      }\n    }\n    flushNowOrSoon();\n  }\n\n  function redo() {\n    if (context.activeBatch) {\n      throw new Error(\"redo is not allowed during a batch\");\n    }\n\n    const historyOps = context.redoStack.pop();\n    if (historyOps === undefined) {\n      return;\n    }\n\n    context.pausedHistory = null;\n    const result = applyOps(historyOps, true);\n\n    notify(result.updates);\n    context.undoStack.push(result.reverse);\n    onHistoryChange();\n\n    for (const op of result.ops) {\n      if (op.type !== \"presence\") {\n        context.buffer.storageOperations.push(op);\n      }\n    }\n    flushNowOrSoon();\n  }\n\n  function clear() {\n    context.undoStack.length = 0;\n    context.redoStack.length = 0;\n  }\n\n  function batch<T>(callback: () => T): T {\n    if (context.activeBatch) {\n      // If there already is an active batch, we don't have to handle this in\n      // any special way. That outer active batch will handle the batch. This\n      // nested call can be a no-op.\n      return callback();\n    }\n\n    let returnValue: T = undefined as unknown as T;\n\n    context.activeBatch = {\n      ops: [],\n      updates: {\n        storageUpdates: new Map(),\n        presence: false,\n        others: [],\n      },\n      reverseOps: new Deque(),\n    };\n    try {\n      returnValue = callback();\n    } finally {\n      // \"Pop\" the current batch of the state, closing the active batch, but\n      // handling it separately here\n      const currentBatch = context.activeBatch;\n      context.activeBatch = null;\n\n      if (currentBatch.reverseOps.length > 0) {\n        addToUndoStack(Array.from(currentBatch.reverseOps));\n      }\n\n      if (currentBatch.ops.length > 0) {\n        // Only clear the redo stack if something has changed during a batch\n        // Clear the redo stack because batch is always called from a local operation\n        context.redoStack.length = 0;\n      }\n\n      if (currentBatch.ops.length > 0) {\n        dispatchOps(currentBatch.ops);\n      }\n\n      notify(currentBatch.updates);\n      flushNowOrSoon();\n    }\n\n    return returnValue;\n  }\n\n  function pauseHistory() {\n    if (context.pausedHistory === null) {\n      context.pausedHistory = new Deque();\n    }\n  }\n\n  function resumeHistory() {\n    const historyOps = context.pausedHistory;\n    context.pausedHistory = null;\n    if (historyOps !== null && historyOps.length > 0) {\n      _addToRealUndoStack(Array.from(historyOps));\n    }\n  }\n\n  // Register a global source of pending changes for Storage™, so that the\n  // useSyncStatus() hook will be able to report this to end users\n  const syncSourceForStorage = config.createSyncSource();\n\n  function getStorageStatus(): StorageStatus {\n    if (context.root === undefined) {\n      return _getStorage$ === null ? \"not-loaded\" : \"loading\";\n    } else {\n      return context.unacknowledgedOps.size === 0\n        ? \"synchronized\"\n        : \"synchronizing\";\n    }\n  }\n\n  /**\n   * Storage status is a computed value based other internal states so we need to keep a reference to the previous computed value to avoid triggering events when it does not change\n   * This is far from ideal because we need to call this function whenever we update our internal states.\n   *\n   * TODO: Encapsulate our internal state differently to make sure this event is triggered whenever necessary.\n   * Currently okay because we only have 4 callers and shielded by tests.\n   */\n  let _lastStorageStatus = getStorageStatus();\n  function notifyStorageStatus() {\n    const storageStatus = getStorageStatus();\n    if (_lastStorageStatus !== storageStatus) {\n      _lastStorageStatus = storageStatus;\n      eventHub.storageStatus.notify(storageStatus);\n    }\n    syncSourceForStorage.setSyncStatus(\n      storageStatus === \"synchronizing\" ? \"synchronizing\" : \"synchronized\"\n    );\n  }\n\n  function isPresenceReady() {\n    return self.get() !== null;\n  }\n\n  async function waitUntilPresenceReady(): Promise<void> {\n    while (!isPresenceReady()) {\n      const { promise, resolve } = Promise_withResolvers();\n\n      const unsub1 = events.self.subscribeOnce(resolve);\n      const unsub2 = events.status.subscribeOnce(resolve);\n      // Return whenever one of these returns, whichever is first\n      await promise;\n      unsub1();\n      unsub2();\n    }\n  }\n\n  function isStorageReady() {\n    return getStorageSnapshot() !== null;\n  }\n\n  async function waitUntilStorageReady(): Promise<void> {\n    while (!isStorageReady()) {\n      // Trigger a load of Storage and wait until it finished\n      await getStorage();\n    }\n  }\n\n  // Derived cached state for use in DevTools\n  const others_forDevTools = DerivedSignal.from(\n    context.others.signal,\n    (others) =>\n      others.map((other, index) => userToTreeNode(`Other ${index}`, other))\n  );\n\n  const events = {\n    status: eventHub.status.observable,\n    lostConnection: eventHub.lostConnection.observable,\n\n    customEvent: eventHub.customEvent.observable,\n    others: eventHub.others.observable,\n    self: eventHub.self.observable,\n    myPresence: eventHub.myPresence.observable,\n    storageBatch: eventHub.storageBatch.observable,\n    history: eventHub.history.observable,\n    storageDidLoad: eventHub.storageDidLoad.observable,\n    storageStatus: eventHub.storageStatus.observable,\n    ydoc: eventHub.ydoc.observable,\n\n    comments: eventHub.comments.observable,\n    roomWillDestroy: eventHub.roomWillDestroy.observable,\n  };\n\n  async function getThreadsSince(options: GetThreadsSinceOptions) {\n    return httpClient.getThreadsSince({\n      roomId,\n      since: options.since,\n      signal: options.signal,\n    });\n  }\n\n  async function getThreads(options?: GetThreadsOptions<M>) {\n    return httpClient.getThreads({\n      roomId,\n      query: options?.query,\n      cursor: options?.cursor,\n    });\n  }\n\n  async function getThread(threadId: string) {\n    return httpClient.getThread({ roomId, threadId });\n  }\n\n  async function createThread(options: {\n    roomId: string;\n    threadId?: string;\n    commentId?: string;\n    metadata: M | undefined;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }) {\n    return httpClient.createThread({\n      roomId,\n      threadId: options.threadId,\n      commentId: options.commentId,\n      metadata: options.metadata,\n      body: options.body,\n      attachmentIds: options.attachmentIds,\n    });\n  }\n\n  async function deleteThread(threadId: string) {\n    return httpClient.deleteThread({ roomId, threadId });\n  }\n\n  async function editThreadMetadata({\n    metadata,\n    threadId,\n  }: {\n    roomId: string;\n    metadata: Patchable<M>;\n    threadId: string;\n  }) {\n    return httpClient.editThreadMetadata({ roomId, threadId, metadata });\n  }\n\n  async function markThreadAsResolved(threadId: string) {\n    return httpClient.markThreadAsResolved({ roomId, threadId });\n  }\n\n  async function markThreadAsUnresolved(threadId: string) {\n    return httpClient.markThreadAsUnresolved({\n      roomId,\n      threadId,\n    });\n  }\n\n  async function subscribeToThread(threadId: string) {\n    return httpClient.subscribeToThread({ roomId, threadId });\n  }\n\n  async function unsubscribeFromThread(threadId: string) {\n    return httpClient.unsubscribeFromThread({ roomId, threadId });\n  }\n\n  async function createComment(options: {\n    threadId: string;\n    commentId?: string;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }) {\n    return httpClient.createComment({\n      roomId,\n      threadId: options.threadId,\n      commentId: options.commentId,\n      body: options.body,\n      attachmentIds: options.attachmentIds,\n    });\n  }\n\n  async function editComment(options: {\n    threadId: string;\n    commentId: string;\n    body: CommentBody;\n    attachmentIds?: string[];\n  }) {\n    return httpClient.editComment({\n      roomId,\n      threadId: options.threadId,\n      commentId: options.commentId,\n      body: options.body,\n      attachmentIds: options.attachmentIds,\n    });\n  }\n\n  async function deleteComment({\n    threadId,\n    commentId,\n  }: {\n    roomId: string;\n    threadId: string;\n    commentId: string;\n  }) {\n    return httpClient.deleteComment({ roomId, threadId, commentId });\n  }\n\n  async function addReaction({\n    threadId,\n    commentId,\n    emoji,\n  }: {\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }) {\n    return httpClient.addReaction({ roomId, threadId, commentId, emoji });\n  }\n\n  async function removeReaction({\n    threadId,\n    commentId,\n    emoji,\n  }: {\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }) {\n    return await httpClient.removeReaction({\n      roomId,\n      threadId,\n      commentId,\n      emoji,\n    });\n  }\n\n  function prepareAttachment(file: File): CommentLocalAttachment {\n    return {\n      type: \"localAttachment\",\n      status: \"idle\",\n      id: createCommentAttachmentId(),\n      name: file.name,\n      size: file.size,\n      mimeType: file.type,\n      file,\n    };\n  }\n\n  async function uploadAttachment(\n    attachment: CommentLocalAttachment,\n    options: UploadAttachmentOptions = {}\n  ): Promise<CommentAttachment> {\n    return httpClient.uploadAttachment({\n      roomId,\n      attachment,\n      signal: options.signal,\n    });\n  }\n\n  function getAttachmentUrl(attachmentId: string) {\n    return httpClient.getAttachmentUrl({ roomId, attachmentId });\n  }\n\n  function getSubscriptionSettings(\n    options?: GetSubscriptionSettingsOptions\n  ): Promise<RoomSubscriptionSettings> {\n    return httpClient.getSubscriptionSettings({\n      roomId,\n      signal: options?.signal,\n    });\n  }\n\n  function updateSubscriptionSettings(\n    settings: Partial<RoomSubscriptionSettings>\n  ): Promise<RoomSubscriptionSettings> {\n    return httpClient.updateSubscriptionSettings({ roomId, settings });\n  }\n\n  async function markInboxNotificationAsRead(inboxNotificationId: string) {\n    await httpClient.markRoomInboxNotificationAsRead({\n      roomId,\n      inboxNotificationId,\n    });\n  }\n\n  // Register a global source of pending changes for Storage™, so that the\n  // useSyncStatus() hook will be able to report this to end users\n  const syncSourceForYjs = config.createSyncSource();\n\n  function yjsStatusDidChange(status: YjsSyncStatus) {\n    return syncSourceForYjs.setSyncStatus(\n      status === \"synchronizing\" ? \"synchronizing\" : \"synchronized\"\n    );\n  }\n\n  return Object.defineProperty(\n    {\n      [kInternal]: {\n        get presenceBuffer() { return deepClone(context.buffer.presenceUpdates?.data ?? null) }, // prettier-ignore\n        get undoStack() { return deepClone(context.undoStack) }, // prettier-ignore\n        get nodeCount() { return context.pool.nodes.size }, // prettier-ignore\n\n        getYjsProvider() {\n          return context.yjsProvider;\n        },\n\n        setYjsProvider(newProvider: IYjsProvider | undefined) {\n          // Deregister status change listener for the old Yjs provider\n          // Register status change listener for the new Yjs provider\n          context.yjsProvider?.off(\"status\", yjsStatusDidChange);\n          context.yjsProvider = newProvider;\n          newProvider?.on(\"status\", yjsStatusDidChange);\n          context.yjsProviderDidChange.notify();\n        },\n\n        yjsProviderDidChange: context.yjsProviderDidChange.observable,\n\n        // send metadata when using a text editor\n        reportTextEditor,\n        // create a text mention when using a text editor\n        createTextMention,\n        // delete a text mention when using a text editor\n        deleteTextMention,\n        // list versions of the document\n        listTextVersions,\n        // List versions of the document since the specified date\n        listTextVersionsSince,\n        // get a specific version\n        getTextVersion,\n        // create a version\n        createTextVersion,\n        // execute a contextual prompt\n        executeContextualPrompt,\n\n        // Support for the Liveblocks browser extension\n        getSelf_forDevTools: () => selfAsTreeNode.get(),\n        getOthers_forDevTools: (): readonly DevTools.UserTreeNode[] =>\n          others_forDevTools.get(),\n\n        // prettier-ignore\n        simulate: {\n          // These exist only for our E2E testing app\n          explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: \"EXPLICIT_SOCKET_CLOSE\", event }),\n          rawSend: (data) => managedSocket.send(data),\n        },\n\n        attachmentUrlsStore: httpClient.getOrCreateAttachmentUrlsStore(roomId),\n      },\n\n      id: roomId,\n      subscribe: makeClassicSubscribeFn(\n        roomId,\n        events,\n        config.errorEventSource\n      ),\n\n      connect: () => managedSocket.connect(),\n      reconnect: () => managedSocket.reconnect(),\n      disconnect: () => managedSocket.disconnect(),\n      destroy: () => {\n        // remove the roomWillDestroy event from the event hub\n        const { roomWillDestroy, ...eventsExceptDestroy } = eventHub;\n        // Unregister all registered callbacks\n        for (const source of Object.values(eventsExceptDestroy)) {\n          source.dispose();\n        }\n        eventHub.roomWillDestroy.notify();\n        context.yjsProvider?.off(\"status\", yjsStatusDidChange);\n        syncSourceForStorage.destroy();\n        syncSourceForYjs.destroy();\n        uninstallBgTabSpy();\n        managedSocket.destroy();\n\n        // cleanup will destroy listener\n        roomWillDestroy.dispose();\n      },\n\n      // Presence\n      updatePresence,\n      updateYDoc,\n      broadcastEvent,\n\n      // Storage\n      batch,\n      history: {\n        undo,\n        redo,\n        canUndo,\n        canRedo,\n        clear,\n        pause: pauseHistory,\n        resume: resumeHistory,\n      },\n\n      fetchYDoc,\n      getStorage,\n      getStorageSnapshot,\n      getStorageStatus,\n\n      isPresenceReady,\n      isStorageReady,\n      waitUntilPresenceReady: memoizeOnSuccess(waitUntilPresenceReady),\n      waitUntilStorageReady: memoizeOnSuccess(waitUntilStorageReady),\n\n      events,\n\n      // Core\n      getStatus: () => managedSocket.getStatus(),\n      getSelf: () => self.get(),\n\n      // Presence\n      getPresence: () => context.myPresence.get(),\n      getOthers: () => context.others.get(),\n\n      // Comments\n      getThreads,\n      getThreadsSince,\n      getThread,\n      createThread,\n      deleteThread,\n      editThreadMetadata,\n      markThreadAsResolved,\n      markThreadAsUnresolved,\n      subscribeToThread,\n      unsubscribeFromThread,\n      createComment,\n      editComment,\n      deleteComment,\n      addReaction,\n      removeReaction,\n      prepareAttachment,\n      uploadAttachment,\n      getAttachmentUrl,\n\n      // Notifications\n      getNotificationSettings: getSubscriptionSettings,\n      getSubscriptionSettings,\n      updateNotificationSettings: updateSubscriptionSettings,\n      updateSubscriptionSettings,\n      markInboxNotificationAsRead,\n    },\n\n    // Explictly make the internal field non-enumerable, to avoid aggressive\n    // freezing when used with Immer\n    kInternal,\n    { enumerable: false }\n  );\n}\n\n/**\n * @internal\n * This recreates the classic single `.subscribe()` method for the Room API, as\n * documented here https://liveblocks.io/docs/api-reference/liveblocks-client#Room.subscribe(storageItem)\n */\nfunction makeClassicSubscribeFn<\n  P extends JsonObject,\n  S extends LsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n  M extends BaseMetadata,\n>(\n  roomId: string,\n  events: Room<P, S, U, E, M>[\"events\"],\n  errorEvents: EventSource<LiveblocksError>\n): SubscribeFn<P, S, U, E> {\n  // Set up the \"subscribe\" wrapper API\n  function subscribeToLiveStructureDeeply<L extends LiveStructure>(\n    node: L,\n    callback: (updates: StorageUpdate[]) => void\n  ): () => void {\n    return events.storageBatch.subscribe((updates) => {\n      const relatedUpdates = updates.filter((update) =>\n        isSameNodeOrChildOf(update.node, node)\n      );\n      if (relatedUpdates.length > 0) {\n        callback(relatedUpdates);\n      }\n    });\n  }\n\n  function subscribeToLiveStructureShallowly<L extends LiveStructure>(\n    node: L,\n    callback: (node: L) => void\n  ): () => void {\n    return events.storageBatch.subscribe((updates) => {\n      for (const update of updates) {\n        if (update.node._id === node._id) {\n          callback(update.node as L);\n        }\n      }\n    });\n  }\n\n  // Generic storage callbacks\n  function subscribe(callback: StorageCallback): () => void; // prettier-ignore\n  // Storage callbacks filtered by Live structure\n  function subscribe<L extends LiveStructure>(liveStructure: L, callback: (node: L) => void): () => void; // prettier-ignore\n  function subscribe(node: LiveStructure, callback: StorageCallback, options: { isDeep: true }): () => void; // prettier-ignore\n  // Room event callbacks\n  function subscribe<K extends RoomEventName>(type: K, listener: RoomEventCallbackFor<K, P, U, E>): () => void; // prettier-ignore\n\n  function subscribe<L extends LiveStructure, K extends RoomEventName>(\n    first: StorageCallback | L | K,\n    second?: ((node: L) => void) | StorageCallback | RoomEventCallback,\n    options?: { isDeep: boolean }\n  ): () => void {\n    if (typeof first === \"string\" && isRoomEventName(first)) {\n      if (typeof second !== \"function\") {\n        throw new Error(\"Second argument must be a callback function\");\n      }\n      const callback = second;\n      switch (first) {\n        case \"event\":\n          return events.customEvent.subscribe(\n            callback as Callback<RoomEventMessage<P, U, E>>\n          );\n\n        case \"my-presence\":\n          return events.myPresence.subscribe(callback as Callback<P>);\n\n        case \"others\": {\n          // NOTE: Others have a different callback structure, where the API\n          // exposed on the outside takes _two_ callback arguments!\n          const cb = callback as LegacyOthersEventCallback<P, U>;\n          return events.others.subscribe((event) => {\n            const { others, ...internalEvent } = event;\n            return cb(others, internalEvent);\n          });\n        }\n\n        case \"error\": {\n          return errorEvents.subscribe((err) => {\n            if (err.roomId === roomId) {\n              return (callback as Callback<Error>)(err);\n            }\n          });\n        }\n\n        case \"status\":\n          return events.status.subscribe(callback as Callback<Status>);\n\n        case \"lost-connection\":\n          return events.lostConnection.subscribe(\n            callback as Callback<LostConnectionEvent>\n          );\n\n        case \"history\":\n          return events.history.subscribe(callback as Callback<HistoryEvent>);\n\n        case \"storage-status\":\n          return events.storageStatus.subscribe(\n            callback as Callback<StorageStatus>\n          );\n\n        case \"comments\":\n          return events.comments.subscribe(\n            callback as Callback<CommentsEventServerMsg>\n          );\n\n        // istanbul ignore next\n        default:\n          return assertNever(\n            first,\n            `\"${String(first)}\" is not a valid event name`\n          );\n      }\n    }\n\n    if (second === undefined || typeof first === \"function\") {\n      if (typeof first === \"function\") {\n        const storageCallback = first;\n        return events.storageBatch.subscribe(storageCallback);\n      } else {\n        // istanbul ignore next\n        throw new Error(\"Please specify a listener callback\");\n      }\n    }\n\n    if (isLiveNode(first)) {\n      const node = first;\n      if (options?.isDeep) {\n        const storageCallback = second as StorageCallback;\n        return subscribeToLiveStructureDeeply(node, storageCallback);\n      } else {\n        const nodeCallback = second as (node: L) => void;\n        return subscribeToLiveStructureShallowly(node, nodeCallback);\n      }\n    }\n\n    throw new Error(\n      `${String(first)} is not a value that can be subscribed to.`\n    );\n  }\n\n  return subscribe;\n}\n\nfunction isRoomEventName(value: string) {\n  return (\n    value === \"my-presence\" ||\n    value === \"others\" ||\n    value === \"event\" ||\n    value === \"error\" ||\n    value === \"history\" ||\n    value === \"status\" ||\n    value === \"storage-status\" ||\n    value === \"lost-connection\" ||\n    value === \"connection\" ||\n    value === \"comments\"\n  );\n}\n\nexport function makeAuthDelegateForRoom(\n  roomId: string,\n  authManager: AuthManager\n): () => Promise<AuthValue> {\n  return async () => {\n    return authManager.getAuthValue({ requestedScope: \"room:read\", roomId });\n  };\n}\n\nexport function makeCreateSocketDelegateForRoom(\n  roomId: string,\n  baseUrl: string,\n  WebSocketPolyfill?: IWebSocket\n) {\n  return (authValue: AuthValue): IWebSocketInstance => {\n    const ws: IWebSocket | undefined =\n      WebSocketPolyfill ??\n      (typeof WebSocket === \"undefined\" ? undefined : WebSocket);\n\n    if (ws === undefined) {\n      throw new StopRetrying(\n        \"To use Liveblocks client in a non-DOM environment, you need to provide a WebSocket polyfill.\"\n      );\n    }\n\n    const url = new URL(baseUrl);\n    url.protocol = url.protocol === \"http:\" ? \"ws\" : \"wss\";\n    url.pathname = \"/v7\";\n    url.searchParams.set(\"roomId\", roomId);\n    if (authValue.type === \"secret\") {\n      url.searchParams.set(\"tok\", authValue.token.raw);\n    } else if (authValue.type === \"public\") {\n      url.searchParams.set(\"pubkey\", authValue.publicApiKey);\n    } else {\n      return assertNever(authValue, \"Unhandled case\");\n    }\n    url.searchParams.set(\"version\", PKG_VERSION || \"dev\");\n    return new ws(url.toString());\n  };\n}\n","import { type Ai, createAi, makeCreateSocketDelegateForAi } from \"./ai\";\nimport type { LiveblocksHttpApi } from \"./api-client\";\nimport { createApiClient } from \"./api-client\";\nimport { createAuthManager } from \"./auth-manager\";\nimport { isIdle, StopRetrying } from \"./connection\";\nimport { DEFAULT_BASE_URL } from \"./constants\";\nimport type { LsonObject } from \"./crdts/Lson\";\nimport { linkDevTools, setupDevTools, unlinkDevTools } from \"./devtools\";\nimport type { DE, DM, DP, DRI, DS, DU } from \"./globals/augmentation\";\nimport { kInternal } from \"./internal\";\nimport type { BatchStore } from \"./lib/batch\";\nimport { Batch, createBatchStore } from \"./lib/batch\";\nimport type { Observable } from \"./lib/EventSource\";\nimport { makeEventSource } from \"./lib/EventSource\";\nimport * as console from \"./lib/fancy-console\";\nimport type { Json, JsonObject } from \"./lib/Json\";\nimport type { NoInfr } from \"./lib/NoInfer\";\nimport type { Relax } from \"./lib/Relax\";\nimport type { Resolve } from \"./lib/Resolve\";\nimport { Signal } from \"./lib/signals\";\nimport type { CustomAuthenticationResult } from \"./protocol/Authentication\";\nimport { TokenKind } from \"./protocol/AuthToken\";\nimport type { BaseUserMeta } from \"./protocol/BaseUserMeta\";\nimport type {\n  BaseMetadata,\n  ThreadData,\n  ThreadDeleteInfo,\n} from \"./protocol/Comments\";\nimport type {\n  InboxNotificationData,\n  InboxNotificationDeleteInfo,\n} from \"./protocol/InboxNotifications\";\nimport type {\n  NotificationSettings,\n  PartialNotificationSettings,\n} from \"./protocol/NotificationSettings\";\nimport { createNotificationSettings } from \"./protocol/NotificationSettings\";\nimport type {\n  SubscriptionData,\n  SubscriptionDeleteInfo,\n} from \"./protocol/Subscriptions\";\nimport type {\n  LargeMessageStrategy,\n  OpaqueRoom,\n  OptionalTupleUnless,\n  PartialUnless,\n  Polyfills,\n  Room,\n  RoomDelegates,\n  SyncSource,\n} from \"./room\";\nimport {\n  createRoom,\n  makeAuthDelegateForRoom,\n  makeCreateSocketDelegateForRoom,\n} from \"./room\";\nimport type { Awaitable } from \"./types/Awaitable\";\nimport type { LiveblocksErrorContext } from \"./types/LiveblocksError\";\nimport { LiveblocksError } from \"./types/LiveblocksError\";\nimport type { MentionData } from \"./types/MentionData\";\n\nconst MIN_THROTTLE = 16;\nconst MAX_THROTTLE = 1_000;\nconst DEFAULT_THROTTLE = 100;\n\nconst MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT = 15_000;\nconst MIN_LOST_CONNECTION_TIMEOUT = 200;\nconst RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT = 1_000;\nconst MAX_LOST_CONNECTION_TIMEOUT = 30_000;\nconst DEFAULT_LOST_CONNECTION_TIMEOUT = 5_000;\n\nconst RESOLVE_USERS_BATCH_DELAY = 50;\nconst RESOLVE_ROOMS_INFO_BATCH_DELAY = 50;\n\nexport type ResolveMentionSuggestionsArgs = {\n  /**\n   * The ID of the current room.\n   */\n  roomId: string;\n\n  /**\n   * The text to search for.\n   */\n  text: string;\n};\n\nexport type ResolveUsersArgs = {\n  /**\n   * The IDs of the users to resolve.\n   */\n  userIds: string[];\n};\n\nexport type ResolveRoomsInfoArgs = {\n  /**\n   * The IDs of the rooms to resolve.\n   */\n  roomIds: string[];\n};\n\nexport type EnterOptions<P extends JsonObject = DP, S extends LsonObject = DS> =\n  // prettier-ignore\n  Resolve<\n  {\n    /**\n     * Whether or not the room automatically connects to Liveblock servers.\n     * Default is true.\n     *\n     * Usually set to false when the client is used from the server to not call\n     * the authentication endpoint or connect via WebSocket.\n     */\n    autoConnect?: boolean;\n  }\n\n  // Initial presence is only mandatory if the custom type requires it to be\n  & PartialUnless<\n    P,\n    {\n      /**\n       * The initial Presence to use and announce when you enter the Room. The\n       * Presence is available on all users in the Room (me & others).\n       */\n      initialPresence: P | ((roomId: string) => P);\n    }\n  >\n  \n  // Initial storage is only mandatory if the custom type requires it to be\n  & PartialUnless<\n    S,\n    {\n      /**\n       * The initial Storage to use when entering a new Room.\n       */\n      initialStorage: S | ((roomId: string) => S);\n    }\n  >\n>;\n\nexport type SyncStatus =\n  /* Liveblocks is in the process of writing changes */\n  | \"synchronizing\"\n  /* Liveblocks has persisted all pending changes */\n  | \"synchronized\";\n\n/**\n * \"synchronizing\"     - Liveblocks is in the process of writing changes\n * \"synchronized\"      - Liveblocks has persisted all pending changes\n * \"has-local-changes\" - There is local pending state inputted by the user, but\n *                       we're not yet \"synchronizing\" it until a user\n *                       interaction, like the draft text in a comment box.\n */\nexport type InternalSyncStatus = SyncStatus | \"has-local-changes\";\n\n/**\n * @private\n *\n * Private methods and variables used in the core internals, but as a user\n * of Liveblocks, NEVER USE ANY OF THESE DIRECTLY, because bad things\n * will probably happen if you do.\n */\nexport type PrivateClientApi<U extends BaseUserMeta, M extends BaseMetadata> = {\n  readonly currentUserId: Signal<string | undefined>;\n  readonly mentionSuggestionsCache: Map<string, MentionData[]>;\n  readonly resolveMentionSuggestions: ClientOptions<U>[\"resolveMentionSuggestions\"];\n  readonly usersStore: BatchStore<U[\"info\"] | undefined, string>;\n  readonly roomsInfoStore: BatchStore<DRI | undefined, string>;\n  readonly getRoomIds: () => string[];\n  readonly httpClient: LiveblocksHttpApi<M>;\n  // Type-level helper\n  as<M2 extends BaseMetadata>(): Client<U, M2>;\n  // Tracking pending changes globally\n  createSyncSource(): SyncSource;\n  emitError(context: LiveblocksErrorContext, cause?: Error): void;\n  ai: Ai;\n};\n\nexport type NotificationsApi<M extends BaseMetadata> = {\n  /**\n   * Gets a page (or the initial page) for user inbox notifications and their\n   * associated threads and thread subscriptions.\n   *\n   * This function should NOT be used for delta updates, only for pagination\n   * (including the first page fetch). For delta updates (done during the\n   * periodic polling), use the `getInboxNotificationsSince` function.\n   *\n   * @example\n   * const {\n   *   inboxNotifications,\n   *   threads,\n   *   subscriptions,\n   *   nextCursor,\n   * } = await client.getInboxNotifications();\n   * const data = await client.getInboxNotifications();  // Fetch initial page (of 20 inbox notifications)\n   * const data = await client.getInboxNotifications({ cursor: nextCursor });  // Fetch next page (= next 20 inbox notifications)\n   */\n  getInboxNotifications(options?: { cursor?: string }): Promise<{\n    inboxNotifications: InboxNotificationData[];\n    threads: ThreadData<M>[];\n    subscriptions: SubscriptionData[];\n    nextCursor: string | null;\n    requestedAt: Date;\n  }>;\n\n  /**\n   * Fetches a \"delta update\" since the last time we updated.\n   *\n   * This function should NOT be used for pagination, for that, see the\n   * `getInboxNotifications` function.\n   *\n   * @example\n   * const {\n   *   inboxNotifications: {\n   *     updated,\n   *     deleted,\n   *   },\n   *   threads: {\n   *     updated,\n   *     deleted,\n   *   },\n   *   subscriptions: {\n   *     updated,\n   *     deleted,\n   *   },\n   *   requestedAt,\n   * } = await client.getInboxNotificationsSince({ since: result.requestedAt }});\n   */\n  getInboxNotificationsSince(options: {\n    since: Date;\n    signal?: AbortSignal;\n  }): Promise<{\n    inboxNotifications: {\n      updated: InboxNotificationData[];\n      deleted: InboxNotificationDeleteInfo[];\n    };\n    threads: {\n      updated: ThreadData<M>[];\n      deleted: ThreadDeleteInfo[];\n    };\n    subscriptions: {\n      updated: SubscriptionData[];\n      deleted: SubscriptionDeleteInfo[];\n    };\n    requestedAt: Date;\n  }>;\n\n  /**\n   * Gets the number of unread inbox notifications for the current user.\n   *\n   * @example\n   * const count = await client.getUnreadInboxNotificationsCount();\n   */\n  getUnreadInboxNotificationsCount(): Promise<number>;\n\n  /**\n   * Marks all inbox notifications as read.\n   *\n   * @example\n   * await client.markAllInboxNotificationsAsRead();\n   */\n  markAllInboxNotificationsAsRead(): Promise<void>;\n\n  /**\n   * Marks an inbox notification as read.\n   *\n   * @example\n   * await client.markInboxNotificationAsRead(\"in_xxx\");\n   */\n  markInboxNotificationAsRead(inboxNotificationId: string): Promise<void>;\n\n  /**\n   * Deletes all inbox notifications for the current user.\n   *\n   * @example\n   * await client.deleteAllInboxNotifications();\n   */\n  deleteAllInboxNotifications(): Promise<void>;\n\n  /**\n   * Deletes an inbox notification for the current user.\n   *\n   * @example\n   * await client.deleteInboxNotification(\"in_xxx\");\n   */\n  deleteInboxNotification(inboxNotificationId: string): Promise<void>;\n\n  /**\n   * Gets notifications settings for a user for a project.\n   *\n   * @example\n   * const notificationSettings = await client.getNotificationSettings();\n   */\n  getNotificationSettings(options?: {\n    signal?: AbortSignal;\n  }): Promise<NotificationSettings>;\n\n  /**\n   * Update notifications settings for a user for a project.\n   *\n   * @example\n   * await client.updateNotificationSettings({\n   *  email: {\n   *    thread: true,\n   *    textMention: false,\n   *    $customKind1: true,\n   *  }\n   * })\n   */\n  updateNotificationSettings(\n    settings: PartialNotificationSettings\n  ): Promise<NotificationSettings>;\n};\n\n/**\n * @private Widest-possible Client type, matching _any_ Client instance. Note\n * that this type is different from `Client`-without-type-arguments. That\n * represents a Client instance using globally augmented types only, which is\n * narrower.\n */\nexport type OpaqueClient = Client<BaseUserMeta>;\n\nexport type Client<U extends BaseUserMeta = DU, M extends BaseMetadata = DM> = {\n  /**\n   * Gets a room. Returns null if {@link Client.enter} has not been called previously.\n   *\n   * @param roomId The id of the room\n   */\n  getRoom<\n    P extends JsonObject = DP,\n    S extends LsonObject = DS,\n    E extends Json = DE,\n    M2 extends BaseMetadata = M,\n  >(\n    roomId: string\n  ): Room<P, S, U, E, M2> | null;\n\n  /**\n   * Enter a room.\n   * @param roomId The id of the room\n   * @param options Optional. You can provide initializers for the Presence or Storage when entering the Room.\n   * @returns The room and a leave function. Call the returned leave() function when you no longer need the room.\n   */\n  enterRoom<\n    P extends JsonObject = DP,\n    S extends LsonObject = DS,\n    E extends Json = DE,\n    M2 extends BaseMetadata = M,\n  >(\n    roomId: string,\n    ...args: OptionalTupleUnless<\n      P & S,\n      [options: EnterOptions<NoInfr<P>, NoInfr<S>>]\n    >\n  ): {\n    room: Room<P, S, U, E, M2>;\n    leave: () => void;\n  };\n\n  /**\n   * Purges all cached auth tokens and reconnects all rooms that are still\n   * connected, if any.\n   *\n   * Call this whenever you log out a user in your application.\n   */\n  logout(): void;\n\n  /**\n   * Advanced APIs related to the resolvers.\n   */\n  resolvers: {\n    /**\n     * Invalidate some or all users that were previously cached by `resolveUsers`.\n     *\n     * @example\n     * // Invalidate all users\n     * client.resolvers.invalidateUsers();\n     *\n     * @example\n     * // Invalidate specific users\n     * client.resolvers.invalidateUsers([\"user-1\", \"user-2\"]);\n     */\n    invalidateUsers(userIds?: string[]): void;\n\n    /**\n     * Invalidate some or all rooms info that were previously cached by `resolveRoomsInfo`.\n     *\n     * @example\n     * // Invalidate all rooms\n     * client.resolvers.invalidateRoomsInfo();\n     *\n     * @example\n     * // Invalidate specific rooms\n     * client.resolvers.invalidateRoomsInfo([\"room-1\", \"room-2\"]);\n     */\n    invalidateRoomsInfo(roomIds?: string[]): void;\n\n    /**\n     * Invalidate all mention suggestions cached by `resolveMentionSuggestions`.\n     *\n     * @example\n     * // Invalidate all mention suggestions\n     * client.resolvers.invalidateMentionSuggestions();\n     */\n    invalidateMentionSuggestions(): void;\n  };\n\n  /**\n   * @private\n   *\n   * Private methods and variables used in the core internals, but as a user\n   * of Liveblocks, NEVER USE ANY OF THESE DIRECTLY, because bad things\n   * will probably happen if you do.\n   */\n  // TODO Make this a getter, so we can provide M\n  readonly [kInternal]: PrivateClientApi<U, M>;\n\n  /**\n   * Returns the current global sync status of the Liveblocks client. If any\n   * part of Liveblocks has any local pending changes that haven't been\n   * confirmed by or persisted by the server yet, this will be \"synchronizing\",\n   * otherwise \"synchronized\".\n   *\n   * This is a combined status for all of the below parts of Liveblocks:\n   * - Storage (realtime APIs)\n   * - Text Editors\n   * - Comments\n   * - Notifications\n   *\n   * @example\n   * const status = client.getSyncStatus();  // \"synchronizing\" | \"synchronized\"\n   */\n  getSyncStatus(): SyncStatus;\n\n  /**\n   * All possible client events, subscribable from a single place.\n   */\n  readonly events: {\n    readonly error: Observable<LiveblocksError>;\n    readonly syncStatus: Observable<void>;\n  };\n} & NotificationsApi<M>;\n\nexport type AuthEndpoint =\n  | string\n  | ((room?: string) => Promise<CustomAuthenticationResult>);\n\n/**\n * The authentication endpoint that is called to ensure that the current user has access to a room.\n * Can be an url or a callback if you need to add additional headers.\n */\nexport type ClientOptions<U extends BaseUserMeta = DU> = {\n  throttle?: number; // in milliseconds\n  lostConnectionTimeout?: number; // in milliseconds\n  backgroundKeepAliveTimeout?: number; // in milliseconds\n  polyfills?: Polyfills;\n  largeMessageStrategy?: LargeMessageStrategy;\n  unstable_streamData?: boolean;\n  /**\n   * A function that returns a list of mention suggestions matching a string.\n   */\n  resolveMentionSuggestions?: (\n    args: ResolveMentionSuggestionsArgs\n  ) => Awaitable<string[] | MentionData[]>;\n\n  /**\n   * A function that returns user info from user IDs.\n   * You should return a list of user objects of the same size, in the same order.\n   */\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n\n  /**\n   * A function that returns room info from room IDs.\n   * You should return a list of room info objects of the same size, in the same order.\n   */\n  resolveRoomsInfo?: (\n    args: ResolveRoomsInfoArgs\n  ) => Awaitable<(DRI | undefined)[] | undefined>;\n\n  /**\n   * Prevent the current browser tab from being closed if there are any locally\n   * pending Liveblocks changes that haven't been submitted to or confirmed by\n   * the server yet.\n   */\n  preventUnsavedChanges?: boolean;\n\n  /**\n   * @internal To point the client to a different Liveblocks server. Only\n   * useful for Liveblocks developers. Not for end users.\n   */\n  baseUrl?: string;\n\n  /** @internal */\n  mockedDelegates?: RoomDelegates;\n\n  /** @internal */\n  enableDebugLogging?: boolean;\n} & Relax<{ publicApiKey: string } | { authEndpoint: AuthEndpoint }>;\n\nfunction getBaseUrl(baseUrl?: string | undefined): string {\n  if (\n    typeof baseUrl === \"string\" &&\n    baseUrl.startsWith(\"http\") // Must be http or https URL\n  ) {\n    return baseUrl;\n  } else {\n    return DEFAULT_BASE_URL;\n  }\n}\n\n/**\n * Create a client that will be responsible to communicate with liveblocks servers.\n *\n * @example\n * const client = createClient({\n *   authEndpoint: \"/api/auth\"\n * });\n *\n * // It's also possible to use a function to call your authentication endpoint.\n * // Useful to add additional headers or use an API wrapper (like Firebase functions)\n * const client = createClient({\n *   authEndpoint: async (room?) => {\n *     const response = await fetch(\"/api/auth\", {\n *       method: \"POST\",\n *       headers: {\n *          Authentication: \"token\",\n *          \"Content-Type\": \"application/json\"\n *       },\n *       body: JSON.stringify({ room })\n *     });\n *\n *     return await response.json(); // should be: { token: \"...\" }\n *   }\n * });\n */\nexport function createClient<U extends BaseUserMeta = DU>(\n  options: ClientOptions<U>\n): Client<U> {\n  const clientOptions = options;\n  const throttleDelay = getThrottle(clientOptions.throttle ?? DEFAULT_THROTTLE);\n  const lostConnectionTimeout = getLostConnectionTimeout(\n    clientOptions.lostConnectionTimeout ?? DEFAULT_LOST_CONNECTION_TIMEOUT\n  );\n  const backgroundKeepAliveTimeout = getBackgroundKeepAliveTimeout(\n    clientOptions.backgroundKeepAliveTimeout\n  );\n  const baseUrl = getBaseUrl(clientOptions.baseUrl);\n\n  const currentUserId = new Signal<string | undefined>(undefined);\n\n  const authManager = createAuthManager(options, (token) => {\n    const userId = token.k === TokenKind.SECRET_LEGACY ? token.id : token.uid;\n    currentUserId.set(() => userId);\n  });\n\n  const fetchPolyfill =\n    clientOptions.polyfills?.fetch ||\n    /* istanbul ignore next */ globalThis.fetch?.bind(globalThis);\n\n  const httpClient = createApiClient({\n    baseUrl,\n    fetchPolyfill,\n    currentUserId,\n    authManager,\n  });\n\n  type RoomDetails = {\n    room: OpaqueRoom;\n    unsubs: Set<() => void>;\n  };\n\n  const roomsById = new Map<string, RoomDetails>();\n\n  const ai = createAi({\n    userId: currentUserId.get(),\n    lostConnectionTimeout,\n    backgroundKeepAliveTimeout: getBackgroundKeepAliveTimeout(\n      clientOptions.backgroundKeepAliveTimeout\n    ),\n    polyfills: clientOptions.polyfills,\n    delegates: {\n      createSocket: makeCreateSocketDelegateForAi(\n        baseUrl,\n        clientOptions.polyfills?.WebSocket\n      ),\n      authenticate: async () => {\n        const resp = await authManager.getAuthValue({\n          requestedScope: \"room:read\",\n        });\n        if (resp.type === \"public\") {\n          throw new StopRetrying(\n            \"Cannot use AI Copilots with a public API key\"\n          );\n        } else if (resp.token.parsed.k === TokenKind.SECRET_LEGACY) {\n          throw new StopRetrying(\"AI Copilots requires an ID or Access token\");\n        } else {\n          if (!resp.token.parsed.ai) {\n            throw new StopRetrying(\n              \"AI Copilots is not yet enabled for this account. To get started, see https://liveblocks.io/docs/get-started/ai-copilots#Quickstart\"\n            );\n          }\n        }\n        return resp;\n      },\n      canZombie: () => false,\n    },\n  });\n\n  function teardownRoom(room: OpaqueRoom) {\n    unlinkDevTools(room.id);\n    roomsById.delete(room.id);\n    room.destroy();\n  }\n\n  function leaseRoom<\n    P extends JsonObject,\n    S extends LsonObject,\n    U extends BaseUserMeta,\n    E extends Json,\n    M extends BaseMetadata,\n  >(\n    details: RoomDetails\n  ): {\n    room: Room<P, S, U, E, M>;\n    leave: () => void;\n  } {\n    // Create a new self-destructing leave function\n    const leave = () => {\n      const self = leave; // A reference to the currently executing function itself\n\n      if (!details.unsubs.delete(self)) {\n        console.warn(\n          \"This leave function was already called. Calling it more than once has no effect.\"\n        );\n      } else {\n        // Was this the last room lease? If so, tear down the room\n        if (details.unsubs.size === 0) {\n          teardownRoom(details.room);\n        }\n      }\n    };\n\n    details.unsubs.add(leave);\n    return {\n      room: details.room as Room<P, S, U, E, M>,\n      leave,\n    };\n  }\n\n  function enterRoom<\n    P extends JsonObject,\n    S extends LsonObject,\n    U extends BaseUserMeta,\n    E extends Json,\n    M extends BaseMetadata,\n  >(\n    roomId: string,\n    ...args: OptionalTupleUnless<\n      P & S,\n      [options: EnterOptions<NoInfr<P>, NoInfr<S>>]\n    >\n  ): {\n    room: Room<P, S, U, E, M>;\n    leave: () => void;\n  } {\n    const existing = roomsById.get(roomId);\n    if (existing !== undefined) {\n      return leaseRoom(existing);\n    }\n\n    const options = args[0] ?? ({} as EnterOptions<P, S>);\n    const initialPresence =\n      (typeof options.initialPresence === \"function\"\n        ? options.initialPresence(roomId)\n        : options.initialPresence) ?? ({} as P);\n\n    const initialStorage =\n      (typeof options.initialStorage === \"function\"\n        ? options.initialStorage(roomId)\n        : options.initialStorage) ?? ({} as S);\n\n    const newRoom = createRoom<P, S, U, E, M>(\n      { initialPresence, initialStorage },\n      {\n        roomId,\n        throttleDelay,\n        lostConnectionTimeout,\n        backgroundKeepAliveTimeout,\n        polyfills: clientOptions.polyfills,\n        delegates: clientOptions.mockedDelegates ?? {\n          createSocket: makeCreateSocketDelegateForRoom(\n            roomId,\n            baseUrl,\n            clientOptions.polyfills?.WebSocket\n          ),\n          authenticate: makeAuthDelegateForRoom(roomId, authManager),\n        },\n        enableDebugLogging: clientOptions.enableDebugLogging,\n        baseUrl,\n        errorEventSource: liveblocksErrorSource,\n        largeMessageStrategy: clientOptions.largeMessageStrategy,\n        unstable_streamData: !!clientOptions.unstable_streamData,\n        roomHttpClient: httpClient as LiveblocksHttpApi<M>,\n        createSyncSource,\n      }\n    );\n\n    const newRoomDetails: RoomDetails = {\n      room: newRoom,\n      unsubs: new Set(),\n    };\n    roomsById.set(roomId, newRoomDetails);\n\n    setupDevTools(() => Array.from(roomsById.keys()));\n    linkDevTools(roomId, newRoom);\n\n    const shouldConnect = options.autoConnect ?? true;\n    if (shouldConnect) {\n      // we need to check here because nextjs would fail earlier with Node < 16\n      if (typeof atob === \"undefined\") {\n        if (clientOptions.polyfills?.atob === undefined) {\n          throw new Error(\n            \"You need to polyfill atob to use the client in your environment. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/atob-polyfill\"\n          );\n        }\n        // At this point, atob does not exist so we are either on React Native or on Node < 16, hence global is available.\n        global.atob = clientOptions.polyfills.atob;\n      }\n\n      newRoom.connect();\n    }\n\n    return leaseRoom(newRoomDetails);\n  }\n\n  function getRoom<\n    P extends JsonObject,\n    S extends LsonObject,\n    U extends BaseUserMeta,\n    E extends Json,\n    M extends BaseMetadata,\n  >(roomId: string): Room<P, S, U, E, M> | null {\n    const room = roomsById.get(roomId)?.room;\n    return room ? (room as Room<P, S, U, E, M>) : null;\n  }\n\n  function logout() {\n    authManager.reset();\n\n    // Reset the current user id store when the client is logged out\n    currentUserId.set(() => undefined);\n\n    // Reconnect all rooms that aren't idle, if any. This ensures that those\n    // rooms will get reauthorized now that the auth cache is reset. If that\n    // fails, they might disconnect.\n    for (const { room } of roomsById.values()) {\n      if (!isIdle(room.getStatus())) {\n        room.reconnect();\n      }\n    }\n  }\n\n  const resolveUsers = clientOptions.resolveUsers;\n  const warnIfNoResolveUsers = createDevelopmentWarning(\n    () => !resolveUsers,\n    \"Set the resolveUsers option in createClient to specify user info.\"\n  );\n\n  const batchedResolveUsers = new Batch(\n    async (batchedUserIds: string[]) => {\n      const userIds = batchedUserIds.flat();\n      const users = await resolveUsers?.({ userIds });\n\n      warnIfNoResolveUsers();\n\n      return users ?? userIds.map(() => undefined);\n    },\n    { delay: RESOLVE_USERS_BATCH_DELAY }\n  );\n  const usersStore = createBatchStore(batchedResolveUsers);\n\n  function invalidateResolvedUsers(userIds?: string[]) {\n    usersStore.invalidate(userIds);\n  }\n\n  const resolveRoomsInfo = clientOptions.resolveRoomsInfo;\n  const warnIfNoResolveRoomsInfo = createDevelopmentWarning(\n    () => !resolveRoomsInfo,\n    \"Set the resolveRoomsInfo option in createClient to specify room info.\"\n  );\n\n  const batchedResolveRoomsInfo = new Batch(\n    async (batchedRoomIds: string[]) => {\n      const roomIds = batchedRoomIds.flat();\n      const roomsInfo = await resolveRoomsInfo?.({ roomIds });\n\n      warnIfNoResolveRoomsInfo();\n\n      return roomsInfo ?? roomIds.map(() => undefined);\n    },\n    { delay: RESOLVE_ROOMS_INFO_BATCH_DELAY }\n  );\n  const roomsInfoStore = createBatchStore(batchedResolveRoomsInfo);\n\n  function invalidateResolvedRoomsInfo(roomIds?: string[]) {\n    roomsInfoStore.invalidate(roomIds);\n  }\n\n  const mentionSuggestionsCache = new Map<string, MentionData[]>();\n\n  function invalidateResolvedMentionSuggestions() {\n    mentionSuggestionsCache.clear();\n  }\n\n  // ----------------------------------------------------------------\n\n  const syncStatusSources: Signal<InternalSyncStatus>[] = [];\n  const syncStatusSignal = new Signal<InternalSyncStatus>(\"synchronized\");\n\n  const liveblocksErrorSource = makeEventSource<LiveblocksError>();\n\n  function getSyncStatus(): SyncStatus {\n    const status = syncStatusSignal.get();\n    return status === \"synchronizing\" ? status : \"synchronized\";\n  }\n\n  function recompute() {\n    syncStatusSignal.set(\n      syncStatusSources.some((src) => src.get() === \"synchronizing\")\n        ? \"synchronizing\"\n        : syncStatusSources.some((src) => src.get() === \"has-local-changes\")\n          ? \"has-local-changes\"\n          : \"synchronized\"\n    );\n  }\n\n  function createSyncSource(): SyncSource {\n    const source = new Signal<InternalSyncStatus>(\"synchronized\");\n    syncStatusSources.push(source);\n\n    const unsub = source.subscribe(() => recompute());\n\n    function setSyncStatus(status: InternalSyncStatus) {\n      source.set(status);\n    }\n\n    function destroy() {\n      unsub();\n      const index = syncStatusSources.findIndex((item) => item === source);\n      if (index > -1) {\n        const [ref] = syncStatusSources.splice(index, 1);\n        const wasStillPending = ref.get() !== \"synchronized\";\n        if (wasStillPending) {\n          // We only have to recompute if it was still pending. Otherwise it\n          // could not have an effect on the global state anyway.\n          recompute();\n        }\n      }\n    }\n\n    return { setSyncStatus, destroy };\n  }\n\n  // ----------------------------------------------------------------\n\n  // Set up event handler that will prevent the browser tab from being closed\n  // if there are locally pending changes to any part of Liveblocks (Storage,\n  // text editors, Threads, Notifications, etc)\n  {\n    const maybePreventClose = (e: BeforeUnloadEvent) => {\n      if (\n        clientOptions.preventUnsavedChanges &&\n        syncStatusSignal.get() !== \"synchronized\"\n      ) {\n        e.preventDefault();\n      }\n    };\n\n    // A Liveblocks client is currently never destroyed.\n    // TODO Call win.removeEventListener(\"beforeunload\", maybePreventClose)\n    // once we have a client.destroy() method\n    const win = typeof window !== \"undefined\" ? window : undefined;\n    win?.addEventListener(\"beforeunload\", maybePreventClose);\n  }\n\n  async function getNotificationSettings(options?: {\n    signal?: AbortSignal;\n  }): Promise<NotificationSettings> {\n    const plainSettings = await httpClient.getNotificationSettings(options);\n    const settings = createNotificationSettings(plainSettings);\n\n    return settings;\n  }\n\n  async function updateNotificationSettings(\n    settings: PartialNotificationSettings\n  ): Promise<NotificationSettings> {\n    const plainSettings = await httpClient.updateNotificationSettings(settings);\n    const settingsObject = createNotificationSettings(plainSettings);\n\n    return settingsObject;\n  }\n\n  const client: Client<U> = Object.defineProperty(\n    {\n      enterRoom,\n      getRoom,\n\n      logout,\n\n      // Public inbox notifications API\n      getInboxNotifications: httpClient.getInboxNotifications,\n      getInboxNotificationsSince: httpClient.getInboxNotificationsSince,\n      getUnreadInboxNotificationsCount:\n        httpClient.getUnreadInboxNotificationsCount,\n      markAllInboxNotificationsAsRead:\n        httpClient.markAllInboxNotificationsAsRead,\n      markInboxNotificationAsRead: httpClient.markInboxNotificationAsRead,\n      deleteAllInboxNotifications: httpClient.deleteAllInboxNotifications,\n      deleteInboxNotification: httpClient.deleteInboxNotification,\n\n      // Public notification settings API\n      getNotificationSettings,\n      updateNotificationSettings,\n\n      // Advanced resolvers APIs\n      resolvers: {\n        invalidateUsers: invalidateResolvedUsers,\n        invalidateRoomsInfo: invalidateResolvedRoomsInfo,\n        invalidateMentionSuggestions: invalidateResolvedMentionSuggestions,\n      },\n\n      getSyncStatus,\n      events: {\n        error: liveblocksErrorSource,\n        syncStatus: syncStatusSignal,\n      },\n\n      // Internal\n      [kInternal]: {\n        currentUserId,\n        mentionSuggestionsCache,\n        ai,\n        resolveMentionSuggestions: clientOptions.resolveMentionSuggestions,\n        usersStore,\n        roomsInfoStore,\n        getRoomIds() {\n          return Array.from(roomsById.keys());\n        },\n        httpClient,\n        // Type-level helper only, it's effectively only an identity-function at runtime\n        as: <M2 extends BaseMetadata>() => client as Client<U, M2>,\n        createSyncSource,\n        emitError: (context: LiveblocksErrorContext, cause?: Error) => {\n          const error = LiveblocksError.from(context, cause);\n          const didNotify = liveblocksErrorSource.notify(error);\n          if (!didNotify) {\n            console.error(error.message);\n          }\n        },\n      },\n    },\n    kInternal,\n    {\n      enumerable: false,\n    }\n  );\n\n  return client;\n}\n\n/**\n * @private Private API, don't use this directly.\n */\nexport function checkBounds(\n  option: string,\n  value: unknown,\n  min: number,\n  max?: number,\n  recommendedMin?: number\n): number {\n  if (\n    typeof value !== \"number\" ||\n    value < min ||\n    (max !== undefined && value > max)\n  ) {\n    throw new Error(\n      max !== undefined\n        ? `${option} should be between ${recommendedMin ?? min} and ${max}.`\n        : `${option} should be at least ${recommendedMin ?? min}.`\n    );\n  }\n  return value;\n}\n\nfunction getBackgroundKeepAliveTimeout(\n  value: number | undefined\n): number | undefined {\n  if (value === undefined) return undefined;\n  return checkBounds(\n    \"backgroundKeepAliveTimeout\",\n    value,\n    MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT\n  );\n}\n\nfunction getThrottle(value: number): number {\n  return checkBounds(\"throttle\", value, MIN_THROTTLE, MAX_THROTTLE);\n}\n\nfunction getLostConnectionTimeout(value: number): number {\n  return checkBounds(\n    \"lostConnectionTimeout\",\n    value,\n    MIN_LOST_CONNECTION_TIMEOUT,\n    MAX_LOST_CONNECTION_TIMEOUT,\n    RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT\n  );\n}\n\n/**\n * Emit a warning only once if a condition is met, in development only.\n */\nfunction createDevelopmentWarning(\n  condition: boolean | (() => boolean),\n  ...args: Parameters<typeof console.warn>\n) {\n  let hasWarned = false;\n\n  if (process.env.NODE_ENV !== \"production\") {\n    return () => {\n      if (\n        !hasWarned &&\n        (typeof condition === \"function\" ? condition() : condition)\n      ) {\n        console.warn(...args);\n\n        hasWarned = true;\n      }\n    };\n  } else {\n    return () => {};\n  }\n}\n","import type { ResolveUsersArgs } from \"../client\";\nimport type { DU } from \"../globals/augmentation\";\nimport { nn } from \"../lib/assert\";\nimport type { BaseUserMeta } from \"../protocol/BaseUserMeta\";\nimport type {\n  CommentBody,\n  CommentBodyBlockElement,\n  CommentBodyElement,\n  CommentBodyInlineElement,\n  CommentBodyLink,\n  CommentBodyMention,\n  CommentBodyParagraph,\n  CommentBodyText,\n} from \"../protocol/Comments\";\nimport type { Awaitable } from \"../types/Awaitable\";\n\ntype CommentBodyBlockElementName = Exclude<\n  CommentBodyBlockElement,\n  CommentBodyText\n>[\"type\"];\n\ntype CommentBodyInlineElementName =\n  | Exclude<CommentBodyInlineElement, CommentBodyText>[\"type\"]\n  | \"text\";\n\ntype CommentBodyElementName =\n  | CommentBodyBlockElementName\n  | CommentBodyInlineElementName;\n\ntype CommentBodyBlockElements = {\n  paragraph: CommentBodyParagraph;\n};\n\ntype CommentBodyInlineElements = {\n  text: CommentBodyText;\n  link: CommentBodyLink;\n  mention: CommentBodyMention;\n};\n\ntype CommentBodyElements = CommentBodyBlockElements & CommentBodyInlineElements;\n\ntype CommentBodyVisitor<T extends CommentBodyElement = CommentBodyElement> = (\n  element: T\n) => void;\n\nexport type CommentBodyParagraphElementArgs = {\n  /**\n   * The paragraph element.\n   */\n  element: CommentBodyParagraph;\n\n  /**\n   * The text content of the paragraph.\n   */\n  children: string;\n};\n\nexport type CommentBodyTextElementArgs = {\n  /**\n   * The text element.\n   */\n  element: CommentBodyText;\n};\n\nexport type CommentBodyLinkElementArgs = {\n  /**\n   * The link element.\n   */\n  element: CommentBodyLink;\n\n  /**\n   * The absolute URL of the link.\n   */\n  href: string;\n};\n\nexport type CommentBodyMentionElementArgs<U extends BaseUserMeta = DU> = {\n  /**\n   * The mention element.\n   */\n  element: CommentBodyMention;\n\n  /**\n   * The mention's user info, if the `resolvedUsers` option was provided.\n   */\n  user?: U[\"info\"];\n};\n\nexport type StringifyCommentBodyElements<U extends BaseUserMeta = DU> = {\n  /**\n   * The element used to display paragraphs.\n   */\n  paragraph: (args: CommentBodyParagraphElementArgs, index: number) => string;\n\n  /**\n   * The element used to display text elements.\n   */\n  text: (args: CommentBodyTextElementArgs, index: number) => string;\n\n  /**\n   * The element used to display links.\n   */\n  link: (args: CommentBodyLinkElementArgs, index: number) => string;\n\n  /**\n   * The element used to display mentions.\n   */\n  mention: (args: CommentBodyMentionElementArgs<U>, index: number) => string;\n};\n\nexport type StringifyCommentBodyOptions<U extends BaseUserMeta = DU> = {\n  /**\n   * Which format to convert the comment to.\n   */\n  format?: \"plain\" | \"html\" | \"markdown\";\n\n  /**\n   * The elements used to customize the resulting string. Each element has\n   * priority over the defaults inherited from the `format` option.\n   */\n  elements?: Partial<StringifyCommentBodyElements<U>>;\n\n  /**\n   * The separator used between paragraphs.\n   */\n  separator?: string;\n\n  /**\n   * A function that returns user info from user IDs.\n   * You should return a list of user objects of the same size, in the same order.\n   */\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n};\n\nexport function isCommentBodyParagraph(\n  element: CommentBodyElement\n): element is CommentBodyParagraph {\n  return \"type\" in element && element.type === \"paragraph\";\n}\n\nexport function isCommentBodyText(\n  element: CommentBodyElement\n): element is CommentBodyText {\n  return (\n    !(\"type\" in element) &&\n    \"text\" in element &&\n    typeof element.text === \"string\"\n  );\n}\n\nexport function isCommentBodyMention(\n  element: CommentBodyElement\n): element is CommentBodyMention {\n  return \"type\" in element && element.type === \"mention\";\n}\n\nexport function isCommentBodyLink(\n  element: CommentBodyElement\n): element is CommentBodyLink {\n  return \"type\" in element && element.type === \"link\";\n}\n\nconst commentBodyElementsGuards = {\n  paragraph: isCommentBodyParagraph,\n  text: isCommentBodyText,\n  link: isCommentBodyLink,\n  mention: isCommentBodyMention,\n};\n\nconst commentBodyElementsTypes: Record<\n  CommentBodyElementName,\n  \"block\" | \"inline\"\n> = {\n  paragraph: \"block\",\n  text: \"inline\",\n  link: \"inline\",\n  mention: \"inline\",\n};\n\nfunction traverseCommentBody(\n  body: CommentBody,\n  visitor: CommentBodyVisitor\n): void;\nfunction traverseCommentBody<T extends CommentBodyElementName>(\n  body: CommentBody,\n  element: T,\n  visitor: CommentBodyVisitor<CommentBodyElements[T]>\n): void;\nfunction traverseCommentBody(\n  body: CommentBody,\n  elementOrVisitor: CommentBodyElementName | CommentBodyVisitor,\n  possiblyVisitor?: CommentBodyVisitor\n): void {\n  if (!body || !body?.content) {\n    return;\n  }\n\n  const element =\n    typeof elementOrVisitor === \"string\" ? elementOrVisitor : undefined;\n  const type = element ? commentBodyElementsTypes[element] : \"all\";\n  const guard = element ? commentBodyElementsGuards[element] : () => true;\n  const visitor =\n    typeof elementOrVisitor === \"function\" ? elementOrVisitor : possiblyVisitor;\n\n  for (const block of body.content) {\n    if (type === \"all\" || type === \"block\") {\n      if (guard(block)) {\n        visitor?.(block);\n      }\n    }\n\n    if (type === \"all\" || type === \"inline\") {\n      for (const inline of block.children) {\n        if (guard(inline)) {\n          visitor?.(inline);\n        }\n      }\n    }\n  }\n}\n\n/**\n * Get an array of all mentions in a `CommentBody`.\n *\n * Narrow results with an optional predicate, e.g.\n * `(mention) => mention.kind === \"user\"` to only get user mentions.\n */\nexport function getMentionsFromCommentBody(\n  body: CommentBody,\n  predicate?: (mention: CommentBodyMention) => boolean\n): CommentBodyMention[] {\n  const mentionIds = new Set<string>();\n  const mentions: CommentBodyMention[] = [];\n\n  traverseCommentBody(body, \"mention\", (mention) => {\n    if (\n      // If this mention isn't already in the list\n      !mentionIds.has(mention.id) &&\n      // And the provided predicate is true\n      (predicate ? predicate(mention) : true)\n    ) {\n      mentionIds.add(mention.id);\n      mentions.push(mention);\n    }\n  });\n\n  return mentions;\n}\n\nexport async function resolveUsersInCommentBody<U extends BaseUserMeta>(\n  body: CommentBody,\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>\n): Promise<Map<string, U[\"info\"]>> {\n  const resolvedUsers = new Map<string, U[\"info\"]>();\n\n  if (!resolveUsers) {\n    return resolvedUsers;\n  }\n\n  const userIds = getMentionsFromCommentBody(\n    body,\n    (mention) => mention.kind === \"user\"\n  ).map((mention) => mention.id);\n\n  const users = await resolveUsers({\n    userIds,\n  });\n\n  for (const [index, userId] of userIds.entries()) {\n    const user = users?.[index];\n\n    if (user) {\n      resolvedUsers.set(userId, user);\n    }\n  }\n\n  return resolvedUsers;\n}\n\nconst htmlEscapables = {\n  \"&\": \"&amp;\",\n  \"<\": \"&lt;\",\n  \">\": \"&gt;\",\n  '\"': \"&quot;\",\n  \"'\": \"&#39;\",\n};\n\nconst htmlEscapablesRegex = new RegExp(\n  Object.keys(htmlEscapables)\n    .map((entity) => `\\\\${entity}`)\n    .join(\"|\"),\n  \"g\"\n);\n\nexport function htmlSafe(value: string): HtmlSafeString {\n  return new HtmlSafeString([String(value)], []);\n}\n\nfunction joinHtml(strings: (string | HtmlSafeString)[]) {\n  if (strings.length <= 0) {\n    return new HtmlSafeString([\"\"], []);\n  }\n\n  return new HtmlSafeString(\n    [\"\", ...(Array(strings.length - 1).fill(\"\") as string[]), \"\"],\n    strings\n  );\n}\n\nfunction escapeHtml(\n  value: string | string[] | HtmlSafeString | HtmlSafeString[]\n) {\n  if (value instanceof HtmlSafeString) {\n    return value.toString();\n  }\n\n  if (Array.isArray(value)) {\n    return joinHtml(value).toString();\n  }\n\n  return String(value).replace(\n    htmlEscapablesRegex,\n    (character) => htmlEscapables[character as keyof typeof htmlEscapables]\n  );\n}\n\n// Adapted from https://github.com/Janpot/escape-html-template-tag\nexport class HtmlSafeString {\n  #strings: readonly string[];\n  #values: readonly (string | string[] | HtmlSafeString | HtmlSafeString[])[];\n\n  constructor(\n    strings: readonly string[],\n    values: readonly (string | string[] | HtmlSafeString | HtmlSafeString[])[]\n  ) {\n    this.#strings = strings;\n    this.#values = values;\n  }\n\n  toString(): string {\n    return this.#strings.reduce((result, str, i) => {\n      return result + escapeHtml(nn(this.#values[i - 1])) + str;\n    });\n  }\n}\n\n/**\n * Build an HTML string from a template literal where the values are escaped.\n * Nested calls are supported and won't be escaped.\n */\nexport function html(\n  strings: TemplateStringsArray,\n  ...values: (string | string[] | HtmlSafeString | HtmlSafeString[])[]\n): string {\n  return new HtmlSafeString(strings, values) as unknown as string;\n}\n\nconst markdownEscapables = {\n  _: \"\\\\_\",\n  \"*\": \"\\\\*\",\n  \"#\": \"\\\\#\",\n  \"`\": \"\\\\`\",\n  \"~\": \"\\\\~\",\n  \"!\": \"\\\\!\",\n  \"|\": \"\\\\|\",\n  \"(\": \"\\\\(\",\n  \")\": \"\\\\)\",\n  \"{\": \"\\\\{\",\n  \"}\": \"\\\\}\",\n  \"[\": \"\\\\[\",\n  \"]\": \"\\\\]\",\n};\n\nconst markdownEscapablesRegex = new RegExp(\n  Object.keys(markdownEscapables)\n    .map((entity) => `\\\\${entity}`)\n    .join(\"|\"),\n  \"g\"\n);\n\nfunction joinMarkdown(strings: (string | MarkdownSafeString)[]) {\n  if (strings.length <= 0) {\n    return new MarkdownSafeString([\"\"], []);\n  }\n\n  return new MarkdownSafeString(\n    [\"\", ...(Array(strings.length - 1).fill(\"\") as string[]), \"\"],\n    strings\n  );\n}\n\nfunction escapeMarkdown(\n  value: string | string[] | MarkdownSafeString | MarkdownSafeString[]\n) {\n  if (value instanceof MarkdownSafeString) {\n    return value.toString();\n  }\n\n  if (Array.isArray(value)) {\n    return joinMarkdown(value).toString();\n  }\n\n  return String(value).replace(\n    markdownEscapablesRegex,\n    (character) =>\n      markdownEscapables[character as keyof typeof markdownEscapables]\n  );\n}\n\n// Adapted from https://github.com/Janpot/escape-html-template-tag\nexport class MarkdownSafeString {\n  #strings: readonly string[];\n  #values: readonly (\n    | string\n    | string[]\n    | MarkdownSafeString\n    | MarkdownSafeString[]\n  )[];\n\n  constructor(\n    strings: readonly string[],\n    values: readonly (\n      | string\n      | string[]\n      | MarkdownSafeString\n      | MarkdownSafeString[]\n    )[]\n  ) {\n    this.#strings = strings;\n    this.#values = values;\n  }\n\n  toString(): string {\n    return this.#strings.reduce((result, str, i) => {\n      return result + escapeMarkdown(nn(this.#values[i - 1])) + str;\n    });\n  }\n}\n\n/**\n * Build a Markdown string from a template literal where the values are escaped.\n * Nested calls are supported and won't be escaped.\n */\nfunction markdown(\n  strings: TemplateStringsArray,\n  ...values: (string | string[] | MarkdownSafeString | MarkdownSafeString[])[]\n) {\n  return new MarkdownSafeString(strings, values) as unknown as string;\n}\n\n/**\n * Helper function to convert a URL (relative or absolute) to an absolute URL.\n *\n * @param url The URL to convert to an absolute URL (relative or absolute).\n * @returns The absolute URL or undefined if the URL is invalid.\n */\nexport function toAbsoluteUrl(url: string): string | undefined {\n  // Check if the URL already contains a scheme\n  if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {\n    return url;\n  } else if (url.startsWith(\"www.\")) {\n    // If the URL starts with \"www.\", prepend \"https://\"\n    return \"https://\" + url;\n  }\n\n  return;\n}\n\nconst stringifyCommentBodyPlainElements: StringifyCommentBodyElements<BaseUserMeta> =\n  {\n    paragraph: ({ children }) => children,\n    text: ({ element }) => element.text,\n    link: ({ element }) => element.text ?? element.url,\n    mention: ({ element, user }) => {\n      return `@${user?.name ?? element.id}`;\n    },\n  };\n\nconst stringifyCommentBodyHtmlElements: StringifyCommentBodyElements<BaseUserMeta> =\n  {\n    paragraph: ({ children }) => {\n      // prettier-ignore\n      return children ? html`<p>${htmlSafe(children)}</p>` : children;\n    },\n    text: ({ element }) => {\n      // <code><s><em><strong>text</strong></s></em></code>\n      let children = element.text;\n\n      if (!children) {\n        return html`${children}`;\n      }\n\n      if (element.bold) {\n        // prettier-ignore\n        children = html`<strong>${children}</strong>`;\n      }\n\n      if (element.italic) {\n        // prettier-ignore\n        children = html`<em>${children}</em>`;\n      }\n\n      if (element.strikethrough) {\n        // prettier-ignore\n        children = html`<s>${children}</s>`;\n      }\n\n      if (element.code) {\n        // prettier-ignore\n        children = html`<code>${children}</code>`;\n      }\n\n      return html`${children}`;\n    },\n    link: ({ element, href }) => {\n      // prettier-ignore\n      return html`<a href=\"${href}\" target=\"_blank\" rel=\"noopener noreferrer\">${element.text ? html`${element.text}` : element.url}</a>`;\n    },\n    mention: ({ element, user }) => {\n      // prettier-ignore\n      return html`<span data-mention>@${user?.name ? html`${user?.name}` : element.id}</span>`;\n    },\n  };\n\nconst stringifyCommentBodyMarkdownElements: StringifyCommentBodyElements<BaseUserMeta> =\n  {\n    paragraph: ({ children }) => {\n      return children;\n    },\n    text: ({ element }) => {\n      // <code><s><em><strong>text</strong></s></em></code>\n      let children = element.text;\n\n      if (!children) {\n        return children;\n      }\n\n      if (element.bold) {\n        // prettier-ignore\n        children = markdown`**${children}**`;\n      }\n\n      if (element.italic) {\n        // prettier-ignore\n        children = markdown`_${children}_`;\n      }\n\n      if (element.strikethrough) {\n        // prettier-ignore\n        children = markdown`~~${children}~~`;\n      }\n\n      if (element.code) {\n        // prettier-ignore\n        children = markdown`\\`${children}\\``;\n      }\n\n      return children;\n    },\n    link: ({ element, href }) => {\n      // prettier-ignore\n      return markdown`[${element.text ?? element.url}](${href})`;\n    },\n    mention: ({ element, user }) => {\n      // prettier-ignore\n      return markdown`@${user?.name ?? element.id}`;\n    },\n  };\n\n/**\n * Convert a `CommentBody` into either a plain string,\n * Markdown, HTML, or a custom format.\n */\nexport async function stringifyCommentBody(\n  body: CommentBody,\n  options?: StringifyCommentBodyOptions<BaseUserMeta>\n): Promise<string> {\n  const format = options?.format ?? \"plain\";\n  const separator =\n    options?.separator ?? (format === \"markdown\" ? \"\\n\\n\" : \"\\n\");\n  const elements = {\n    ...(format === \"html\"\n      ? stringifyCommentBodyHtmlElements\n      : format === \"markdown\"\n        ? stringifyCommentBodyMarkdownElements\n        : stringifyCommentBodyPlainElements),\n    ...options?.elements,\n  };\n  const resolvedUsers = await resolveUsersInCommentBody(\n    body,\n    options?.resolveUsers\n  );\n\n  const blocks = body.content.flatMap((block, blockIndex) => {\n    switch (block.type) {\n      case \"paragraph\": {\n        const inlines = block.children.flatMap((inline, inlineIndex) => {\n          if (isCommentBodyMention(inline)) {\n            return inline.id\n              ? [\n                  elements.mention(\n                    {\n                      element: inline,\n                      user: resolvedUsers.get(inline.id),\n                    },\n                    inlineIndex\n                  ),\n                ]\n              : [];\n          }\n\n          if (isCommentBodyLink(inline)) {\n            return [\n              elements.link(\n                {\n                  element: inline,\n                  href: toAbsoluteUrl(inline.url) ?? inline.url,\n                },\n                inlineIndex\n              ),\n            ];\n          }\n\n          if (isCommentBodyText(inline)) {\n            return [elements.text({ element: inline }, inlineIndex)];\n          }\n\n          return [];\n        });\n\n        return [\n          elements.paragraph(\n            { element: block, children: inlines.join(\"\") },\n            blockIndex\n          ),\n        ];\n      }\n\n      default:\n        return [];\n    }\n  });\n\n  return blocks.join(separator);\n}\n","const PLACEHOLDER_BASE_URL = \"https://localhost:9999\";\nconst ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\\d+\\-.]*?:/;\n\nexport function generateCommentUrl({\n  roomUrl,\n  commentId,\n}: {\n  roomUrl: string;\n  commentId: string;\n}): string {\n  const isAbsolute = ABSOLUTE_URL_REGEX.test(roomUrl);\n  const urlObject = new URL(\n    roomUrl,\n    isAbsolute ? undefined : PLACEHOLDER_BASE_URL\n  );\n\n  urlObject.hash = `#${commentId}`;\n\n  return isAbsolute\n    ? urlObject.href\n    : urlObject.href.replace(PLACEHOLDER_BASE_URL, \"\");\n}\n","import type { Json } from \"../lib/Json\";\nimport type { PlainLson } from \"../types/PlainLson\";\nimport { LiveList } from \"./LiveList\";\nimport { LiveMap } from \"./LiveMap\";\nimport { LiveObject } from \"./LiveObject\";\nimport type { Lson, LsonObject } from \"./Lson\";\n\n/**\n * Helper type to convert any valid Lson type to the equivalent Json type.\n *\n * Examples:\n *\n *   ToImmutable<42>                         // 42\n *   ToImmutable<'hi'>                       // 'hi'\n *   ToImmutable<number>                     // number\n *   ToImmutable<string>                     // string\n *   ToImmutable<string | LiveList<number>>  // string | readonly number[]\n *   ToImmutable<LiveMap<string, LiveList<number>>>\n *                                           // ReadonlyMap<string, readonly number[]>\n *   ToImmutable<LiveObject<{ a: number, b: LiveList<string>, c?: number }>>\n *                                           // { readonly a: null, readonly b: readonly string[], readonly c?: number }\n *\n */\n// prettier-ignore\nexport type ToImmutable<L extends Lson | LsonObject> =\n  // A LiveList serializes to an equivalent JSON array\n  L extends LiveList<infer I> ? readonly ToImmutable<I>[] :\n\n  // A LiveObject serializes to an equivalent JSON object\n  L extends LiveObject<infer O> ? ToImmutable<O> :\n\n  // A LiveMap serializes to a JSON object with string-V pairs\n  L extends LiveMap<infer K, infer V> ? ReadonlyMap<K, ToImmutable<V>> :\n\n  // Any LsonObject recursively becomes a JsonObject\n  L extends LsonObject ?\n    { readonly [K in keyof L]: ToImmutable<Exclude<L[K], undefined>>\n                                 | (undefined extends L[K] ? undefined : never) } :\n\n  // Any Json value already is a legal Json value\n  L extends Json ? L :\n\n  // Otherwise, this is not possible\n  never;\n\n/**\n * Returns PlainLson for a given Json or LiveStructure, suitable for calling the storage init api\n */\nexport function toPlainLson(lson: Lson): PlainLson {\n  if (lson instanceof LiveObject) {\n    return {\n      liveblocksType: \"LiveObject\",\n      data: Object.fromEntries(\n        Object.entries(lson.toObject()).flatMap(([key, value]) =>\n          value !== undefined ? [[key, toPlainLson(value)]] : []\n        )\n      ),\n    };\n  } else if (lson instanceof LiveMap) {\n    return {\n      liveblocksType: \"LiveMap\",\n      data: Object.fromEntries(\n        [...lson].map(([key, value]) => [key, toPlainLson(value)])\n      ),\n    };\n  } else if (lson instanceof LiveList) {\n    return {\n      liveblocksType: \"LiveList\",\n      data: [...lson].map((item) => toPlainLson(item)),\n    };\n  } else {\n    return lson;\n  }\n}\n","import {\n  findNonSerializableValue,\n  isLiveList,\n  isLiveObject,\n} from \"./crdts/liveblocks-helpers\";\nimport { LiveList } from \"./crdts/LiveList\";\nimport { LiveMap } from \"./crdts/LiveMap\";\nimport { LiveObject } from \"./crdts/LiveObject\";\nimport { LiveRegister } from \"./crdts/LiveRegister\";\nimport type { LiveNode, Lson, LsonObject, ToJson } from \"./crdts/Lson\";\nimport type { StorageUpdate } from \"./crdts/StorageUpdates\";\nimport * as console from \"./lib/fancy-console\";\nimport { isPlainObject } from \"./lib/guards\";\nimport type { Json, JsonObject } from \"./lib/Json\";\nimport { isJsonObject } from \"./lib/Json\";\n\nfunction lsonObjectToJson<O extends LsonObject>(\n  obj: O\n): { [K in keyof O]: Json } {\n  const result = {} as { [K in keyof O]: Json };\n  for (const key in obj) {\n    const val = obj[key];\n    if (val !== undefined) {\n      result[key] = lsonToJson(val);\n    }\n  }\n  return result;\n}\n\nexport function liveObjectToJson<O extends LsonObject>(\n  liveObject: LiveObject<O>\n): { [K in keyof O]: Json } {\n  return lsonObjectToJson(liveObject.toObject());\n}\n\nfunction liveMapToJson<TKey extends string>(\n  map: LiveMap<TKey, Lson>\n): { [K in TKey]: Json } {\n  const result = {} as { [K in TKey]: Json };\n  for (const [key, value] of map.entries()) {\n    result[key] = lsonToJson(value);\n  }\n  return result;\n}\n\nfunction lsonListToJson(value: Lson[]): Json[] {\n  return value.map(lsonToJson);\n}\n\nfunction liveListToJson(value: LiveList<Lson>): Json[] {\n  return lsonListToJson(value.toArray());\n}\n\nexport function lsonToJson(value: Lson): Json {\n  // Check for LiveStructure datastructures first\n  if (value instanceof LiveObject) {\n    return liveObjectToJson(value);\n  } else if (value instanceof LiveList) {\n    return liveListToJson(value);\n  } else if (value instanceof LiveMap) {\n    return liveMapToJson(value);\n  } else if (value instanceof LiveRegister) {\n    // NOTE: This branch should never be taken, because LiveRegister isn't a valid Lson value\n    return value.data as Json;\n  }\n\n  // Then for composite Lson values\n  if (Array.isArray(value)) {\n    return lsonListToJson(value);\n  } else if (isPlainObject(value)) {\n    return lsonObjectToJson(value);\n  }\n\n  // Finally, if value is an LsonScalar, then it's also a valid JsonScalar\n  return value;\n}\n\n/**\n * Deeply converts all nested lists to LiveLists, and all nested objects to\n * LiveObjects.\n *\n * As such, the returned result will not contain any Json arrays or Json\n * objects anymore.\n */\nfunction deepLiveify(value: Lson | LsonObject): Lson {\n  if (Array.isArray(value)) {\n    return new LiveList(value.map(deepLiveify));\n  } else if (isPlainObject(value)) {\n    const init: LsonObject = {};\n    for (const key in value) {\n      const val = value[key];\n      if (val === undefined) {\n        continue;\n      }\n      init[key] = deepLiveify(val);\n    }\n    return new LiveObject(init);\n  } else {\n    return value;\n  }\n}\n\nexport function patchLiveList<T extends Lson>(\n  liveList: LiveList<T>,\n  prev: Array<T>,\n  next: Array<T>\n): void {\n  let i = 0;\n  let prevEnd = prev.length - 1;\n  let nextEnd = next.length - 1;\n\n  let prevNode = prev[0];\n  let nextNode = next[0];\n\n  /**\n   * For A,B,C => A,B,C,D\n   * i = 3, prevEnd = 2, nextEnd = 3\n   *\n   * For A,B,C => B,C\n   * i = 2, prevEnd = 2, nextEnd = 1\n   *\n   * For B,C => A,B,C\n   * i = 0, pre\n   */\n\n  outer: {\n    while (prevNode === nextNode) {\n      ++i;\n      if (i > prevEnd || i > nextEnd) {\n        break outer;\n      }\n      prevNode = prev[i];\n      nextNode = next[i];\n    }\n\n    prevNode = prev[prevEnd];\n    nextNode = next[nextEnd];\n\n    while (prevNode === nextNode) {\n      prevEnd--;\n      nextEnd--;\n\n      if (i > prevEnd || i > nextEnd) {\n        break outer;\n      }\n\n      prevNode = prev[prevEnd];\n      nextNode = next[nextEnd];\n    }\n  }\n\n  if (i > prevEnd) {\n    if (i <= nextEnd) {\n      while (i <= nextEnd) {\n        liveList.insert(deepLiveify(next[i]) as T, i);\n        //                                   ^^^^ FIXME Not entirely true\n        i++;\n      }\n    }\n  } else if (i > nextEnd) {\n    let localI = i;\n    while (localI <= prevEnd) {\n      liveList.delete(i);\n      localI++;\n    }\n  } else {\n    while (i <= prevEnd && i <= nextEnd) {\n      prevNode = prev[i];\n      nextNode = next[i];\n      const liveListNode = liveList.get(i);\n\n      if (\n        isLiveObject(liveListNode) &&\n        isPlainObject(prevNode) &&\n        isPlainObject(nextNode)\n      ) {\n        patchLiveObject(liveListNode, prevNode, nextNode);\n      } else {\n        liveList.set(i, deepLiveify(nextNode) as T);\n        //                                    ^^^^ FIXME Not entirely true\n      }\n\n      i++;\n    }\n    while (i <= nextEnd) {\n      liveList.insert(deepLiveify(next[i]) as T, i);\n      //                                   ^^^^ FIXME Not entirely true\n      i++;\n    }\n    let localI = i;\n    while (localI <= prevEnd) {\n      liveList.delete(i);\n      localI++;\n    }\n  }\n}\n\nexport function patchLiveObjectKey<\n  O extends LsonObject,\n  K extends keyof O,\n  V extends Json,\n>(liveObject: LiveObject<O>, key: K, prev?: V, next?: V): void {\n  if (process.env.NODE_ENV !== \"production\") {\n    const nonSerializableValue = findNonSerializableValue(next);\n    if (nonSerializableValue) {\n      console.error(\n        `New state path: '${nonSerializableValue.path}' value: '${String(\n          nonSerializableValue.value\n        )}' is not serializable.\\nOnly serializable value can be synced with Liveblocks.`\n      );\n      return;\n    }\n  }\n\n  const value = liveObject.get(key);\n\n  if (next === undefined) {\n    liveObject.delete(key);\n  } else if (value === undefined) {\n    liveObject.set(key, deepLiveify(next) as O[K]);\n    //                                    ^^^^^^^ FIXME Not entirely true\n  } else if (prev === next) {\n    return;\n  } else if (isLiveList(value) && Array.isArray(prev) && Array.isArray(next)) {\n    patchLiveList(value, prev, next);\n  } else if (\n    isLiveObject(value) &&\n    isPlainObject(prev) &&\n    isPlainObject(next)\n  ) {\n    patchLiveObject(value, prev, next);\n  } else {\n    liveObject.set(key, deepLiveify(next) as O[K]);\n    //                                    ^^^^^^^ FIXME Not entirely true\n  }\n}\n\nexport function patchLiveObject<O extends LsonObject>(\n  root: LiveObject<O>,\n  prev: ToJson<O>,\n  next: ToJson<O>\n): void {\n  const updates: Partial<O> = {};\n\n  for (const key in next) {\n    patchLiveObjectKey(root, key, prev[key] as Json, next[key] as Json);\n  }\n\n  for (const key in prev) {\n    if (next[key] === undefined) {\n      root.delete(key);\n    }\n  }\n\n  if (Object.keys(updates).length > 0) {\n    root.update(updates);\n  }\n}\n\nfunction getParentsPath(node: LiveNode): Array<string | number> {\n  const path = [];\n  while (node.parent.type === \"HasParent\") {\n    if (isLiveList(node.parent.node)) {\n      path.push(node.parent.node._indexOfPosition(node.parent.key));\n    } else {\n      path.push(node.parent.key);\n    }\n    node = node.parent.node;\n  }\n  return path;\n}\n\n//\n// TODO: Remove `patchImmutableObject`!\n//\n// This helper is now only used internally, to support our Zustand and\n// Redux packages. We should be able to reimplement those using the new\n// `.toImmutable()` APIs.\n//\nexport function legacy_patchImmutableObject<TState extends JsonObject>(\n  state: TState,\n  updates: StorageUpdate[]\n): TState {\n  return updates.reduce(\n    (state, update) => legacy_patchImmutableObjectWithUpdate(state, update),\n    state\n  );\n}\n\nfunction legacy_patchImmutableObjectWithUpdate<TState extends JsonObject>(\n  state: TState,\n  update: StorageUpdate\n): TState {\n  const path = getParentsPath(update.node);\n  return legacy_patchImmutableNode(state, path, update);\n}\n\nfunction legacy_patchImmutableNode<S extends Json>(\n  state: S,\n  path: Array<string | number>,\n  update: StorageUpdate\n): S {\n  // FIXME: Split this function up into a few smaller ones! In each of them,\n  // the types can be define much more narrowly and correctly, and there will\n  // be less type shoehorning necessary.\n\n  const pathItem = path.pop();\n  if (pathItem === undefined) {\n    switch (update.type) {\n      case \"LiveObject\": {\n        if (!isJsonObject(state)) {\n          throw new Error(\n            \"Internal: received update on LiveObject but state was not an object\"\n          );\n        }\n\n        const newState: JsonObject = Object.assign({}, state);\n\n        for (const key in update.updates) {\n          if (update.updates[key]?.type === \"update\") {\n            const val = update.node.get(key);\n            if (val !== undefined) {\n              newState[key] = lsonToJson(val);\n            }\n          } else if (update.updates[key]?.type === \"delete\") {\n            delete newState[key];\n          }\n        }\n\n        return newState as S;\n        //              ^^^^\n        //              FIXME Not completely true, because we could have been\n        //              updating keys from StorageUpdate here that aren't in S,\n        //              technically.\n      }\n\n      case \"LiveList\": {\n        if (!Array.isArray(state)) {\n          throw new Error(\n            \"Internal: received update on LiveList but state was not an array\"\n          );\n        }\n\n        let newState: Json[] = state.map((x: Json) => x);\n\n        for (const listUpdate of update.updates) {\n          if (listUpdate.type === \"set\") {\n            newState = newState.map((item, index) =>\n              index === listUpdate.index ? lsonToJson(listUpdate.item) : item\n            );\n          } else if (listUpdate.type === \"insert\") {\n            if (listUpdate.index === newState.length) {\n              newState.push(lsonToJson(listUpdate.item));\n            } else {\n              newState = [\n                ...newState.slice(0, listUpdate.index),\n                lsonToJson(listUpdate.item),\n                ...newState.slice(listUpdate.index),\n              ];\n            }\n          } else if (listUpdate.type === \"delete\") {\n            newState.splice(listUpdate.index, 1);\n          } else if (listUpdate.type === \"move\") {\n            if (listUpdate.previousIndex > listUpdate.index) {\n              newState = [\n                ...newState.slice(0, listUpdate.index),\n                lsonToJson(listUpdate.item),\n                ...newState.slice(listUpdate.index, listUpdate.previousIndex),\n                ...newState.slice(listUpdate.previousIndex + 1),\n              ];\n            } else {\n              newState = [\n                ...newState.slice(0, listUpdate.previousIndex),\n                ...newState.slice(\n                  listUpdate.previousIndex + 1,\n                  listUpdate.index + 1\n                ),\n                lsonToJson(listUpdate.item),\n                ...newState.slice(listUpdate.index + 1),\n              ];\n            }\n          }\n        }\n\n        return newState as S;\n        //              ^^^^\n        //              FIXME Not completely true, because we could have been\n        //              updating keys from StorageUpdate here that aren't in S,\n        //              technically.\n      }\n\n      case \"LiveMap\": {\n        if (!isJsonObject(state)) {\n          throw new Error(\n            \"Internal: received update on LiveMap but state was not an object\"\n          );\n        }\n        const newState: JsonObject = Object.assign({}, state);\n\n        for (const key in update.updates) {\n          if (update.updates[key]?.type === \"update\") {\n            const value = update.node.get(key);\n            if (value !== undefined) {\n              newState[key] = lsonToJson(value);\n            }\n          } else if (update.updates[key]?.type === \"delete\") {\n            delete newState[key];\n          }\n        }\n\n        return newState as S;\n        //              ^^^^\n        //              FIXME Not completely true, because we could have been\n        //              updating keys from StorageUpdate here that aren't in S,\n        //              technically.\n      }\n    }\n  }\n\n  if (Array.isArray(state)) {\n    const newArray: Json[] = [...state];\n    newArray[pathItem as number] = legacy_patchImmutableNode(\n      state[pathItem as number],\n      path,\n      update\n    );\n    return newArray as S;\n    //              ^^^^\n    //              FIXME Not completely true, because we could have been\n    //              updating indexes from StorageUpdate here that aren't in S,\n    //              technically.\n  } else if (isJsonObject(state)) {\n    const node = state[pathItem];\n    if (node === undefined) {\n      return state;\n    } else {\n      const stateAsObj: JsonObject = state;\n      return {\n        ...stateAsObj,\n        [pathItem]: legacy_patchImmutableNode(node, path, update),\n      } as S;\n      //   ^\n      //   FIXME Not completely true, because we could have been updating\n      //   indexes from StorageUpdate here that aren't in S, technically.\n    }\n  } else {\n    return state;\n  }\n}\n","/**\n * Like `new AbortController()`, but where the result can be unpacked\n * safely, i.e. `const { signal, abort } = makeAbortController()`.\n *\n * This unpacking is unsafe to do with a regular `AbortController` because\n * the `abort` method is not bound to the controller instance.\n *\n * In addition to this, you can also pass an optional (external)\n * AbortSignal to \"wrap\", in which case the returned signal will be in\n * aborted state when either the signal is aborted externally or\n * internally.\n */\nexport function makeAbortController(externalSignal?: AbortSignal): {\n  signal: AbortSignal;\n  abort: (reason?: unknown) => void;\n} {\n  const ctl = new AbortController();\n  return {\n    signal: externalSignal\n      ? AbortSignal.any([ctl.signal, externalSignal])\n      : ctl.signal,\n    abort: ctl.abort.bind(ctl),\n  };\n}\n","import * as console from \"./fancy-console\";\n\n/**\n * Tools to help with the controlled deprecation of public APIs.\n *\n * First warn, then error, then remove eventually.\n */\n\n// Keeps a set of deprecation messages in memory that it has warned about\n// already. There will be only one deprecation message in the console, no\n// matter how often it gets called.\nconst _emittedDeprecationWarnings: Set<string> = new Set();\n\n/**\n * Displays a deprecation warning in the dev console. Only in dev mode, and\n * only once per message/key. In production, this is a no-op.\n */\n// istanbul ignore next\nexport function deprecate(message: string, key = message): void {\n  if (process.env.NODE_ENV !== \"production\") {\n    if (!_emittedDeprecationWarnings.has(key)) {\n      _emittedDeprecationWarnings.add(key);\n      console.errorWithTitle(\"Deprecation warning\", message);\n    }\n  }\n}\n\n/**\n * Conditionally displays a deprecation warning in the dev\n * console if the first argument is truthy. Only in dev mode, and\n * only once per message/key. In production, this is a no-op.\n */\n// istanbul ignore next\nexport function deprecateIf(\n  condition: unknown,\n  message: string,\n  key = message\n): void {\n  if (process.env.NODE_ENV !== \"production\") {\n    // istanbul ignore if\n    if (condition) {\n      deprecate(message, key);\n    }\n  }\n}\n\n/**\n * Throws a deprecation error in the dev console.\n *\n * Only triggers in dev mode. In production, this is a no-op.\n */\n// istanbul ignore next\nexport function throwUsageError(message: string): void {\n  if (process.env.NODE_ENV !== \"production\") {\n    const usageError = new Error(message);\n    usageError.name = \"Usage error\";\n    console.errorWithTitle(\"Usage error\", message);\n    throw usageError;\n  }\n}\n\n/**\n * Conditionally throws a usage error in the dev console if the first argument\n * is truthy. Use this to \"escalate\" usage patterns that in previous versions\n * we already warned about with deprecation warnings.\n *\n * Only has effect in dev mode. In production, this is a no-op.\n */\n// istanbul ignore next\nexport function errorIf(condition: unknown, message: string): void {\n  if (process.env.NODE_ENV !== \"production\") {\n    if (condition) {\n      throwUsageError(message);\n    }\n  }\n}\n","import { FSM } from \"./fsm\";\n\nexport type Poller = {\n  /**\n   * Increments the subscriber count for this poller. If it becomes > 0, the\n   * poller will be enabled.\n   */\n  inc(): void;\n  /**\n   * Decrements the subscriber count for this poller. If it becomes = 0, the\n   * poller will be disabled.\n   */\n  dec(): void;\n\n  /**\n   * Polls immediately only if it has been more than `maxStaleTimeMs` milliseconds since\n   * the last poll and no poll is currently in progress. After polling, schedules\n   * the next poll at the regular interval.\n   */\n  pollNowIfStale(): void;\n\n  /**\n   * Marks the poller as stale. This can be used to force the next call\n   * to `.pollNowIfStale()` to poll immediately.\n   */\n  markAsStale(): void;\n\n  /**\n   * Used in unit tests only.\n   * @internal\n   */\n  setInForeground(condition: boolean): void;\n};\n\ntype Context = {\n  inForeground: boolean; // Whether the visibility state is visible\n  lastSuccessfulPollAt: number; // The timestamp of the last successful poll (or when the poller was initialized)\n  count: number; // Subscriber count\n  backoff: number; // Backoff delay in ms\n};\n\ntype State =\n  | \"@idle\" //\n  | \"@enabled\" //\n  | \"@polling\";\n\ntype Event =\n  | { type: \"START\" } //\n  | { type: \"STOP\" } //\n  | { type: \"POLL\" };\n\nconst BACKOFF_DELAYS = [1_000, 2_000, 4_000, 8_000, 10_000] as const;\n\n/**\n * Makes a poller that will call `await callback()` at the desired interval (in\n * millis).\n *\n * The poller has only three public APIs, all side effects:\n * - .inc(): void\n * - .dec(): void\n * - .pollNowIfStale(): void\n *\n * It has the following behaviors/guarantees:\n * - Performing a \"poll\" literally means calling the provided callback (and\n *   awaiting it)\n * - It will only ever start polling if .inc() was called (more often than .dec())\n * - It will not _immediately_ poll if .inc() is called. The first poll\n *   can be expected no earlier than the specified interval.\n * - If .dec() is called as many times as .inc(), it stops the poller. This\n *   means that any next poll will get unscheduled. If .dev() is called while\n *   a poll is ongoing, it will still finish that poll, but after that stop\n *   further polling.\n * - If the document's visibility state changes to hidden (tab is moved to the\n *   background), polling will be paused until the document's made visible again\n * - If the document becomes visible again, the poller will:\n *   - Still do nothing if the poller isn't enabled\n *   - Still do nothing if the poller is enabled, but the last time a poll\n *     happened recently enough (= less than the maxStaleTimeMs, which defaults\n *     to infinity)\n *   - Trigger a poll right away otherwise. If an existing poll was already\n *     scheduled, think of it as if this future poll is \"earlied\" and just\n *     happening right now instead\n */\nexport function makePoller(\n  callback: (signal: AbortSignal) => Promise<void> | void,\n  intervalMs: number,\n  options?: {\n    maxStaleTimeMs?: number;\n  }\n): Poller {\n  const startTime = performance.now();\n  const doc = typeof document !== \"undefined\" ? document : undefined;\n  const win = typeof window !== \"undefined\" ? window : undefined;\n\n  const maxStaleTimeMs = options?.maxStaleTimeMs ?? Number.POSITIVE_INFINITY;\n  const context: Context = {\n    inForeground: doc?.visibilityState !== \"hidden\",\n    lastSuccessfulPollAt: startTime,\n    count: 0,\n    backoff: 0,\n  };\n\n  function mayPoll() {\n    return context.count > 0 && context.inForeground;\n  }\n\n  /**\n   *                                    +----------+\n   *        +-------------------------> |  @idle   |\n   *        |   else                    +----------+\n   *        |                             |      ^\n   *        |                     on STOP |      | on START\n   *        |                             v      |\n   *   +--------+   if mayPoll()        +----------+      on POLL             +----------+\n   *   | decide |---------------------> | @enabled | -----------------------> | @polling |\n   *   +--------+                       +----------+   after POLL_INTERVAL    +----------+\n   *        ^                                                                      |\n   *        |                                                                      |\n   *        +----------------------------------------------------------------------+\n   */\n  const fsm = new FSM<object, Event, State>({})\n    .addState(\"@idle\")\n    .addState(\"@enabled\")\n    .addState(\"@polling\");\n\n  fsm.addTransitions(\"@idle\", { START: \"@enabled\" });\n  fsm.addTransitions(\"@enabled\", { STOP: \"@idle\", POLL: \"@polling\" });\n  fsm.addTimedTransition(\n    \"@enabled\",\n    () => {\n      const lastPoll = context.lastSuccessfulPollAt;\n      const nextPoll = lastPoll + intervalMs;\n      return Math.max(0, nextPoll - performance.now()) + context.backoff;\n    },\n    \"@polling\"\n  );\n\n  fsm.onEnterAsync(\n    \"@polling\",\n    async (_ctx, signal) => {\n      await callback(signal);\n      if (!signal.aborted) {\n        context.lastSuccessfulPollAt = performance.now();\n      }\n    },\n    // When OK\n    () => {\n      return {\n        target: mayPoll() ? \"@enabled\" : \"@idle\",\n        effect: () => {\n          // Reset backoff delay to 0 if the callback was successful\n          context.backoff = 0;\n        },\n      };\n    },\n    // When error\n    () => {\n      return {\n        target: mayPoll() ? \"@enabled\" : \"@idle\",\n        effect: () => {\n          // Increase the backoff delay if an error occured\n          context.backoff =\n            BACKOFF_DELAYS.find((delay) => delay > context.backoff) ??\n            BACKOFF_DELAYS[BACKOFF_DELAYS.length - 1];\n        },\n      };\n    },\n    30_000 // Abort the poll if the callback takes more than 30 seconds to complete\n  );\n\n  function startOrStop() {\n    if (mayPoll()) {\n      fsm.send({ type: \"START\" });\n    } else {\n      fsm.send({ type: \"STOP\" });\n    }\n  }\n\n  function inc() {\n    context.count++;\n    startOrStop();\n  }\n\n  function dec() {\n    context.count--;\n    if (context.count < 0) {\n      context.count = 0;\n    }\n    startOrStop();\n  }\n\n  function pollNowIfStale() {\n    if (performance.now() - context.lastSuccessfulPollAt > maxStaleTimeMs) {\n      fsm.send({ type: \"POLL\" });\n    }\n  }\n\n  function markAsStale() {\n    // Set the last successful poll timestamp to a stale time\n    context.lastSuccessfulPollAt = performance.now() - maxStaleTimeMs - 1;\n  }\n\n  function setInForeground(inForeground: boolean) {\n    context.inForeground = inForeground;\n    startOrStop();\n    pollNowIfStale(); // Won't do anything if in @idle\n  }\n\n  function onVisibilityChange() {\n    setInForeground(doc?.visibilityState !== \"hidden\");\n  }\n\n  // NOTE: Currently, poller instances are only ever created and never\n  // destroyed. If we add a destroy() method in the future, then we should also\n  // unregister these event handlers.\n  doc?.addEventListener(\"visibilitychange\", onVisibilityChange);\n  win?.addEventListener(\"online\", onVisibilityChange);\n  win?.addEventListener(\"focus\", pollNowIfStale);\n\n  fsm.start();\n  return {\n    inc,\n    dec,\n    pollNowIfStale,\n    markAsStale,\n\n    // Internal API, used by unit tests only to simulate visibility events\n    setInForeground,\n  };\n}\n","import type { DAD } from \"../globals/augmentation\";\nimport type { DateToString } from \"../lib/DateToString\";\nimport type { NotificationKind } from \"./NotificationSettings\";\n\nexport type SubscriptionData<K extends keyof DAD = keyof DAD> = {\n  kind: NotificationKind<K>;\n  subjectId: string;\n  createdAt: Date;\n};\n\nexport type SubscriptionDataPlain = DateToString<SubscriptionData>;\n\nexport type UserSubscriptionData<K extends keyof DAD = keyof DAD> =\n  SubscriptionData<K> & {\n    userId: string;\n  };\n\nexport type UserSubscriptionDataPlain = DateToString<UserSubscriptionData>;\n\nexport type SubscriptionDeleteInfo = {\n  type: \"deletedSubscription\";\n  kind: NotificationKind;\n  subjectId: string;\n  deletedAt: Date;\n};\n\nexport type SubscriptionDeleteInfoPlain = DateToString<SubscriptionDeleteInfo>;\n\nexport type SubscriptionKey = `${NotificationKind}:${string}`;\n\nexport function getSubscriptionKey(\n  subscription: SubscriptionData | SubscriptionDeleteInfo\n): SubscriptionKey;\nexport function getSubscriptionKey(\n  kind: NotificationKind,\n  subjectId: string\n): SubscriptionKey;\nexport function getSubscriptionKey(\n  subscription: SubscriptionData | SubscriptionDeleteInfo | NotificationKind,\n  subjectId?: string\n): SubscriptionKey {\n  if (typeof subscription === \"string\") {\n    return `${subscription}:${subjectId}`;\n  }\n\n  return `${subscription.kind}:${subscription.subjectId}`;\n}\n","import type { DP, DU } from \"../globals/augmentation\";\nimport type { JsonObject } from \"../lib/Json\";\nimport type { Relax } from \"../lib/Relax\";\nimport type { Resolve } from \"../lib/Resolve\";\nimport type { BaseUserMeta } from \"../protocol/BaseUserMeta\";\nimport type { User } from \"./User\";\n\nexport type InternalOthersEvent<\n  P extends JsonObject,\n  U extends BaseUserMeta,\n> = Relax<\n  | { type: \"leave\"; user: User<P, U> }\n  | { type: \"enter\"; user: User<P, U> }\n  | {\n      type: \"update\";\n      user: User<P, U>;\n      updates: Partial<P>;\n    }\n  | { type: \"reset\" }\n>;\n\nexport type OthersEvent<\n  P extends JsonObject = DP,\n  U extends BaseUserMeta = DU,\n> = Resolve<\n  InternalOthersEvent<P, U> & {\n    others: readonly User<P, U>[];\n  }\n>;\n\nexport enum TextEditorType {\n  Lexical = \"lexical\",\n  TipTap = \"tiptap\",\n  BlockNote = \"blocknote\",\n}\n","import { detectDupes } from \"./dupe-detection\";\nimport { PKG_FORMAT, PKG_NAME, PKG_VERSION } from \"./version\";\n\ndetectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);\n\n/**\n * PRIVATE / INTERNAL APIS\n * -----------------------\n *\n * This module is intended for internal use only, PLEASE DO NOT RELY ON ANY OF\n * THE EXPORTS IN HERE. These are implementation details that can change at any\n * time and without announcement. This module purely exists to share code\n * between the several Liveblocks packages.\n *\n * But since you're so deep inside Liveblocks code... we're hiring!\n * https://join.team/liveblocks ;)\n */\n\nexport type {\n  AiOpaqueToolDefinition,\n  AiOpaqueToolInvocationProps,\n  AiToolDefinition,\n  AiToolExecuteCallback,\n  AiToolExecuteContext,\n  AiToolInvocationProps,\n  AiToolTypePack,\n  WithNavigation,\n} from \"./ai\";\nexport { defineAiTool } from \"./ai\";\nexport type {\n  Client,\n  ClientOptions,\n  EnterOptions,\n  OpaqueClient,\n  PrivateClientApi,\n  ResolveMentionSuggestionsArgs,\n  ResolveRoomsInfoArgs,\n  ResolveUsersArgs,\n  SyncStatus,\n} from \"./client\";\nexport { checkBounds, createClient } from \"./client\";\nexport type {\n  CommentBodyLinkElementArgs,\n  CommentBodyMentionElementArgs,\n  CommentBodyParagraphElementArgs,\n  CommentBodyTextElementArgs,\n  StringifyCommentBodyElements,\n  StringifyCommentBodyOptions,\n} from \"./comments/comment-body\";\nexport {\n  getMentionsFromCommentBody,\n  html,\n  htmlSafe,\n  isCommentBodyLink,\n  isCommentBodyMention,\n  isCommentBodyText,\n  resolveUsersInCommentBody,\n  stringifyCommentBody,\n  toAbsoluteUrl,\n} from \"./comments/comment-body\";\nexport { generateCommentUrl } from \"./comments/comment-url\";\nexport type { BaseAuthResult, Delegates } from \"./connection\";\nexport type { LostConnectionEvent, Status } from \"./connection\";\nexport {\n  convertToCommentData,\n  convertToCommentUserReaction,\n  convertToInboxNotificationData,\n  convertToSubscriptionData,\n  convertToThreadData,\n  convertToUserSubscriptionData,\n} from \"./convert-plain-data\";\nexport type {\n  CreateManagedPoolOptions,\n  ManagedPool,\n} from \"./crdts/AbstractCrdt\";\nexport { createManagedPool } from \"./crdts/AbstractCrdt\";\nexport { cloneLson, isLiveNode } from \"./crdts/liveblocks-helpers\";\nexport { LiveList } from \"./crdts/LiveList\";\nexport { LiveMap } from \"./crdts/LiveMap\";\nexport { LiveObject } from \"./crdts/LiveObject\";\nexport type {\n  LiveNode,\n  LiveStructure,\n  Lson,\n  LsonObject,\n  ToJson,\n} from \"./crdts/Lson\";\nexport type {\n  LiveListUpdate,\n  LiveMapUpdate,\n  LiveObjectUpdate,\n  StorageUpdate,\n} from \"./crdts/StorageUpdates\";\nexport type { ToImmutable } from \"./crdts/utils\";\nexport { toPlainLson } from \"./crdts/utils\";\nexport type {\n  DAD,\n  DE,\n  DM,\n  DP,\n  DRI,\n  DS,\n  DU,\n  KDAD,\n} from \"./globals/augmentation\";\nexport {\n  legacy_patchImmutableObject,\n  lsonToJson,\n  patchLiveObjectKey,\n} from \"./immutable\";\nexport { kInternal } from \"./internal\";\nexport { makeAbortController } from \"./lib/abortController\";\nexport { assert, assertNever, nn } from \"./lib/assert\";\nexport type {\n  AsyncError,\n  AsyncLoading,\n  AsyncResult,\n  AsyncSuccess,\n} from \"./lib/AsyncResult\";\nexport { autoRetry, HttpError } from \"./lib/autoRetry\";\nexport { chunk } from \"./lib/chunk\";\nexport { Promise_withResolvers } from \"./lib/controlledPromise\";\nexport {\n  createCommentAttachmentId,\n  createCommentId,\n  createInboxNotificationId,\n  createThreadId,\n} from \"./lib/createIds\";\nexport { DefaultMap } from \"./lib/DefaultMap\";\nexport {\n  deprecate,\n  deprecateIf,\n  errorIf,\n  throwUsageError,\n} from \"./lib/deprecation\";\nexport { Deque } from \"./lib/Deque\";\nexport type {\n  EventSource,\n  Observable,\n  UnsubscribeCallback,\n} from \"./lib/EventSource\";\nexport { makeEventSource } from \"./lib/EventSource\";\nexport * as console from \"./lib/fancy-console\";\nexport { freeze } from \"./lib/freeze\";\nexport { isPlainObject, isStartsWithOperator } from \"./lib/guards\";\nexport type { Json, JsonArray, JsonObject, JsonScalar } from \"./lib/Json\";\nexport { isJsonArray, isJsonObject, isJsonScalar } from \"./lib/Json\";\nexport { nanoid } from \"./lib/nanoid\";\nexport type { NoInfr } from \"./lib/NoInfer\";\nexport { objectToQuery } from \"./lib/objectToQuery\";\nexport type { Poller } from \"./lib/Poller\";\nexport { makePoller } from \"./lib/Poller\";\nexport { asPos, makePosition } from \"./lib/position\";\nexport type { Relax } from \"./lib/Relax\";\nexport type { Resolve } from \"./lib/Resolve\";\nexport { shallow, shallow2 } from \"./lib/shallow\";\nexport type { ISignal, SignalType } from \"./lib/signals\";\nexport { batch, DerivedSignal, MutableSignal, Signal } from \"./lib/signals\";\nexport { SortedList } from \"./lib/SortedList\";\nexport { stableStringify } from \"./lib/stringify\";\nexport type { QueryParams, URLSafeString } from \"./lib/url\";\nexport { url, urljoin } from \"./lib/url\";\nexport type { Brand, DistributiveOmit } from \"./lib/utils\";\nexport {\n  b64decode,\n  compactObject,\n  entries,\n  keys,\n  mapValues,\n  memoizeOnSuccess,\n  raise,\n  tryParseJson,\n  wait,\n  withTimeout,\n} from \"./lib/utils\";\nexport type {\n  ContextualPromptContext,\n  ContextualPromptResponse,\n} from \"./protocol/Ai\";\nexport type { CustomAuthenticationResult } from \"./protocol/Authentication\";\nexport { Permission } from \"./protocol/AuthToken\";\nexport type { BaseActivitiesData } from \"./protocol/BaseActivitiesData\";\nexport type { BaseRoomInfo } from \"./protocol/BaseRoomInfo\";\nexport type { BaseUserMeta, IUserInfo } from \"./protocol/BaseUserMeta\";\nexport type {\n  BroadcastEventClientMsg,\n  ClientMsg,\n  FetchStorageClientMsg,\n  FetchYDocClientMsg,\n  UpdatePresenceClientMsg,\n  UpdateStorageClientMsg,\n  UpdateYDocClientMsg,\n} from \"./protocol/ClientMsg\";\nexport { ClientMsgCode } from \"./protocol/ClientMsg\";\nexport type { BaseMetadata } from \"./protocol/Comments\";\nexport type {\n  CommentBody,\n  CommentBodyBlockElement,\n  CommentBodyElement,\n  CommentBodyInlineElement,\n  CommentBodyLink,\n  CommentBodyMention,\n  CommentBodyParagraph,\n  CommentBodyText,\n} from \"./protocol/Comments\";\nexport type {\n  CommentAttachment,\n  CommentData,\n  CommentDataPlain,\n  CommentLocalAttachment,\n  CommentMixedAttachment,\n  CommentReaction,\n} from \"./protocol/Comments\";\nexport type {\n  CommentUserReaction,\n  CommentUserReactionPlain,\n} from \"./protocol/Comments\";\nexport type { QueryMetadata } from \"./protocol/Comments\";\nexport type {\n  ThreadData,\n  ThreadDataPlain,\n  ThreadDataWithDeleteInfo,\n} from \"./protocol/Comments\";\nexport type { ThreadDeleteInfo } from \"./protocol/Comments\";\nexport type {\n  ActivityData,\n  InboxNotificationCustomData,\n  InboxNotificationCustomDataPlain,\n  InboxNotificationData,\n  InboxNotificationDataPlain,\n  InboxNotificationTextMentionData,\n  InboxNotificationTextMentionDataPlain,\n  InboxNotificationThreadData,\n  InboxNotificationThreadDataPlain,\n} from \"./protocol/InboxNotifications\";\nexport type { InboxNotificationDeleteInfo } from \"./protocol/InboxNotifications\";\nexport type {\n  NotificationChannel,\n  NotificationChannelSettings,\n  NotificationKind,\n  NotificationSettings,\n  NotificationSettingsPlain,\n  PartialNotificationSettings,\n} from \"./protocol/NotificationSettings\";\nexport {\n  createNotificationSettings,\n  isNotificationChannelEnabled,\n  patchNotificationSettings,\n} from \"./protocol/NotificationSettings\";\nexport type {\n  AckOp,\n  CreateListOp,\n  CreateMapOp,\n  CreateObjectOp,\n  CreateOp,\n  CreateRegisterOp,\n  DeleteCrdtOp,\n  DeleteObjectKeyOp,\n  Op,\n  SetParentKeyOp,\n  UpdateObjectOp,\n} from \"./protocol/Op\";\nexport { ackOp, OpCode } from \"./protocol/Op\";\nexport type {\n  RoomSubscriptionSettings,\n  UserRoomSubscriptionSettings,\n} from \"./protocol/RoomSubscriptionSettings\";\nexport type {\n  IdTuple,\n  SerializedChild,\n  SerializedCrdt,\n  SerializedList,\n  SerializedMap,\n  SerializedObject,\n  SerializedRegister,\n  SerializedRootObject,\n} from \"./protocol/SerializedCrdt\";\nexport { CrdtType } from \"./protocol/SerializedCrdt\";\nexport { isChildCrdt, isRootCrdt } from \"./protocol/SerializedCrdt\";\nexport type {\n  BroadcastedEventServerMsg,\n  CommentsEventServerMsg,\n  InitialDocumentStateServerMsg,\n  RejectedStorageOpServerMsg,\n  RoomStateServerMsg,\n  ServerMsg,\n  UpdatePresenceServerMsg,\n  UpdateStorageServerMsg,\n  UserJoinServerMsg,\n  UserLeftServerMsg,\n  YDocUpdateServerMsg,\n} from \"./protocol/ServerMsg\";\nexport { ServerMsgCode } from \"./protocol/ServerMsg\";\nexport type {\n  SubscriptionData,\n  SubscriptionDataPlain,\n  SubscriptionDeleteInfo,\n  SubscriptionDeleteInfoPlain,\n  SubscriptionKey,\n  UserSubscriptionData,\n  UserSubscriptionDataPlain,\n} from \"./protocol/Subscriptions\";\nexport { getSubscriptionKey } from \"./protocol/Subscriptions\";\nexport type { HistoryVersion } from \"./protocol/VersionHistory\";\nexport type {\n  IYjsProvider,\n  LargeMessageStrategy,\n  PrivateRoomApi,\n  YjsSyncStatus,\n} from \"./room\";\nexport type {\n  BroadcastOptions,\n  History,\n  OpaqueRoom,\n  OptionalTupleUnless,\n  PartialUnless,\n  Room,\n  RoomEventMessage,\n  StorageStatus,\n  SyncSource,\n} from \"./room\";\nexport type { GetThreadsOptions, UploadAttachmentOptions } from \"./room\";\nexport type {\n  AiAssistantContentPart,\n  AiAssistantMessage,\n  AiChat,\n  AiChatMessage,\n  AiKnowledgeSource,\n  AiReasoningPart,\n  AiTextPart,\n  AiToolInvocationPart,\n  AiUserMessage,\n  CopilotId,\n  Cursor,\n  MessageId,\n  RenderableToolResultResponse,\n  ToolResultResponse,\n} from \"./types/ai\";\nexport type { Awaitable } from \"./types/Awaitable\";\nexport type { Immutable } from \"./types/Immutable\";\nexport type { InferFromSchema } from \"./types/InferFromSchema\";\nexport type {\n  IWebSocket,\n  IWebSocketCloseEvent,\n  IWebSocketEvent,\n  IWebSocketInstance,\n  IWebSocketMessageEvent,\n} from \"./types/IWebSocket\";\nexport { WebsocketCloseCodes } from \"./types/IWebSocket\";\nexport type { LiveblocksErrorContext } from \"./types/LiveblocksError\";\nexport { LiveblocksError } from \"./types/LiveblocksError\";\nexport type { NodeMap, ParentToChildNodeMap } from \"./types/NodeMap\";\nexport type { OthersEvent } from \"./types/Others\";\nexport { TextEditorType } from \"./types/Others\";\nexport type { Patchable } from \"./types/Patchable\";\nexport type {\n  PlainLson,\n  PlainLsonFields,\n  PlainLsonList,\n  PlainLsonMap,\n  PlainLsonObject,\n} from \"./types/PlainLson\";\nexport type { User } from \"./types/User\";\nexport { detectDupes };\nexport type { MentionData, UserMentionData } from \"./types/MentionData\";\n\n/**\n * Helper type to help users adopt to Lson types from interface definitions.\n * You should only use this to wrap interfaces you don't control. For more\n * information, see\n * https://liveblocks.io/docs/guides/limits#lson-constraint-and-interfaces\n */\n// prettier-ignore\nexport type EnsureJson<T> =\n  // Retain all valid `JSON` fields\n  T extends Json ? T :\n  // Retain all valid arrays\n  T extends Array<infer I> ? (EnsureJson<I>)[] :\n  // Retain `unknown` fields, but just treat them as if they're Json | undefined\n  [unknown] extends [T] ? Json | undefined :\n  // Dates become strings when serialized to JSON\n  T extends Date ? string :\n  // Remove functions\n  T extends (...args: any[]) => any ? never :\n  // Resolve all other values explicitly\n  { [K in keyof T as EnsureJson<T[K]> extends never ? never : K]: EnsureJson<T[K]> };\n\n// Support for DevTools\nimport type * as DevToolsMsg from \"./devtools/protocol\";\nexport type { DevToolsMsg };\nimport type { Json } from \"./lib/Json\";\nimport type * as DevTools from \"./types/DevToolsTreeNode\";\nexport type { DevTools };\n"]}