@liveblocks/core 1.8.2 → 1.8.3-oss1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "1.8.2";
9
+ var PKG_VERSION = "1.8.3-oss1";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -1744,1402 +1744,1770 @@ function errorIf(condition, message) {
1744
1744
  }
1745
1745
  }
1746
1746
 
1747
- // src/comments/index.ts
1748
- function getAuthBearerHeaderFromAuthValue(authValue) {
1749
- if (authValue.type === "public") {
1750
- return authValue.publicApiKey;
1751
- } else {
1752
- return authValue.token.raw;
1753
- }
1747
+ // src/comments/comment-body.ts
1748
+ function isCommentBodyParagraph(element) {
1749
+ return "type" in element && element.type === "mention";
1754
1750
  }
1755
- var CommentsApiError = class extends Error {
1756
- constructor(message, status, details) {
1757
- super(message);
1758
- this.message = message;
1759
- this.status = status;
1760
- this.details = details;
1761
- }
1751
+ function isCommentBodyText(element) {
1752
+ return "text" in element && typeof element.text === "string";
1753
+ }
1754
+ function isCommentBodyMention(element) {
1755
+ return "type" in element && element.type === "mention";
1756
+ }
1757
+ function isCommentBodyLink(element) {
1758
+ return "type" in element && element.type === "link";
1759
+ }
1760
+ var commentBodyElementsGuards = {
1761
+ paragraph: isCommentBodyParagraph,
1762
+ text: isCommentBodyText,
1763
+ link: isCommentBodyLink,
1764
+ mention: isCommentBodyMention
1762
1765
  };
1763
- function createCommentsApi(roomId, getAuthValue, config) {
1764
- async function fetchJson(endpoint, options) {
1765
- const response = await fetchApi(roomId, endpoint, options);
1766
- if (!response.ok) {
1767
- if (response.status >= 400 && response.status < 600) {
1768
- let error3;
1769
- try {
1770
- const errorBody = await response.json();
1771
- error3 = new CommentsApiError(
1772
- errorBody.message,
1773
- response.status,
1774
- errorBody
1775
- );
1776
- } catch {
1777
- error3 = new CommentsApiError(response.statusText, response.status);
1778
- }
1779
- throw error3;
1766
+ var commentBodyElementsTypes = {
1767
+ paragraph: "block",
1768
+ text: "inline",
1769
+ link: "inline",
1770
+ mention: "inline"
1771
+ };
1772
+ function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
1773
+ if (!body || !body?.content) {
1774
+ return;
1775
+ }
1776
+ const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
1777
+ const type = element ? commentBodyElementsTypes[element] : "all";
1778
+ const guard = element ? commentBodyElementsGuards[element] : () => true;
1779
+ const visitor = typeof elementOrVisitor === "function" ? elementOrVisitor : possiblyVisitor;
1780
+ for (const block of body.content) {
1781
+ if (type === "all" || type === "block") {
1782
+ if (guard(block)) {
1783
+ visitor?.(block);
1780
1784
  }
1781
1785
  }
1782
- let body;
1783
- try {
1784
- body = await response.json();
1785
- } catch {
1786
- body = {};
1787
- }
1788
- return body;
1789
- }
1790
- async function fetchApi(roomId2, endpoint, options) {
1791
- const authValue = await getAuthValue();
1792
- const url = new URL(
1793
- `/v2/c/rooms/${encodeURIComponent(roomId2)}${endpoint}`,
1794
- config.baseUrl
1795
- ).toString();
1796
- return await fetch(url, {
1797
- ...options,
1798
- headers: {
1799
- ...options?.headers,
1800
- Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
1786
+ if (type === "all" || type === "inline") {
1787
+ for (const inline of block.children) {
1788
+ if (guard(inline)) {
1789
+ visitor?.(inline);
1790
+ }
1801
1791
  }
1802
- });
1803
- }
1804
- async function getThreads() {
1805
- const response = await fetchApi(roomId, "/threads");
1806
- if (response.ok) {
1807
- const json = await response.json();
1808
- return json.data;
1809
- } else if (response.status === 404) {
1810
- return [];
1811
- } else {
1812
- throw new Error("There was an error while getting threads.");
1813
1792
  }
1814
1793
  }
1815
- function createThread({
1816
- metadata,
1794
+ }
1795
+ function getMentionedIdsFromCommentBody(body) {
1796
+ const mentionedIds = /* @__PURE__ */ new Set();
1797
+ traverseCommentBody(
1817
1798
  body,
1818
- commentId,
1819
- threadId
1820
- }) {
1821
- return fetchJson("/threads", {
1822
- method: "POST",
1823
- headers: {
1824
- "Content-Type": "application/json"
1825
- },
1826
- body: JSON.stringify({
1827
- id: threadId,
1828
- comment: {
1829
- id: commentId,
1830
- body
1831
- },
1832
- metadata
1833
- })
1834
- });
1835
- }
1836
- function editThreadMetadata({
1837
- metadata,
1838
- threadId
1839
- }) {
1840
- return fetchJson(
1841
- `/threads/${encodeURIComponent(threadId)}/metadata`,
1842
- {
1843
- method: "POST",
1844
- headers: {
1845
- "Content-Type": "application/json"
1846
- },
1847
- body: JSON.stringify(metadata)
1848
- }
1849
- );
1799
+ "mention",
1800
+ (mention) => mentionedIds.add(mention.id)
1801
+ );
1802
+ return Array.from(mentionedIds);
1803
+ }
1804
+ async function resolveUsersInCommentBody(body, resolveUsers) {
1805
+ const resolvedUsers = /* @__PURE__ */ new Map();
1806
+ if (!resolveUsers) {
1807
+ return resolvedUsers;
1850
1808
  }
1851
- function createComment({
1852
- threadId,
1853
- commentId,
1854
- body
1855
- }) {
1856
- return fetchJson(
1857
- `/threads/${encodeURIComponent(threadId)}/comments`,
1858
- {
1859
- method: "POST",
1860
- headers: {
1861
- "Content-Type": "application/json"
1862
- },
1863
- body: JSON.stringify({
1864
- id: commentId,
1865
- body
1866
- })
1867
- }
1868
- );
1809
+ const userIds = getMentionedIdsFromCommentBody(body);
1810
+ const users = await resolveUsers({
1811
+ userIds
1812
+ });
1813
+ for (const [index, userId] of userIds.entries()) {
1814
+ const user = users?.[index];
1815
+ if (user) {
1816
+ resolvedUsers.set(userId, user);
1817
+ }
1869
1818
  }
1870
- function editComment({
1871
- threadId,
1872
- commentId,
1873
- body
1874
- }) {
1875
- return fetchJson(
1876
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
1877
- commentId
1878
- )}`,
1879
- {
1880
- method: "POST",
1881
- headers: {
1882
- "Content-Type": "application/json"
1883
- },
1884
- body: JSON.stringify({
1885
- body
1886
- })
1887
- }
1888
- );
1819
+ return resolvedUsers;
1820
+ }
1821
+ var htmlEscapables = {
1822
+ "&": "&amp;",
1823
+ "<": "&lt;",
1824
+ ">": "&gt;",
1825
+ '"': "&quot;",
1826
+ "'": "&#39;"
1827
+ };
1828
+ var htmlEscapablesRegex = new RegExp(
1829
+ Object.keys(htmlEscapables).map((entity) => `\\${entity}`).join("|"),
1830
+ "g"
1831
+ );
1832
+ function htmlSafe(value) {
1833
+ return new HtmlSafeString([String(value)], []);
1834
+ }
1835
+ function joinHtml(strings) {
1836
+ if (strings.length <= 0) {
1837
+ return new HtmlSafeString([""], []);
1889
1838
  }
1890
- async function deleteComment({
1891
- threadId,
1892
- commentId
1893
- }) {
1894
- await fetchJson(
1895
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
1896
- commentId
1897
- )}`,
1898
- {
1899
- method: "DELETE"
1900
- }
1901
- );
1839
+ return new HtmlSafeString(
1840
+ ["", ...Array(strings.length - 1).fill(""), ""],
1841
+ strings
1842
+ );
1843
+ }
1844
+ function escapeHtml(value) {
1845
+ if (value instanceof HtmlSafeString) {
1846
+ return value.toString();
1902
1847
  }
1903
- function addReaction({
1904
- threadId,
1905
- commentId,
1906
- emoji
1907
- }) {
1908
- return fetchJson(
1909
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
1910
- commentId
1911
- )}/reactions`,
1912
- {
1913
- method: "POST",
1914
- headers: {
1915
- "Content-Type": "application/json"
1916
- },
1917
- body: JSON.stringify({ emoji })
1918
- }
1919
- );
1848
+ if (Array.isArray(value)) {
1849
+ return joinHtml(value).toString();
1920
1850
  }
1921
- function removeReaction({
1922
- threadId,
1923
- commentId,
1924
- emoji
1925
- }) {
1926
- return fetchJson(
1927
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
1928
- commentId
1929
- )}/reactions/${encodeURIComponent(emoji)}`,
1930
- {
1931
- method: "DELETE"
1932
- }
1933
- );
1851
+ return String(value).replace(
1852
+ htmlEscapablesRegex,
1853
+ (character) => htmlEscapables[character]
1854
+ );
1855
+ }
1856
+ var HtmlSafeString = class {
1857
+ constructor(strings, values) {
1858
+ this._strings = strings;
1859
+ this._values = values;
1934
1860
  }
1935
- return {
1936
- getThreads,
1937
- createThread,
1938
- editThreadMetadata,
1939
- createComment,
1940
- editComment,
1941
- deleteComment,
1942
- addReaction,
1943
- removeReaction
1944
- };
1945
- }
1946
-
1947
- // src/lib/position.ts
1948
- var MIN_CODE = 32;
1949
- var MAX_CODE = 126;
1950
- var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
1951
- var ZERO = nthDigit(0);
1952
- var ONE = nthDigit(1);
1953
- var ZERO_NINE = ZERO + nthDigit(-1);
1954
- function nthDigit(n) {
1955
- const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
1956
- if (code < MIN_CODE || code > MAX_CODE) {
1957
- throw new Error(`Invalid n value: ${n}`);
1861
+ toString() {
1862
+ return this._strings.reduce((result, str, i) => {
1863
+ return result + escapeHtml(nn(this._values[i - 1])) + str;
1864
+ });
1958
1865
  }
1959
- return String.fromCharCode(code);
1866
+ };
1867
+ function html(strings, ...values) {
1868
+ return new HtmlSafeString(strings, values);
1960
1869
  }
1961
- function makePosition(x, y) {
1962
- if (x !== void 0 && y !== void 0) {
1963
- return between(x, y);
1964
- } else if (x !== void 0) {
1965
- return after(x);
1966
- } else if (y !== void 0) {
1967
- return before(y);
1968
- } else {
1969
- return ONE;
1870
+ var markdownEscapables = {
1871
+ _: "\\_",
1872
+ "*": "\\*",
1873
+ "#": "\\#",
1874
+ "`": "\\`",
1875
+ "~": "\\~",
1876
+ "!": "\\!",
1877
+ "|": "\\|",
1878
+ "(": "\\(",
1879
+ ")": "\\)",
1880
+ "{": "\\{",
1881
+ "}": "\\}",
1882
+ "[": "\\[",
1883
+ "]": "\\]"
1884
+ };
1885
+ var markdownEscapablesRegex = new RegExp(
1886
+ Object.keys(markdownEscapables).map((entity) => `\\${entity}`).join("|"),
1887
+ "g"
1888
+ );
1889
+ function joinMarkdown(strings) {
1890
+ if (strings.length <= 0) {
1891
+ return new MarkdownSafeString([""], []);
1970
1892
  }
1893
+ return new MarkdownSafeString(
1894
+ ["", ...Array(strings.length - 1).fill(""), ""],
1895
+ strings
1896
+ );
1971
1897
  }
1972
- function before(pos) {
1973
- const lastIndex = pos.length - 1;
1974
- for (let i = 0; i <= lastIndex; i++) {
1975
- const code = pos.charCodeAt(i);
1976
- if (code <= MIN_CODE) {
1977
- continue;
1978
- }
1979
- if (i === lastIndex) {
1980
- if (code === MIN_CODE + 1) {
1981
- return pos.substring(0, i) + ZERO_NINE;
1982
- } else {
1983
- return pos.substring(0, i) + String.fromCharCode(code - 1);
1984
- }
1985
- } else {
1986
- return pos.substring(0, i + 1);
1987
- }
1898
+ function escapeMarkdown(value) {
1899
+ if (value instanceof MarkdownSafeString) {
1900
+ return value.toString();
1988
1901
  }
1989
- return ONE;
1990
- }
1991
- function after(pos) {
1992
- for (let i = 0; i <= pos.length - 1; i++) {
1993
- const code = pos.charCodeAt(i);
1994
- if (code >= MAX_CODE) {
1995
- continue;
1996
- }
1997
- return pos.substring(0, i) + String.fromCharCode(code + 1);
1902
+ if (Array.isArray(value)) {
1903
+ return joinMarkdown(value).toString();
1998
1904
  }
1999
- return pos + ONE;
1905
+ return String(value).replace(
1906
+ markdownEscapablesRegex,
1907
+ (character) => markdownEscapables[character]
1908
+ );
2000
1909
  }
2001
- function between(lo, hi) {
2002
- if (lo < hi) {
2003
- return _between(lo, hi);
2004
- } else if (lo > hi) {
2005
- return _between(hi, lo);
2006
- } else {
2007
- throw new Error("Cannot compute value between two equal positions");
1910
+ var MarkdownSafeString = class {
1911
+ constructor(strings, values) {
1912
+ this._strings = strings;
1913
+ this._values = values;
2008
1914
  }
2009
- }
2010
- function _between(lo, hi) {
2011
- let index = 0;
2012
- const loLen = lo.length;
2013
- const hiLen = hi.length;
2014
- while (true) {
2015
- const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
2016
- const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
2017
- if (loCode === hiCode) {
2018
- index++;
2019
- continue;
2020
- }
2021
- if (hiCode - loCode === 1) {
2022
- const size = index + 1;
2023
- let prefix = lo.substring(0, size);
2024
- if (prefix.length < size) {
2025
- prefix += ZERO.repeat(size - prefix.length);
2026
- }
2027
- const suffix = lo.substring(size);
2028
- const nines = "";
2029
- return prefix + _between(suffix, nines);
2030
- } else {
2031
- return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
2032
- }
1915
+ toString() {
1916
+ return this._strings.reduce((result, str, i) => {
1917
+ return result + escapeMarkdown(nn(this._values[i - 1])) + str;
1918
+ });
2033
1919
  }
1920
+ };
1921
+ function markdown(strings, ...values) {
1922
+ return new MarkdownSafeString(strings, values);
2034
1923
  }
2035
- function takeN(pos, n) {
2036
- return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
2037
- }
2038
- var MIN_NON_ZERO_CODE = MIN_CODE + 1;
2039
- function isPos(str) {
2040
- if (str === "") {
2041
- return false;
1924
+ function toAbsoluteUrl(url) {
1925
+ if (url.startsWith("http://") || url.startsWith("https://")) {
1926
+ return url;
1927
+ } else if (url.startsWith("www.")) {
1928
+ return "https://" + url;
2042
1929
  }
2043
- const lastIdx = str.length - 1;
2044
- const last = str.charCodeAt(lastIdx);
2045
- if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
2046
- return false;
1930
+ return;
1931
+ }
1932
+ var stringifyCommentBodyPlainElements = {
1933
+ paragraph: ({ children }) => children,
1934
+ text: ({ element }) => element.text,
1935
+ link: ({ element }) => element.url,
1936
+ mention: ({ element, user }) => {
1937
+ return `@${user?.name ?? element.id}`;
2047
1938
  }
2048
- for (let i = 0; i < lastIdx; i++) {
2049
- const code = str.charCodeAt(i);
2050
- if (code < MIN_CODE || code > MAX_CODE) {
2051
- return false;
1939
+ };
1940
+ var stringifyCommentBodyHtmlElements = {
1941
+ paragraph: ({ children }) => {
1942
+ return children ? html`<p>${htmlSafe(children)}</p>` : children;
1943
+ },
1944
+ text: ({ element }) => {
1945
+ let children = element.text;
1946
+ if (!children) {
1947
+ return children;
2052
1948
  }
1949
+ if (element.bold) {
1950
+ children = html`<strong>${children}</strong>`;
1951
+ }
1952
+ if (element.italic) {
1953
+ children = html`<em>${children}</em>`;
1954
+ }
1955
+ if (element.strikethrough) {
1956
+ children = html`<s>${children}</s>`;
1957
+ }
1958
+ if (element.code) {
1959
+ children = html`<code>${children}</code>`;
1960
+ }
1961
+ return children;
1962
+ },
1963
+ link: ({ element, href }) => {
1964
+ return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.url}</a>`;
1965
+ },
1966
+ mention: ({ element, user }) => {
1967
+ return html`<span data-mention>@${user?.name ?? element.id}</span>`;
2053
1968
  }
2054
- return true;
2055
- }
2056
- function convertToPos(str) {
2057
- const codes = [];
2058
- for (let i = 0; i < str.length; i++) {
2059
- const code = str.charCodeAt(i);
2060
- codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
2061
- }
2062
- while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
2063
- codes.length--;
1969
+ };
1970
+ var stringifyCommentBodyMarkdownElements = {
1971
+ paragraph: ({ children }) => {
1972
+ return children;
1973
+ },
1974
+ text: ({ element }) => {
1975
+ let children = element.text;
1976
+ if (!children) {
1977
+ return children;
1978
+ }
1979
+ if (element.bold) {
1980
+ children = markdown`**${children}**`;
1981
+ }
1982
+ if (element.italic) {
1983
+ children = markdown`_${children}_`;
1984
+ }
1985
+ if (element.strikethrough) {
1986
+ children = markdown`~~${children}~~`;
1987
+ }
1988
+ if (element.code) {
1989
+ children = markdown`\`${children}\``;
1990
+ }
1991
+ return children;
1992
+ },
1993
+ link: ({ element, href }) => {
1994
+ return markdown`[${element.url}](${href})`;
1995
+ },
1996
+ mention: ({ element, user }) => {
1997
+ return markdown`@${user?.name ?? element.id}`;
2064
1998
  }
2065
- return codes.length > 0 ? String.fromCharCode(...codes) : (
2066
- // Edge case: the str was a 0-only string, which is invalid. Default back to .1
2067
- ONE
1999
+ };
2000
+ async function stringifyCommentBody(body, options) {
2001
+ const format = options?.format ?? "plain";
2002
+ const separator = options?.separator ?? (format === "markdown" ? "\n\n" : "\n");
2003
+ const elements = {
2004
+ ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
2005
+ ...options?.elements
2006
+ };
2007
+ const resolvedUsers = await resolveUsersInCommentBody(
2008
+ body,
2009
+ options?.resolveUsers
2068
2010
  );
2011
+ const blocks = body.content.flatMap((block, blockIndex) => {
2012
+ switch (block.type) {
2013
+ case "paragraph": {
2014
+ const inlines = block.children.flatMap((inline, inlineIndex) => {
2015
+ if (isCommentBodyMention(inline)) {
2016
+ return inline.id ? [
2017
+ elements.mention(
2018
+ {
2019
+ element: inline,
2020
+ user: resolvedUsers.get(inline.id)
2021
+ },
2022
+ inlineIndex
2023
+ )
2024
+ ] : [];
2025
+ }
2026
+ if (isCommentBodyLink(inline)) {
2027
+ return [
2028
+ elements.link(
2029
+ {
2030
+ element: inline,
2031
+ href: toAbsoluteUrl(inline.url) ?? inline.url
2032
+ },
2033
+ inlineIndex
2034
+ )
2035
+ ];
2036
+ }
2037
+ if (isCommentBodyText(inline)) {
2038
+ return [elements.text({ element: inline }, inlineIndex)];
2039
+ }
2040
+ return [];
2041
+ });
2042
+ return [
2043
+ elements.paragraph(
2044
+ { element: block, children: inlines.join("") },
2045
+ blockIndex
2046
+ )
2047
+ ];
2048
+ }
2049
+ default:
2050
+ return [];
2051
+ }
2052
+ });
2053
+ return blocks.join(separator);
2069
2054
  }
2070
- function asPos(str) {
2071
- return isPos(str) ? str : convertToPos(str);
2072
- }
2073
-
2074
- // src/protocol/Op.ts
2075
- var OpCode = /* @__PURE__ */ ((OpCode2) => {
2076
- OpCode2[OpCode2["INIT"] = 0] = "INIT";
2077
- OpCode2[OpCode2["SET_PARENT_KEY"] = 1] = "SET_PARENT_KEY";
2078
- OpCode2[OpCode2["CREATE_LIST"] = 2] = "CREATE_LIST";
2079
- OpCode2[OpCode2["UPDATE_OBJECT"] = 3] = "UPDATE_OBJECT";
2080
- OpCode2[OpCode2["CREATE_OBJECT"] = 4] = "CREATE_OBJECT";
2081
- OpCode2[OpCode2["DELETE_CRDT"] = 5] = "DELETE_CRDT";
2082
- OpCode2[OpCode2["DELETE_OBJECT_KEY"] = 6] = "DELETE_OBJECT_KEY";
2083
- OpCode2[OpCode2["CREATE_MAP"] = 7] = "CREATE_MAP";
2084
- OpCode2[OpCode2["CREATE_REGISTER"] = 8] = "CREATE_REGISTER";
2085
- return OpCode2;
2086
- })(OpCode || {});
2087
- function isAckOp(op) {
2088
- return op.type === 5 /* DELETE_CRDT */ && op.id === "ACK";
2089
- }
2090
-
2091
- // src/crdts/AbstractCrdt.ts
2092
- function crdtAsLiveNode(value) {
2093
- return value;
2055
+ function convertToCommentData(data) {
2056
+ const editedAt = data.editedAt ? new Date(data.editedAt) : void 0;
2057
+ const createdAt = new Date(data.createdAt);
2058
+ const reactions = data.reactions.map((reaction) => ({
2059
+ ...reaction,
2060
+ createdAt: new Date(reaction.createdAt)
2061
+ }));
2062
+ if (data.body) {
2063
+ return {
2064
+ ...data,
2065
+ reactions,
2066
+ createdAt,
2067
+ editedAt
2068
+ };
2069
+ } else {
2070
+ const deletedAt = new Date(data.deletedAt);
2071
+ return {
2072
+ ...data,
2073
+ reactions,
2074
+ createdAt,
2075
+ editedAt,
2076
+ deletedAt
2077
+ };
2078
+ }
2094
2079
  }
2095
- function HasParent(node, key, pos = asPos(key)) {
2096
- return Object.freeze({ type: "HasParent", node, key, pos });
2080
+ function convertToThreadData(data) {
2081
+ const updatedAt = data.updatedAt ? new Date(data.updatedAt) : void 0;
2082
+ const createdAt = new Date(data.createdAt);
2083
+ const comments = data.comments.map(
2084
+ (comment) => convertToCommentData(comment)
2085
+ );
2086
+ return {
2087
+ ...data,
2088
+ createdAt,
2089
+ updatedAt,
2090
+ comments
2091
+ };
2097
2092
  }
2098
- var NoParent = Object.freeze({ type: "NoParent" });
2099
- function Orphaned(oldKey, oldPos = asPos(oldKey)) {
2100
- return Object.freeze({ type: "Orphaned", oldKey, oldPos });
2093
+ function convertToCommentUserReaction(data) {
2094
+ return {
2095
+ ...data,
2096
+ createdAt: new Date(data.createdAt)
2097
+ };
2101
2098
  }
2102
- var AbstractCrdt = class {
2103
- constructor() {
2104
- /** @internal */
2105
- this._parent = NoParent;
2106
- }
2107
- /** @internal */
2108
- _getParentKeyOrThrow() {
2109
- switch (this.parent.type) {
2110
- case "HasParent":
2111
- return this.parent.key;
2112
- case "NoParent":
2113
- throw new Error("Parent key is missing");
2114
- case "Orphaned":
2115
- return this.parent.oldKey;
2116
- default:
2117
- return assertNever(this.parent, "Unknown state");
2118
- }
2119
- }
2120
- /** @internal */
2121
- get _parentPos() {
2122
- switch (this.parent.type) {
2123
- case "HasParent":
2124
- return this.parent.pos;
2125
- case "NoParent":
2126
- throw new Error("Parent key is missing");
2127
- case "Orphaned":
2128
- return this.parent.oldPos;
2129
- default:
2130
- return assertNever(this.parent, "Unknown state");
2131
- }
2132
- }
2133
- /** @internal */
2134
- get _pool() {
2135
- return this.__pool;
2136
- }
2137
- get roomId() {
2138
- return this.__pool ? this.__pool.roomId : null;
2139
- }
2140
- /** @internal */
2141
- get _id() {
2142
- return this.__id;
2143
- }
2144
- /** @internal */
2145
- get parent() {
2146
- return this._parent;
2099
+
2100
+ // src/comments/index.ts
2101
+ function getAuthBearerHeaderFromAuthValue(authValue) {
2102
+ if (authValue.type === "public") {
2103
+ return authValue.publicApiKey;
2104
+ } else {
2105
+ return authValue.token.raw;
2147
2106
  }
2148
- /** @internal */
2149
- get _parentKey() {
2150
- switch (this.parent.type) {
2151
- case "HasParent":
2152
- return this.parent.key;
2153
- case "NoParent":
2154
- return null;
2155
- case "Orphaned":
2156
- return this.parent.oldKey;
2157
- default:
2158
- return assertNever(this.parent, "Unknown state");
2159
- }
2107
+ }
2108
+ var CommentsApiError = class extends Error {
2109
+ constructor(message, status, details) {
2110
+ super(message);
2111
+ this.message = message;
2112
+ this.status = status;
2113
+ this.details = details;
2160
2114
  }
2161
- /** @internal */
2162
- _apply(op, _isLocal) {
2163
- switch (op.type) {
2164
- case 5 /* DELETE_CRDT */: {
2165
- if (this.parent.type === "HasParent") {
2166
- return this.parent.node._detachChild(crdtAsLiveNode(this));
2115
+ };
2116
+ function createCommentsApi(roomId, getAuthValue, config) {
2117
+ async function fetchJson(endpoint, options) {
2118
+ const response = await fetchApi(roomId, endpoint, options);
2119
+ if (!response.ok) {
2120
+ if (response.status >= 400 && response.status < 600) {
2121
+ let error3;
2122
+ try {
2123
+ const errorBody = await response.json();
2124
+ error3 = new CommentsApiError(
2125
+ errorBody.message,
2126
+ response.status,
2127
+ errorBody
2128
+ );
2129
+ } catch {
2130
+ error3 = new CommentsApiError(response.statusText, response.status);
2167
2131
  }
2168
- return { modified: false };
2132
+ throw error3;
2169
2133
  }
2170
2134
  }
2171
- return { modified: false };
2135
+ let body;
2136
+ try {
2137
+ body = await response.json();
2138
+ } catch {
2139
+ body = {};
2140
+ }
2141
+ return body;
2172
2142
  }
2173
- /** @internal */
2174
- _setParentLink(newParentNode, newParentKey) {
2175
- switch (this.parent.type) {
2176
- case "HasParent":
2177
- if (this.parent.node !== newParentNode) {
2178
- throw new Error("Cannot set parent: node already has a parent");
2179
- } else {
2180
- this._parent = HasParent(newParentNode, newParentKey);
2181
- return;
2182
- }
2183
- case "Orphaned":
2184
- case "NoParent": {
2185
- this._parent = HasParent(newParentNode, newParentKey);
2186
- return;
2143
+ async function fetchApi(roomId2, endpoint, options) {
2144
+ const authValue = await getAuthValue();
2145
+ const url = new URL(
2146
+ `/v2/c/rooms/${encodeURIComponent(roomId2)}${endpoint}`,
2147
+ config.baseUrl
2148
+ ).toString();
2149
+ return await fetch(url, {
2150
+ ...options,
2151
+ headers: {
2152
+ ...options?.headers,
2153
+ Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
2187
2154
  }
2188
- default:
2189
- return assertNever(this.parent, "Unknown state");
2190
- }
2155
+ });
2191
2156
  }
2192
- /** @internal */
2193
- _attach(id, pool) {
2194
- if (this.__id || this.__pool) {
2195
- throw new Error("Cannot attach node: already attached");
2157
+ async function getThreads() {
2158
+ const response = await fetchApi(roomId, "/threads");
2159
+ if (response.ok) {
2160
+ const json = await response.json();
2161
+ return json.data.map((thread) => convertToThreadData(thread));
2162
+ } else if (response.status === 404) {
2163
+ return [];
2164
+ } else {
2165
+ throw new Error("There was an error while getting threads.");
2196
2166
  }
2197
- pool.addNode(id, crdtAsLiveNode(this));
2198
- this.__id = id;
2199
- this.__pool = pool;
2200
2167
  }
2201
- /** @internal */
2202
- _detach() {
2203
- if (this.__pool && this.__id) {
2204
- this.__pool.deleteNode(this.__id);
2205
- }
2206
- switch (this.parent.type) {
2207
- case "HasParent": {
2208
- this._parent = Orphaned(this.parent.key, this.parent.pos);
2209
- break;
2210
- }
2211
- case "NoParent": {
2212
- this._parent = NoParent;
2213
- break;
2214
- }
2215
- case "Orphaned": {
2216
- break;
2168
+ async function createThread({
2169
+ metadata,
2170
+ body,
2171
+ commentId,
2172
+ threadId
2173
+ }) {
2174
+ const thread = await fetchJson(
2175
+ "/threads",
2176
+ {
2177
+ method: "POST",
2178
+ headers: {
2179
+ "Content-Type": "application/json"
2180
+ },
2181
+ body: JSON.stringify({
2182
+ id: threadId,
2183
+ comment: {
2184
+ id: commentId,
2185
+ body
2186
+ },
2187
+ metadata
2188
+ })
2217
2189
  }
2218
- default:
2219
- assertNever(this.parent, "Unknown state");
2220
- }
2221
- this.__pool = void 0;
2190
+ );
2191
+ return convertToThreadData(thread);
2222
2192
  }
2223
- /**
2224
- * @internal
2225
- *
2226
- * Clear the Immutable cache, so that the next call to `.toImmutable()` will
2227
- * recompute the equivalent Immutable value again. Call this after every
2228
- * mutation to the Live node.
2229
- */
2230
- invalidate() {
2231
- if (this._cachedImmutable !== void 0 || this._cachedTreeNode !== void 0) {
2232
- this._cachedImmutable = void 0;
2233
- this._cachedTreeNode = void 0;
2234
- if (this.parent.type === "HasParent") {
2235
- this.parent.node.invalidate();
2193
+ async function editThreadMetadata({
2194
+ metadata,
2195
+ threadId
2196
+ }) {
2197
+ return await fetchJson(
2198
+ `/threads/${encodeURIComponent(threadId)}/metadata`,
2199
+ {
2200
+ method: "POST",
2201
+ headers: {
2202
+ "Content-Type": "application/json"
2203
+ },
2204
+ body: JSON.stringify(metadata)
2236
2205
  }
2237
- }
2206
+ );
2238
2207
  }
2239
- /**
2240
- * @internal
2241
- *
2242
- * Return an snapshot of this Live tree for use in DevTools.
2243
- */
2244
- toTreeNode(key) {
2245
- if (this._cachedTreeNode === void 0 || this._cachedTreeNodeKey !== key) {
2246
- this._cachedTreeNodeKey = key;
2247
- this._cachedTreeNode = this._toTreeNode(key);
2248
- }
2249
- return this._cachedTreeNode;
2208
+ async function createComment({
2209
+ threadId,
2210
+ commentId,
2211
+ body
2212
+ }) {
2213
+ const comment = await fetchJson(
2214
+ `/threads/${encodeURIComponent(threadId)}/comments`,
2215
+ {
2216
+ method: "POST",
2217
+ headers: {
2218
+ "Content-Type": "application/json"
2219
+ },
2220
+ body: JSON.stringify({
2221
+ id: commentId,
2222
+ body
2223
+ })
2224
+ }
2225
+ );
2226
+ return convertToCommentData(comment);
2250
2227
  }
2251
- /**
2252
- * Return an immutable snapshot of this Live node and its children.
2253
- */
2254
- toImmutable() {
2255
- if (this._cachedImmutable === void 0) {
2256
- this._cachedImmutable = this._toImmutable();
2257
- }
2258
- return this._cachedImmutable;
2228
+ async function editComment({
2229
+ threadId,
2230
+ commentId,
2231
+ body
2232
+ }) {
2233
+ const comment = await fetchJson(
2234
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2235
+ commentId
2236
+ )}`,
2237
+ {
2238
+ method: "POST",
2239
+ headers: {
2240
+ "Content-Type": "application/json"
2241
+ },
2242
+ body: JSON.stringify({
2243
+ body
2244
+ })
2245
+ }
2246
+ );
2247
+ return convertToCommentData(comment);
2259
2248
  }
2260
- };
2261
-
2262
- // src/protocol/SerializedCrdt.ts
2263
- var CrdtType = /* @__PURE__ */ ((CrdtType2) => {
2264
- CrdtType2[CrdtType2["OBJECT"] = 0] = "OBJECT";
2265
- CrdtType2[CrdtType2["LIST"] = 1] = "LIST";
2266
- CrdtType2[CrdtType2["MAP"] = 2] = "MAP";
2267
- CrdtType2[CrdtType2["REGISTER"] = 3] = "REGISTER";
2268
- return CrdtType2;
2269
- })(CrdtType || {});
2270
- function isRootCrdt(crdt) {
2271
- return crdt.type === 0 /* OBJECT */ && !isChildCrdt(crdt);
2272
- }
2273
- function isChildCrdt(crdt) {
2274
- return crdt.parentId !== void 0 && crdt.parentKey !== void 0;
2275
- }
2276
-
2277
- // src/lib/nanoid.ts
2278
- function nanoid(length = 7) {
2279
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,./;[]~!@#$%&*()_+=-";
2280
- const len = alphabet.length;
2281
- return Array.from(
2282
- { length },
2283
- () => alphabet.charAt(Math.floor(Math.random() * len))
2284
- ).join("");
2249
+ async function deleteComment({
2250
+ threadId,
2251
+ commentId
2252
+ }) {
2253
+ await fetchJson(
2254
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2255
+ commentId
2256
+ )}`,
2257
+ {
2258
+ method: "DELETE"
2259
+ }
2260
+ );
2261
+ }
2262
+ async function addReaction({
2263
+ threadId,
2264
+ commentId,
2265
+ emoji
2266
+ }) {
2267
+ const reaction = await fetchJson(
2268
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2269
+ commentId
2270
+ )}/reactions`,
2271
+ {
2272
+ method: "POST",
2273
+ headers: {
2274
+ "Content-Type": "application/json"
2275
+ },
2276
+ body: JSON.stringify({ emoji })
2277
+ }
2278
+ );
2279
+ return convertToCommentUserReaction(reaction);
2280
+ }
2281
+ async function removeReaction({
2282
+ threadId,
2283
+ commentId,
2284
+ emoji
2285
+ }) {
2286
+ await fetchJson(
2287
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2288
+ commentId
2289
+ )}/reactions/${encodeURIComponent(emoji)}`,
2290
+ {
2291
+ method: "DELETE"
2292
+ }
2293
+ );
2294
+ }
2295
+ return {
2296
+ getThreads,
2297
+ createThread,
2298
+ editThreadMetadata,
2299
+ createComment,
2300
+ editComment,
2301
+ deleteComment,
2302
+ addReaction,
2303
+ removeReaction
2304
+ };
2285
2305
  }
2286
2306
 
2287
- // src/crdts/LiveRegister.ts
2288
- var LiveRegister = class _LiveRegister extends AbstractCrdt {
2289
- constructor(data) {
2290
- super();
2291
- this._data = data;
2292
- }
2293
- get data() {
2294
- return this._data;
2307
+ // src/lib/position.ts
2308
+ var MIN_CODE = 32;
2309
+ var MAX_CODE = 126;
2310
+ var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
2311
+ var ZERO = nthDigit(0);
2312
+ var ONE = nthDigit(1);
2313
+ var ZERO_NINE = ZERO + nthDigit(-1);
2314
+ function nthDigit(n) {
2315
+ const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
2316
+ if (code < MIN_CODE || code > MAX_CODE) {
2317
+ throw new Error(`Invalid n value: ${n}`);
2295
2318
  }
2296
- /** @internal */
2297
- static _deserialize([id, item], _parentToChildren, pool) {
2298
- const register = new _LiveRegister(item.data);
2299
- register._attach(id, pool);
2300
- return register;
2319
+ return String.fromCharCode(code);
2320
+ }
2321
+ function makePosition(x, y) {
2322
+ if (x !== void 0 && y !== void 0) {
2323
+ return between(x, y);
2324
+ } else if (x !== void 0) {
2325
+ return after(x);
2326
+ } else if (y !== void 0) {
2327
+ return before(y);
2328
+ } else {
2329
+ return ONE;
2301
2330
  }
2302
- /** @internal */
2303
- _toOps(parentId, parentKey, pool) {
2304
- if (this._id === void 0) {
2305
- throw new Error(
2306
- "Cannot serialize register if parentId or parentKey is undefined"
2307
- );
2331
+ }
2332
+ function before(pos) {
2333
+ const lastIndex = pos.length - 1;
2334
+ for (let i = 0; i <= lastIndex; i++) {
2335
+ const code = pos.charCodeAt(i);
2336
+ if (code <= MIN_CODE) {
2337
+ continue;
2308
2338
  }
2309
- return [
2310
- {
2311
- type: 8 /* CREATE_REGISTER */,
2312
- opId: pool?.generateOpId(),
2313
- id: this._id,
2314
- parentId,
2315
- parentKey,
2316
- data: this.data
2339
+ if (i === lastIndex) {
2340
+ if (code === MIN_CODE + 1) {
2341
+ return pos.substring(0, i) + ZERO_NINE;
2342
+ } else {
2343
+ return pos.substring(0, i) + String.fromCharCode(code - 1);
2317
2344
  }
2318
- ];
2319
- }
2320
- /** @internal */
2321
- _serialize() {
2322
- if (this.parent.type !== "HasParent") {
2323
- throw new Error("Cannot serialize LiveRegister if parent is missing");
2345
+ } else {
2346
+ return pos.substring(0, i + 1);
2324
2347
  }
2325
- return {
2326
- type: 3 /* REGISTER */,
2327
- parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
2328
- parentKey: this.parent.key,
2329
- data: this.data
2330
- };
2331
2348
  }
2332
- /** @internal */
2333
- _attachChild(_op) {
2334
- throw new Error("Method not implemented.");
2349
+ return ONE;
2350
+ }
2351
+ function after(pos) {
2352
+ for (let i = 0; i <= pos.length - 1; i++) {
2353
+ const code = pos.charCodeAt(i);
2354
+ if (code >= MAX_CODE) {
2355
+ continue;
2356
+ }
2357
+ return pos.substring(0, i) + String.fromCharCode(code + 1);
2335
2358
  }
2336
- /** @internal */
2337
- _detachChild(_crdt) {
2338
- throw new Error("Method not implemented.");
2359
+ return pos + ONE;
2360
+ }
2361
+ function between(lo, hi) {
2362
+ if (lo < hi) {
2363
+ return _between(lo, hi);
2364
+ } else if (lo > hi) {
2365
+ return _between(hi, lo);
2366
+ } else {
2367
+ throw new Error("Cannot compute value between two equal positions");
2339
2368
  }
2340
- /** @internal */
2341
- _apply(op, isLocal) {
2342
- return super._apply(op, isLocal);
2369
+ }
2370
+ function _between(lo, hi) {
2371
+ let index = 0;
2372
+ const loLen = lo.length;
2373
+ const hiLen = hi.length;
2374
+ while (true) {
2375
+ const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
2376
+ const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
2377
+ if (loCode === hiCode) {
2378
+ index++;
2379
+ continue;
2380
+ }
2381
+ if (hiCode - loCode === 1) {
2382
+ const size = index + 1;
2383
+ let prefix = lo.substring(0, size);
2384
+ if (prefix.length < size) {
2385
+ prefix += ZERO.repeat(size - prefix.length);
2386
+ }
2387
+ const suffix = lo.substring(size);
2388
+ const nines = "";
2389
+ return prefix + _between(suffix, nines);
2390
+ } else {
2391
+ return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
2392
+ }
2343
2393
  }
2344
- /** @internal */
2345
- _toTreeNode(key) {
2346
- return {
2347
- type: "Json",
2348
- id: this._id ?? nanoid(),
2349
- key,
2350
- payload: this._data
2351
- };
2394
+ }
2395
+ function takeN(pos, n) {
2396
+ return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
2397
+ }
2398
+ var MIN_NON_ZERO_CODE = MIN_CODE + 1;
2399
+ function isPos(str) {
2400
+ if (str === "") {
2401
+ return false;
2352
2402
  }
2353
- /** @internal */
2354
- _toImmutable() {
2355
- return this._data;
2403
+ const lastIdx = str.length - 1;
2404
+ const last = str.charCodeAt(lastIdx);
2405
+ if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
2406
+ return false;
2356
2407
  }
2357
- clone() {
2358
- return deepClone(this.data);
2408
+ for (let i = 0; i < lastIdx; i++) {
2409
+ const code = str.charCodeAt(i);
2410
+ if (code < MIN_CODE || code > MAX_CODE) {
2411
+ return false;
2412
+ }
2359
2413
  }
2360
- };
2414
+ return true;
2415
+ }
2416
+ function convertToPos(str) {
2417
+ const codes = [];
2418
+ for (let i = 0; i < str.length; i++) {
2419
+ const code = str.charCodeAt(i);
2420
+ codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
2421
+ }
2422
+ while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
2423
+ codes.length--;
2424
+ }
2425
+ return codes.length > 0 ? String.fromCharCode(...codes) : (
2426
+ // Edge case: the str was a 0-only string, which is invalid. Default back to .1
2427
+ ONE
2428
+ );
2429
+ }
2430
+ function asPos(str) {
2431
+ return isPos(str) ? str : convertToPos(str);
2432
+ }
2361
2433
 
2362
- // src/crdts/LiveList.ts
2363
- function compareNodePosition(itemA, itemB) {
2364
- const posA = itemA._parentPos;
2365
- const posB = itemB._parentPos;
2366
- return posA === posB ? 0 : posA < posB ? -1 : 1;
2434
+ // src/protocol/Op.ts
2435
+ var OpCode = /* @__PURE__ */ ((OpCode2) => {
2436
+ OpCode2[OpCode2["INIT"] = 0] = "INIT";
2437
+ OpCode2[OpCode2["SET_PARENT_KEY"] = 1] = "SET_PARENT_KEY";
2438
+ OpCode2[OpCode2["CREATE_LIST"] = 2] = "CREATE_LIST";
2439
+ OpCode2[OpCode2["UPDATE_OBJECT"] = 3] = "UPDATE_OBJECT";
2440
+ OpCode2[OpCode2["CREATE_OBJECT"] = 4] = "CREATE_OBJECT";
2441
+ OpCode2[OpCode2["DELETE_CRDT"] = 5] = "DELETE_CRDT";
2442
+ OpCode2[OpCode2["DELETE_OBJECT_KEY"] = 6] = "DELETE_OBJECT_KEY";
2443
+ OpCode2[OpCode2["CREATE_MAP"] = 7] = "CREATE_MAP";
2444
+ OpCode2[OpCode2["CREATE_REGISTER"] = 8] = "CREATE_REGISTER";
2445
+ return OpCode2;
2446
+ })(OpCode || {});
2447
+ function ackOp(opId) {
2448
+ return {
2449
+ type: 5 /* DELETE_CRDT */,
2450
+ id: "ACK",
2451
+ // (H)ACK
2452
+ opId
2453
+ };
2367
2454
  }
2368
- var LiveList = class _LiveList extends AbstractCrdt {
2369
- constructor(items = []) {
2370
- super();
2371
- this._items = [];
2372
- this._implicitlyDeletedItems = /* @__PURE__ */ new WeakSet();
2373
- this._unacknowledgedSets = /* @__PURE__ */ new Map();
2374
- let position = void 0;
2375
- for (const item of items) {
2376
- const newPosition = makePosition(position);
2377
- const node = lsonToLiveNode(item);
2378
- node._setParentLink(this, newPosition);
2379
- this._items.push(node);
2380
- position = newPosition;
2381
- }
2455
+ function isAckOp(op) {
2456
+ return op.type === 5 /* DELETE_CRDT */ && op.id === "ACK";
2457
+ }
2458
+
2459
+ // src/crdts/AbstractCrdt.ts
2460
+ function crdtAsLiveNode(value) {
2461
+ return value;
2462
+ }
2463
+ function HasParent(node, key, pos = asPos(key)) {
2464
+ return Object.freeze({ type: "HasParent", node, key, pos });
2465
+ }
2466
+ var NoParent = Object.freeze({ type: "NoParent" });
2467
+ function Orphaned(oldKey, oldPos = asPos(oldKey)) {
2468
+ return Object.freeze({ type: "Orphaned", oldKey, oldPos });
2469
+ }
2470
+ var AbstractCrdt = class {
2471
+ constructor() {
2472
+ /** @internal */
2473
+ this._parent = NoParent;
2382
2474
  }
2383
2475
  /** @internal */
2384
- static _deserialize([id], parentToChildren, pool) {
2385
- const list = new _LiveList();
2386
- list._attach(id, pool);
2387
- const children = parentToChildren.get(id);
2388
- if (children === void 0) {
2389
- return list;
2390
- }
2391
- for (const [id2, crdt] of children) {
2392
- const child = deserialize([id2, crdt], parentToChildren, pool);
2393
- child._setParentLink(list, crdt.parentKey);
2394
- list._insertAndSort(child);
2476
+ _getParentKeyOrThrow() {
2477
+ switch (this.parent.type) {
2478
+ case "HasParent":
2479
+ return this.parent.key;
2480
+ case "NoParent":
2481
+ throw new Error("Parent key is missing");
2482
+ case "Orphaned":
2483
+ return this.parent.oldKey;
2484
+ default:
2485
+ return assertNever(this.parent, "Unknown state");
2395
2486
  }
2396
- return list;
2397
2487
  }
2398
- /**
2399
- * @internal
2400
- * This function assumes that the resulting ops will be sent to the server if they have an 'opId'
2401
- * so we mutate _unacknowledgedSets to avoid potential flickering
2402
- * https://github.com/liveblocks/liveblocks/pull/1177
2403
- *
2404
- * This is quite unintuitive and should disappear as soon as
2405
- * we introduce an explicit LiveList.Set operation
2406
- */
2407
- _toOps(parentId, parentKey, pool) {
2408
- if (this._id === void 0) {
2409
- throw new Error("Cannot serialize item is not attached");
2410
- }
2411
- const ops = [];
2412
- const op = {
2413
- id: this._id,
2414
- opId: pool?.generateOpId(),
2415
- type: 2 /* CREATE_LIST */,
2416
- parentId,
2417
- parentKey
2418
- };
2419
- ops.push(op);
2420
- for (const item of this._items) {
2421
- const parentKey2 = item._getParentKeyOrThrow();
2422
- const childOps = HACK_addIntentAndDeletedIdToOperation(
2423
- item._toOps(this._id, parentKey2, pool),
2424
- void 0
2425
- );
2426
- const childOpId = childOps[0].opId;
2427
- if (childOpId !== void 0) {
2428
- this._unacknowledgedSets.set(parentKey2, childOpId);
2429
- }
2430
- ops.push(...childOps);
2488
+ /** @internal */
2489
+ get _parentPos() {
2490
+ switch (this.parent.type) {
2491
+ case "HasParent":
2492
+ return this.parent.pos;
2493
+ case "NoParent":
2494
+ throw new Error("Parent key is missing");
2495
+ case "Orphaned":
2496
+ return this.parent.oldPos;
2497
+ default:
2498
+ return assertNever(this.parent, "Unknown state");
2431
2499
  }
2432
- return ops;
2433
2500
  }
2434
- /**
2435
- * @internal
2436
- *
2437
- * Adds a new item into the sorted list, in the correct position.
2438
- */
2439
- _insertAndSort(item) {
2440
- this._items.push(item);
2441
- this._sortItems();
2501
+ /** @internal */
2502
+ get _pool() {
2503
+ return this.__pool;
2504
+ }
2505
+ get roomId() {
2506
+ return this.__pool ? this.__pool.roomId : null;
2442
2507
  }
2443
2508
  /** @internal */
2444
- _sortItems() {
2445
- this._items.sort(compareNodePosition);
2446
- this.invalidate();
2509
+ get _id() {
2510
+ return this.__id;
2447
2511
  }
2448
2512
  /** @internal */
2449
- _indexOfPosition(position) {
2450
- return this._items.findIndex(
2451
- (item) => item._getParentKeyOrThrow() === position
2452
- );
2513
+ get parent() {
2514
+ return this._parent;
2453
2515
  }
2454
2516
  /** @internal */
2455
- _attach(id, pool) {
2456
- super._attach(id, pool);
2457
- for (const item of this._items) {
2458
- item._attach(pool.generateId(), pool);
2517
+ get _parentKey() {
2518
+ switch (this.parent.type) {
2519
+ case "HasParent":
2520
+ return this.parent.key;
2521
+ case "NoParent":
2522
+ return null;
2523
+ case "Orphaned":
2524
+ return this.parent.oldKey;
2525
+ default:
2526
+ return assertNever(this.parent, "Unknown state");
2459
2527
  }
2460
2528
  }
2461
2529
  /** @internal */
2462
- _detach() {
2463
- super._detach();
2464
- for (const item of this._items) {
2465
- item._detach();
2530
+ _apply(op, _isLocal) {
2531
+ switch (op.type) {
2532
+ case 5 /* DELETE_CRDT */: {
2533
+ if (this.parent.type === "HasParent") {
2534
+ return this.parent.node._detachChild(crdtAsLiveNode(this));
2535
+ }
2536
+ return { modified: false };
2537
+ }
2466
2538
  }
2539
+ return { modified: false };
2467
2540
  }
2468
2541
  /** @internal */
2469
- _applySetRemote(op) {
2470
- if (this._pool === void 0) {
2471
- throw new Error("Can't attach child if managed pool is not present");
2472
- }
2473
- const { id, parentKey: key } = op;
2474
- const child = creationOpToLiveNode(op);
2475
- child._attach(id, this._pool);
2476
- child._setParentLink(this, key);
2477
- const deletedId = op.deletedId;
2478
- const indexOfItemWithSamePosition = this._indexOfPosition(key);
2479
- if (indexOfItemWithSamePosition !== -1) {
2480
- const itemWithSamePosition = this._items[indexOfItemWithSamePosition];
2481
- if (itemWithSamePosition._id === deletedId) {
2482
- itemWithSamePosition._detach();
2483
- this._items[indexOfItemWithSamePosition] = child;
2484
- return {
2485
- modified: makeUpdate(this, [
2486
- setDelta(indexOfItemWithSamePosition, child)
2487
- ]),
2488
- reverse: []
2489
- };
2490
- } else {
2491
- this._implicitlyDeletedItems.add(itemWithSamePosition);
2492
- this._items[indexOfItemWithSamePosition] = child;
2493
- const delta = [
2494
- setDelta(indexOfItemWithSamePosition, child)
2495
- ];
2496
- const deleteDelta2 = this._detachItemAssociatedToSetOperation(
2497
- op.deletedId
2498
- );
2499
- if (deleteDelta2) {
2500
- delta.push(deleteDelta2);
2542
+ _setParentLink(newParentNode, newParentKey) {
2543
+ switch (this.parent.type) {
2544
+ case "HasParent":
2545
+ if (this.parent.node !== newParentNode) {
2546
+ throw new Error("Cannot set parent: node already has a parent");
2547
+ } else {
2548
+ this._parent = HasParent(newParentNode, newParentKey);
2549
+ return;
2501
2550
  }
2502
- return {
2503
- modified: makeUpdate(this, delta),
2504
- reverse: []
2505
- };
2506
- }
2507
- } else {
2508
- const updates = [];
2509
- const deleteDelta2 = this._detachItemAssociatedToSetOperation(
2510
- op.deletedId
2511
- );
2512
- if (deleteDelta2) {
2513
- updates.push(deleteDelta2);
2551
+ case "Orphaned":
2552
+ case "NoParent": {
2553
+ this._parent = HasParent(newParentNode, newParentKey);
2554
+ return;
2514
2555
  }
2515
- this._insertAndSort(child);
2516
- updates.push(insertDelta(this._indexOfPosition(key), child));
2517
- return {
2518
- reverse: [],
2519
- modified: makeUpdate(this, updates)
2520
- };
2556
+ default:
2557
+ return assertNever(this.parent, "Unknown state");
2521
2558
  }
2522
2559
  }
2523
2560
  /** @internal */
2524
- _applySetAck(op) {
2525
- if (this._pool === void 0) {
2526
- throw new Error("Can't attach child if managed pool is not present");
2527
- }
2528
- const delta = [];
2529
- const deletedDelta = this._detachItemAssociatedToSetOperation(op.deletedId);
2530
- if (deletedDelta) {
2531
- delta.push(deletedDelta);
2561
+ _attach(id, pool) {
2562
+ if (this.__id || this.__pool) {
2563
+ throw new Error("Cannot attach node: already attached");
2532
2564
  }
2533
- const unacknowledgedOpId = this._unacknowledgedSets.get(op.parentKey);
2534
- if (unacknowledgedOpId !== void 0) {
2535
- if (unacknowledgedOpId !== op.opId) {
2536
- return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
2537
- } else {
2538
- this._unacknowledgedSets.delete(op.parentKey);
2539
- }
2565
+ pool.addNode(id, crdtAsLiveNode(this));
2566
+ this.__id = id;
2567
+ this.__pool = pool;
2568
+ }
2569
+ /** @internal */
2570
+ _detach() {
2571
+ if (this.__pool && this.__id) {
2572
+ this.__pool.deleteNode(this.__id);
2540
2573
  }
2541
- const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);
2542
- const existingItem = this._items.find((item) => item._id === op.id);
2543
- if (existingItem !== void 0) {
2544
- if (existingItem._parentKey === op.parentKey) {
2545
- return {
2546
- modified: delta.length > 0 ? makeUpdate(this, delta) : false,
2547
- reverse: []
2548
- };
2549
- }
2550
- if (indexOfItemWithSamePosition !== -1) {
2551
- this._implicitlyDeletedItems.add(
2552
- this._items[indexOfItemWithSamePosition]
2553
- );
2554
- this._items.splice(indexOfItemWithSamePosition, 1);
2555
- delta.push(deleteDelta(indexOfItemWithSamePosition));
2574
+ switch (this.parent.type) {
2575
+ case "HasParent": {
2576
+ this._parent = Orphaned(this.parent.key, this.parent.pos);
2577
+ break;
2556
2578
  }
2557
- const previousIndex = this._items.indexOf(existingItem);
2558
- existingItem._setParentLink(this, op.parentKey);
2559
- this._sortItems();
2560
- const newIndex = this._items.indexOf(existingItem);
2561
- if (newIndex !== previousIndex) {
2562
- delta.push(moveDelta(previousIndex, newIndex, existingItem));
2579
+ case "NoParent": {
2580
+ this._parent = NoParent;
2581
+ break;
2563
2582
  }
2564
- return {
2565
- modified: delta.length > 0 ? makeUpdate(this, delta) : false,
2566
- reverse: []
2567
- };
2568
- } else {
2569
- const orphan = this._pool.getNode(op.id);
2570
- if (orphan && this._implicitlyDeletedItems.has(orphan)) {
2571
- orphan._setParentLink(this, op.parentKey);
2572
- this._implicitlyDeletedItems.delete(orphan);
2573
- this._insertAndSort(orphan);
2574
- const recreatedItemIndex = this._items.indexOf(orphan);
2575
- return {
2576
- modified: makeUpdate(this, [
2577
- // If there is an item at this position, update is a set, else it's an insert
2578
- indexOfItemWithSamePosition === -1 ? insertDelta(recreatedItemIndex, orphan) : setDelta(recreatedItemIndex, orphan),
2579
- ...delta
2580
- ]),
2581
- reverse: []
2582
- };
2583
- } else {
2584
- if (indexOfItemWithSamePosition !== -1) {
2585
- this._items.splice(indexOfItemWithSamePosition, 1);
2586
- }
2587
- const { newItem, newIndex } = this._createAttachItemAndSort(
2588
- op,
2589
- op.parentKey
2590
- );
2591
- return {
2592
- modified: makeUpdate(this, [
2593
- // If there is an item at this position, update is a set, else it's an insert
2594
- indexOfItemWithSamePosition === -1 ? insertDelta(newIndex, newItem) : setDelta(newIndex, newItem),
2595
- ...delta
2596
- ]),
2597
- reverse: []
2598
- };
2583
+ case "Orphaned": {
2584
+ break;
2599
2585
  }
2586
+ default:
2587
+ assertNever(this.parent, "Unknown state");
2600
2588
  }
2589
+ this.__pool = void 0;
2601
2590
  }
2602
2591
  /**
2603
- * Returns the update delta of the deletion or null
2604
2592
  * @internal
2593
+ *
2594
+ * Clear the Immutable cache, so that the next call to `.toImmutable()` will
2595
+ * recompute the equivalent Immutable value again. Call this after every
2596
+ * mutation to the Live node.
2605
2597
  */
2606
- _detachItemAssociatedToSetOperation(deletedId) {
2607
- if (deletedId === void 0 || this._pool === void 0) {
2608
- return null;
2598
+ invalidate() {
2599
+ if (this._cachedImmutable !== void 0 || this._cachedTreeNode !== void 0) {
2600
+ this._cachedImmutable = void 0;
2601
+ this._cachedTreeNode = void 0;
2602
+ if (this.parent.type === "HasParent") {
2603
+ this.parent.node.invalidate();
2604
+ }
2609
2605
  }
2610
- const deletedItem = this._pool.getNode(deletedId);
2611
- if (deletedItem === void 0) {
2612
- return null;
2606
+ }
2607
+ /**
2608
+ * @internal
2609
+ *
2610
+ * Return an snapshot of this Live tree for use in DevTools.
2611
+ */
2612
+ toTreeNode(key) {
2613
+ if (this._cachedTreeNode === void 0 || this._cachedTreeNodeKey !== key) {
2614
+ this._cachedTreeNodeKey = key;
2615
+ this._cachedTreeNode = this._toTreeNode(key);
2613
2616
  }
2614
- const result = this._detachChild(deletedItem);
2615
- if (result.modified === false) {
2616
- return null;
2617
+ return this._cachedTreeNode;
2618
+ }
2619
+ /**
2620
+ * Return an immutable snapshot of this Live node and its children.
2621
+ */
2622
+ toImmutable() {
2623
+ if (this._cachedImmutable === void 0) {
2624
+ this._cachedImmutable = this._toImmutable();
2617
2625
  }
2618
- return result.modified.updates[0];
2626
+ return this._cachedImmutable;
2627
+ }
2628
+ };
2629
+
2630
+ // src/protocol/SerializedCrdt.ts
2631
+ var CrdtType = /* @__PURE__ */ ((CrdtType2) => {
2632
+ CrdtType2[CrdtType2["OBJECT"] = 0] = "OBJECT";
2633
+ CrdtType2[CrdtType2["LIST"] = 1] = "LIST";
2634
+ CrdtType2[CrdtType2["MAP"] = 2] = "MAP";
2635
+ CrdtType2[CrdtType2["REGISTER"] = 3] = "REGISTER";
2636
+ return CrdtType2;
2637
+ })(CrdtType || {});
2638
+ function isRootCrdt(crdt) {
2639
+ return crdt.type === 0 /* OBJECT */ && !isChildCrdt(crdt);
2640
+ }
2641
+ function isChildCrdt(crdt) {
2642
+ return crdt.parentId !== void 0 && crdt.parentKey !== void 0;
2643
+ }
2644
+
2645
+ // src/lib/nanoid.ts
2646
+ function nanoid(length = 7) {
2647
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,./;[]~!@#$%&*()_+=-";
2648
+ const len = alphabet.length;
2649
+ return Array.from(
2650
+ { length },
2651
+ () => alphabet.charAt(Math.floor(Math.random() * len))
2652
+ ).join("");
2653
+ }
2654
+
2655
+ // src/crdts/LiveRegister.ts
2656
+ var LiveRegister = class _LiveRegister extends AbstractCrdt {
2657
+ constructor(data) {
2658
+ super();
2659
+ this._data = data;
2660
+ }
2661
+ get data() {
2662
+ return this._data;
2619
2663
  }
2620
2664
  /** @internal */
2621
- _applyRemoteInsert(op) {
2622
- if (this._pool === void 0) {
2623
- throw new Error("Can't attach child if managed pool is not present");
2624
- }
2625
- const key = asPos(op.parentKey);
2626
- const existingItemIndex = this._indexOfPosition(key);
2627
- if (existingItemIndex !== -1) {
2628
- this._shiftItemPosition(existingItemIndex, key);
2629
- }
2630
- const { newItem, newIndex } = this._createAttachItemAndSort(op, key);
2631
- return {
2632
- modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),
2633
- reverse: []
2634
- };
2665
+ static _deserialize([id, item], _parentToChildren, pool) {
2666
+ const register = new _LiveRegister(item.data);
2667
+ register._attach(id, pool);
2668
+ return register;
2635
2669
  }
2636
2670
  /** @internal */
2637
- _applyInsertAck(op) {
2638
- const existingItem = this._items.find((item) => item._id === op.id);
2639
- const key = asPos(op.parentKey);
2640
- const itemIndexAtPosition = this._indexOfPosition(key);
2641
- if (existingItem) {
2642
- if (existingItem._parentKey === key) {
2643
- return {
2644
- modified: false
2645
- };
2646
- } else {
2647
- const oldPositionIndex = this._items.indexOf(existingItem);
2648
- if (itemIndexAtPosition !== -1) {
2649
- this._shiftItemPosition(itemIndexAtPosition, key);
2650
- }
2651
- existingItem._setParentLink(this, key);
2652
- this._sortItems();
2653
- const newIndex = this._indexOfPosition(key);
2654
- if (newIndex === oldPositionIndex) {
2655
- return { modified: false };
2656
- }
2657
- return {
2658
- modified: makeUpdate(this, [
2659
- moveDelta(oldPositionIndex, newIndex, existingItem)
2660
- ]),
2661
- reverse: []
2662
- };
2663
- }
2664
- } else {
2665
- const orphan = nn(this._pool).getNode(op.id);
2666
- if (orphan && this._implicitlyDeletedItems.has(orphan)) {
2667
- orphan._setParentLink(this, key);
2668
- this._implicitlyDeletedItems.delete(orphan);
2669
- this._insertAndSort(orphan);
2670
- const newIndex = this._indexOfPosition(key);
2671
- return {
2672
- modified: makeUpdate(this, [insertDelta(newIndex, orphan)]),
2673
- reverse: []
2674
- };
2675
- } else {
2676
- if (itemIndexAtPosition !== -1) {
2677
- this._shiftItemPosition(itemIndexAtPosition, key);
2678
- }
2679
- const { newItem, newIndex } = this._createAttachItemAndSort(op, key);
2680
- return {
2681
- modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),
2682
- reverse: []
2683
- };
2684
- }
2671
+ _toOps(parentId, parentKey, pool) {
2672
+ if (this._id === void 0) {
2673
+ throw new Error(
2674
+ "Cannot serialize register if parentId or parentKey is undefined"
2675
+ );
2685
2676
  }
2677
+ return [
2678
+ {
2679
+ type: 8 /* CREATE_REGISTER */,
2680
+ opId: pool?.generateOpId(),
2681
+ id: this._id,
2682
+ parentId,
2683
+ parentKey,
2684
+ data: this.data
2685
+ }
2686
+ ];
2686
2687
  }
2687
2688
  /** @internal */
2688
- _applyInsertUndoRedo(op) {
2689
- const { id, parentKey: key } = op;
2690
- const child = creationOpToLiveNode(op);
2691
- if (this._pool?.getNode(id) !== void 0) {
2692
- return { modified: false };
2693
- }
2694
- child._attach(id, nn(this._pool));
2695
- child._setParentLink(this, key);
2696
- const existingItemIndex = this._indexOfPosition(key);
2697
- let newKey = key;
2698
- if (existingItemIndex !== -1) {
2699
- const before2 = this._items[existingItemIndex]?._parentPos;
2700
- const after2 = this._items[existingItemIndex + 1]?._parentPos;
2701
- newKey = makePosition(before2, after2);
2702
- child._setParentLink(this, newKey);
2689
+ _serialize() {
2690
+ if (this.parent.type !== "HasParent") {
2691
+ throw new Error("Cannot serialize LiveRegister if parent is missing");
2703
2692
  }
2704
- this._insertAndSort(child);
2705
- const newIndex = this._indexOfPosition(newKey);
2706
2693
  return {
2707
- modified: makeUpdate(this, [insertDelta(newIndex, child)]),
2708
- reverse: [{ type: 5 /* DELETE_CRDT */, id }]
2694
+ type: 3 /* REGISTER */,
2695
+ parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
2696
+ parentKey: this.parent.key,
2697
+ data: this.data
2709
2698
  };
2710
2699
  }
2711
2700
  /** @internal */
2712
- _applySetUndoRedo(op) {
2713
- const { id, parentKey: key } = op;
2714
- const child = creationOpToLiveNode(op);
2715
- if (this._pool?.getNode(id) !== void 0) {
2716
- return { modified: false };
2717
- }
2718
- this._unacknowledgedSets.set(key, nn(op.opId));
2719
- const indexOfItemWithSameKey = this._indexOfPosition(key);
2720
- child._attach(id, nn(this._pool));
2721
- child._setParentLink(this, key);
2722
- const newKey = key;
2723
- if (indexOfItemWithSameKey !== -1) {
2724
- const existingItem = this._items[indexOfItemWithSameKey];
2725
- existingItem._detach();
2726
- this._items[indexOfItemWithSameKey] = child;
2727
- const reverse = HACK_addIntentAndDeletedIdToOperation(
2728
- existingItem._toOps(nn(this._id), key, this._pool),
2729
- op.id
2730
- );
2731
- const delta = [setDelta(indexOfItemWithSameKey, child)];
2732
- const deletedDelta = this._detachItemAssociatedToSetOperation(
2733
- op.deletedId
2734
- );
2735
- if (deletedDelta) {
2736
- delta.push(deletedDelta);
2737
- }
2738
- return {
2739
- modified: makeUpdate(this, delta),
2740
- reverse
2741
- };
2742
- } else {
2743
- this._insertAndSort(child);
2744
- this._detachItemAssociatedToSetOperation(op.deletedId);
2745
- const newIndex = this._indexOfPosition(newKey);
2746
- return {
2747
- reverse: [{ type: 5 /* DELETE_CRDT */, id }],
2748
- modified: makeUpdate(this, [insertDelta(newIndex, child)])
2749
- };
2750
- }
2701
+ _attachChild(_op) {
2702
+ throw new Error("Method not implemented.");
2751
2703
  }
2752
2704
  /** @internal */
2753
- _attachChild(op, source) {
2754
- if (this._pool === void 0) {
2755
- throw new Error("Can't attach child if managed pool is not present");
2756
- }
2757
- let result;
2758
- if (op.intent === "set") {
2759
- if (source === 1 /* REMOTE */) {
2760
- result = this._applySetRemote(op);
2761
- } else if (source === 2 /* ACK */) {
2762
- result = this._applySetAck(op);
2763
- } else {
2764
- result = this._applySetUndoRedo(op);
2765
- }
2766
- } else {
2767
- if (source === 1 /* REMOTE */) {
2768
- result = this._applyRemoteInsert(op);
2769
- } else if (source === 2 /* ACK */) {
2770
- result = this._applyInsertAck(op);
2771
- } else {
2772
- result = this._applyInsertUndoRedo(op);
2773
- }
2774
- }
2775
- if (result.modified !== false) {
2776
- this.invalidate();
2777
- }
2778
- return result;
2705
+ _detachChild(_crdt) {
2706
+ throw new Error("Method not implemented.");
2779
2707
  }
2780
2708
  /** @internal */
2781
- _detachChild(child) {
2782
- if (child) {
2783
- const parentKey = nn(child._parentKey);
2784
- const reverse = child._toOps(nn(this._id), parentKey, this._pool);
2785
- const indexToDelete = this._items.indexOf(child);
2786
- if (indexToDelete === -1) {
2787
- return {
2788
- modified: false
2789
- };
2790
- }
2791
- this._items.splice(indexToDelete, 1);
2792
- this.invalidate();
2793
- child._detach();
2794
- return {
2795
- modified: makeUpdate(this, [deleteDelta(indexToDelete)]),
2796
- reverse
2797
- };
2798
- }
2799
- return { modified: false };
2709
+ _apply(op, isLocal) {
2710
+ return super._apply(op, isLocal);
2800
2711
  }
2801
2712
  /** @internal */
2802
- _applySetChildKeyRemote(newKey, child) {
2803
- if (this._implicitlyDeletedItems.has(child)) {
2804
- this._implicitlyDeletedItems.delete(child);
2805
- child._setParentLink(this, newKey);
2806
- this._insertAndSort(child);
2807
- const newIndex = this._items.indexOf(child);
2808
- return {
2809
- modified: makeUpdate(this, [insertDelta(newIndex, child)]),
2810
- reverse: []
2811
- };
2812
- }
2813
- const previousKey = child._parentKey;
2814
- if (newKey === previousKey) {
2815
- return {
2816
- modified: false
2817
- };
2818
- }
2819
- const existingItemIndex = this._indexOfPosition(newKey);
2820
- if (existingItemIndex === -1) {
2821
- const previousIndex = this._items.indexOf(child);
2822
- child._setParentLink(this, newKey);
2823
- this._sortItems();
2824
- const newIndex = this._items.indexOf(child);
2825
- if (newIndex === previousIndex) {
2826
- return {
2827
- modified: false
2828
- };
2829
- }
2830
- return {
2831
- modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
2832
- reverse: []
2833
- };
2834
- } else {
2835
- this._items[existingItemIndex]._setParentLink(
2836
- this,
2837
- makePosition(newKey, this._items[existingItemIndex + 1]?._parentPos)
2838
- );
2839
- const previousIndex = this._items.indexOf(child);
2840
- child._setParentLink(this, newKey);
2841
- this._sortItems();
2842
- const newIndex = this._items.indexOf(child);
2843
- if (newIndex === previousIndex) {
2844
- return {
2845
- modified: false
2846
- };
2847
- }
2848
- return {
2849
- modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
2850
- reverse: []
2851
- };
2713
+ _toTreeNode(key) {
2714
+ return {
2715
+ type: "Json",
2716
+ id: this._id ?? nanoid(),
2717
+ key,
2718
+ payload: this._data
2719
+ };
2720
+ }
2721
+ /** @internal */
2722
+ _toImmutable() {
2723
+ return this._data;
2724
+ }
2725
+ clone() {
2726
+ return deepClone(this.data);
2727
+ }
2728
+ };
2729
+
2730
+ // src/crdts/LiveList.ts
2731
+ function compareNodePosition(itemA, itemB) {
2732
+ const posA = itemA._parentPos;
2733
+ const posB = itemB._parentPos;
2734
+ return posA === posB ? 0 : posA < posB ? -1 : 1;
2735
+ }
2736
+ var LiveList = class _LiveList extends AbstractCrdt {
2737
+ constructor(items = []) {
2738
+ super();
2739
+ this._items = [];
2740
+ this._implicitlyDeletedItems = /* @__PURE__ */ new WeakSet();
2741
+ this._unacknowledgedSets = /* @__PURE__ */ new Map();
2742
+ let position = void 0;
2743
+ for (const item of items) {
2744
+ const newPosition = makePosition(position);
2745
+ const node = lsonToLiveNode(item);
2746
+ node._setParentLink(this, newPosition);
2747
+ this._items.push(node);
2748
+ position = newPosition;
2852
2749
  }
2853
2750
  }
2854
2751
  /** @internal */
2855
- _applySetChildKeyAck(newKey, child) {
2856
- const previousKey = nn(child._parentKey);
2857
- if (this._implicitlyDeletedItems.has(child)) {
2858
- const existingItemIndex = this._indexOfPosition(newKey);
2859
- this._implicitlyDeletedItems.delete(child);
2860
- if (existingItemIndex !== -1) {
2861
- this._items[existingItemIndex]._setParentLink(
2862
- this,
2863
- makePosition(newKey, this._items[existingItemIndex + 1]?._parentPos)
2752
+ static _deserialize([id], parentToChildren, pool) {
2753
+ const list = new _LiveList();
2754
+ list._attach(id, pool);
2755
+ const children = parentToChildren.get(id);
2756
+ if (children === void 0) {
2757
+ return list;
2758
+ }
2759
+ for (const [id2, crdt] of children) {
2760
+ const child = deserialize([id2, crdt], parentToChildren, pool);
2761
+ child._setParentLink(list, crdt.parentKey);
2762
+ list._insertAndSort(child);
2763
+ }
2764
+ return list;
2765
+ }
2766
+ /**
2767
+ * @internal
2768
+ * This function assumes that the resulting ops will be sent to the server if they have an 'opId'
2769
+ * so we mutate _unacknowledgedSets to avoid potential flickering
2770
+ * https://github.com/liveblocks/liveblocks/pull/1177
2771
+ *
2772
+ * This is quite unintuitive and should disappear as soon as
2773
+ * we introduce an explicit LiveList.Set operation
2774
+ */
2775
+ _toOps(parentId, parentKey, pool) {
2776
+ if (this._id === void 0) {
2777
+ throw new Error("Cannot serialize item is not attached");
2778
+ }
2779
+ const ops = [];
2780
+ const op = {
2781
+ id: this._id,
2782
+ opId: pool?.generateOpId(),
2783
+ type: 2 /* CREATE_LIST */,
2784
+ parentId,
2785
+ parentKey
2786
+ };
2787
+ ops.push(op);
2788
+ for (const item of this._items) {
2789
+ const parentKey2 = item._getParentKeyOrThrow();
2790
+ const childOps = HACK_addIntentAndDeletedIdToOperation(
2791
+ item._toOps(this._id, parentKey2, pool),
2792
+ void 0
2793
+ );
2794
+ const childOpId = childOps[0].opId;
2795
+ if (childOpId !== void 0) {
2796
+ this._unacknowledgedSets.set(parentKey2, childOpId);
2797
+ }
2798
+ ops.push(...childOps);
2799
+ }
2800
+ return ops;
2801
+ }
2802
+ /**
2803
+ * @internal
2804
+ *
2805
+ * Adds a new item into the sorted list, in the correct position.
2806
+ */
2807
+ _insertAndSort(item) {
2808
+ this._items.push(item);
2809
+ this._sortItems();
2810
+ }
2811
+ /** @internal */
2812
+ _sortItems() {
2813
+ this._items.sort(compareNodePosition);
2814
+ this.invalidate();
2815
+ }
2816
+ /** @internal */
2817
+ _indexOfPosition(position) {
2818
+ return this._items.findIndex(
2819
+ (item) => item._getParentKeyOrThrow() === position
2820
+ );
2821
+ }
2822
+ /** @internal */
2823
+ _attach(id, pool) {
2824
+ super._attach(id, pool);
2825
+ for (const item of this._items) {
2826
+ item._attach(pool.generateId(), pool);
2827
+ }
2828
+ }
2829
+ /** @internal */
2830
+ _detach() {
2831
+ super._detach();
2832
+ for (const item of this._items) {
2833
+ item._detach();
2834
+ }
2835
+ }
2836
+ /** @internal */
2837
+ _applySetRemote(op) {
2838
+ if (this._pool === void 0) {
2839
+ throw new Error("Can't attach child if managed pool is not present");
2840
+ }
2841
+ const { id, parentKey: key } = op;
2842
+ const child = creationOpToLiveNode(op);
2843
+ child._attach(id, this._pool);
2844
+ child._setParentLink(this, key);
2845
+ const deletedId = op.deletedId;
2846
+ const indexOfItemWithSamePosition = this._indexOfPosition(key);
2847
+ if (indexOfItemWithSamePosition !== -1) {
2848
+ const itemWithSamePosition = this._items[indexOfItemWithSamePosition];
2849
+ if (itemWithSamePosition._id === deletedId) {
2850
+ itemWithSamePosition._detach();
2851
+ this._items[indexOfItemWithSamePosition] = child;
2852
+ return {
2853
+ modified: makeUpdate(this, [
2854
+ setDelta(indexOfItemWithSamePosition, child)
2855
+ ]),
2856
+ reverse: []
2857
+ };
2858
+ } else {
2859
+ this._implicitlyDeletedItems.add(itemWithSamePosition);
2860
+ this._items[indexOfItemWithSamePosition] = child;
2861
+ const delta = [
2862
+ setDelta(indexOfItemWithSamePosition, child)
2863
+ ];
2864
+ const deleteDelta2 = this._detachItemAssociatedToSetOperation(
2865
+ op.deletedId
2864
2866
  );
2867
+ if (deleteDelta2) {
2868
+ delta.push(deleteDelta2);
2869
+ }
2870
+ return {
2871
+ modified: makeUpdate(this, delta),
2872
+ reverse: []
2873
+ };
2874
+ }
2875
+ } else {
2876
+ const updates = [];
2877
+ const deleteDelta2 = this._detachItemAssociatedToSetOperation(
2878
+ op.deletedId
2879
+ );
2880
+ if (deleteDelta2) {
2881
+ updates.push(deleteDelta2);
2865
2882
  }
2866
- child._setParentLink(this, newKey);
2867
2883
  this._insertAndSort(child);
2884
+ updates.push(insertDelta(this._indexOfPosition(key), child));
2868
2885
  return {
2869
- modified: false
2886
+ reverse: [],
2887
+ modified: makeUpdate(this, updates)
2870
2888
  };
2871
- } else {
2872
- if (newKey === previousKey) {
2889
+ }
2890
+ }
2891
+ /** @internal */
2892
+ _applySetAck(op) {
2893
+ if (this._pool === void 0) {
2894
+ throw new Error("Can't attach child if managed pool is not present");
2895
+ }
2896
+ const delta = [];
2897
+ const deletedDelta = this._detachItemAssociatedToSetOperation(op.deletedId);
2898
+ if (deletedDelta) {
2899
+ delta.push(deletedDelta);
2900
+ }
2901
+ const unacknowledgedOpId = this._unacknowledgedSets.get(op.parentKey);
2902
+ if (unacknowledgedOpId !== void 0) {
2903
+ if (unacknowledgedOpId !== op.opId) {
2904
+ return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
2905
+ } else {
2906
+ this._unacknowledgedSets.delete(op.parentKey);
2907
+ }
2908
+ }
2909
+ const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);
2910
+ const existingItem = this._items.find((item) => item._id === op.id);
2911
+ if (existingItem !== void 0) {
2912
+ if (existingItem._parentKey === op.parentKey) {
2873
2913
  return {
2874
- modified: false
2914
+ modified: delta.length > 0 ? makeUpdate(this, delta) : false,
2915
+ reverse: []
2875
2916
  };
2876
2917
  }
2877
- const previousIndex = this._items.indexOf(child);
2878
- const existingItemIndex = this._indexOfPosition(newKey);
2879
- if (existingItemIndex !== -1) {
2880
- this._items[existingItemIndex]._setParentLink(
2881
- this,
2882
- makePosition(newKey, this._items[existingItemIndex + 1]?._parentPos)
2918
+ if (indexOfItemWithSamePosition !== -1) {
2919
+ this._implicitlyDeletedItems.add(
2920
+ this._items[indexOfItemWithSamePosition]
2883
2921
  );
2922
+ this._items.splice(indexOfItemWithSamePosition, 1);
2923
+ delta.push(deleteDelta(indexOfItemWithSamePosition));
2884
2924
  }
2885
- child._setParentLink(this, newKey);
2925
+ const previousIndex = this._items.indexOf(existingItem);
2926
+ existingItem._setParentLink(this, op.parentKey);
2886
2927
  this._sortItems();
2887
- const newIndex = this._items.indexOf(child);
2888
- if (previousIndex === newIndex) {
2928
+ const newIndex = this._items.indexOf(existingItem);
2929
+ if (newIndex !== previousIndex) {
2930
+ delta.push(moveDelta(previousIndex, newIndex, existingItem));
2931
+ }
2932
+ return {
2933
+ modified: delta.length > 0 ? makeUpdate(this, delta) : false,
2934
+ reverse: []
2935
+ };
2936
+ } else {
2937
+ const orphan = this._pool.getNode(op.id);
2938
+ if (orphan && this._implicitlyDeletedItems.has(orphan)) {
2939
+ orphan._setParentLink(this, op.parentKey);
2940
+ this._implicitlyDeletedItems.delete(orphan);
2941
+ this._insertAndSort(orphan);
2942
+ const recreatedItemIndex = this._items.indexOf(orphan);
2889
2943
  return {
2890
- modified: false
2944
+ modified: makeUpdate(this, [
2945
+ // If there is an item at this position, update is a set, else it's an insert
2946
+ indexOfItemWithSamePosition === -1 ? insertDelta(recreatedItemIndex, orphan) : setDelta(recreatedItemIndex, orphan),
2947
+ ...delta
2948
+ ]),
2949
+ reverse: []
2891
2950
  };
2892
2951
  } else {
2952
+ if (indexOfItemWithSamePosition !== -1) {
2953
+ this._items.splice(indexOfItemWithSamePosition, 1);
2954
+ }
2955
+ const { newItem, newIndex } = this._createAttachItemAndSort(
2956
+ op,
2957
+ op.parentKey
2958
+ );
2893
2959
  return {
2894
2960
  modified: makeUpdate(this, [
2895
- moveDelta(previousIndex, newIndex, child)
2961
+ // If there is an item at this position, update is a set, else it's an insert
2962
+ indexOfItemWithSamePosition === -1 ? insertDelta(newIndex, newItem) : setDelta(newIndex, newItem),
2963
+ ...delta
2896
2964
  ]),
2897
2965
  reverse: []
2898
2966
  };
2899
2967
  }
2900
2968
  }
2901
2969
  }
2970
+ /**
2971
+ * Returns the update delta of the deletion or null
2972
+ * @internal
2973
+ */
2974
+ _detachItemAssociatedToSetOperation(deletedId) {
2975
+ if (deletedId === void 0 || this._pool === void 0) {
2976
+ return null;
2977
+ }
2978
+ const deletedItem = this._pool.getNode(deletedId);
2979
+ if (deletedItem === void 0) {
2980
+ return null;
2981
+ }
2982
+ const result = this._detachChild(deletedItem);
2983
+ if (result.modified === false) {
2984
+ return null;
2985
+ }
2986
+ return result.modified.updates[0];
2987
+ }
2902
2988
  /** @internal */
2903
- _applySetChildKeyUndoRedo(newKey, child) {
2904
- const previousKey = nn(child._parentKey);
2905
- const previousIndex = this._items.indexOf(child);
2906
- const existingItemIndex = this._indexOfPosition(newKey);
2989
+ _applyRemoteInsert(op) {
2990
+ if (this._pool === void 0) {
2991
+ throw new Error("Can't attach child if managed pool is not present");
2992
+ }
2993
+ const key = asPos(op.parentKey);
2994
+ const existingItemIndex = this._indexOfPosition(key);
2907
2995
  if (existingItemIndex !== -1) {
2908
- this._items[existingItemIndex]._setParentLink(
2909
- this,
2910
- makePosition(newKey, this._items[existingItemIndex + 1]?._parentPos)
2911
- );
2912
- }
2913
- child._setParentLink(this, newKey);
2914
- this._sortItems();
2915
- const newIndex = this._items.indexOf(child);
2916
- if (previousIndex === newIndex) {
2917
- return {
2918
- modified: false
2919
- };
2996
+ this._shiftItemPosition(existingItemIndex, key);
2920
2997
  }
2998
+ const { newItem, newIndex } = this._createAttachItemAndSort(op, key);
2921
2999
  return {
2922
- modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
2923
- reverse: [
2924
- {
2925
- type: 1 /* SET_PARENT_KEY */,
2926
- id: nn(child._id),
2927
- parentKey: previousKey
2928
- }
2929
- ]
3000
+ modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),
3001
+ reverse: []
2930
3002
  };
2931
3003
  }
2932
3004
  /** @internal */
2933
- _setChildKey(newKey, child, source) {
2934
- if (source === 1 /* REMOTE */) {
2935
- return this._applySetChildKeyRemote(newKey, child);
2936
- } else if (source === 2 /* ACK */) {
2937
- return this._applySetChildKeyAck(newKey, child);
3005
+ _applyInsertAck(op) {
3006
+ const existingItem = this._items.find((item) => item._id === op.id);
3007
+ const key = asPos(op.parentKey);
3008
+ const itemIndexAtPosition = this._indexOfPosition(key);
3009
+ if (existingItem) {
3010
+ if (existingItem._parentKey === key) {
3011
+ return {
3012
+ modified: false
3013
+ };
3014
+ } else {
3015
+ const oldPositionIndex = this._items.indexOf(existingItem);
3016
+ if (itemIndexAtPosition !== -1) {
3017
+ this._shiftItemPosition(itemIndexAtPosition, key);
3018
+ }
3019
+ existingItem._setParentLink(this, key);
3020
+ this._sortItems();
3021
+ const newIndex = this._indexOfPosition(key);
3022
+ if (newIndex === oldPositionIndex) {
3023
+ return { modified: false };
3024
+ }
3025
+ return {
3026
+ modified: makeUpdate(this, [
3027
+ moveDelta(oldPositionIndex, newIndex, existingItem)
3028
+ ]),
3029
+ reverse: []
3030
+ };
3031
+ }
2938
3032
  } else {
2939
- return this._applySetChildKeyUndoRedo(newKey, child);
3033
+ const orphan = nn(this._pool).getNode(op.id);
3034
+ if (orphan && this._implicitlyDeletedItems.has(orphan)) {
3035
+ orphan._setParentLink(this, key);
3036
+ this._implicitlyDeletedItems.delete(orphan);
3037
+ this._insertAndSort(orphan);
3038
+ const newIndex = this._indexOfPosition(key);
3039
+ return {
3040
+ modified: makeUpdate(this, [insertDelta(newIndex, orphan)]),
3041
+ reverse: []
3042
+ };
3043
+ } else {
3044
+ if (itemIndexAtPosition !== -1) {
3045
+ this._shiftItemPosition(itemIndexAtPosition, key);
3046
+ }
3047
+ const { newItem, newIndex } = this._createAttachItemAndSort(op, key);
3048
+ return {
3049
+ modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),
3050
+ reverse: []
3051
+ };
3052
+ }
2940
3053
  }
2941
3054
  }
2942
3055
  /** @internal */
2943
- _apply(op, isLocal) {
2944
- return super._apply(op, isLocal);
2945
- }
2946
- /** @internal */
2947
- _serialize() {
2948
- if (this.parent.type !== "HasParent") {
2949
- throw new Error("Cannot serialize LiveList if parent is missing");
3056
+ _applyInsertUndoRedo(op) {
3057
+ const { id, parentKey: key } = op;
3058
+ const child = creationOpToLiveNode(op);
3059
+ if (this._pool?.getNode(id) !== void 0) {
3060
+ return { modified: false };
3061
+ }
3062
+ child._attach(id, nn(this._pool));
3063
+ child._setParentLink(this, key);
3064
+ const existingItemIndex = this._indexOfPosition(key);
3065
+ let newKey = key;
3066
+ if (existingItemIndex !== -1) {
3067
+ const before2 = this._items[existingItemIndex]?._parentPos;
3068
+ const after2 = this._items[existingItemIndex + 1]?._parentPos;
3069
+ newKey = makePosition(before2, after2);
3070
+ child._setParentLink(this, newKey);
2950
3071
  }
3072
+ this._insertAndSort(child);
3073
+ const newIndex = this._indexOfPosition(newKey);
2951
3074
  return {
2952
- type: 1 /* LIST */,
2953
- parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
2954
- parentKey: this.parent.key
3075
+ modified: makeUpdate(this, [insertDelta(newIndex, child)]),
3076
+ reverse: [{ type: 5 /* DELETE_CRDT */, id }]
2955
3077
  };
2956
3078
  }
2957
- /**
2958
- * Returns the number of elements.
2959
- */
2960
- get length() {
2961
- return this._items.length;
2962
- }
2963
- /**
2964
- * Adds one element to the end of the LiveList.
2965
- * @param element The element to add to the end of the LiveList.
2966
- */
2967
- push(element) {
2968
- this._pool?.assertStorageIsWritable();
2969
- return this.insert(element, this.length);
2970
- }
2971
- /**
2972
- * Inserts one element at a specified index.
2973
- * @param element The element to insert.
2974
- * @param index The index at which you want to insert the element.
2975
- */
2976
- insert(element, index) {
2977
- this._pool?.assertStorageIsWritable();
2978
- if (index < 0 || index > this._items.length) {
2979
- throw new Error(
2980
- `Cannot insert list item at index "${index}". index should be between 0 and ${this._items.length}`
2981
- );
3079
+ /** @internal */
3080
+ _applySetUndoRedo(op) {
3081
+ const { id, parentKey: key } = op;
3082
+ const child = creationOpToLiveNode(op);
3083
+ if (this._pool?.getNode(id) !== void 0) {
3084
+ return { modified: false };
2982
3085
  }
2983
- const before2 = this._items[index - 1] ? this._items[index - 1]._parentPos : void 0;
2984
- const after2 = this._items[index] ? this._items[index]._parentPos : void 0;
2985
- const position = makePosition(before2, after2);
2986
- const value = lsonToLiveNode(element);
2987
- value._setParentLink(this, position);
2988
- this._insertAndSort(value);
2989
- if (this._pool && this._id) {
2990
- const id = this._pool.generateId();
2991
- value._attach(id, this._pool);
2992
- this._pool.dispatch(
2993
- value._toOps(this._id, position, this._pool),
2994
- [{ type: 5 /* DELETE_CRDT */, id }],
2995
- /* @__PURE__ */ new Map([
2996
- [this._id, makeUpdate(this, [insertDelta(index, value)])]
2997
- ])
3086
+ this._unacknowledgedSets.set(key, nn(op.opId));
3087
+ const indexOfItemWithSameKey = this._indexOfPosition(key);
3088
+ child._attach(id, nn(this._pool));
3089
+ child._setParentLink(this, key);
3090
+ const newKey = key;
3091
+ if (indexOfItemWithSameKey !== -1) {
3092
+ const existingItem = this._items[indexOfItemWithSameKey];
3093
+ existingItem._detach();
3094
+ this._items[indexOfItemWithSameKey] = child;
3095
+ const reverse = HACK_addIntentAndDeletedIdToOperation(
3096
+ existingItem._toOps(nn(this._id), key, this._pool),
3097
+ op.id
2998
3098
  );
2999
- }
3000
- }
3001
- /**
3002
- * Move one element from one index to another.
3003
- * @param index The index of the element to move
3004
- * @param targetIndex The index where the element should be after moving.
3005
- */
3006
- move(index, targetIndex) {
3007
- this._pool?.assertStorageIsWritable();
3008
- if (targetIndex < 0) {
3009
- throw new Error("targetIndex cannot be less than 0");
3010
- }
3011
- if (targetIndex >= this._items.length) {
3012
- throw new Error(
3013
- "targetIndex cannot be greater or equal than the list length"
3099
+ const delta = [setDelta(indexOfItemWithSameKey, child)];
3100
+ const deletedDelta = this._detachItemAssociatedToSetOperation(
3101
+ op.deletedId
3014
3102
  );
3103
+ if (deletedDelta) {
3104
+ delta.push(deletedDelta);
3105
+ }
3106
+ return {
3107
+ modified: makeUpdate(this, delta),
3108
+ reverse
3109
+ };
3110
+ } else {
3111
+ this._insertAndSort(child);
3112
+ this._detachItemAssociatedToSetOperation(op.deletedId);
3113
+ const newIndex = this._indexOfPosition(newKey);
3114
+ return {
3115
+ reverse: [{ type: 5 /* DELETE_CRDT */, id }],
3116
+ modified: makeUpdate(this, [insertDelta(newIndex, child)])
3117
+ };
3015
3118
  }
3016
- if (index < 0) {
3017
- throw new Error("index cannot be less than 0");
3018
- }
3019
- if (index >= this._items.length) {
3020
- throw new Error("index cannot be greater or equal than the list length");
3119
+ }
3120
+ /** @internal */
3121
+ _attachChild(op, source) {
3122
+ if (this._pool === void 0) {
3123
+ throw new Error("Can't attach child if managed pool is not present");
3021
3124
  }
3022
- let beforePosition = null;
3023
- let afterPosition = null;
3024
- if (index < targetIndex) {
3025
- afterPosition = targetIndex === this._items.length - 1 ? void 0 : this._items[targetIndex + 1]._parentPos;
3026
- beforePosition = this._items[targetIndex]._parentPos;
3125
+ let result;
3126
+ if (op.intent === "set") {
3127
+ if (source === 1 /* REMOTE */) {
3128
+ result = this._applySetRemote(op);
3129
+ } else if (source === 2 /* ACK */) {
3130
+ result = this._applySetAck(op);
3131
+ } else {
3132
+ result = this._applySetUndoRedo(op);
3133
+ }
3027
3134
  } else {
3028
- afterPosition = this._items[targetIndex]._parentPos;
3029
- beforePosition = targetIndex === 0 ? void 0 : this._items[targetIndex - 1]._parentPos;
3135
+ if (source === 1 /* REMOTE */) {
3136
+ result = this._applyRemoteInsert(op);
3137
+ } else if (source === 2 /* ACK */) {
3138
+ result = this._applyInsertAck(op);
3139
+ } else {
3140
+ result = this._applyInsertUndoRedo(op);
3141
+ }
3030
3142
  }
3031
- const position = makePosition(beforePosition, afterPosition);
3032
- const item = this._items[index];
3033
- const previousPosition = item._getParentKeyOrThrow();
3034
- item._setParentLink(this, position);
3035
- this._sortItems();
3036
- if (this._pool && this._id) {
3037
- const storageUpdates = /* @__PURE__ */ new Map([
3038
- [this._id, makeUpdate(this, [moveDelta(index, targetIndex, item)])]
3039
- ]);
3040
- this._pool.dispatch(
3041
- [
3042
- {
3043
- type: 1 /* SET_PARENT_KEY */,
3044
- id: nn(item._id),
3045
- opId: this._pool.generateOpId(),
3046
- parentKey: position
3047
- }
3048
- ],
3049
- [
3050
- {
3051
- type: 1 /* SET_PARENT_KEY */,
3052
- id: nn(item._id),
3053
- parentKey: previousPosition
3054
- }
3055
- ],
3056
- storageUpdates
3057
- );
3143
+ if (result.modified !== false) {
3144
+ this.invalidate();
3058
3145
  }
3146
+ return result;
3059
3147
  }
3060
- /**
3061
- * Deletes an element at the specified index
3062
- * @param index The index of the element to delete
3063
- */
3064
- delete(index) {
3065
- this._pool?.assertStorageIsWritable();
3066
- if (index < 0 || index >= this._items.length) {
3067
- throw new Error(
3068
- `Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length - 1}`
3148
+ /** @internal */
3149
+ _detachChild(child) {
3150
+ if (child) {
3151
+ const parentKey = nn(child._parentKey);
3152
+ const reverse = child._toOps(nn(this._id), parentKey, this._pool);
3153
+ const indexToDelete = this._items.indexOf(child);
3154
+ if (indexToDelete === -1) {
3155
+ return {
3156
+ modified: false
3157
+ };
3158
+ }
3159
+ this._items.splice(indexToDelete, 1);
3160
+ this.invalidate();
3161
+ child._detach();
3162
+ return {
3163
+ modified: makeUpdate(this, [deleteDelta(indexToDelete)]),
3164
+ reverse
3165
+ };
3166
+ }
3167
+ return { modified: false };
3168
+ }
3169
+ /** @internal */
3170
+ _applySetChildKeyRemote(newKey, child) {
3171
+ if (this._implicitlyDeletedItems.has(child)) {
3172
+ this._implicitlyDeletedItems.delete(child);
3173
+ child._setParentLink(this, newKey);
3174
+ this._insertAndSort(child);
3175
+ const newIndex = this._items.indexOf(child);
3176
+ return {
3177
+ modified: makeUpdate(this, [insertDelta(newIndex, child)]),
3178
+ reverse: []
3179
+ };
3180
+ }
3181
+ const previousKey = child._parentKey;
3182
+ if (newKey === previousKey) {
3183
+ return {
3184
+ modified: false
3185
+ };
3186
+ }
3187
+ const existingItemIndex = this._indexOfPosition(newKey);
3188
+ if (existingItemIndex === -1) {
3189
+ const previousIndex = this._items.indexOf(child);
3190
+ child._setParentLink(this, newKey);
3191
+ this._sortItems();
3192
+ const newIndex = this._items.indexOf(child);
3193
+ if (newIndex === previousIndex) {
3194
+ return {
3195
+ modified: false
3196
+ };
3197
+ }
3198
+ return {
3199
+ modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
3200
+ reverse: []
3201
+ };
3202
+ } else {
3203
+ this._items[existingItemIndex]._setParentLink(
3204
+ this,
3205
+ makePosition(newKey, this._items[existingItemIndex + 1]?._parentPos)
3069
3206
  );
3207
+ const previousIndex = this._items.indexOf(child);
3208
+ child._setParentLink(this, newKey);
3209
+ this._sortItems();
3210
+ const newIndex = this._items.indexOf(child);
3211
+ if (newIndex === previousIndex) {
3212
+ return {
3213
+ modified: false
3214
+ };
3215
+ }
3216
+ return {
3217
+ modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
3218
+ reverse: []
3219
+ };
3070
3220
  }
3071
- const item = this._items[index];
3072
- item._detach();
3073
- this._items.splice(index, 1);
3074
- this.invalidate();
3075
- if (this._pool) {
3076
- const childRecordId = item._id;
3077
- if (childRecordId) {
3078
- const storageUpdates = /* @__PURE__ */ new Map();
3079
- storageUpdates.set(
3080
- nn(this._id),
3081
- makeUpdate(this, [deleteDelta(index)])
3221
+ }
3222
+ /** @internal */
3223
+ _applySetChildKeyAck(newKey, child) {
3224
+ const previousKey = nn(child._parentKey);
3225
+ if (this._implicitlyDeletedItems.has(child)) {
3226
+ const existingItemIndex = this._indexOfPosition(newKey);
3227
+ this._implicitlyDeletedItems.delete(child);
3228
+ if (existingItemIndex !== -1) {
3229
+ this._items[existingItemIndex]._setParentLink(
3230
+ this,
3231
+ makePosition(newKey, this._items[existingItemIndex + 1]?._parentPos)
3082
3232
  );
3083
- this._pool.dispatch(
3084
- [
3085
- {
3086
- id: childRecordId,
3087
- opId: this._pool.generateOpId(),
3088
- type: 5 /* DELETE_CRDT */
3089
- }
3090
- ],
3091
- item._toOps(nn(this._id), item._getParentKeyOrThrow()),
3092
- storageUpdates
3233
+ }
3234
+ child._setParentLink(this, newKey);
3235
+ this._insertAndSort(child);
3236
+ return {
3237
+ modified: false
3238
+ };
3239
+ } else {
3240
+ if (newKey === previousKey) {
3241
+ return {
3242
+ modified: false
3243
+ };
3244
+ }
3245
+ const previousIndex = this._items.indexOf(child);
3246
+ const existingItemIndex = this._indexOfPosition(newKey);
3247
+ if (existingItemIndex !== -1) {
3248
+ this._items[existingItemIndex]._setParentLink(
3249
+ this,
3250
+ makePosition(newKey, this._items[existingItemIndex + 1]?._parentPos)
3093
3251
  );
3094
3252
  }
3253
+ child._setParentLink(this, newKey);
3254
+ this._sortItems();
3255
+ const newIndex = this._items.indexOf(child);
3256
+ if (previousIndex === newIndex) {
3257
+ return {
3258
+ modified: false
3259
+ };
3260
+ } else {
3261
+ return {
3262
+ modified: makeUpdate(this, [
3263
+ moveDelta(previousIndex, newIndex, child)
3264
+ ]),
3265
+ reverse: []
3266
+ };
3267
+ }
3095
3268
  }
3096
3269
  }
3097
- clear() {
3098
- this._pool?.assertStorageIsWritable();
3099
- if (this._pool) {
3100
- const ops = [];
3101
- const reverseOps = [];
3102
- const updateDelta = [];
3103
- for (const item of this._items) {
3104
- item._detach();
3105
- const childId = item._id;
3106
- if (childId) {
3107
- ops.push({
3108
- type: 5 /* DELETE_CRDT */,
3109
- id: childId,
3110
- opId: this._pool.generateOpId()
3111
- });
3112
- reverseOps.push(
3113
- ...item._toOps(nn(this._id), item._getParentKeyOrThrow())
3114
- );
3115
- updateDelta.push(deleteDelta(0));
3270
+ /** @internal */
3271
+ _applySetChildKeyUndoRedo(newKey, child) {
3272
+ const previousKey = nn(child._parentKey);
3273
+ const previousIndex = this._items.indexOf(child);
3274
+ const existingItemIndex = this._indexOfPosition(newKey);
3275
+ if (existingItemIndex !== -1) {
3276
+ this._items[existingItemIndex]._setParentLink(
3277
+ this,
3278
+ makePosition(newKey, this._items[existingItemIndex + 1]?._parentPos)
3279
+ );
3280
+ }
3281
+ child._setParentLink(this, newKey);
3282
+ this._sortItems();
3283
+ const newIndex = this._items.indexOf(child);
3284
+ if (previousIndex === newIndex) {
3285
+ return {
3286
+ modified: false
3287
+ };
3288
+ }
3289
+ return {
3290
+ modified: makeUpdate(this, [moveDelta(previousIndex, newIndex, child)]),
3291
+ reverse: [
3292
+ {
3293
+ type: 1 /* SET_PARENT_KEY */,
3294
+ id: nn(child._id),
3295
+ parentKey: previousKey
3116
3296
  }
3117
- }
3118
- this._items = [];
3119
- this.invalidate();
3120
- const storageUpdates = /* @__PURE__ */ new Map();
3121
- storageUpdates.set(nn(this._id), makeUpdate(this, updateDelta));
3122
- this._pool.dispatch(ops, reverseOps, storageUpdates);
3297
+ ]
3298
+ };
3299
+ }
3300
+ /** @internal */
3301
+ _setChildKey(newKey, child, source) {
3302
+ if (source === 1 /* REMOTE */) {
3303
+ return this._applySetChildKeyRemote(newKey, child);
3304
+ } else if (source === 2 /* ACK */) {
3305
+ return this._applySetChildKeyAck(newKey, child);
3123
3306
  } else {
3124
- for (const item of this._items) {
3125
- item._detach();
3126
- }
3127
- this._items = [];
3128
- this.invalidate();
3307
+ return this._applySetChildKeyUndoRedo(newKey, child);
3129
3308
  }
3130
3309
  }
3131
- set(index, item) {
3310
+ /** @internal */
3311
+ _apply(op, isLocal) {
3312
+ return super._apply(op, isLocal);
3313
+ }
3314
+ /** @internal */
3315
+ _serialize() {
3316
+ if (this.parent.type !== "HasParent") {
3317
+ throw new Error("Cannot serialize LiveList if parent is missing");
3318
+ }
3319
+ return {
3320
+ type: 1 /* LIST */,
3321
+ parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
3322
+ parentKey: this.parent.key
3323
+ };
3324
+ }
3325
+ /**
3326
+ * Returns the number of elements.
3327
+ */
3328
+ get length() {
3329
+ return this._items.length;
3330
+ }
3331
+ /**
3332
+ * Adds one element to the end of the LiveList.
3333
+ * @param element The element to add to the end of the LiveList.
3334
+ */
3335
+ push(element) {
3132
3336
  this._pool?.assertStorageIsWritable();
3133
- if (index < 0 || index >= this._items.length) {
3337
+ return this.insert(element, this.length);
3338
+ }
3339
+ /**
3340
+ * Inserts one element at a specified index.
3341
+ * @param element The element to insert.
3342
+ * @param index The index at which you want to insert the element.
3343
+ */
3344
+ insert(element, index) {
3345
+ this._pool?.assertStorageIsWritable();
3346
+ if (index < 0 || index > this._items.length) {
3134
3347
  throw new Error(
3135
- `Cannot set list item at index "${index}". index should be between 0 and ${this._items.length - 1}`
3348
+ `Cannot insert list item at index "${index}". index should be between 0 and ${this._items.length}`
3136
3349
  );
3137
3350
  }
3138
- const existingItem = this._items[index];
3139
- const position = existingItem._getParentKeyOrThrow();
3140
- const existingId = existingItem._id;
3141
- existingItem._detach();
3142
- const value = lsonToLiveNode(item);
3351
+ const before2 = this._items[index - 1] ? this._items[index - 1]._parentPos : void 0;
3352
+ const after2 = this._items[index] ? this._items[index]._parentPos : void 0;
3353
+ const position = makePosition(before2, after2);
3354
+ const value = lsonToLiveNode(element);
3355
+ value._setParentLink(this, position);
3356
+ this._insertAndSort(value);
3357
+ if (this._pool && this._id) {
3358
+ const id = this._pool.generateId();
3359
+ value._attach(id, this._pool);
3360
+ this._pool.dispatch(
3361
+ value._toOps(this._id, position, this._pool),
3362
+ [{ type: 5 /* DELETE_CRDT */, id }],
3363
+ /* @__PURE__ */ new Map([
3364
+ [this._id, makeUpdate(this, [insertDelta(index, value)])]
3365
+ ])
3366
+ );
3367
+ }
3368
+ }
3369
+ /**
3370
+ * Move one element from one index to another.
3371
+ * @param index The index of the element to move
3372
+ * @param targetIndex The index where the element should be after moving.
3373
+ */
3374
+ move(index, targetIndex) {
3375
+ this._pool?.assertStorageIsWritable();
3376
+ if (targetIndex < 0) {
3377
+ throw new Error("targetIndex cannot be less than 0");
3378
+ }
3379
+ if (targetIndex >= this._items.length) {
3380
+ throw new Error(
3381
+ "targetIndex cannot be greater or equal than the list length"
3382
+ );
3383
+ }
3384
+ if (index < 0) {
3385
+ throw new Error("index cannot be less than 0");
3386
+ }
3387
+ if (index >= this._items.length) {
3388
+ throw new Error("index cannot be greater or equal than the list length");
3389
+ }
3390
+ let beforePosition = null;
3391
+ let afterPosition = null;
3392
+ if (index < targetIndex) {
3393
+ afterPosition = targetIndex === this._items.length - 1 ? void 0 : this._items[targetIndex + 1]._parentPos;
3394
+ beforePosition = this._items[targetIndex]._parentPos;
3395
+ } else {
3396
+ afterPosition = this._items[targetIndex]._parentPos;
3397
+ beforePosition = targetIndex === 0 ? void 0 : this._items[targetIndex - 1]._parentPos;
3398
+ }
3399
+ const position = makePosition(beforePosition, afterPosition);
3400
+ const item = this._items[index];
3401
+ const previousPosition = item._getParentKeyOrThrow();
3402
+ item._setParentLink(this, position);
3403
+ this._sortItems();
3404
+ if (this._pool && this._id) {
3405
+ const storageUpdates = /* @__PURE__ */ new Map([
3406
+ [this._id, makeUpdate(this, [moveDelta(index, targetIndex, item)])]
3407
+ ]);
3408
+ this._pool.dispatch(
3409
+ [
3410
+ {
3411
+ type: 1 /* SET_PARENT_KEY */,
3412
+ id: nn(item._id),
3413
+ opId: this._pool.generateOpId(),
3414
+ parentKey: position
3415
+ }
3416
+ ],
3417
+ [
3418
+ {
3419
+ type: 1 /* SET_PARENT_KEY */,
3420
+ id: nn(item._id),
3421
+ parentKey: previousPosition
3422
+ }
3423
+ ],
3424
+ storageUpdates
3425
+ );
3426
+ }
3427
+ }
3428
+ /**
3429
+ * Deletes an element at the specified index
3430
+ * @param index The index of the element to delete
3431
+ */
3432
+ delete(index) {
3433
+ this._pool?.assertStorageIsWritable();
3434
+ if (index < 0 || index >= this._items.length) {
3435
+ throw new Error(
3436
+ `Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length - 1}`
3437
+ );
3438
+ }
3439
+ const item = this._items[index];
3440
+ item._detach();
3441
+ this._items.splice(index, 1);
3442
+ this.invalidate();
3443
+ if (this._pool) {
3444
+ const childRecordId = item._id;
3445
+ if (childRecordId) {
3446
+ const storageUpdates = /* @__PURE__ */ new Map();
3447
+ storageUpdates.set(
3448
+ nn(this._id),
3449
+ makeUpdate(this, [deleteDelta(index)])
3450
+ );
3451
+ this._pool.dispatch(
3452
+ [
3453
+ {
3454
+ id: childRecordId,
3455
+ opId: this._pool.generateOpId(),
3456
+ type: 5 /* DELETE_CRDT */
3457
+ }
3458
+ ],
3459
+ item._toOps(nn(this._id), item._getParentKeyOrThrow()),
3460
+ storageUpdates
3461
+ );
3462
+ }
3463
+ }
3464
+ }
3465
+ clear() {
3466
+ this._pool?.assertStorageIsWritable();
3467
+ if (this._pool) {
3468
+ const ops = [];
3469
+ const reverseOps = [];
3470
+ const updateDelta = [];
3471
+ for (const item of this._items) {
3472
+ item._detach();
3473
+ const childId = item._id;
3474
+ if (childId) {
3475
+ ops.push({
3476
+ type: 5 /* DELETE_CRDT */,
3477
+ id: childId,
3478
+ opId: this._pool.generateOpId()
3479
+ });
3480
+ reverseOps.push(
3481
+ ...item._toOps(nn(this._id), item._getParentKeyOrThrow())
3482
+ );
3483
+ updateDelta.push(deleteDelta(0));
3484
+ }
3485
+ }
3486
+ this._items = [];
3487
+ this.invalidate();
3488
+ const storageUpdates = /* @__PURE__ */ new Map();
3489
+ storageUpdates.set(nn(this._id), makeUpdate(this, updateDelta));
3490
+ this._pool.dispatch(ops, reverseOps, storageUpdates);
3491
+ } else {
3492
+ for (const item of this._items) {
3493
+ item._detach();
3494
+ }
3495
+ this._items = [];
3496
+ this.invalidate();
3497
+ }
3498
+ }
3499
+ set(index, item) {
3500
+ this._pool?.assertStorageIsWritable();
3501
+ if (index < 0 || index >= this._items.length) {
3502
+ throw new Error(
3503
+ `Cannot set list item at index "${index}". index should be between 0 and ${this._items.length - 1}`
3504
+ );
3505
+ }
3506
+ const existingItem = this._items[index];
3507
+ const position = existingItem._getParentKeyOrThrow();
3508
+ const existingId = existingItem._id;
3509
+ existingItem._detach();
3510
+ const value = lsonToLiveNode(item);
3143
3511
  value._setParentLink(this, position);
3144
3512
  this._items[index] = value;
3145
3513
  this.invalidate();
@@ -4995,9 +5363,13 @@ function createRoom(options, config) {
4995
5363
  ydoc: makeEventSource(),
4996
5364
  comments: makeEventSource()
4997
5365
  };
4998
- async function httpSend(authTokenOrPublicApiKey, roomId, nonce, messages) {
5366
+ async function httpPostToRoom(endpoint, body) {
5367
+ if (!managedSocket.authValue) {
5368
+ throw new Error("Not authorized");
5369
+ }
5370
+ const authTokenOrPublicApiKey = managedSocket.authValue.type === "public" ? managedSocket.authValue.publicApiKey : managedSocket.authValue.token.raw;
4999
5371
  const url = new URL(
5000
- `/v2/c/rooms/${encodeURIComponent(roomId)}/send-message`,
5372
+ `/v2/c/rooms/${encodeURIComponent(config.roomId)}${endpoint}`,
5001
5373
  config.baseUrl
5002
5374
  ).toString();
5003
5375
  const fetcher = config.polyfills?.fetch || /* istanbul ignore next */
@@ -5008,25 +5380,22 @@ function createRoom(options, config) {
5008
5380
  "Content-Type": "application/json",
5009
5381
  Authorization: `Bearer ${authTokenOrPublicApiKey}`
5010
5382
  },
5011
- body: JSON.stringify({ nonce, messages })
5383
+ body: JSON.stringify(body)
5012
5384
  });
5013
5385
  }
5014
5386
  function sendMessages(messages) {
5015
5387
  const serializedPayload = JSON.stringify(messages);
5016
5388
  const nonce = context.dynamicSessionInfo.current?.nonce;
5017
- if (config.unstable_fallbackToHTTP && managedSocket.authValue && nonce) {
5389
+ if (config.unstable_fallbackToHTTP && nonce) {
5018
5390
  const size = new TextEncoder().encode(serializedPayload).length;
5019
5391
  if (size > MAX_SOCKET_MESSAGE_SIZE) {
5020
- void httpSend(
5021
- managedSocket.authValue.type === "public" ? managedSocket.authValue.publicApiKey : managedSocket.authValue.token.raw,
5022
- config.roomId,
5023
- nonce,
5024
- messages
5025
- ).then((resp) => {
5026
- if (!resp.ok && resp.status === 403) {
5027
- managedSocket.reconnect();
5392
+ void httpPostToRoom("/send-message", { nonce, messages }).then(
5393
+ (resp) => {
5394
+ if (!resp.ok && resp.status === 403) {
5395
+ managedSocket.reconnect();
5396
+ }
5028
5397
  }
5029
- });
5398
+ );
5030
5399
  warn(
5031
5400
  "Message was too large for websockets and sent over HTTP instead"
5032
5401
  );
@@ -5539,7 +5908,7 @@ ${Array.from(traces).join("\n\n")}`
5539
5908
  }
5540
5909
  const now = Date.now();
5541
5910
  const elapsedMillis = now - context.buffer.lastFlushedAt;
5542
- if (elapsedMillis > config.throttleDelay) {
5911
+ if (elapsedMillis >= config.throttleDelay) {
5543
5912
  const messagesToFlush = serializeBuffer();
5544
5913
  if (messagesToFlush.length === 0) {
5545
5914
  return;
@@ -5906,1161 +6275,852 @@ function makeClassicSubscribeFn(events) {
5906
6275
  const cb = callback;
5907
6276
  return events.others.subscribe((event) => {
5908
6277
  const { others, ...internalEvent } = event;
5909
- return cb(others, internalEvent);
5910
- });
5911
- }
5912
- case "error":
5913
- return events.error.subscribe(callback);
5914
- case "connection": {
5915
- const cb = callback;
5916
- return events.status.subscribe(
5917
- (status) => cb(newToLegacyStatus(status))
5918
- );
5919
- }
5920
- case "status":
5921
- return events.status.subscribe(callback);
5922
- case "lost-connection":
5923
- return events.lostConnection.subscribe(
5924
- callback
5925
- );
5926
- case "history":
5927
- return events.history.subscribe(callback);
5928
- case "storage-status":
5929
- return events.storageStatus.subscribe(
5930
- callback
5931
- );
5932
- default:
5933
- return assertNever(
5934
- first,
5935
- `"${String(first)}" is not a valid event name`
5936
- );
5937
- }
5938
- }
5939
- if (second === void 0 || typeof first === "function") {
5940
- if (typeof first === "function") {
5941
- const storageCallback = first;
5942
- return events.storage.subscribe(storageCallback);
5943
- } else {
5944
- throw new Error("Please specify a listener callback");
5945
- }
5946
- }
5947
- if (isLiveNode(first)) {
5948
- const node = first;
5949
- if (options?.isDeep) {
5950
- const storageCallback = second;
5951
- return subscribeToLiveStructureDeeply(node, storageCallback);
5952
- } else {
5953
- const nodeCallback = second;
5954
- return subscribeToLiveStructureShallowly(node, nodeCallback);
5955
- }
5956
- }
5957
- throw new Error(
5958
- `${String(first)} is not a value that can be subscribed to.`
5959
- );
5960
- }
5961
- return subscribe;
5962
- }
5963
- function isRoomEventName(value) {
5964
- return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "history" || value === "status" || value === "storage-status" || value === "lost-connection" || value === "connection";
5965
- }
5966
- function makeAuthDelegateForRoom(roomId, authManager) {
5967
- return async () => {
5968
- return authManager.getAuthValue("room:read", roomId);
5969
- };
5970
- }
5971
- function makeCreateSocketDelegateForRoom(roomId, baseUrl, WebSocketPolyfill) {
5972
- return (authValue) => {
5973
- const ws = WebSocketPolyfill ?? (typeof WebSocket === "undefined" ? void 0 : WebSocket);
5974
- if (ws === void 0) {
5975
- throw new StopRetrying(
5976
- "To use Liveblocks client in a non-DOM environment, you need to provide a WebSocket polyfill."
5977
- );
5978
- }
5979
- const url = new URL(baseUrl);
5980
- url.protocol = url.protocol === "http:" ? "ws" : "wss";
5981
- url.pathname = "/v7";
5982
- url.searchParams.set("roomId", roomId);
5983
- if (authValue.type === "secret") {
5984
- url.searchParams.set("tok", authValue.token.raw);
5985
- } else if (authValue.type === "public") {
5986
- url.searchParams.set("pubkey", authValue.publicApiKey);
5987
- } else {
5988
- return assertNever(authValue, "Unhandled case");
5989
- }
5990
- url.searchParams.set("version", PKG_VERSION || "dev");
5991
- return new ws(url.toString());
5992
- };
5993
- }
5994
-
5995
- // src/client.ts
5996
- var MIN_THROTTLE = 16;
5997
- var MAX_THROTTLE = 1e3;
5998
- var DEFAULT_THROTTLE = 100;
5999
- var MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT = 15e3;
6000
- var MIN_LOST_CONNECTION_TIMEOUT = 200;
6001
- var RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT = 1e3;
6002
- var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
6003
- var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
6004
- function getBaseUrlFromClientOptions(clientOptions) {
6005
- if ("liveblocksServer" in clientOptions) {
6006
- throw new Error("Client option no longer supported");
6007
- }
6008
- if (typeof clientOptions.baseUrl === "string" && clientOptions.baseUrl.startsWith("http")) {
6009
- return clientOptions.baseUrl;
6010
- } else {
6011
- return DEFAULT_BASE_URL;
6012
- }
6013
- }
6014
- function createClient(options) {
6015
- const clientOptions = options;
6016
- const throttleDelay = getThrottle(clientOptions.throttle ?? DEFAULT_THROTTLE);
6017
- const lostConnectionTimeout = getLostConnectionTimeout(
6018
- clientOptions.lostConnectionTimeout ?? DEFAULT_LOST_CONNECTION_TIMEOUT
6019
- );
6020
- const backgroundKeepAliveTimeout = getBackgroundKeepAliveTimeout(
6021
- clientOptions.backgroundKeepAliveTimeout
6022
- );
6023
- const authManager = createAuthManager(options);
6024
- const roomsById = /* @__PURE__ */ new Map();
6025
- function teardownRoom(room) {
6026
- unlinkDevTools(room.id);
6027
- roomsById.delete(room.id);
6028
- room.destroy();
6029
- }
6030
- function leaseRoom(info) {
6031
- const leave = () => {
6032
- const self = leave;
6033
- if (!info.unsubs.delete(self)) {
6034
- warn(
6035
- "This leave function was already called. Calling it more than once has no effect."
6036
- );
6037
- } else {
6038
- if (info.unsubs.size === 0) {
6039
- teardownRoom(info.room);
6040
- }
6041
- }
6042
- };
6043
- info.unsubs.add(leave);
6044
- return {
6045
- room: info.room,
6046
- leave
6047
- };
6048
- }
6049
- function enterRoom(roomId, options2) {
6050
- const existing = roomsById.get(roomId);
6051
- if (existing !== void 0) {
6052
- return leaseRoom(existing);
6053
- }
6054
- deprecateIf(
6055
- options2.initialPresence === null || options2.initialPresence === void 0,
6056
- "Please provide an initial presence value for the current user when entering the room."
6057
- );
6058
- const baseUrl = getBaseUrlFromClientOptions(clientOptions);
6059
- const newRoom = createRoom(
6060
- {
6061
- initialPresence: options2.initialPresence ?? {},
6062
- initialStorage: options2.initialStorage
6063
- },
6064
- {
6065
- roomId,
6066
- throttleDelay,
6067
- lostConnectionTimeout,
6068
- backgroundKeepAliveTimeout,
6069
- polyfills: clientOptions.polyfills,
6070
- delegates: clientOptions.mockedDelegates ?? {
6071
- createSocket: makeCreateSocketDelegateForRoom(
6072
- roomId,
6073
- baseUrl,
6074
- clientOptions.polyfills?.WebSocket
6075
- ),
6076
- authenticate: makeAuthDelegateForRoom(roomId, authManager)
6077
- },
6078
- enableDebugLogging: clientOptions.enableDebugLogging,
6079
- unstable_batchedUpdates: options2?.unstable_batchedUpdates,
6080
- baseUrl,
6081
- unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP
6082
- }
6083
- );
6084
- const newRoomInfo = {
6085
- room: newRoom,
6086
- unsubs: /* @__PURE__ */ new Set()
6087
- };
6088
- roomsById.set(roomId, newRoomInfo);
6089
- setupDevTools(() => Array.from(roomsById.keys()));
6090
- linkDevTools(roomId, newRoom);
6091
- const shouldConnect = options2.autoConnect ?? options2.shouldInitiallyConnect ?? true;
6092
- if (shouldConnect) {
6093
- if (typeof atob === "undefined") {
6094
- if (clientOptions.polyfills?.atob === void 0) {
6095
- throw new Error(
6096
- "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"
6097
- );
6098
- }
6099
- global.atob = clientOptions.polyfills.atob;
6100
- }
6101
- newRoom.connect();
6102
- }
6103
- return leaseRoom(newRoomInfo);
6104
- }
6105
- function enter(roomId, options2) {
6106
- const { room, leave: _ } = enterRoom(roomId, options2);
6107
- return room;
6108
- }
6109
- function getRoom(roomId) {
6110
- const room = roomsById.get(roomId)?.room;
6111
- return room ? room : null;
6112
- }
6113
- function forceLeave(roomId) {
6114
- const unsubs = roomsById.get(roomId)?.unsubs ?? /* @__PURE__ */ new Set();
6115
- for (const unsub of unsubs) {
6116
- unsub();
6117
- }
6118
- }
6119
- function logout() {
6120
- authManager.reset();
6121
- for (const { room } of roomsById.values()) {
6122
- if (!isIdle(room.getStatus())) {
6123
- room.reconnect();
6124
- }
6125
- }
6126
- }
6127
- return {
6128
- logout,
6129
- // Old, deprecated APIs
6130
- enter,
6131
- getRoom,
6132
- leave: forceLeave,
6133
- // New, preferred API
6134
- enterRoom
6135
- };
6136
- }
6137
- function checkBounds(option, value, min, max, recommendedMin) {
6138
- if (typeof value !== "number" || value < min || max !== void 0 && value > max) {
6139
- throw new Error(
6140
- max !== void 0 ? `${option} should be between ${recommendedMin ?? min} and ${max}.` : `${option} should be at least ${recommendedMin ?? min}.`
6141
- );
6142
- }
6143
- return value;
6144
- }
6145
- function getBackgroundKeepAliveTimeout(value) {
6146
- if (value === void 0)
6147
- return void 0;
6148
- return checkBounds(
6149
- "backgroundKeepAliveTimeout",
6150
- value,
6151
- MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT
6152
- );
6153
- }
6154
- function getThrottle(value) {
6155
- return checkBounds("throttle", value, MIN_THROTTLE, MAX_THROTTLE);
6156
- }
6157
- function getLostConnectionTimeout(value) {
6158
- return checkBounds(
6159
- "lostConnectionTimeout",
6160
- value,
6161
- MIN_LOST_CONNECTION_TIMEOUT,
6162
- MAX_LOST_CONNECTION_TIMEOUT,
6163
- RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
6164
- );
6165
- }
6166
-
6167
- // src/crdts/utils.ts
6168
- function toPlainLson(lson) {
6169
- if (lson instanceof LiveObject) {
6170
- return {
6171
- liveblocksType: "LiveObject",
6172
- data: Object.fromEntries(
6173
- Object.entries(lson.toObject()).flatMap(
6174
- ([key, value]) => value !== void 0 ? [[key, toPlainLson(value)]] : []
6175
- )
6176
- )
6177
- };
6178
- } else if (lson instanceof LiveMap) {
6179
- return {
6180
- liveblocksType: "LiveMap",
6181
- data: Object.fromEntries(
6182
- [...lson].map(([key, value]) => [key, toPlainLson(value)])
6183
- )
6184
- };
6185
- } else if (lson instanceof LiveList) {
6186
- return {
6187
- liveblocksType: "LiveList",
6188
- data: [...lson].map((item) => toPlainLson(item))
6189
- };
6190
- } else {
6191
- return lson;
6192
- }
6193
- }
6194
-
6195
- // src/immutable.ts
6196
- function lsonObjectToJson(obj) {
6197
- const result = {};
6198
- for (const key in obj) {
6199
- const val = obj[key];
6200
- if (val !== void 0) {
6201
- result[key] = lsonToJson(val);
6202
- }
6203
- }
6204
- return result;
6205
- }
6206
- function liveObjectToJson(liveObject) {
6207
- return lsonObjectToJson(liveObject.toObject());
6208
- }
6209
- function liveMapToJson(map) {
6210
- const result = {};
6211
- for (const [key, value] of map.entries()) {
6212
- result[key] = lsonToJson(value);
6213
- }
6214
- return result;
6215
- }
6216
- function lsonListToJson(value) {
6217
- return value.map(lsonToJson);
6218
- }
6219
- function liveListToJson(value) {
6220
- return lsonListToJson(value.toArray());
6221
- }
6222
- function lsonToJson(value) {
6223
- if (value instanceof LiveObject) {
6224
- return liveObjectToJson(value);
6225
- } else if (value instanceof LiveList) {
6226
- return liveListToJson(value);
6227
- } else if (value instanceof LiveMap) {
6228
- return liveMapToJson(value);
6229
- } else if (value instanceof LiveRegister) {
6230
- return value.data;
6231
- }
6232
- if (Array.isArray(value)) {
6233
- return lsonListToJson(value);
6234
- } else if (isPlainObject(value)) {
6235
- return lsonObjectToJson(value);
6236
- }
6237
- return value;
6238
- }
6239
- function deepLiveify(value) {
6240
- if (Array.isArray(value)) {
6241
- return new LiveList(value.map(deepLiveify));
6242
- } else if (isPlainObject(value)) {
6243
- const init = {};
6244
- for (const key in value) {
6245
- const val = value[key];
6246
- if (val === void 0) {
6247
- continue;
6248
- }
6249
- init[key] = deepLiveify(val);
6250
- }
6251
- return new LiveObject(init);
6252
- } else {
6253
- return value;
6254
- }
6255
- }
6256
- function patchLiveList(liveList, prev, next) {
6257
- let i = 0;
6258
- let prevEnd = prev.length - 1;
6259
- let nextEnd = next.length - 1;
6260
- let prevNode = prev[0];
6261
- let nextNode = next[0];
6262
- outer: {
6263
- while (prevNode === nextNode) {
6264
- ++i;
6265
- if (i > prevEnd || i > nextEnd) {
6266
- break outer;
6267
- }
6268
- prevNode = prev[i];
6269
- nextNode = next[i];
6270
- }
6271
- prevNode = prev[prevEnd];
6272
- nextNode = next[nextEnd];
6273
- while (prevNode === nextNode) {
6274
- prevEnd--;
6275
- nextEnd--;
6276
- if (i > prevEnd || i > nextEnd) {
6277
- break outer;
6278
- }
6279
- prevNode = prev[prevEnd];
6280
- nextNode = next[nextEnd];
6281
- }
6282
- }
6283
- if (i > prevEnd) {
6284
- if (i <= nextEnd) {
6285
- while (i <= nextEnd) {
6286
- liveList.insert(deepLiveify(next[i]), i);
6287
- i++;
6288
- }
6289
- }
6290
- } else if (i > nextEnd) {
6291
- let localI = i;
6292
- while (localI <= prevEnd) {
6293
- liveList.delete(i);
6294
- localI++;
6295
- }
6296
- } else {
6297
- while (i <= prevEnd && i <= nextEnd) {
6298
- prevNode = prev[i];
6299
- nextNode = next[i];
6300
- const liveListNode = liveList.get(i);
6301
- if (isLiveObject(liveListNode) && isPlainObject(prevNode) && isPlainObject(nextNode)) {
6302
- patchLiveObject(liveListNode, prevNode, nextNode);
6303
- } else {
6304
- liveList.set(i, deepLiveify(nextNode));
6305
- }
6306
- i++;
6307
- }
6308
- while (i <= nextEnd) {
6309
- liveList.insert(deepLiveify(next[i]), i);
6310
- i++;
6311
- }
6312
- let localI = i;
6313
- while (localI <= prevEnd) {
6314
- liveList.delete(i);
6315
- localI++;
6316
- }
6317
- }
6318
- }
6319
- function patchLiveObjectKey(liveObject, key, prev, next) {
6320
- if (process.env.NODE_ENV !== "production") {
6321
- const nonSerializableValue = findNonSerializableValue(next);
6322
- if (nonSerializableValue) {
6323
- error2(
6324
- `New state path: '${nonSerializableValue.path}' value: '${String(
6325
- nonSerializableValue.value
6326
- )}' is not serializable.
6327
- Only serializable value can be synced with Liveblocks.`
6328
- );
6329
- return;
6330
- }
6331
- }
6332
- const value = liveObject.get(key);
6333
- if (next === void 0) {
6334
- liveObject.delete(key);
6335
- } else if (value === void 0) {
6336
- liveObject.set(key, deepLiveify(next));
6337
- } else if (prev === next) {
6338
- return;
6339
- } else if (isLiveList(value) && Array.isArray(prev) && Array.isArray(next)) {
6340
- patchLiveList(value, prev, next);
6341
- } else if (isLiveObject(value) && isPlainObject(prev) && isPlainObject(next)) {
6342
- patchLiveObject(value, prev, next);
6343
- } else {
6344
- liveObject.set(key, deepLiveify(next));
6345
- }
6346
- }
6347
- function patchLiveObject(root, prev, next) {
6348
- const updates = {};
6349
- for (const key in next) {
6350
- patchLiveObjectKey(root, key, prev[key], next[key]);
6351
- }
6352
- for (const key in prev) {
6353
- if (next[key] === void 0) {
6354
- root.delete(key);
6355
- }
6356
- }
6357
- if (Object.keys(updates).length > 0) {
6358
- root.update(updates);
6359
- }
6360
- }
6361
- function getParentsPath(node) {
6362
- const path = [];
6363
- while (node.parent.type === "HasParent") {
6364
- if (isLiveList(node.parent.node)) {
6365
- path.push(node.parent.node._indexOfPosition(node.parent.key));
6366
- } else {
6367
- path.push(node.parent.key);
6368
- }
6369
- node = node.parent.node;
6370
- }
6371
- return path;
6372
- }
6373
- function legacy_patchImmutableObject(state, updates) {
6374
- return updates.reduce(
6375
- (state2, update) => legacy_patchImmutableObjectWithUpdate(state2, update),
6376
- state
6377
- );
6378
- }
6379
- function legacy_patchImmutableObjectWithUpdate(state, update) {
6380
- const path = getParentsPath(update.node);
6381
- return legacy_patchImmutableNode(state, path, update);
6382
- }
6383
- function legacy_patchImmutableNode(state, path, update) {
6384
- const pathItem = path.pop();
6385
- if (pathItem === void 0) {
6386
- switch (update.type) {
6387
- case "LiveObject": {
6388
- if (!isJsonObject(state)) {
6389
- throw new Error(
6390
- "Internal: received update on LiveObject but state was not an object"
6391
- );
6392
- }
6393
- const newState = Object.assign({}, state);
6394
- for (const key in update.updates) {
6395
- if (update.updates[key]?.type === "update") {
6396
- const val = update.node.get(key);
6397
- if (val !== void 0) {
6398
- newState[key] = lsonToJson(val);
6399
- }
6400
- } else if (update.updates[key]?.type === "delete") {
6401
- delete newState[key];
6402
- }
6403
- }
6404
- return newState;
6405
- }
6406
- case "LiveList": {
6407
- if (!Array.isArray(state)) {
6408
- throw new Error(
6409
- "Internal: received update on LiveList but state was not an array"
6410
- );
6411
- }
6412
- let newState = state.map((x) => x);
6413
- for (const listUpdate of update.updates) {
6414
- if (listUpdate.type === "set") {
6415
- newState = newState.map(
6416
- (item, index) => index === listUpdate.index ? lsonToJson(listUpdate.item) : item
6417
- );
6418
- } else if (listUpdate.type === "insert") {
6419
- if (listUpdate.index === newState.length) {
6420
- newState.push(lsonToJson(listUpdate.item));
6421
- } else {
6422
- newState = [
6423
- ...newState.slice(0, listUpdate.index),
6424
- lsonToJson(listUpdate.item),
6425
- ...newState.slice(listUpdate.index)
6426
- ];
6427
- }
6428
- } else if (listUpdate.type === "delete") {
6429
- newState.splice(listUpdate.index, 1);
6430
- } else if (listUpdate.type === "move") {
6431
- if (listUpdate.previousIndex > listUpdate.index) {
6432
- newState = [
6433
- ...newState.slice(0, listUpdate.index),
6434
- lsonToJson(listUpdate.item),
6435
- ...newState.slice(listUpdate.index, listUpdate.previousIndex),
6436
- ...newState.slice(listUpdate.previousIndex + 1)
6437
- ];
6438
- } else {
6439
- newState = [
6440
- ...newState.slice(0, listUpdate.previousIndex),
6441
- ...newState.slice(
6442
- listUpdate.previousIndex + 1,
6443
- listUpdate.index + 1
6444
- ),
6445
- lsonToJson(listUpdate.item),
6446
- ...newState.slice(listUpdate.index + 1)
6447
- ];
6448
- }
6449
- }
6450
- }
6451
- return newState;
6452
- }
6453
- case "LiveMap": {
6454
- if (!isJsonObject(state)) {
6455
- throw new Error(
6456
- "Internal: received update on LiveMap but state was not an object"
6457
- );
6278
+ return cb(others, internalEvent);
6279
+ });
6458
6280
  }
6459
- const newState = Object.assign({}, state);
6460
- for (const key in update.updates) {
6461
- if (update.updates[key]?.type === "update") {
6462
- const value = update.node.get(key);
6463
- if (value !== void 0) {
6464
- newState[key] = lsonToJson(value);
6465
- }
6466
- } else if (update.updates[key]?.type === "delete") {
6467
- delete newState[key];
6468
- }
6281
+ case "error":
6282
+ return events.error.subscribe(callback);
6283
+ case "connection": {
6284
+ const cb = callback;
6285
+ return events.status.subscribe(
6286
+ (status) => cb(newToLegacyStatus(status))
6287
+ );
6469
6288
  }
6470
- return newState;
6289
+ case "status":
6290
+ return events.status.subscribe(callback);
6291
+ case "lost-connection":
6292
+ return events.lostConnection.subscribe(
6293
+ callback
6294
+ );
6295
+ case "history":
6296
+ return events.history.subscribe(callback);
6297
+ case "storage-status":
6298
+ return events.storageStatus.subscribe(
6299
+ callback
6300
+ );
6301
+ default:
6302
+ return assertNever(
6303
+ first,
6304
+ `"${String(first)}" is not a valid event name`
6305
+ );
6471
6306
  }
6472
6307
  }
6473
- }
6474
- if (Array.isArray(state)) {
6475
- const newArray = [...state];
6476
- newArray[pathItem] = legacy_patchImmutableNode(
6477
- state[pathItem],
6478
- path,
6479
- update
6480
- );
6481
- return newArray;
6482
- } else if (isJsonObject(state)) {
6483
- const node = state[pathItem];
6484
- if (node === void 0) {
6485
- return state;
6486
- } else {
6487
- const stateAsObj = state;
6488
- return {
6489
- ...stateAsObj,
6490
- [pathItem]: legacy_patchImmutableNode(node, path, update)
6491
- };
6308
+ if (second === void 0 || typeof first === "function") {
6309
+ if (typeof first === "function") {
6310
+ const storageCallback = first;
6311
+ return events.storage.subscribe(storageCallback);
6312
+ } else {
6313
+ throw new Error("Please specify a listener callback");
6314
+ }
6492
6315
  }
6493
- } else {
6494
- return state;
6495
- }
6496
- }
6497
-
6498
- // src/lib/shallow.ts
6499
- function shallowArray(xs, ys) {
6500
- if (xs.length !== ys.length) {
6501
- return false;
6502
- }
6503
- for (let i = 0; i < xs.length; i++) {
6504
- if (!Object.is(xs[i], ys[i])) {
6505
- return false;
6316
+ if (isLiveNode(first)) {
6317
+ const node = first;
6318
+ if (options?.isDeep) {
6319
+ const storageCallback = second;
6320
+ return subscribeToLiveStructureDeeply(node, storageCallback);
6321
+ } else {
6322
+ const nodeCallback = second;
6323
+ return subscribeToLiveStructureShallowly(node, nodeCallback);
6324
+ }
6506
6325
  }
6326
+ throw new Error(
6327
+ `${String(first)} is not a value that can be subscribed to.`
6328
+ );
6507
6329
  }
6508
- return true;
6330
+ return subscribe;
6509
6331
  }
6510
- function shallowObj(objA, objB) {
6511
- if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null || Object.prototype.toString.call(objA) !== "[object Object]" || Object.prototype.toString.call(objB) !== "[object Object]") {
6512
- return false;
6513
- }
6514
- const keysA = Object.keys(objA);
6515
- if (keysA.length !== Object.keys(objB).length) {
6516
- return false;
6517
- }
6518
- return keysA.every(
6519
- (key) => Object.prototype.hasOwnProperty.call(objB, key) && Object.is(objA[key], objB[key])
6520
- );
6332
+ function isRoomEventName(value) {
6333
+ return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "history" || value === "status" || value === "storage-status" || value === "lost-connection" || value === "connection";
6521
6334
  }
6522
- function shallow(a, b) {
6523
- if (Object.is(a, b)) {
6524
- return true;
6525
- }
6526
- const isArrayA = Array.isArray(a);
6527
- const isArrayB = Array.isArray(b);
6528
- if (isArrayA || isArrayB) {
6529
- if (!isArrayA || !isArrayB) {
6530
- return false;
6335
+ function makeAuthDelegateForRoom(roomId, authManager) {
6336
+ return async () => {
6337
+ return authManager.getAuthValue("room:read", roomId);
6338
+ };
6339
+ }
6340
+ function makeCreateSocketDelegateForRoom(roomId, baseUrl, WebSocketPolyfill) {
6341
+ return (authValue) => {
6342
+ const ws = WebSocketPolyfill ?? (typeof WebSocket === "undefined" ? void 0 : WebSocket);
6343
+ if (ws === void 0) {
6344
+ throw new StopRetrying(
6345
+ "To use Liveblocks client in a non-DOM environment, you need to provide a WebSocket polyfill."
6346
+ );
6531
6347
  }
6532
- return shallowArray(a, b);
6533
- }
6534
- return shallowObj(a, b);
6348
+ const url = new URL(baseUrl);
6349
+ url.protocol = url.protocol === "http:" ? "ws" : "wss";
6350
+ url.pathname = "/v7";
6351
+ url.searchParams.set("roomId", roomId);
6352
+ if (authValue.type === "secret") {
6353
+ url.searchParams.set("tok", authValue.token.raw);
6354
+ } else if (authValue.type === "public") {
6355
+ url.searchParams.set("pubkey", authValue.publicApiKey);
6356
+ } else {
6357
+ return assertNever(authValue, "Unhandled case");
6358
+ }
6359
+ url.searchParams.set("version", PKG_VERSION || "dev");
6360
+ return new ws(url.toString());
6361
+ };
6535
6362
  }
6536
6363
 
6537
- // src/lib/AsyncCache.ts
6538
- var noop = () => {
6539
- };
6540
- function isShallowEqual(a, b) {
6541
- if (a.isLoading !== b.isLoading || a.data === void 0 !== (b.data === void 0) || a.error === void 0 !== (b.error === void 0)) {
6542
- return false;
6364
+ // src/client.ts
6365
+ var MIN_THROTTLE = 16;
6366
+ var MAX_THROTTLE = 1e3;
6367
+ var DEFAULT_THROTTLE = 100;
6368
+ var MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT = 15e3;
6369
+ var MIN_LOST_CONNECTION_TIMEOUT = 200;
6370
+ var RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT = 1e3;
6371
+ var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
6372
+ var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
6373
+ function getBaseUrlFromClientOptions(clientOptions) {
6374
+ if ("liveblocksServer" in clientOptions) {
6375
+ throw new Error("Client option no longer supported");
6376
+ }
6377
+ if (typeof clientOptions.baseUrl === "string" && clientOptions.baseUrl.startsWith("http")) {
6378
+ return clientOptions.baseUrl;
6543
6379
  } else {
6544
- return shallow(a.data, b.data) && shallow(a.error, b.error);
6380
+ return DEFAULT_BASE_URL;
6545
6381
  }
6546
6382
  }
6547
- function createCacheItem(key, asyncFunction, options) {
6548
- const $asyncFunction = async () => asyncFunction(key);
6549
- const context = {
6550
- isInvalid: true
6551
- };
6552
- let state = { isLoading: false };
6553
- let previousState = { isLoading: false };
6554
- const eventSource2 = makeEventSource();
6555
- function notify() {
6556
- const isEqual = options?.isStateEqual ?? isShallowEqual;
6557
- if (!isEqual(previousState, state)) {
6558
- previousState = state;
6559
- eventSource2.notify(state);
6383
+ function createClient(options) {
6384
+ const clientOptions = options;
6385
+ const throttleDelay = getThrottle(clientOptions.throttle ?? DEFAULT_THROTTLE);
6386
+ const lostConnectionTimeout = getLostConnectionTimeout(
6387
+ clientOptions.lostConnectionTimeout ?? DEFAULT_LOST_CONNECTION_TIMEOUT
6388
+ );
6389
+ const backgroundKeepAliveTimeout = getBackgroundKeepAliveTimeout(
6390
+ clientOptions.backgroundKeepAliveTimeout
6391
+ );
6392
+ const authManager = createAuthManager(options);
6393
+ const roomsById = /* @__PURE__ */ new Map();
6394
+ function teardownRoom(room) {
6395
+ unlinkDevTools(room.id);
6396
+ roomsById.delete(room.id);
6397
+ room.destroy();
6398
+ }
6399
+ function leaseRoom(info) {
6400
+ const leave = () => {
6401
+ const self = leave;
6402
+ if (!info.unsubs.delete(self)) {
6403
+ warn(
6404
+ "This leave function was already called. Calling it more than once has no effect."
6405
+ );
6406
+ } else {
6407
+ if (info.unsubs.size === 0) {
6408
+ teardownRoom(info.room);
6409
+ }
6410
+ }
6411
+ };
6412
+ info.unsubs.add(leave);
6413
+ return {
6414
+ room: info.room,
6415
+ leave
6416
+ };
6417
+ }
6418
+ function enterRoom(roomId, options2) {
6419
+ const existing = roomsById.get(roomId);
6420
+ if (existing !== void 0) {
6421
+ return leaseRoom(existing);
6422
+ }
6423
+ deprecateIf(
6424
+ options2.initialPresence === null || options2.initialPresence === void 0,
6425
+ "Please provide an initial presence value for the current user when entering the room."
6426
+ );
6427
+ const baseUrl = getBaseUrlFromClientOptions(clientOptions);
6428
+ const newRoom = createRoom(
6429
+ {
6430
+ initialPresence: options2.initialPresence ?? {},
6431
+ initialStorage: options2.initialStorage
6432
+ },
6433
+ {
6434
+ roomId,
6435
+ throttleDelay,
6436
+ lostConnectionTimeout,
6437
+ backgroundKeepAliveTimeout,
6438
+ polyfills: clientOptions.polyfills,
6439
+ delegates: clientOptions.mockedDelegates ?? {
6440
+ createSocket: makeCreateSocketDelegateForRoom(
6441
+ roomId,
6442
+ baseUrl,
6443
+ clientOptions.polyfills?.WebSocket
6444
+ ),
6445
+ authenticate: makeAuthDelegateForRoom(roomId, authManager)
6446
+ },
6447
+ enableDebugLogging: clientOptions.enableDebugLogging,
6448
+ unstable_batchedUpdates: options2?.unstable_batchedUpdates,
6449
+ baseUrl,
6450
+ unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP
6451
+ }
6452
+ );
6453
+ const newRoomInfo = {
6454
+ room: newRoom,
6455
+ unsubs: /* @__PURE__ */ new Set()
6456
+ };
6457
+ roomsById.set(roomId, newRoomInfo);
6458
+ setupDevTools(() => Array.from(roomsById.keys()));
6459
+ linkDevTools(roomId, newRoom);
6460
+ const shouldConnect = options2.autoConnect ?? options2.shouldInitiallyConnect ?? true;
6461
+ if (shouldConnect) {
6462
+ if (typeof atob === "undefined") {
6463
+ if (clientOptions.polyfills?.atob === void 0) {
6464
+ throw new Error(
6465
+ "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"
6466
+ );
6467
+ }
6468
+ global.atob = clientOptions.polyfills.atob;
6469
+ }
6470
+ newRoom.connect();
6560
6471
  }
6472
+ return leaseRoom(newRoomInfo);
6561
6473
  }
6562
- async function resolve() {
6563
- if (!context.promise) {
6564
- return;
6565
- }
6566
- try {
6567
- const data = await context.promise;
6568
- context.isInvalid = false;
6569
- state = {
6570
- isLoading: false,
6571
- data
6572
- };
6573
- } catch (error3) {
6574
- state = {
6575
- isLoading: false,
6576
- data: state.data,
6577
- error: error3
6578
- };
6579
- }
6580
- context.promise = void 0;
6581
- notify();
6474
+ function enter(roomId, options2) {
6475
+ const { room, leave: _ } = enterRoom(roomId, options2);
6476
+ return room;
6582
6477
  }
6583
- async function revalidate() {
6584
- context.isInvalid = true;
6585
- return get();
6478
+ function getRoom(roomId) {
6479
+ const room = roomsById.get(roomId)?.room;
6480
+ return room ? room : null;
6586
6481
  }
6587
- async function get() {
6588
- if (context.isInvalid) {
6589
- if (!context.promise) {
6590
- context.isInvalid = true;
6591
- context.promise = $asyncFunction();
6592
- state = { isLoading: true, data: state.data };
6593
- notify();
6594
- }
6595
- await resolve();
6482
+ function forceLeave(roomId) {
6483
+ const unsubs = roomsById.get(roomId)?.unsubs ?? /* @__PURE__ */ new Set();
6484
+ for (const unsub of unsubs) {
6485
+ unsub();
6596
6486
  }
6597
- return getState();
6598
6487
  }
6599
- function getState() {
6600
- return state;
6488
+ function logout() {
6489
+ authManager.reset();
6490
+ for (const { room } of roomsById.values()) {
6491
+ if (!isIdle(room.getStatus())) {
6492
+ room.reconnect();
6493
+ }
6494
+ }
6601
6495
  }
6602
6496
  return {
6603
- ...eventSource2.observable,
6604
- get,
6605
- getState,
6606
- revalidate
6497
+ logout,
6498
+ // Old, deprecated APIs
6499
+ enter,
6500
+ getRoom,
6501
+ leave: forceLeave,
6502
+ // New, preferred API
6503
+ enterRoom
6607
6504
  };
6608
6505
  }
6609
- function createAsyncCache(asyncFunction, options) {
6610
- const cache = /* @__PURE__ */ new Map();
6611
- function create(key) {
6612
- let cacheItem = cache.get(key);
6613
- if (cacheItem) {
6614
- return cacheItem;
6615
- }
6616
- cacheItem = createCacheItem(key, asyncFunction, options);
6617
- cache.set(key, cacheItem);
6618
- return cacheItem;
6619
- }
6620
- function get(key) {
6621
- return create(key).get();
6506
+ function checkBounds(option, value, min, max, recommendedMin) {
6507
+ if (typeof value !== "number" || value < min || max !== void 0 && value > max) {
6508
+ throw new Error(
6509
+ max !== void 0 ? `${option} should be between ${recommendedMin ?? min} and ${max}.` : `${option} should be at least ${recommendedMin ?? min}.`
6510
+ );
6622
6511
  }
6623
- function getState(key) {
6624
- return cache.get(key)?.getState();
6512
+ return value;
6513
+ }
6514
+ function getBackgroundKeepAliveTimeout(value) {
6515
+ if (value === void 0)
6516
+ return void 0;
6517
+ return checkBounds(
6518
+ "backgroundKeepAliveTimeout",
6519
+ value,
6520
+ MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT
6521
+ );
6522
+ }
6523
+ function getThrottle(value) {
6524
+ return checkBounds("throttle", value, MIN_THROTTLE, MAX_THROTTLE);
6525
+ }
6526
+ function getLostConnectionTimeout(value) {
6527
+ return checkBounds(
6528
+ "lostConnectionTimeout",
6529
+ value,
6530
+ MIN_LOST_CONNECTION_TIMEOUT,
6531
+ MAX_LOST_CONNECTION_TIMEOUT,
6532
+ RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
6533
+ );
6534
+ }
6535
+
6536
+ // src/crdts/utils.ts
6537
+ function toPlainLson(lson) {
6538
+ if (lson instanceof LiveObject) {
6539
+ return {
6540
+ liveblocksType: "LiveObject",
6541
+ data: Object.fromEntries(
6542
+ Object.entries(lson.toObject()).flatMap(
6543
+ ([key, value]) => value !== void 0 ? [[key, toPlainLson(value)]] : []
6544
+ )
6545
+ )
6546
+ };
6547
+ } else if (lson instanceof LiveMap) {
6548
+ return {
6549
+ liveblocksType: "LiveMap",
6550
+ data: Object.fromEntries(
6551
+ [...lson].map(([key, value]) => [key, toPlainLson(value)])
6552
+ )
6553
+ };
6554
+ } else if (lson instanceof LiveList) {
6555
+ return {
6556
+ liveblocksType: "LiveList",
6557
+ data: [...lson].map((item) => toPlainLson(item))
6558
+ };
6559
+ } else {
6560
+ return lson;
6625
6561
  }
6626
- function revalidate(key) {
6627
- return create(key).revalidate();
6562
+ }
6563
+
6564
+ // src/immutable.ts
6565
+ function lsonObjectToJson(obj) {
6566
+ const result = {};
6567
+ for (const key in obj) {
6568
+ const val = obj[key];
6569
+ if (val !== void 0) {
6570
+ result[key] = lsonToJson(val);
6571
+ }
6628
6572
  }
6629
- function subscribe(key, callback) {
6630
- return create(key).subscribe(callback) ?? noop;
6573
+ return result;
6574
+ }
6575
+ function liveObjectToJson(liveObject) {
6576
+ return lsonObjectToJson(liveObject.toObject());
6577
+ }
6578
+ function liveMapToJson(map) {
6579
+ const result = {};
6580
+ for (const [key, value] of map.entries()) {
6581
+ result[key] = lsonToJson(value);
6631
6582
  }
6632
- function subscribeOnce(key, callback) {
6633
- return create(key).subscribeOnce(callback) ?? noop;
6583
+ return result;
6584
+ }
6585
+ function lsonListToJson(value) {
6586
+ return value.map(lsonToJson);
6587
+ }
6588
+ function liveListToJson(value) {
6589
+ return lsonListToJson(value.toArray());
6590
+ }
6591
+ function lsonToJson(value) {
6592
+ if (value instanceof LiveObject) {
6593
+ return liveObjectToJson(value);
6594
+ } else if (value instanceof LiveList) {
6595
+ return liveListToJson(value);
6596
+ } else if (value instanceof LiveMap) {
6597
+ return liveMapToJson(value);
6598
+ } else if (value instanceof LiveRegister) {
6599
+ return value.data;
6634
6600
  }
6635
- function has(key) {
6636
- return cache.has(key);
6601
+ if (Array.isArray(value)) {
6602
+ return lsonListToJson(value);
6603
+ } else if (isPlainObject(value)) {
6604
+ return lsonObjectToJson(value);
6637
6605
  }
6638
- function clear() {
6639
- cache.clear();
6606
+ return value;
6607
+ }
6608
+ function deepLiveify(value) {
6609
+ if (Array.isArray(value)) {
6610
+ return new LiveList(value.map(deepLiveify));
6611
+ } else if (isPlainObject(value)) {
6612
+ const init = {};
6613
+ for (const key in value) {
6614
+ const val = value[key];
6615
+ if (val === void 0) {
6616
+ continue;
6617
+ }
6618
+ init[key] = deepLiveify(val);
6619
+ }
6620
+ return new LiveObject(init);
6621
+ } else {
6622
+ return value;
6640
6623
  }
6641
- return {
6642
- create,
6643
- get,
6644
- getState,
6645
- revalidate,
6646
- subscribe,
6647
- subscribeOnce,
6648
- has,
6649
- clear
6650
- };
6651
6624
  }
6652
-
6653
- // src/lib/Poller.ts
6654
- function makePoller(callback) {
6655
- let context = {
6656
- state: "stopped",
6657
- timeoutHandle: null,
6658
- interval: null,
6659
- lastScheduledAt: null,
6660
- remainingInterval: null
6661
- };
6662
- function poll() {
6663
- if (context.state === "running") {
6664
- schedule(context.interval);
6625
+ function patchLiveList(liveList, prev, next) {
6626
+ let i = 0;
6627
+ let prevEnd = prev.length - 1;
6628
+ let nextEnd = next.length - 1;
6629
+ let prevNode = prev[0];
6630
+ let nextNode = next[0];
6631
+ outer: {
6632
+ while (prevNode === nextNode) {
6633
+ ++i;
6634
+ if (i > prevEnd || i > nextEnd) {
6635
+ break outer;
6636
+ }
6637
+ prevNode = prev[i];
6638
+ nextNode = next[i];
6639
+ }
6640
+ prevNode = prev[prevEnd];
6641
+ nextNode = next[nextEnd];
6642
+ while (prevNode === nextNode) {
6643
+ prevEnd--;
6644
+ nextEnd--;
6645
+ if (i > prevEnd || i > nextEnd) {
6646
+ break outer;
6647
+ }
6648
+ prevNode = prev[prevEnd];
6649
+ nextNode = next[nextEnd];
6665
6650
  }
6666
- void callback();
6667
- }
6668
- function schedule(interval) {
6669
- context = {
6670
- state: "running",
6671
- interval: context.state !== "stopped" ? context.interval : interval,
6672
- lastScheduledAt: performance.now(),
6673
- timeoutHandle: setTimeout(poll, interval),
6674
- remainingInterval: null
6675
- };
6676
6651
  }
6677
- function scheduleRemaining(remaining) {
6678
- if (context.state !== "paused") {
6679
- return;
6652
+ if (i > prevEnd) {
6653
+ if (i <= nextEnd) {
6654
+ while (i <= nextEnd) {
6655
+ liveList.insert(deepLiveify(next[i]), i);
6656
+ i++;
6657
+ }
6658
+ }
6659
+ } else if (i > nextEnd) {
6660
+ let localI = i;
6661
+ while (localI <= prevEnd) {
6662
+ liveList.delete(i);
6663
+ localI++;
6664
+ }
6665
+ } else {
6666
+ while (i <= prevEnd && i <= nextEnd) {
6667
+ prevNode = prev[i];
6668
+ nextNode = next[i];
6669
+ const liveListNode = liveList.get(i);
6670
+ if (isLiveObject(liveListNode) && isPlainObject(prevNode) && isPlainObject(nextNode)) {
6671
+ patchLiveObject(liveListNode, prevNode, nextNode);
6672
+ } else {
6673
+ liveList.set(i, deepLiveify(nextNode));
6674
+ }
6675
+ i++;
6676
+ }
6677
+ while (i <= nextEnd) {
6678
+ liveList.insert(deepLiveify(next[i]), i);
6679
+ i++;
6680
+ }
6681
+ let localI = i;
6682
+ while (localI <= prevEnd) {
6683
+ liveList.delete(i);
6684
+ localI++;
6680
6685
  }
6681
- context = {
6682
- state: "running",
6683
- interval: context.interval,
6684
- lastScheduledAt: context.lastScheduledAt,
6685
- timeoutHandle: setTimeout(poll, remaining),
6686
- remainingInterval: null
6687
- };
6688
6686
  }
6689
- function start(interval) {
6690
- if (context.state === "running") {
6687
+ }
6688
+ function patchLiveObjectKey(liveObject, key, prev, next) {
6689
+ if (process.env.NODE_ENV !== "production") {
6690
+ const nonSerializableValue = findNonSerializableValue(next);
6691
+ if (nonSerializableValue) {
6692
+ error2(
6693
+ `New state path: '${nonSerializableValue.path}' value: '${String(
6694
+ nonSerializableValue.value
6695
+ )}' is not serializable.
6696
+ Only serializable value can be synced with Liveblocks.`
6697
+ );
6691
6698
  return;
6692
6699
  }
6693
- schedule(interval);
6694
6700
  }
6695
- function restart(interval) {
6696
- stop();
6697
- start(interval);
6701
+ const value = liveObject.get(key);
6702
+ if (next === void 0) {
6703
+ liveObject.delete(key);
6704
+ } else if (value === void 0) {
6705
+ liveObject.set(key, deepLiveify(next));
6706
+ } else if (prev === next) {
6707
+ return;
6708
+ } else if (isLiveList(value) && Array.isArray(prev) && Array.isArray(next)) {
6709
+ patchLiveList(value, prev, next);
6710
+ } else if (isLiveObject(value) && isPlainObject(prev) && isPlainObject(next)) {
6711
+ patchLiveObject(value, prev, next);
6712
+ } else {
6713
+ liveObject.set(key, deepLiveify(next));
6698
6714
  }
6699
- function pause() {
6700
- if (context.state !== "running") {
6701
- return;
6702
- }
6703
- clearTimeout(context.timeoutHandle);
6704
- context = {
6705
- state: "paused",
6706
- interval: context.interval,
6707
- lastScheduledAt: context.lastScheduledAt,
6708
- timeoutHandle: null,
6709
- remainingInterval: context.interval - (performance.now() - context.lastScheduledAt)
6710
- };
6715
+ }
6716
+ function patchLiveObject(root, prev, next) {
6717
+ const updates = {};
6718
+ for (const key in next) {
6719
+ patchLiveObjectKey(root, key, prev[key], next[key]);
6711
6720
  }
6712
- function resume() {
6713
- if (context.state !== "paused") {
6714
- return;
6721
+ for (const key in prev) {
6722
+ if (next[key] === void 0) {
6723
+ root.delete(key);
6715
6724
  }
6716
- scheduleRemaining(context.remainingInterval);
6717
6725
  }
6718
- function stop() {
6719
- if (context.state === "stopped") {
6720
- return;
6721
- }
6722
- if (context.timeoutHandle) {
6723
- clearTimeout(context.timeoutHandle);
6726
+ if (Object.keys(updates).length > 0) {
6727
+ root.update(updates);
6728
+ }
6729
+ }
6730
+ function getParentsPath(node) {
6731
+ const path = [];
6732
+ while (node.parent.type === "HasParent") {
6733
+ if (isLiveList(node.parent.node)) {
6734
+ path.push(node.parent.node._indexOfPosition(node.parent.key));
6735
+ } else {
6736
+ path.push(node.parent.key);
6724
6737
  }
6725
- context = {
6726
- state: "stopped",
6727
- interval: null,
6728
- lastScheduledAt: null,
6729
- timeoutHandle: null,
6730
- remainingInterval: null
6731
- };
6738
+ node = node.parent.node;
6732
6739
  }
6733
- return {
6734
- start,
6735
- restart,
6736
- pause,
6737
- resume,
6738
- stop
6739
- };
6740
+ return path;
6740
6741
  }
6741
-
6742
- // src/lib/stringify.ts
6743
- function stringify(object, ...args) {
6744
- if (typeof object !== "object" || object === null || Array.isArray(object)) {
6745
- return JSON.stringify(object, ...args);
6742
+ function legacy_patchImmutableObject(state, updates) {
6743
+ return updates.reduce(
6744
+ (state2, update) => legacy_patchImmutableObjectWithUpdate(state2, update),
6745
+ state
6746
+ );
6747
+ }
6748
+ function legacy_patchImmutableObjectWithUpdate(state, update) {
6749
+ const path = getParentsPath(update.node);
6750
+ return legacy_patchImmutableNode(state, path, update);
6751
+ }
6752
+ function legacy_patchImmutableNode(state, path, update) {
6753
+ const pathItem = path.pop();
6754
+ if (pathItem === void 0) {
6755
+ switch (update.type) {
6756
+ case "LiveObject": {
6757
+ if (!isJsonObject(state)) {
6758
+ throw new Error(
6759
+ "Internal: received update on LiveObject but state was not an object"
6760
+ );
6761
+ }
6762
+ const newState = Object.assign({}, state);
6763
+ for (const key in update.updates) {
6764
+ if (update.updates[key]?.type === "update") {
6765
+ const val = update.node.get(key);
6766
+ if (val !== void 0) {
6767
+ newState[key] = lsonToJson(val);
6768
+ }
6769
+ } else if (update.updates[key]?.type === "delete") {
6770
+ delete newState[key];
6771
+ }
6772
+ }
6773
+ return newState;
6774
+ }
6775
+ case "LiveList": {
6776
+ if (!Array.isArray(state)) {
6777
+ throw new Error(
6778
+ "Internal: received update on LiveList but state was not an array"
6779
+ );
6780
+ }
6781
+ let newState = state.map((x) => x);
6782
+ for (const listUpdate of update.updates) {
6783
+ if (listUpdate.type === "set") {
6784
+ newState = newState.map(
6785
+ (item, index) => index === listUpdate.index ? lsonToJson(listUpdate.item) : item
6786
+ );
6787
+ } else if (listUpdate.type === "insert") {
6788
+ if (listUpdate.index === newState.length) {
6789
+ newState.push(lsonToJson(listUpdate.item));
6790
+ } else {
6791
+ newState = [
6792
+ ...newState.slice(0, listUpdate.index),
6793
+ lsonToJson(listUpdate.item),
6794
+ ...newState.slice(listUpdate.index)
6795
+ ];
6796
+ }
6797
+ } else if (listUpdate.type === "delete") {
6798
+ newState.splice(listUpdate.index, 1);
6799
+ } else if (listUpdate.type === "move") {
6800
+ if (listUpdate.previousIndex > listUpdate.index) {
6801
+ newState = [
6802
+ ...newState.slice(0, listUpdate.index),
6803
+ lsonToJson(listUpdate.item),
6804
+ ...newState.slice(listUpdate.index, listUpdate.previousIndex),
6805
+ ...newState.slice(listUpdate.previousIndex + 1)
6806
+ ];
6807
+ } else {
6808
+ newState = [
6809
+ ...newState.slice(0, listUpdate.previousIndex),
6810
+ ...newState.slice(
6811
+ listUpdate.previousIndex + 1,
6812
+ listUpdate.index + 1
6813
+ ),
6814
+ lsonToJson(listUpdate.item),
6815
+ ...newState.slice(listUpdate.index + 1)
6816
+ ];
6817
+ }
6818
+ }
6819
+ }
6820
+ return newState;
6821
+ }
6822
+ case "LiveMap": {
6823
+ if (!isJsonObject(state)) {
6824
+ throw new Error(
6825
+ "Internal: received update on LiveMap but state was not an object"
6826
+ );
6827
+ }
6828
+ const newState = Object.assign({}, state);
6829
+ for (const key in update.updates) {
6830
+ if (update.updates[key]?.type === "update") {
6831
+ const value = update.node.get(key);
6832
+ if (value !== void 0) {
6833
+ newState[key] = lsonToJson(value);
6834
+ }
6835
+ } else if (update.updates[key]?.type === "delete") {
6836
+ delete newState[key];
6837
+ }
6838
+ }
6839
+ return newState;
6840
+ }
6841
+ }
6842
+ }
6843
+ if (Array.isArray(state)) {
6844
+ const newArray = [...state];
6845
+ newArray[pathItem] = legacy_patchImmutableNode(
6846
+ state[pathItem],
6847
+ path,
6848
+ update
6849
+ );
6850
+ return newArray;
6851
+ } else if (isJsonObject(state)) {
6852
+ const node = state[pathItem];
6853
+ if (node === void 0) {
6854
+ return state;
6855
+ } else {
6856
+ const stateAsObj = state;
6857
+ return {
6858
+ ...stateAsObj,
6859
+ [pathItem]: legacy_patchImmutableNode(node, path, update)
6860
+ };
6861
+ }
6862
+ } else {
6863
+ return state;
6746
6864
  }
6747
- const sortedObject = Object.keys(object).sort().reduce(
6748
- (sortedObject2, key) => {
6749
- sortedObject2[key] = object[key];
6750
- return sortedObject2;
6751
- },
6752
- {}
6753
- );
6754
- return JSON.stringify(sortedObject, ...args);
6755
6865
  }
6756
6866
 
6757
- // src/comments/comment-body.ts
6758
- function isCommentBodyParagraph(element) {
6759
- return "type" in element && element.type === "mention";
6760
- }
6761
- function isCommentBodyText(element) {
6762
- return "text" in element && typeof element.text === "string";
6763
- }
6764
- function isCommentBodyMention(element) {
6765
- return "type" in element && element.type === "mention";
6766
- }
6767
- function isCommentBodyLink(element) {
6768
- return "type" in element && element.type === "link";
6769
- }
6770
- var commentBodyElementsGuards = {
6771
- paragraph: isCommentBodyParagraph,
6772
- text: isCommentBodyText,
6773
- link: isCommentBodyLink,
6774
- mention: isCommentBodyMention
6775
- };
6776
- var commentBodyElementsTypes = {
6777
- paragraph: "block",
6778
- text: "inline",
6779
- link: "inline",
6780
- mention: "inline"
6781
- };
6782
- function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
6783
- if (!body || !body?.content) {
6784
- return;
6867
+ // src/lib/shallow.ts
6868
+ function shallowArray(xs, ys) {
6869
+ if (xs.length !== ys.length) {
6870
+ return false;
6785
6871
  }
6786
- const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
6787
- const type = element ? commentBodyElementsTypes[element] : "all";
6788
- const guard = element ? commentBodyElementsGuards[element] : () => true;
6789
- const visitor = typeof elementOrVisitor === "function" ? elementOrVisitor : possiblyVisitor;
6790
- for (const block of body.content) {
6791
- if (type === "all" || type === "block") {
6792
- if (guard(block)) {
6793
- visitor?.(block);
6794
- }
6795
- }
6796
- if (type === "all" || type === "inline") {
6797
- for (const inline of block.children) {
6798
- if (guard(inline)) {
6799
- visitor?.(inline);
6800
- }
6801
- }
6872
+ for (let i = 0; i < xs.length; i++) {
6873
+ if (!Object.is(xs[i], ys[i])) {
6874
+ return false;
6802
6875
  }
6803
6876
  }
6877
+ return true;
6804
6878
  }
6805
- function getMentionedIdsFromCommentBody(body) {
6806
- const mentionedIds = /* @__PURE__ */ new Set();
6807
- traverseCommentBody(
6808
- body,
6809
- "mention",
6810
- (mention) => mentionedIds.add(mention.id)
6879
+ function shallowObj(objA, objB) {
6880
+ if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null || Object.prototype.toString.call(objA) !== "[object Object]" || Object.prototype.toString.call(objB) !== "[object Object]") {
6881
+ return false;
6882
+ }
6883
+ const keysA = Object.keys(objA);
6884
+ if (keysA.length !== Object.keys(objB).length) {
6885
+ return false;
6886
+ }
6887
+ return keysA.every(
6888
+ (key) => Object.prototype.hasOwnProperty.call(objB, key) && Object.is(objA[key], objB[key])
6811
6889
  );
6812
- return Array.from(mentionedIds);
6813
6890
  }
6814
- async function resolveUsersInCommentBody(body, resolveUsers) {
6815
- const resolvedUsers = /* @__PURE__ */ new Map();
6816
- if (!resolveUsers) {
6817
- return resolvedUsers;
6891
+ function shallow(a, b) {
6892
+ if (Object.is(a, b)) {
6893
+ return true;
6818
6894
  }
6819
- const userIds = getMentionedIdsFromCommentBody(body);
6820
- const users = await resolveUsers({
6821
- userIds
6822
- });
6823
- for (const [index, userId] of userIds.entries()) {
6824
- const user = users?.[index];
6825
- if (user) {
6826
- resolvedUsers.set(userId, user);
6895
+ const isArrayA = Array.isArray(a);
6896
+ const isArrayB = Array.isArray(b);
6897
+ if (isArrayA || isArrayB) {
6898
+ if (!isArrayA || !isArrayB) {
6899
+ return false;
6827
6900
  }
6901
+ return shallowArray(a, b);
6828
6902
  }
6829
- return resolvedUsers;
6903
+ return shallowObj(a, b);
6830
6904
  }
6831
- var htmlEscapables = {
6832
- "&": "&amp;",
6833
- "<": "&lt;",
6834
- ">": "&gt;",
6835
- '"': "&quot;",
6836
- "'": "&#39;"
6905
+
6906
+ // src/lib/AsyncCache.ts
6907
+ var noop = () => {
6837
6908
  };
6838
- var htmlEscapablesRegex = new RegExp(
6839
- Object.keys(htmlEscapables).map((entity) => `\\${entity}`).join("|"),
6840
- "g"
6841
- );
6842
- function htmlSafe(value) {
6843
- return new HtmlSafeString([String(value)], []);
6844
- }
6845
- function joinHtml(strings) {
6846
- if (strings.length <= 0) {
6847
- return new HtmlSafeString([""], []);
6909
+ function isShallowEqual(a, b) {
6910
+ if (a.isLoading !== b.isLoading || a.data === void 0 !== (b.data === void 0) || a.error === void 0 !== (b.error === void 0)) {
6911
+ return false;
6912
+ } else {
6913
+ return shallow(a.data, b.data) && shallow(a.error, b.error);
6848
6914
  }
6849
- return new HtmlSafeString(
6850
- ["", ...Array(strings.length - 1).fill(""), ""],
6851
- strings
6852
- );
6853
6915
  }
6854
- function escapeHtml(value) {
6855
- if (value instanceof HtmlSafeString) {
6856
- return value.toString();
6916
+ function createCacheItem(key, asyncFunction, options) {
6917
+ const $asyncFunction = async () => asyncFunction(key);
6918
+ const context = {
6919
+ isInvalid: true
6920
+ };
6921
+ let state = { isLoading: false };
6922
+ let previousState = { isLoading: false };
6923
+ const eventSource2 = makeEventSource();
6924
+ function notify() {
6925
+ const isEqual = options?.isStateEqual ?? isShallowEqual;
6926
+ if (!isEqual(previousState, state)) {
6927
+ previousState = state;
6928
+ eventSource2.notify(state);
6929
+ }
6857
6930
  }
6858
- if (Array.isArray(value)) {
6859
- return joinHtml(value).toString();
6931
+ async function resolve() {
6932
+ if (!context.promise) {
6933
+ return;
6934
+ }
6935
+ try {
6936
+ const data = await context.promise;
6937
+ context.isInvalid = false;
6938
+ state = {
6939
+ isLoading: false,
6940
+ data
6941
+ };
6942
+ } catch (error3) {
6943
+ state = {
6944
+ isLoading: false,
6945
+ data: state.data,
6946
+ error: error3
6947
+ };
6948
+ }
6949
+ context.promise = void 0;
6950
+ notify();
6860
6951
  }
6861
- return String(value).replace(
6862
- htmlEscapablesRegex,
6863
- (character) => htmlEscapables[character]
6864
- );
6865
- }
6866
- var HtmlSafeString = class {
6867
- constructor(strings, values) {
6868
- this._strings = strings;
6869
- this._values = values;
6952
+ async function revalidate() {
6953
+ context.isInvalid = true;
6954
+ return get();
6870
6955
  }
6871
- toString() {
6872
- return this._strings.reduce((result, str, i) => {
6873
- return result + escapeHtml(nn(this._values[i - 1])) + str;
6874
- });
6956
+ async function get() {
6957
+ if (context.isInvalid) {
6958
+ if (!context.promise) {
6959
+ context.isInvalid = true;
6960
+ context.promise = $asyncFunction();
6961
+ state = { isLoading: true, data: state.data };
6962
+ notify();
6963
+ }
6964
+ await resolve();
6965
+ }
6966
+ return getState();
6875
6967
  }
6876
- };
6877
- function html(strings, ...values) {
6878
- return new HtmlSafeString(strings, values);
6968
+ function getState() {
6969
+ return state;
6970
+ }
6971
+ return {
6972
+ ...eventSource2.observable,
6973
+ get,
6974
+ getState,
6975
+ revalidate
6976
+ };
6879
6977
  }
6880
- var markdownEscapables = {
6881
- _: "\\_",
6882
- "*": "\\*",
6883
- "#": "\\#",
6884
- "`": "\\`",
6885
- "~": "\\~",
6886
- "!": "\\!",
6887
- "|": "\\|",
6888
- "(": "\\(",
6889
- ")": "\\)",
6890
- "{": "\\{",
6891
- "}": "\\}",
6892
- "[": "\\[",
6893
- "]": "\\]"
6894
- };
6895
- var markdownEscapablesRegex = new RegExp(
6896
- Object.keys(markdownEscapables).map((entity) => `\\${entity}`).join("|"),
6897
- "g"
6898
- );
6899
- function joinMarkdown(strings) {
6900
- if (strings.length <= 0) {
6901
- return new MarkdownSafeString([""], []);
6978
+ function createAsyncCache(asyncFunction, options) {
6979
+ const cache = /* @__PURE__ */ new Map();
6980
+ function create(key) {
6981
+ let cacheItem = cache.get(key);
6982
+ if (cacheItem) {
6983
+ return cacheItem;
6984
+ }
6985
+ cacheItem = createCacheItem(key, asyncFunction, options);
6986
+ cache.set(key, cacheItem);
6987
+ return cacheItem;
6988
+ }
6989
+ function get(key) {
6990
+ return create(key).get();
6991
+ }
6992
+ function getState(key) {
6993
+ return cache.get(key)?.getState();
6902
6994
  }
6903
- return new MarkdownSafeString(
6904
- ["", ...Array(strings.length - 1).fill(""), ""],
6905
- strings
6906
- );
6907
- }
6908
- function escapeMarkdown(value) {
6909
- if (value instanceof MarkdownSafeString) {
6910
- return value.toString();
6995
+ function revalidate(key) {
6996
+ return create(key).revalidate();
6911
6997
  }
6912
- if (Array.isArray(value)) {
6913
- return joinMarkdown(value).toString();
6998
+ function subscribe(key, callback) {
6999
+ return create(key).subscribe(callback) ?? noop;
6914
7000
  }
6915
- return String(value).replace(
6916
- markdownEscapablesRegex,
6917
- (character) => markdownEscapables[character]
6918
- );
6919
- }
6920
- var MarkdownSafeString = class {
6921
- constructor(strings, values) {
6922
- this._strings = strings;
6923
- this._values = values;
7001
+ function subscribeOnce(key, callback) {
7002
+ return create(key).subscribeOnce(callback) ?? noop;
6924
7003
  }
6925
- toString() {
6926
- return this._strings.reduce((result, str, i) => {
6927
- return result + escapeMarkdown(nn(this._values[i - 1])) + str;
6928
- });
7004
+ function has(key) {
7005
+ return cache.has(key);
6929
7006
  }
6930
- };
6931
- function markdown(strings, ...values) {
6932
- return new MarkdownSafeString(strings, values);
6933
- }
6934
- function toAbsoluteUrl(url) {
6935
- if (url.startsWith("http://") || url.startsWith("https://")) {
6936
- return url;
6937
- } else if (url.startsWith("www.")) {
6938
- return "https://" + url;
7007
+ function clear() {
7008
+ cache.clear();
6939
7009
  }
6940
- return;
7010
+ return {
7011
+ create,
7012
+ get,
7013
+ getState,
7014
+ revalidate,
7015
+ subscribe,
7016
+ subscribeOnce,
7017
+ has,
7018
+ clear
7019
+ };
6941
7020
  }
6942
- var stringifyCommentBodyPlainElements = {
6943
- paragraph: ({ children }) => children,
6944
- text: ({ element }) => element.text,
6945
- link: ({ element }) => element.url,
6946
- mention: ({ element, user }) => {
6947
- return `@${user?.name ?? element.id}`;
6948
- }
6949
- };
6950
- var stringifyCommentBodyHtmlElements = {
6951
- paragraph: ({ children }) => {
6952
- return children ? html`<p>${htmlSafe(children)}</p>` : children;
6953
- },
6954
- text: ({ element }) => {
6955
- let children = element.text;
6956
- if (!children) {
6957
- return children;
6958
- }
6959
- if (element.bold) {
6960
- children = html`<strong>${children}</strong>`;
6961
- }
6962
- if (element.italic) {
6963
- children = html`<em>${children}</em>`;
6964
- }
6965
- if (element.strikethrough) {
6966
- children = html`<s>${children}</s>`;
7021
+
7022
+ // src/lib/Poller.ts
7023
+ function makePoller(callback) {
7024
+ let context = {
7025
+ state: "stopped",
7026
+ timeoutHandle: null,
7027
+ interval: null,
7028
+ lastScheduledAt: null,
7029
+ remainingInterval: null
7030
+ };
7031
+ function poll() {
7032
+ if (context.state === "running") {
7033
+ schedule(context.interval);
6967
7034
  }
6968
- if (element.code) {
6969
- children = html`<code>${children}</code>`;
7035
+ void callback();
7036
+ }
7037
+ function schedule(interval) {
7038
+ context = {
7039
+ state: "running",
7040
+ interval: context.state !== "stopped" ? context.interval : interval,
7041
+ lastScheduledAt: performance.now(),
7042
+ timeoutHandle: setTimeout(poll, interval),
7043
+ remainingInterval: null
7044
+ };
7045
+ }
7046
+ function scheduleRemaining(remaining) {
7047
+ if (context.state !== "paused") {
7048
+ return;
6970
7049
  }
6971
- return children;
6972
- },
6973
- link: ({ element, href }) => {
6974
- return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.url}</a>`;
6975
- },
6976
- mention: ({ element, user }) => {
6977
- return html`<span data-mention>@${user?.name ?? element.id}</span>`;
7050
+ context = {
7051
+ state: "running",
7052
+ interval: context.interval,
7053
+ lastScheduledAt: context.lastScheduledAt,
7054
+ timeoutHandle: setTimeout(poll, remaining),
7055
+ remainingInterval: null
7056
+ };
6978
7057
  }
6979
- };
6980
- var stringifyCommentBodyMarkdownElements = {
6981
- paragraph: ({ children }) => {
6982
- return children;
6983
- },
6984
- text: ({ element }) => {
6985
- let children = element.text;
6986
- if (!children) {
6987
- return children;
7058
+ function start(interval) {
7059
+ if (context.state === "running") {
7060
+ return;
6988
7061
  }
6989
- if (element.bold) {
6990
- children = markdown`**${children}**`;
7062
+ schedule(interval);
7063
+ }
7064
+ function restart(interval) {
7065
+ stop();
7066
+ start(interval);
7067
+ }
7068
+ function pause() {
7069
+ if (context.state !== "running") {
7070
+ return;
6991
7071
  }
6992
- if (element.italic) {
6993
- children = markdown`_${children}_`;
7072
+ clearTimeout(context.timeoutHandle);
7073
+ context = {
7074
+ state: "paused",
7075
+ interval: context.interval,
7076
+ lastScheduledAt: context.lastScheduledAt,
7077
+ timeoutHandle: null,
7078
+ remainingInterval: context.interval - (performance.now() - context.lastScheduledAt)
7079
+ };
7080
+ }
7081
+ function resume() {
7082
+ if (context.state !== "paused") {
7083
+ return;
6994
7084
  }
6995
- if (element.strikethrough) {
6996
- children = markdown`~~${children}~~`;
7085
+ scheduleRemaining(context.remainingInterval);
7086
+ }
7087
+ function stop() {
7088
+ if (context.state === "stopped") {
7089
+ return;
6997
7090
  }
6998
- if (element.code) {
6999
- children = markdown`\`${children}\``;
7091
+ if (context.timeoutHandle) {
7092
+ clearTimeout(context.timeoutHandle);
7000
7093
  }
7001
- return children;
7002
- },
7003
- link: ({ element, href }) => {
7004
- return markdown`[${element.url}](${href})`;
7005
- },
7006
- mention: ({ element, user }) => {
7007
- return markdown`@${user?.name ?? element.id}`;
7094
+ context = {
7095
+ state: "stopped",
7096
+ interval: null,
7097
+ lastScheduledAt: null,
7098
+ timeoutHandle: null,
7099
+ remainingInterval: null
7100
+ };
7008
7101
  }
7009
- };
7010
- async function stringifyCommentBody(body, options) {
7011
- const format = options?.format ?? "plain";
7012
- const separator = options?.separator ?? (format === "markdown" ? "\n\n" : "\n");
7013
- const elements = {
7014
- ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
7015
- ...options?.elements
7102
+ return {
7103
+ start,
7104
+ restart,
7105
+ pause,
7106
+ resume,
7107
+ stop
7016
7108
  };
7017
- const resolvedUsers = await resolveUsersInCommentBody(
7018
- body,
7019
- options?.resolveUsers
7109
+ }
7110
+
7111
+ // src/lib/stringify.ts
7112
+ function stringify(object, ...args) {
7113
+ if (typeof object !== "object" || object === null || Array.isArray(object)) {
7114
+ return JSON.stringify(object, ...args);
7115
+ }
7116
+ const sortedObject = Object.keys(object).sort().reduce(
7117
+ (sortedObject2, key) => {
7118
+ sortedObject2[key] = object[key];
7119
+ return sortedObject2;
7120
+ },
7121
+ {}
7020
7122
  );
7021
- const blocks = body.content.flatMap((block, blockIndex) => {
7022
- switch (block.type) {
7023
- case "paragraph": {
7024
- const inlines = block.children.flatMap((inline, inlineIndex) => {
7025
- if (isCommentBodyMention(inline)) {
7026
- return inline.id ? [
7027
- elements.mention(
7028
- {
7029
- element: inline,
7030
- user: resolvedUsers.get(inline.id)
7031
- },
7032
- inlineIndex
7033
- )
7034
- ] : [];
7035
- }
7036
- if (isCommentBodyLink(inline)) {
7037
- return [
7038
- elements.link(
7039
- {
7040
- element: inline,
7041
- href: toAbsoluteUrl(inline.url) ?? inline.url
7042
- },
7043
- inlineIndex
7044
- )
7045
- ];
7046
- }
7047
- if (isCommentBodyText(inline)) {
7048
- return [elements.text({ element: inline }, inlineIndex)];
7049
- }
7050
- return [];
7051
- });
7052
- return [
7053
- elements.paragraph(
7054
- { element: block, children: inlines.join("") },
7055
- blockIndex
7056
- )
7057
- ];
7058
- }
7059
- default:
7060
- return [];
7061
- }
7062
- });
7063
- return blocks.join(separator);
7123
+ return JSON.stringify(sortedObject, ...args);
7064
7124
  }
7065
7125
 
7066
7126
  // src/index.ts
@@ -7075,12 +7135,16 @@ export {
7075
7135
  OpCode,
7076
7136
  ServerMsgCode,
7077
7137
  WebsocketCloseCodes,
7138
+ ackOp,
7078
7139
  asPos,
7079
7140
  assert,
7080
7141
  assertNever,
7081
7142
  b64decode,
7082
7143
  cloneLson,
7083
7144
  fancy_console_exports as console,
7145
+ convertToCommentData,
7146
+ convertToCommentUserReaction,
7147
+ convertToThreadData,
7084
7148
  createAsyncCache,
7085
7149
  createClient,
7086
7150
  createCommentsApi,