Repository: yorkie-team/yorkie-js-sdk
Pull request #946: Add YorkieProvider, DocumentProvider and suspense hooks
Author: login: hackerwins
Created: 2025-03-05T10:16:01Z | Merged: 2025-03-06T02:51:56Z

<!--  Thanks for sending a pull request! -->

#### What this PR does / why we need it?

Add YorkieProvider, DocumentProvider and suspense hooks

YorkieProvider and DocumentProvider are React context providers that enable
easy integration of Yorkie clients and documents into your React applications.
They provide convenient hooks for accessing and updating shared documents
in real-time.

```tsx
<YorkieProvider apiKey={API_KEY}>
  <DocumentProvider
    docKey={DOC_KEY}
    initialRoot={{}}
  >
    <App />
  </DocumentProvider>
</YorkieProvider>

// ...

export default function App() {
  const { root, update, loading, error } = useDocument();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

// ...
}
```

#### Any background context you want to provide?


#### What are the relevant tickets?
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

### Checklist
- [x] Added relevant tests or not required
- [x] Didn't break anything


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
	- Introduced real-time synchronization that enhances document management and collaboration within the to-do application.
	- Added new `YorkieProvider` and `DocumentProvider` components for improved state management.
	- Implemented a custom hook `useYorkieDoc` for better document management.

- **Refactor**
	- Streamlined component structure and simplified loading/error handling for an improved user experience.
	- Updated function signatures and prop destructuring for better code clarity.
	- Renamed the `useDocument` hook to `useYorkieDoc` to better reflect its purpose.

- **Chores**
	- Updated development configuration to support the latest React capabilities, including JSX transform and Vite plugin integration.
	- Enhanced module exports to include the `Presence` entity for improved functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/examples/react-todomvc/src/App.tsx b/examples/react-todomvc/src/App.tsx
index 44a301a1e..7d0caccaa 100644
--- a/examples/react-todomvc/src/App.tsx
+++ b/examples/react-todomvc/src/App.tsx
@@ -1,46 +1,29 @@
-import { useDocument, JSONArray, JSONObject } from '@yorkie-js/react';
-import 'todomvc-app-css/index.css';
+import { JSONArray, JSONObject, useDocument } from '@yorkie-js/react';
 
 import Header from './Header';
 import MainSection from './MainSection';
 import { Todo } from './model';
 import './App.css';
 
+import 'todomvc-app-css/index.css';
+
 /**
  * `App` is the root component of the application.
  */
 export default function App() {
   const { root, update, loading, error } = useDocument<{
     todos: JSONArray<Todo>;
-  }>(
-    import.meta.env.VITE_YORKIE_API_KEY,
-    `react-todomvc-${new Date().toISOString().substring(0, 10).replace(/-/g, '')}`,
-    {
-      todos: [
-        { id: 0, text: 'Yorkie JS SDK', completed: false },
-        { id: 1, text: 'Garbage collection', completed: false },
-        { id: 2, text: 'RichText datatype', completed: false },
-      ],
-    },
-    {
-      rpcAddr: import.meta.env.VITE_YORKIE_API_ADDR,
-    },
-  );
-
-  if (loading) {
-    return <div>Loading...</div>;
-  }
+  }>();
 
-  if (error) {
-    return <div>Error: {error.message}</div>;
-  }
+  if (loading) return <div>Loading...</div>;
+  if (error) return <div>Error: {error.message}</div>;
 
   const actions = {
     addTodo: (text: string) => {
       update((root) => {
         root.todos.push({
           id:
-            root.todos.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) +
+            root.todos.reduce((maxID, todo) => Math.max(todo.id, maxID), -1) +
             1,
           completed: false,
           text,
diff --git a/examples/react-todomvc/src/MainSection.tsx b/examples/react-todomvc/src/MainSection.tsx
index 2e503cc54..632be24fa 100644
--- a/examples/react-todomvc/src/MainSection.tsx
+++ b/examples/react-todomvc/src/MainSection.tsx
@@ -16,9 +16,8 @@ interface MainSectionProps {
   actions: { [name: string]: Function };
 }
 
-export default function MainSection(props: MainSectionProps) {
+export default function MainSection({ todos, actions }: MainSectionProps) {
   const [filter, setFilter] = useState('SHOW_ALL');
-  const { todos, actions } = props;
   const filteredTodos = todos.filter(TODO_FILTERS[filter]);
   const completedCount = todos.reduce((count, todo) => {
     return todo.completed ? count + 1 : count;
@@ -37,17 +36,15 @@ export default function MainSection(props: MainSectionProps) {
         onChange={actions.completeAll as ChangeEventHandler}
       />
       <ul className="todo-list">
-        {
-          filteredTodos.map((todo) => (
-            <TodoItem
-              key={todo.id}
-              todo={todo}
-              editTodo={actions.editTodo}
-              deleteTodo={actions.deleteTodo}
-              completeTodo={actions.completeTodo}
-            />
-          ))
-        }
+        {filteredTodos.map((todo) => (
+          <TodoItem
+            key={todo.id}
+            todo={todo}
+            editTodo={actions.editTodo}
+            deleteTodo={actions.deleteTodo}
+            completeTodo={actions.completeTodo}
+          />
+        ))}
       </ul>
       <Footer
         completedCount={completedCount}
diff --git a/examples/react-todomvc/src/main.tsx b/examples/react-todomvc/src/main.tsx
index 7ab94a52c..0bc96eda1 100644
--- a/examples/react-todomvc/src/main.tsx
+++ b/examples/react-todomvc/src/main.tsx
@@ -1,7 +1,22 @@
-import React from 'react';
 import ReactDOM from 'react-dom/client';
 import App from './App';
+import { DocumentProvider, YorkieProvider } from '@yorkie-js/react';
+
+const initalRoot = {
+  todos: [
+    { id: 0, text: 'Yorkie JS SDK', completed: false },
+    { id: 1, text: 'Garbage collection', completed: false },
+    { id: 2, text: 'RichText datatype', completed: false },
+  ],
+};
 
 ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-  <App />,
+  <YorkieProvider apiKey={import.meta.env.VITE_YORKIE_API_KEY}>
+    <DocumentProvider
+      docKey={`react-todomvc-${new Date().toISOString().substring(0, 10).replace(/-/g, '')}`}
+      initialRoot={initalRoot}
+    >
+      <App />
+    </DocumentProvider>
+  </YorkieProvider>,
 );
diff --git a/packages/react/src/DocumentProvider.tsx b/packages/react/src/DocumentProvider.tsx
new file mode 100644
index 000000000..50016c4c1
--- /dev/null
+++ b/packages/react/src/DocumentProvider.tsx
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2025 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+  createContext,
+  useCallback,
+  useContext,
+  useEffect,
+  useState,
+} from 'react';
+import { Document, Presence, Indexable } from '@yorkie-js/sdk';
+import { useYorkie } from './YorkieProvider';
+
+type DocumentContextType<R, P extends Indexable = Indexable> = {
+  root: R;
+  presences: { clientID: string; presence: P }[];
+  update: (callback: (root: R) => void) => void;
+  loading: boolean;
+  error: Error | undefined;
+};
+
+const DocumentContext = createContext<DocumentContextType<any> | null>(null);
+
+/**
+ * `DocumentProvider` is a component that provides a document to its children.
+ * This component must be under a `YorkieProvider` component to initialize the
+ * Yorkie client properly.
+ */
+export const DocumentProvider = <R, P extends Indexable = Indexable>({
+  docKey,
+  initialRoot,
+  children,
+}: {
+  docKey: string;
+  initialRoot: R;
+  children: React.ReactNode;
+}) => {
+  const client = useYorkie();
+  const [doc, setDoc] = useState<Document<R, P> | undefined>(undefined);
+  const [loading, setLoading] = useState<boolean>(true);
+  const [error, setError] = useState<Error | undefined>(undefined);
+  const [root, setRoot] = useState(initialRoot);
+  const [presences, setPresences] = useState<
+    { clientID: string; presence: any }[]
+  >([]);
+
+  useEffect(() => {
+    setLoading(true);
+    setError(undefined);
+
+    if (!client) return;
+
+    const newDoc = new Document<R, P>(docKey);
+    async function attachDocument() {
+      try {
+        await client?.attach(newDoc);
+
+        newDoc.subscribe(() => {
+          setRoot({ ...newDoc.getRoot() });
+        });
+
+        newDoc.subscribe('presence', () => {
+          setPresences(newDoc.getPresences());
+        });
+
+        setDoc(newDoc);
+        setRoot({ ...newDoc.getRoot() });
+      } catch (err) {
+        setError(
+          err instanceof Error ? err : new Error('Failed to attach document'),
+        );
+      } finally {
+        setLoading(false);
+      }
+    }
+
+    attachDocument();
+
+    return () => {
+      client.detach(newDoc);
+    };
+  }, [client, docKey]);
+
+  const update = useCallback(
+    (callback: (root: R, presence: Presence<P>) => void) => {
+      if (!doc) {
+        return;
+      }
+
+      try {
+        doc.update(callback);
+      } catch (err) {
+        setError(
+          err instanceof Error ? err : new Error('Failed to update document'),
+        );
+      }
+    },
+    [doc],
+  );
+
+  return (
+    <DocumentContext.Provider
+      value={{ root, presences, update, loading, error }}
+    >
+      {children}
+    </DocumentContext.Provider>
+  );
+};
+
+/**
+ * `useDocument` is a custom hook that returns the root object and update function of the document.
+ * This hook must be used within a `DocumentProvider`.
+ */
+export const useDocument = <R, P extends Indexable = Indexable>() => {
+  const context = useContext(DocumentContext);
+  if (!context) {
+    throw new Error('useDocument must be used within a DocumentProvider');
+  }
+  return {
+    root: context.root as R,
+    presences: context.presences as { clientID: string; presence: P }[],
+    update: context.update as (
+      callback: (root: R, presence: P) => void,
+    ) => void,
+    loading: context.loading,
+    error: context.error,
+  };
+};
+
+/**
+ * `useRoot` is a custom hook that returns the root object of the document.
+ * This hook must be used within a `DocumentProvider`.
+ */
+export const useRoot = () => {
+  const context = useContext(DocumentContext);
+  if (!context) {
+    throw new Error('useRoot must be used within a DocumentProvider');
+  }
+  return { root: context.root };
+};
+
+/**
+ * `usePresences` is a custom hook that returns the presences of the document.
+ */
+export const usePresences = () => {
+  const context = useContext(DocumentContext);
+  if (!context) {
+    throw new Error('usePresences must be used within a DocumentProvider');
+  }
+  return context.presences;
+};
diff --git a/packages/react/src/YorkieProvider.tsx b/packages/react/src/YorkieProvider.tsx
new file mode 100644
index 000000000..554de95a4
--- /dev/null
+++ b/packages/react/src/YorkieProvider.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2025 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+  createContext,
+  PropsWithChildren,
+  useContext,
+  useEffect,
+  useState,
+} from 'react';
+import { Client } from '@yorkie-js/sdk';
+
+const YorkieContext = createContext<{ client: Client | undefined }>({
+  client: undefined,
+});
+
+/**
+ * `YorkieProviderProps` is a set of properties for `YorkieProvider`.
+ */
+interface YorkieProviderProps {
+  apiKey: string;
+  rpcAddr?: string;
+}
+
+/**
+ * `YorkieProvider` is a component that provides the Yorkie client to its children.
+ * It initializes the Yorkie client with the given API key and RPC address.
+ */
+export const YorkieProvider: React.FC<
+  PropsWithChildren<YorkieProviderProps>
+> = ({ apiKey, rpcAddr = 'https://api.yorkie.dev', children }) => {
+  const [client, setClient] = useState<Client | undefined>(undefined);
+
+  useEffect(() => {
+    async function initClient() {
+      const newClient = new Client(rpcAddr, {
+        apiKey,
+      });
+      await newClient.activate();
+      setClient(newClient);
+    }
+    initClient();
+
+    return () => {
+      client?.deactivate({ keepalive: true });
+    };
+  }, [apiKey, rpcAddr]);
+
+  return (
+    <YorkieContext.Provider value={{ client }}>
+      {children}
+    </YorkieContext.Provider>
+  );
+};
+
+export const useYorkie = () => {
+  const context = useContext(YorkieContext);
+  return context?.client;
+};
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index b621f5574..03c1d1e1f 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -15,7 +15,22 @@
  */
 
 import type { JSONArray, JSONObject } from '@yorkie-js/sdk';
-import { useDocument } from './useDocument';
 
-export { useDocument };
+import { useYorkieDoc } from './useYorkieDoc';
+import { YorkieProvider } from './YorkieProvider';
+import {
+  DocumentProvider,
+  useDocument,
+  useRoot,
+  usePresences,
+} from './DocumentProvider';
+
 export type { JSONArray, JSONObject };
+export {
+  YorkieProvider,
+  DocumentProvider,
+  useDocument,
+  useRoot,
+  usePresences,
+  useYorkieDoc,
+};
diff --git a/packages/react/src/useDocument.ts b/packages/react/src/useYorkieDoc.ts
similarity index 95%
rename from packages/react/src/useDocument.ts
rename to packages/react/src/useYorkieDoc.ts
index a214323e0..9307d9e4c 100644
--- a/packages/react/src/useDocument.ts
+++ b/packages/react/src/useYorkieDoc.ts
@@ -18,14 +18,14 @@ import { useCallback, useEffect, useState } from 'react';
 import { Client, Document } from '@yorkie-js/sdk';
 
 /**
- * `useDocument` is a custom hook that returns the root object of the document.
+ * `useYorkieDoc` is a custom hook that initializes a Yorkie client and attaches a document.
  *
  * @param apiKey
  * @param docKey
  * @param initialRoot
  * @returns
  */
-export function useDocument<T>(
+export function useYorkieDoc<T>(
   apiKey: string,
   docKey: string,
   initialRoot: T,
diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json
index 0d2b11a0e..7db391fa3 100644
--- a/packages/react/tsconfig.json
+++ b/packages/react/tsconfig.json
@@ -6,6 +6,7 @@
     "lib": ["ES2020", "DOM"],
     "skipLibCheck": true,
     "outDir": "./lib",
+    "jsx": "react-jsx",
 
     /* Bundler mode */
     "moduleResolution": "node",
diff --git a/packages/react/vite.config.js b/packages/react/vite.config.js
index ea1b3dab2..35e2bcd5a 100644
--- a/packages/react/vite.config.js
+++ b/packages/react/vite.config.js
@@ -1,5 +1,6 @@
 import { defineConfig } from 'vite';
 import dts from 'vite-plugin-dts';
+import react from '@vitejs/plugin-react';
 import path from 'path';
 
 // https://vitejs.dev/config/
@@ -24,6 +25,7 @@ export default defineConfig({
     },
   },
   plugins: [
+    react(),
     dts({
       rollupTypes: true,
     }),
diff --git a/packages/sdk/src/yorkie.ts b/packages/sdk/src/yorkie.ts
index 8abcb9ccc..dff5b1e7e 100644
--- a/packages/sdk/src/yorkie.ts
+++ b/packages/sdk/src/yorkie.ts
@@ -50,6 +50,7 @@ export {
   Document,
   type ChangeInfo,
 } from '@yorkie-js-sdk/src/document/document';
+export { Presence } from '@yorkie-js-sdk/src/document/presence/presence';
 export {
   type Observer,
   type Observable,


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>examples/react-todomvc/src/main.tsx (2)</summary><blockquote>

`5-11`: **Fix the typo in variable name.**

There's a typo in the variable name: `initalRoot` should be `initialRoot`.


```diff
-const initalRoot = {
+const initialRoot = {
  todos: [
    { id: 0, text: 'Yorkie JS SDK', completed: false },
    { id: 1, text: 'Garbage collection', completed: false },
    { id: 2, text: 'RichText datatype', completed: false },
  ],
};
```

---

`14-21`: **Check for environment variable existence.**

The API key is directly accessed without checking if it exists, which could lead to runtime errors if the environment variable is not defined.


Consider adding a fallback or showing a meaningful error:

```diff
-  <YorkieProvider apiKey={import.meta.env.VITE_YORKIE_API_KEY}>
+  <YorkieProvider apiKey={import.meta.env.VITE_YORKIE_API_KEY || ''}>
```

Additionally, you might want to consider if creating a new document each day (based on the date in the docKey) is the intended behavior.

Does the application need to create a new document every day, or should it maintain the same document across days?

</blockquote></details>
<details>
<summary>examples/react-todomvc/src/App.tsx (1)</summary><blockquote>

`32-43`: **Consider optimizing todo lookup.**

The current implementation iterates through all todos to find a matching id. This could be optimized using methods like `find` or by maintaining a map of todos by id.


```diff
deleteTodo: (id: number) => {
  update((root) => {
-    let target: (Todo & JSONObject<Todo>) | undefined;
-    for (const todo of root.todos) {
-      if (todo.id === id) {
-        target = todo as Todo & JSONObject<Todo>;
-        break;
-      }
-    }
+    const target = root.todos.find(todo => todo.id === id) as (Todo & JSONObject<Todo>) | undefined;
    if (target) {
      root.todos.deleteByID!(target.getID!());
    }
  });
},
```

This pattern could be applied to other similar blocks in the file (editTodo, completeTodo).

</blockquote></details>
<details>
<summary>packages/react/src/YorkieProvider.tsx (3)</summary><blockquote>

`1-3`: **Check copyright year**

The copyright notice shows 2025, which is in the future. This should be updated to the current year or the year when the code was created.

```diff
-/*
- * Copyright 2025 The Yorkie Authors. All rights reserved.
+/*
+ * Copyright 2024 The Yorkie Authors. All rights reserved.
```

---

`24-24`: **Consider importing from package root instead of src directory**

Importing directly from `src` folder is generally not recommended as it bypasses the package's public API and may lead to issues if the internal structure changes.

```diff
-import { Client } from '@yorkie-js-sdk/src/yorkie';
+import { Client } from '@yorkie-js/sdk';
```

---

`68-71`: **Improve error handling in useYorkie hook**

The `useYorkie` hook should check if the context exists before accessing the client property to prevent potential runtime errors.

```diff
export const useYorkie = () => {
  const context = useContext(YorkieContext);
+  if (!context) {
+    throw new Error('useYorkie must be used within a YorkieProvider');
+  }
  return context?.client;
};
```

</blockquote></details>
<details>
<summary>packages/react/src/DocumentProvider.tsx (4)</summary><blockquote>

`1-3`: **Check copyright year**

The copyright notice shows 2025, which is in the future. This should be updated to the current year or the year when the code was created.

```diff
-/*
- * Copyright 2025 The Yorkie Authors. All rights reserved.
+/*
+ * Copyright 2024 The Yorkie Authors. All rights reserved.
```

---

`21-27`: **Consider using more specific types instead of 'any'**

Using `any` type loses TypeScript's benefits. Consider using more specific types for `root` and `presence`.

```diff
const DocumentContext = createContext<{
-  root: any;
-  presences: { clientID: string; presence: any }[];
+  root: Indexable;
+  presences: { clientID: string; presence: Record<string, unknown> }[];
  update: (callback: (root: any) => void) => void;
  loading: boolean;
  error: Error | undefined;
} | null>(null);
```

---

`80-84`: **Add loading and error state to the update function**

The `update` function should handle potential errors and update loading state to provide better feedback to users.

```diff
const update = (callback: (root: any) => void) => {
  if (doc) {
-    doc.update(callback);
+    try {
+      setLoading(true);
+      doc.update(callback);
+    } catch (err) {
+      setError(err instanceof Error ? err : new Error('Failed to update document'));
+    } finally {
+      setLoading(false);
+    }
  }
};
```

---

`125-134`: **Complete documentation for usePresence hook**

The `usePresence` hook has incomplete documentation compared to other hooks.

```diff
/**
 * `usePresence` is a custom hook that returns the presence of the document.
+ * This hook must be used within a `DocumentProvider`.
 */
export const usePresence = () => {
  const context = useContext(DocumentContext);
  if (!context) {
    throw new Error('usePresence must be used within a DocumentProvider');
  }
  return context.presences;
};
```

</blockquote></details>
<details>
<summary>packages/react/src/index.ts (1)</summary><blockquote>

`29-36`: **Consider adding JSDoc comments for exported entities**

To improve developer experience, consider adding JSDoc comments for the exported entities that briefly explain their purpose.

```diff
export type { JSONArray, JSONObject };
+/**
+ * Exports components and hooks for Yorkie integration with React
+ * - YorkieProvider: Provider for Yorkie client
+ * - DocumentProvider: Provider for Yorkie document
+ * - useDocument: Hook to access document data
+ * - useRoot: Hook to access document root
+ * - usePresence: Hook to access document presences
+ * - useYorkieDoc: Legacy hook for Yorkie document
+ */
export {
  YorkieProvider,
  DocumentProvider,
  useDocument,
  useRoot,
  usePresence,
  useYorkieDoc,
};
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 11116369ed9fa5678814e0af75440ff9ee9db7ee and 96a75b5526dcb04920e3d1f9b14089bdeb990e00.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `examples/react-todomvc/src/App.tsx` (1 hunks)
* `examples/react-todomvc/src/MainSection.tsx` (2 hunks)
* `examples/react-todomvc/src/main.tsx` (1 hunks)
* `packages/react/src/DocumentProvider.tsx` (1 hunks)
* `packages/react/src/YorkieProvider.tsx` (1 hunks)
* `packages/react/src/index.ts` (1 hunks)
* `packages/react/src/useYorkieDoc.ts` (1 hunks)
* `packages/react/tsconfig.json` (1 hunks)
* `packages/react/vite.config.js` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* packages/react/src/useYorkieDoc.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build (18.x)

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>packages/react/tsconfig.json (1)</summary>

`9-9`: **Good addition of React JSX support!**

Adding `"jsx": "react-jsx"` to the TypeScript configuration is essential for the new React components being introduced. This enables the newer JSX transform from React 17+ which is more efficient and doesn't require importing React in every JSX file.

</details>
<details>
<summary>packages/react/vite.config.js (1)</summary>

`3-3`: **Appropriate addition of the React Vite plugin.**

Adding the React plugin is necessary for proper React support in the Vite build process. This enables React-specific optimizations, Fast Refresh during development, and proper JSX handling.



Also applies to: 28-28

</details>
<details>
<summary>examples/react-todomvc/src/main.tsx (1)</summary>

`3-3`: **Good import of the new provider components.**

The import of `DocumentProvider` and `YorkieProvider` aligns with the PR objectives to introduce these new components.

</details>
<details>
<summary>examples/react-todomvc/src/App.tsx (4)</summary>

`1-1`: **Clean import statement from the React package.**

The import now correctly references the components from the '@yorkie-js/react' package, which aligns with the introduction of the new providers.

---

`14-14`: **Simplified useDocument hook usage.**

The hook call has been simplified by removing the explicit configuration, as it's now handled by the providers in `main.tsx`. This follows good React practices by moving configuration to higher-level components.

---

`16-17`: **Improved conditional rendering.**

The loading and error states now use concise return statements instead of block statements, which is a more modern and readable React pattern.

---

`24-24`: **Consistent variable naming.**

Changed from `maxId` to `maxID`, which provides better consistency in the codebase.

</details>
<details>
<summary>examples/react-todomvc/src/MainSection.tsx (2)</summary>

`19-19`: **LGTM: Good use of parameter destructuring**

The refactoring to use destructuring in the function parameters improves code readability.

---

`39-47`: **LGTM: Improved JSX formatting**

The JSX formatting changes make the code more concise while maintaining readability.

</details>
<details>
<summary>packages/react/src/index.ts (1)</summary>

`19-26`: **LGTM: Well-organized imports**

The new import structure clearly organizes all the necessary components and hooks.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-05T10:19:26Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>examples/react-todomvc/src/main.tsx (2)</summary><blockquote>

`5-5`: **Fix typo in variable name `initalRoot`**

The variable name has a spelling error - it should be `initialRoot` instead of `initalRoot` (missing an "i").

```diff
-const initalRoot = {
+const initialRoot = {
```

---

`14-21`: **Consider more unique docKey generation for multi-user scenarios**

The current docKey is based only on the date, which could lead to collisions if multiple users access the application on the same day.

```diff
-      docKey={`react-todomvc-${new Date().toISOString().substring(0, 10).replace(/-/g, '')}`}
+      docKey={`react-todomvc-${new Date().toISOString().substring(0, 10).replace(/-/g, '')}-${Math.random().toString(36).substring(2, 9)}`}
```

</blockquote></details>
<details>
<summary>packages/react/src/DocumentProvider.tsx (3)</summary><blockquote>

`51-58`: **Consider adding a timeout or retry mechanism for document attachment**

The document attachment logic could benefit from a timeout or retry mechanism to handle network issues or service unavailability.

```diff
useEffect(() => {
  setLoading(true);
  setError(undefined);

  if (!client) return;

  const newDoc = new Document<R, P>(docKey);
+  let retryCount = 0;
+  const maxRetries = 3;

  async function attachDocument() {
+    if (retryCount >= maxRetries) {
+      setError(new Error(`Failed to attach document after ${maxRetries} attempts`));
+      setLoading(false);
+      return;
+    }
```

---

`135-137`: **Complete the JSDoc comment for usePresence**

The JSDoc comment for `usePresence` is incomplete. It should mention that the hook must be used within a DocumentProvider like the other hook comments do.

```diff
/**
- * `usePresence` is a custom hook that returns the presence of the document.
+ * `usePresence` is a custom hook that returns the presence of the document.
+ * This hook must be used within a `DocumentProvider`.
 */
```

---

`57-86`: **Consider using a more robust document attachment pattern**

The document attachment logic could be improved by separating the subscription setup into its own function and adding more robust error handling.

```diff
const newDoc = new Document<R, P>(docKey);
async function attachDocument() {
  try {
    await client?.attach(newDoc);
-
-    newDoc.subscribe(() => {
-      setRoot({ ...newDoc.getRoot() });
-    });
-
-    newDoc.subscribe('presence', () => {
-      setPresences(newDoc.getPresences());
-    });
+    setupSubscriptions(newDoc);
    
    setDoc(newDoc);
    setRoot({ ...newDoc.getRoot() });
  } catch (err) {
    setError(
      err instanceof Error ? err : new Error('Failed to attach document'),
    );
  } finally {
    setLoading(false);
  }
}

+function setupSubscriptions(document: Document<R, P>) {
+  document.subscribe(() => {
+    setRoot({ ...document.getRoot() });
+  });
+
+  document.subscribe('presence', () => {
+    setPresences(document.getPresences());
+  });
+}

attachDocument();

return () => {
-  client.detach(newDoc);
+  if (client && newDoc) {
+    try {
+      client.detach(newDoc);
+    } catch (err) {
+      console.error('Failed to detach document:', err);
+    }
+  }
};
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 96a75b5526dcb04920e3d1f9b14089bdeb990e00 and 1e379761918abed8b75218579add009c444e7b2c.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `examples/react-todomvc/src/App.tsx` (1 hunks)
* `examples/react-todomvc/src/MainSection.tsx` (2 hunks)
* `examples/react-todomvc/src/main.tsx` (1 hunks)
* `packages/react/src/DocumentProvider.tsx` (1 hunks)
* `packages/react/src/YorkieProvider.tsx` (1 hunks)
* `packages/react/src/index.ts` (1 hunks)
* `packages/react/src/useYorkieDoc.ts` (1 hunks)
* `packages/react/tsconfig.json` (1 hunks)
* `packages/react/vite.config.js` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (6)</summary>

* packages/react/vite.config.js
* packages/react/tsconfig.json
* packages/react/src/useYorkieDoc.ts
* packages/react/src/YorkieProvider.tsx
* examples/react-todomvc/src/App.tsx
* examples/react-todomvc/src/MainSection.tsx

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>examples/react-todomvc/src/main.tsx (1)</summary>

`5-11`: **The default todos look good**

The initial todo items provide a clear example of how the todos should be structured, with appropriate fields (id, text, completed).

</details>
<details>
<summary>packages/react/src/index.ts (2)</summary>

`17-28`: **Organized imports look clean**

The structure of imports is well-organized, clearly separating the core types from the custom hooks and providers.

---

`29-36`: **Well-structured exports provide a complete public API**

The exports include all necessary components (YorkieProvider, DocumentProvider) and hooks (useDocument, useRoot, usePresence, useYorkieDoc), making them accessible to consumers of the package.

</details>
<details>
<summary>packages/react/src/DocumentProvider.tsx (4)</summary>

`18-18`: **Use consistent import path for Yorkie SDK**

There appears to be an inconsistency in import paths across files. Ensure that all files use the same import path for the Yorkie SDK.

---

`21-27`: **Well-defined context type with appropriate fields**

The `DocumentContextType` is well-structured, including all necessary fields for document management: root, presences, update function, loading state, and error handling.

---

`33-41`: **Good use of generics for type safety**

The `DocumentProvider` component properly uses TypeScript generics to ensure type safety across the document root and presence objects.

---

`83-85`: **Add client null check in cleanup function**

The cleanup function should check if the client exists before attempting to detach the document.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-05T11:03:20Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>packages/react/src/DocumentProvider.tsx (2)</summary><blockquote>

`86-88`: _⚠️ Potential issue_

**Add client null check in cleanup function**

The cleanup function should check if the client exists before attempting to detach the document.



```diff
return () => {
-  client.detach(newDoc);
+  if (client) {
+    client.detach(newDoc);
+  }
};
```

---

`119-120`: _⚠️ Potential issue_

**Fix type mismatch in useDocument update function**

The update function type defined in `useDocument` doesn't match the original context definition. The callback in the returned object includes a presence parameter that isn't present in the context type definition.




```diff
update: context.update as (
-  callback: (root: R, presence: P) => void,
+  callback: (root: R) => void,
) => void,
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>packages/react/src/DocumentProvider.tsx (2)</summary><blockquote>

`138-147`: **Complete JSDoc comment for usePresences hook**

The JSDoc comment for `usePresences` is incomplete compared to other hooks. It should include the same information about it being used within a `DocumentProvider`.



```diff
/**
 * `usePresences` is a custom hook that returns the presences of the document.
+ * This hook must be used within a `DocumentProvider`.
 */
```

---

`65-67`: **Consider optimizing root object updates**

Creating a new object on every update with `{ ...newDoc.getRoot() }` could lead to performance issues if the root object is large. Consider implementing a more efficient update mechanism that only triggers re-renders when necessary.



```diff
newDoc.subscribe(() => {
-  setRoot({ ...newDoc.getRoot() });
+  // Only update if there are actual changes
+  const newRoot = newDoc.getRoot();
+  setRoot(prevRoot => {
+    // Simple comparison might not be sufficient for complex objects
+    // Consider using a deep comparison library if needed
+    return JSON.stringify(prevRoot) !== JSON.stringify(newRoot) ? { ...newRoot } : prevRoot;
+  });
});
```

</blockquote></details>
<details>
<summary>packages/sdk/src/yorkie.ts (1)</summary><blockquote>

`53-53`: **Ensure Presence is included in default export and globalThis**

You've correctly exported the Presence type, but for consistency, consider also adding it to the default export object (lines 142-153) and the globalThis exposure (lines 156-169).



```diff
export default {
  Client,
  Document,
  Primitive,
  Text,
  Counter,
  Tree,
+ Presence,
  LogLevel,
  setLogLevel,
  IntType: CounterType.IntegerCnt,
  LongType: CounterType.LongCnt,
};

// And also update the globalThis definition
if (typeof globalThis !== 'undefined') {
  (globalThis as any).yorkie = {
    Client,
    Document,
    Primitive,
    Text,
    Counter,
    Tree,
+   Presence,
    LogLevel,
    setLogLevel,
    IntType: CounterType.IntegerCnt,
    LongType: CounterType.LongCnt,
  };
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1e379761918abed8b75218579add009c444e7b2c and 28d783e9880a09ea68ee50128b9a5192dffb0fdc.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `examples/react-todomvc/src/App.tsx` (1 hunks)
* `packages/react/src/DocumentProvider.tsx` (1 hunks)
* `packages/react/src/YorkieProvider.tsx` (1 hunks)
* `packages/react/src/index.ts` (1 hunks)
* `packages/sdk/src/yorkie.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* examples/react-todomvc/src/App.tsx
* packages/react/src/YorkieProvider.tsx

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build (18.x)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-06T01:07:34Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>packages/react/src/DocumentProvider.tsx (3)</summary><blockquote>

`86-88`: _⚠️ Potential issue_

**Add client null check in cleanup function**

The cleanup function should check if the client exists before attempting to detach the document to prevent potential errors if the component unmounts when the client is null or undefined.



```diff
return () => {
-  client.detach(newDoc);
+  if (client) {
+    client.detach(newDoc);
+  }
};
```

---

`91-95`: _⚠️ Potential issue_

**Fix parameter type mismatch and add error handling to update function**

There are two issues with the update function:
1. The parameter type (`callback: (root: R, presence: Presence<P>) => void`) doesn't match the type defined in `DocumentContextType` on line 24 (`update: (callback: (root: R) => void) => void`).
2. The function lacks error handling to prevent unhandled exceptions.




```diff
-  const update = (callback: (root: R, presence: Presence<P>) => void) => {
+  const update = (callback: (root: R) => void) => {
     if (doc) {
-      doc.update(callback);
+      try {
+        doc.update(callback);
+      } catch (err) {
+        setError(err instanceof Error ? err : new Error('Failed to update document'));
+      }
     }
   };
```

---

`116-123`: _⚠️ Potential issue_

**Fix type mismatch in useDocument update function**

The update function type in the useDocument hook doesn't match what's defined in the DocumentContextType. The callback includes a presence parameter that's not present in the context type definition.



```diff
update: context.update as (
-  callback: (root: R, presence: P) => void,
+  callback: (root: R) => void,
) => void,
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>packages/react/src/DocumentProvider.tsx (3)</summary><blockquote>

`138-147`: **Update usePresences documentation for consistency**

The documentation for usePresences is missing the statement that it must be used within a DocumentProvider, unlike the other hooks which include this important information.



```diff
/**
 * `usePresences` is a custom hook that returns the presences of the document.
+ * This hook must be used within a `DocumentProvider`.
 */
```

---

`45-52`: **Optimize state initialization and add types for better type safety**

The state initialization could be more type-safe and explicit to prevent potential type errors.



```diff
  const client = useYorkie();
-  const [doc, setDoc] = useState<Document<R, P> | undefined>(undefined);
+  const [doc, setDoc] = useState<Document<R, P> | undefined>();
  const [loading, setLoading] = useState<boolean>(true);
-  const [error, setError] = useState<Error | undefined>(undefined);
+  const [error, setError] = useState<Error | undefined>();
  const [root, setRoot] = useState(initialRoot);
  const [presences, setPresences] = useState<
-    { clientID: string; presence: any }[]
+    { clientID: string; presence: P }[]
  >([]);
```

---

`97-103`: **Memoize the context value to prevent unnecessary re-renders**

The context value object is recreated on every render, which can cause unnecessary re-renders in consuming components. Consider memoizing it with useMemo.



```diff
+ import { createContext, useContext, useEffect, useState, useMemo } from 'react';

  // ...

+  const contextValue = useMemo(
+    () => ({ root, presences, update, loading, error }),
+    [root, presences, update, loading, error]
+  );

  return (
    <DocumentContext.Provider
-      value={{ root, presences, update, loading, error }}
+      value={contextValue}
    >
      {children}
    </DocumentContext.Provider>
  );
```

</blockquote></details>
<details>
<summary>packages/react/src/index.ts (1)</summary><blockquote>

`28-36`: **Consider adding proper JSDoc comments for exports**

Adding JSDoc comments for the exported components and hooks would improve the developer experience by providing documentation directly in the IDE.



```diff
export type { JSONArray, JSONObject };
+
+/**
+ * Provides Yorkie client context to the application
+ */
export {
  YorkieProvider,
+
+/**
+ * Provides document context to manage document state
+ */
  DocumentProvider,
+
+/**
+ * Hook to access document state including root, presences, update function, loading and error states
+ */
  useDocument,
+
+/**
+ * Hook to access only the root object of the document
+ */
  useRoot,
+
+/**
+ * Hook to access only the presences of the document
+ */
  usePresences,
+
+/**
+ * Legacy hook for backward compatibility
+ */
  useYorkieDoc,
};
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 28d783e9880a09ea68ee50128b9a5192dffb0fdc and a05528922162a5936c935614cabfea930d0de090.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `examples/react-todomvc/src/App.tsx` (1 hunks)
* `packages/react/src/DocumentProvider.tsx` (1 hunks)
* `packages/react/src/YorkieProvider.tsx` (1 hunks)
* `packages/react/src/index.ts` (1 hunks)
* `packages/sdk/src/yorkie.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* examples/react-todomvc/src/App.tsx
* packages/react/src/YorkieProvider.tsx
* packages/sdk/src/yorkie.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build (18.x)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>packages/react/src/index.ts (1)</summary>

`25-26`: <details>
<summary>✅ Verification successful</summary>

**Maintain consistent naming between import and export for usePresences**

There's a potential naming consistency issue with `usePresences` (plural form). If this is intended to be used as a singular concept elsewhere in the codebase, consider standardizing the naming to prevent confusion.





Also applies to: 34-35

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Check how the usePresences hook is used in the codebase to verify naming consistency

# Check for usage of usePresence(s) in the codebase
echo "Checking for usage of usePresence(s) in the codebase:"
rg -i "usePresence" --type ts --type tsx

# Check for patterns related to presence in the codebase
echo -e "\nChecking for presence-related patterns:"
rg -i "presence" --type ts --type tsx | grep -i "function\|hook\|const"
```

Length of output: 380

---

Below is a new shell script to re-run the verification using file globs rather than file type identifiers. This should accurately search for both “usePresences” and “usePresence” in all TS/TSX files:



Please run the above script to gather a clearer picture of how these hooks are used across the codebase. Once you’ve confirmed that the naming is consistent (or identified areas where singular versus plural usage might cause confusion), consider one of the following actions:
 
- **If usage is consistent:** Approve keeping `usePresences` as is.
- **If naming differs (e.g. both singular and plural forms are used):** Standardize the naming (either switch to `usePresence` or update all occurrences to `usePresences`) to avoid confusion. This applies to the import at lines 25–26 and the similar pattern at lines 34–35 in `packages/react/src/index.ts`.

Once you verify this manually (or if the re-run script provides the expected output), please update the code as needed.

**Action Required: Verify Naming Consistency for usePresences Across TS/TSX Files**  
- Confirm consistent usage of `usePresences` vs. `usePresence` in the DocumentProvider and elsewhere.
- Update naming if discrepancies are detected.



---

🏁 Script executed:

```shell

#!/bin/bash
# Re-run search with file globs to verify naming consistency for usePresences
echo "Searching for 'usePresences' occurrences in TS/TSX files:"
rg -i "usePresences" --glob "*.ts" --glob "*.tsx"

echo -e "\nSearching for 'usePresence' occurrences in TS/TSX files:"
rg -i "usePresence" --glob "*.ts" --glob "*.tsx"

echo -e "\nSearching for generic 'presence' patterns (function/hook/const) in TS/TSX files:"
rg -i "presence" --glob "*.ts" --glob "*.tsx" | grep -i -E "function|hook|const"
```

Length of output: 16600

---



</details>

**Naming Consistency Verified – No Changes Needed**

After verifying the codebase, the custom hook is consistently exported and imported as `usePresences` in both `packages/react/src/DocumentProvider.tsx` and `packages/react/src/index.ts` (lines 25–26 and 34–35). No occurrences of a singular form (`usePresence`) were found, so the current naming is consistent across the codebase.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-06T01:13:34Z

Review comments:
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * Copyright 2025 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+  createContext,
+  PropsWithChildren,
+  useContext,
+  useEffect,
+  useState,
+} from 'react';
+import { Client } from '@yorkie-js-sdk/src/yorkie';
+
+const YorkieContext = createContext<{ client: Client | undefined }>({
+  client: undefined,
+});
+
+/**
+ * `YorkieProviderProps` is a set of properties for `YorkieProvider`.
+ */
+interface YorkieProviderProps {
+  apiKey: string;
+  rpcAddr?: string;
+}
+
+/**
+ * `YorkieProvider` is a component that provides the Yorkie client to its children.
+ */
+export const YorkieProvider: React.FC<
+  PropsWithChildren<YorkieProviderProps>
+> = ({ apiKey, rpcAddr = 'https://api.yorkie.dev', children }) => {
+  const [client, setClient] = useState<Client | undefined>(undefined);
+
+  useEffect(() => {
+    async function initClient() {
+      const newClient = new Client(rpcAddr, {
+        apiKey,
+      });
+      await newClient.activate();
+      setClient(newClient);
+    }
+    initClient();
+
+    return () => {
+      client?.deactivate({ keepalive: true });
+    };
+  }, [apiKey]);
path: packages/react/src/YorkieProvider.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add missing dependency and fix potential stale closure**

Your `useEffect` has two issues:
1. `rpcAddr` is used in the effect but not included in the dependency array
2. The cleanup function uses the `client` state which may become stale


```diff
  useEffect(() => {
    async function initClient() {
      const newClient = new Client(rpcAddr, {
        apiKey,
      });
      await newClient.activate();
      setClient(newClient);
    }
    initClient();

    return () => {
-     client?.deactivate({ keepalive: true });
+     // Use a ref to the current client to ensure it's not stale
+     if (newClient) {
+       newClient.deactivate({ keepalive: true });
+     }
    };
-  }, [apiKey]);
+  }, [apiKey, rpcAddr]);
```


> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-05T10:19:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,134 @@
+/*
+ * Copyright 2025 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createContext, useContext, useEffect, useState } from 'react';
+import { Document, Indexable } from '@yorkie-js/sdk';
+import { useYorkie } from './YorkieProvider';
+
+const DocumentContext = createContext<{
+  root: any;
+  presences: { clientID: string; presence: any }[];
+  update: (callback: (root: any) => void) => void;
+  loading: boolean;
+  error: Error | undefined;
+} | null>(null);
+
+export const DocumentProvider: React.FC<{
+  docKey: string;
+  initialRoot: Indexable;
+  children: React.ReactNode;
+}> = ({ docKey, initialRoot, children }) => {
+  const client = useYorkie();
+  const [doc, setDoc] = useState<Document<Indexable> | undefined>(undefined);
+  const [loading, setLoading] = useState<boolean>(true);
+  const [error, setError] = useState<Error | undefined>(undefined);
+  const [root, setRoot] = useState(initialRoot);
+  const [presences, setPresences] = useState<
+    { clientID: string; presence: any }[]
+  >([]);
+
+  useEffect(() => {
+    setLoading(true);
+    setError(undefined);
+
+    if (!client) return;
+
+    const newDoc = new Document<Indexable>(docKey);
+    async function attachDocument() {
+      try {
+        await client?.attach(newDoc, { initialPresence: {} });
+
+        newDoc.subscribe(() => {
+          setRoot({ ...newDoc.getRoot() });
+        });
+
+        newDoc.subscribe('presence', () => {
+          setPresences(newDoc.getPresences());
+        });
+
+        setDoc(newDoc);
+        setRoot({ ...newDoc.getRoot() });
+      } catch (err) {
+        setError(
+          err instanceof Error ? err : new Error('Failed to attach document'),
+        );
+      } finally {
+        setLoading(false);
+      }
+    }
+
+    attachDocument();
+
+    return () => {
+      client.detach(newDoc);
+    };
path: packages/react/src/DocumentProvider.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add client null check in cleanup function**

The cleanup function should check if the client exists before attempting to detach the document.

```diff
return () => {
-  client.detach(newDoc);
+  if (client) {
+    client.detach(newDoc);
+  }
};
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    return () => {
      if (client) {
        client.detach(newDoc);
      }
    };
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-05T10:19:26Z
author_association: NONE
diff_hunk: @@ -0,0 +1,134 @@
+/*
+ * Copyright 2025 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createContext, useContext, useEffect, useState } from 'react';
+import { Document, Indexable } from '@yorkie-js/sdk';
path: packages/react/src/DocumentProvider.tsx
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Ensure consistent import path with other files**

The import path for Yorkie SDK differs from the one used in `YorkieProvider.tsx`. Maintain consistency across files.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check for import consistency across files
grep -r "import.*yorkie" packages/react/src/
```

Length of output: 405

---



</details>

**Action Required: Unify Yorkie SDK Import Path**

The majority of files (e.g., `useYorkieDoc.ts` and `index.ts`) import the Yorkie SDK using:

```js
import { ... } from '@yorkie-js/sdk';
```

However, in `YorkieProvider.tsx` the import is different:

```diff
- import { Client } from '@yorkie-js-sdk/src/yorkie';
+ import { Client } from '@yorkie-js/sdk';
```

**Please update `YorkieProvider.tsx` to use the consistent import path `@yorkie-js/sdk` so that all modules remain aligned.**

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-05T10:19:26Z
author_association: NONE
diff_hunk: @@ -1,7 +1,22 @@
-import React from 'react';
 import ReactDOM from 'react-dom/client';
 import App from './App';
+import { DocumentProvider, YorkieProvider } from '@yorkie-js/react';
+
+const initalRoot = {
+  todos: [
+    { id: 0, text: 'Yorkie JS SDK', completed: false },
+    { id: 1, text: 'Garbage collection', completed: false },
+    { id: 2, text: 'RichText datatype', completed: false },
+  ],
+};
 
 ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-  <App />,
+  <YorkieProvider apiKey={import.meta.env.VITE_YORKIE_API_KEY}>
path: examples/react-todomvc/src/main.tsx
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Verify environment variable availability for the API key**

The code relies on `import.meta.env.VITE_YORKIE_API_KEY` but doesn't include any fallback if this environment variable is missing.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the environment variable is defined in any .env files

echo "Checking for VITE_YORKIE_API_KEY in .env files..."
grep -r "VITE_YORKIE_API_KEY" --include="*.env*" .

echo "Checking for usage of the environment variable in other files..."
grep -r "VITE_YORKIE_API_KEY" --include="*.tsx" --include="*.ts" --exclude="main.tsx" .
```

Length of output: 1710

---



</details>

**Action Required: Implement Fallback/Error Handling for Missing API Key**

- The `<YorkieProvider>` in `examples/react-todomvc/src/main.tsx` uses `import.meta.env.VITE_YORKIE_API_KEY` without verifying if a valid API key is provided.
- Our investigation shows that while the production environment (.env.production) supplies a value, the standard `.env` for react-todomvc has an empty entry, leaving the API key unset in development.
- Please add a fallback (or error messaging) mechanism to ensure the API key is valid at runtime, preventing potential failures when the variable is missing or empty.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-05T11:03:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,144 @@
+/*
+ * Copyright 2025 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createContext, useContext, useEffect, useState } from 'react';
+import { Document, Indexable } from '@yorkie-js/sdk';
+import { useYorkie } from './YorkieProvider';
+
+type DocumentContextType<R, P> = {
+  root: R;
+  presences: { clientID: string; presence: P }[];
+  update: (callback: (root: R) => void) => void;
+  loading: boolean;
+  error: Error | undefined;
+};
+
+const DocumentContext = createContext<DocumentContextType<any, any> | null>(
+  null,
+);
+
+export const DocumentProvider = <R, P extends Indexable>({
+  docKey,
+  initialRoot,
+  children,
+}: {
+  docKey: string;
+  initialRoot: R;
+  children: React.ReactNode;
+}) => {
+  const client = useYorkie();
+  const [doc, setDoc] = useState<Document<R, P> | undefined>(undefined);
+  const [loading, setLoading] = useState<boolean>(true);
+  const [error, setError] = useState<Error | undefined>(undefined);
+  const [root, setRoot] = useState(initialRoot);
+  const [presences, setPresences] = useState<
+    { clientID: string; presence: any }[]
+  >([]);
+
+  useEffect(() => {
+    setLoading(true);
+    setError(undefined);
+
+    if (!client) return;
+
+    const newDoc = new Document<R, P>(docKey);
+    async function attachDocument() {
+      try {
+        await client?.attach(newDoc);
+
+        newDoc.subscribe(() => {
+          setRoot({ ...newDoc.getRoot() });
+        });
+
+        newDoc.subscribe('presence', () => {
+          setPresences(newDoc.getPresences());
+        });
+
+        setDoc(newDoc);
+        setRoot({ ...newDoc.getRoot() });
+      } catch (err) {
+        setError(
+          err instanceof Error ? err : new Error('Failed to attach document'),
+        );
+      } finally {
+        setLoading(false);
+      }
+    }
+
+    attachDocument();
+
+    return () => {
+      client.detach(newDoc);
+    };
+  }, [client, docKey]);
+
+  const update = (callback: (root: any) => void) => {
+    if (doc) {
+      doc.update(callback);
+    }
+  };
+
+  return (
+    <DocumentContext.Provider
+      value={{ root, presences, update, loading, error }}
+    >
+      {children}
+    </DocumentContext.Provider>
+  );
+};
+
+/**
+ * `useDocument` is a custom hook that returns the root object and update function of the document.
+ * This hook must be used within a `DocumentProvider`.
+ */
+export const useDocument = <R, P extends Indexable>() => {
+  const context = useContext(DocumentContext);
+  if (!context) {
+    throw new Error('useDocument must be used within a DocumentProvider');
+  }
+  return {
+    root: context.root as R,
+    presences: context.presences as { clientID: string; presence: P }[],
+    update: context.update as (
+      callback: (root: R, presence: P) => void,
+    ) => void,
+    loading: context.loading,
+    error: context.error,
+  };
+};
path: packages/react/src/DocumentProvider.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix type mismatch in useDocument update function**

The update function type defined in `useDocument` doesn't match the original context definition. The callback in line 116 includes a presence parameter that isn't present in the context type.

```diff
update: context.update as (
-  callback: (root: R, presence: P) => void,
+  callback: (root: R) => void,
) => void,
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export const useDocument = <R, P extends Indexable>() => {
  const context = useContext(DocumentContext);
  if (!context) {
    throw new Error('useDocument must be used within a DocumentProvider');
  }
  return {
    root: context.root as R,
    presences: context.presences as { clientID: string; presence: P }[],
    update: context.update as (
      callback: (root: R) => void,
    ) => void,
    loading: context.loading,
    error: context.error,
  };
};
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-05T11:03:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,144 @@
+/*
+ * Copyright 2025 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createContext, useContext, useEffect, useState } from 'react';
+import { Document, Indexable } from '@yorkie-js/sdk';
+import { useYorkie } from './YorkieProvider';
+
+type DocumentContextType<R, P> = {
+  root: R;
+  presences: { clientID: string; presence: P }[];
+  update: (callback: (root: R) => void) => void;
+  loading: boolean;
+  error: Error | undefined;
+};
+
+const DocumentContext = createContext<DocumentContextType<any, any> | null>(
+  null,
+);
+
+export const DocumentProvider = <R, P extends Indexable>({
+  docKey,
+  initialRoot,
+  children,
+}: {
+  docKey: string;
+  initialRoot: R;
+  children: React.ReactNode;
+}) => {
+  const client = useYorkie();
+  const [doc, setDoc] = useState<Document<R, P> | undefined>(undefined);
+  const [loading, setLoading] = useState<boolean>(true);
+  const [error, setError] = useState<Error | undefined>(undefined);
+  const [root, setRoot] = useState(initialRoot);
+  const [presences, setPresences] = useState<
+    { clientID: string; presence: any }[]
+  >([]);
+
+  useEffect(() => {
+    setLoading(true);
+    setError(undefined);
+
+    if (!client) return;
+
+    const newDoc = new Document<R, P>(docKey);
+    async function attachDocument() {
+      try {
+        await client?.attach(newDoc);
+
+        newDoc.subscribe(() => {
+          setRoot({ ...newDoc.getRoot() });
+        });
+
+        newDoc.subscribe('presence', () => {
+          setPresences(newDoc.getPresences());
+        });
+
+        setDoc(newDoc);
+        setRoot({ ...newDoc.getRoot() });
+      } catch (err) {
+        setError(
+          err instanceof Error ? err : new Error('Failed to attach document'),
+        );
+      } finally {
+        setLoading(false);
+      }
+    }
+
+    attachDocument();
+
+    return () => {
+      client.detach(newDoc);
+    };
+  }, [client, docKey]);
+
+  const update = (callback: (root: any) => void) => {
+    if (doc) {
+      doc.update(callback);
+    }
+  };
path: packages/react/src/DocumentProvider.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling to the update function**

The update function should include try-catch error handling to prevent unhandled exceptions when document updates fail.

```diff
const update = (callback: (root: any) => void) => {
  if (doc) {
-    doc.update(callback);
+    try {
+      doc.update(callback);
+    } catch (err) {
+      setError(err instanceof Error ? err : new Error('Failed to update document'));
+    }
  }
};
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  const update = (callback: (root: any) => void) => {
    if (doc) {
      try {
        doc.update(callback);
      } catch (err) {
        setError(err instanceof Error ? err : new Error('Failed to update document'));
      }
    }
  };
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-05T11:03:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,147 @@
+/*
+ * Copyright 2025 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createContext, useContext, useEffect, useState } from 'react';
+import { Document, Presence, Indexable } from '@yorkie-js/sdk';
+import { useYorkie } from './YorkieProvider';
+
+type DocumentContextType<R, P extends Indexable = Indexable> = {
+  root: R;
+  presences: { clientID: string; presence: P }[];
+  update: (callback: (root: R) => void) => void;
+  loading: boolean;
+  error: Error | undefined;
+};
+
+const DocumentContext = createContext<DocumentContextType<any> | null>(null);
+
+/**
+ * `DocumentProvider` is a component that provides a document to its children.
+ * This component must be under a `YorkieProvider` component to initialize the
+ * Yorkie client properly.
+ */
+export const DocumentProvider = <R, P extends Indexable = Indexable>({
+  docKey,
+  initialRoot,
+  children,
+}: {
+  docKey: string;
+  initialRoot: R;
+  children: React.ReactNode;
+}) => {
+  const client = useYorkie();
+  const [doc, setDoc] = useState<Document<R, P> | undefined>(undefined);
+  const [loading, setLoading] = useState<boolean>(true);
+  const [error, setError] = useState<Error | undefined>(undefined);
+  const [root, setRoot] = useState(initialRoot);
+  const [presences, setPresences] = useState<
+    { clientID: string; presence: any }[]
+  >([]);
+
+  useEffect(() => {
+    setLoading(true);
+    setError(undefined);
+
+    if (!client) return;
+
+    const newDoc = new Document<R, P>(docKey);
+    async function attachDocument() {
+      try {
+        await client?.attach(newDoc);
+
+        newDoc.subscribe(() => {
+          setRoot({ ...newDoc.getRoot() });
+        });
+
+        newDoc.subscribe('presence', () => {
+          setPresences(newDoc.getPresences());
+        });
+
+        setDoc(newDoc);
+        setRoot({ ...newDoc.getRoot() });
+      } catch (err) {
+        setError(
+          err instanceof Error ? err : new Error('Failed to attach document'),
+        );
+      } finally {
+        setLoading(false);
+      }
+    }
+
+    attachDocument();
+
+    return () => {
+      client.detach(newDoc);
+    };
+  }, [client, docKey]);
+
+  const update = (callback: (root: R, presence: Presence<P>) => void) => {
+    if (doc) {
+      doc.update(callback);
+    }
+  };
path: packages/react/src/DocumentProvider.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix parameter type mismatch and add error handling to update function**

There are two issues with the update function:
1. The parameter type (`callback: (root: R, presence: Presence<P>) => void`) doesn't match the type defined in `DocumentContextType` on line 24 (`update: (callback: (root: R) => void) => void`).
2. The function lacks error handling to prevent unhandled exceptions.



```diff
-  const update = (callback: (root: R, presence: Presence<P>) => void) => {
+  const update = (callback: (root: R) => void) => {
     if (doc) {
-      doc.update(callback);
+      try {
+        doc.update(callback);
+      } catch (err) {
+        setError(err instanceof Error ? err : new Error('Failed to update document'));
+      }
     }
   };
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  const update = (callback: (root: R) => void) => {
    if (doc) {
      try {
        doc.update(callback);
      } catch (err) {
        setError(err instanceof Error ? err : new Error('Failed to update document'));
      }
    }
  };
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T01:07:33Z
author_association: NONE
diff_hunk: @@ -15,7 +15,22 @@
  */
 
 import type { JSONArray, JSONObject } from '@yorkie-js/sdk';
-import { useDocument } from './useDocument';
 
-export { useDocument };
+import { useYorkieDoc } from './useYorkieDoc';
+import { YorkieProvider } from './YorkieProvider';
+import {
+  DocumentProvider,
+  useDocument,
+  useRoot,
+  usePresences,
+} from './DocumentProvider';
path: packages/react/src/index.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Maintain consistent naming between import and export for usePresences/usePresence**

There's a naming inconsistency between the imported hook `usePresences` and the exported hook `usePresences as usePresence`. This could cause confusion for users and maintainers.



Either standardize on `usePresences`:

```diff
import {
  DocumentProvider,
  useDocument,
  useRoot,
- usePresences,
+ usePresence,
} from './DocumentProvider';

export {
  YorkieProvider,
  DocumentProvider,
  useDocument,
  useRoot,
- usePresences as usePresence,
+ usePresence,
  useYorkieDoc,
};
```

Or rename the hook in `DocumentProvider.tsx` to `usePresence` and update all references accordingly.


Also applies to: 34-35

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T01:07:33Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-05T10:16:09Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: rate limited by coderabbit.ai -->

> [!WARNING]
> ## Rate limit exceeded
> 
> @hackerwins has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait **21 minutes and 6 seconds** before requesting another review.
> 
> <details>
> <summary>⌛ How to resolve this issue?</summary>
> 
> After the wait time has elapsed, a review can be triggered using the `@coderabbitai review` command as a PR comment. Alternatively, push new commits to this PR.
> 
> We recommend that you space out your commits to avoid hitting the rate limit.
> 
> </details>
> 
> 
> <details>
> <summary>🚦 How do rate limits work?</summary>
> 
> CodeRabbit enforces hourly rate limits for each developer per organization.
> 
> Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
> 
> Please see our [FAQ](https://docs.coderabbit.ai/faq) for further information.
> 
> </details>
> 
> <details>
> <summary>📥 Commits</summary>
> 
> Reviewing files that changed from the base of the PR and between a05528922162a5936c935614cabfea930d0de090 and 469b9a6a95a31a8fa17d52b03838c4265bd47acc.
> 
> </details>
> 
> <details>
> <summary>📒 Files selected for processing (5)</summary>
> 
> * `examples/react-todomvc/src/App.tsx` (1 hunks)
> * `packages/react/src/DocumentProvider.tsx` (1 hunks)
> * `packages/react/src/YorkieProvider.tsx` (1 hunks)
> * `packages/react/src/index.ts` (1 hunks)
> * `packages/sdk/src/yorkie.ts` (1 hunks)
> 
> </details>

<!-- end of auto-generated comment: rate limited by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request refactors several parts of the codebase by simplifying hook usage and component structure in the React TodoMVC example. It removes unnecessary configurations, standardizes variable names, and integrates new Yorkie SDK–based providers and hooks. New components for document and client context management have been added, and the exports are updated accordingly. Additionally, minor configuration updates in TypeScript and Vite strengthen React support.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `examples/react-todomvc/App.tsx`<br>`examples/react-todomvc/MainSection.tsx`<br>`examples/react-todomvc/main.tsx` | Simplified `useDocument` hook usage (removed parameters), streamlined error/loading checks, standardized variable naming, refactored function signature via destructuring, and integrated `YorkieProvider` and `DocumentProvider` around the App component. |
| `packages/react/src/DocumentProvider.tsx`<br>`packages/react/src/YorkieProvider.tsx`<br>`packages/react/src/useYorkieDoc.ts`<br>`packages/react/src/index.ts` | Introduced new context providers and custom hooks for Yorkie client and document management; renamed `useDocument` to `useYorkieDoc` for clarity; updated module exports to include the new functionalities. |
| `packages/react/tsconfig.json`<br>`packages/react/vite.config.js` | Updated TypeScript configuration with `"jsx": "react-jsx"` and added the React plugin in Vite to support React features during development and build time. |

## Possibly related PRs
- **yorkie-team/yorkie-js-sdk#945:** Introduced the `@yorkie-js/react` package and its `useDocument` hook, making this PR's refactoring of hook usage and state management directly related.

## Suggested labels
`enhancement 🌟`

## Suggested reviewers
- chacha912

## Poem
> I'm a rabbit in a coding land,  
> Hopping through files with a nimble hand.  
> Doc and Yorkie now lead the play,  
> Simplifying our work day by day.  
> With clean hooks and a joyful beat,  
> Our code races—oh so neat! 🐰✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpADK2VmgqyjcEj2wAbb5AokAI7Y0riQ4rjedJAARACCtPQAmvgUANbwJFYU+BLwShQANJAAIvgMTuzZufmU6Bj0iNiI3GSInrD4+GmIMZAA7mjITQLM6jT0cuGwns11sGgMaZT98BjImPSotRjiAGaZ9EOQGE4CdQCcACwAbPxYAOLqABLYAhowMzwU8Mwu8txsBRuPh2vw9tNULYUMgCChdjlaNgGJ5cLAAp5yP0FMwQeRdoguAADFLpTLVPIFInFIllCpsXYU2oUan1RrNVrrDpdHoadxfEmpDJZHKUyhEnF49gwyBKRDwIjkehw/pUbjTTxobjcbzwBjUeD4LCbfxBbDwAIbLBErXwADSJFkEuOaG++G4Hw8kFp5UqjNFzIlTFxRul212ZCUyvwkD+GDQpHQst9DNwAHJhrhqJqGqbghbpEmibRyg6nWz6t61up4GhvDYurgJbx3cV+ggGLAZdXxHX4AAvaIurAkXG4eSCERiD5xEcADzQuKikGaCc8+AhaOknmDUoJMpbYq2WC3sukDB+3HERuKSmYRsQuCo4gwREgnWxW/k+qw5xX4l1Qd6FWNE1krLUdT1A0jUlUNdkYOsonoIk4m1Ik+QMAB1dQEBPAVUO4IMWD3XBilPIk5jpP0m3fHkZVHHV8FkaI4UWFFEFhL4S3pdgM0gR9s2KNYGG8bBaDWN8W1aChxELJpO3QZAiRyfAmxpbBuFobNWSJbx8DQcTX1ZE0iUoHIWU9L5dzgsJhNEuUFAaGsjTrU0GkoCTwhjBYGmXPSDM8k0zNSfisxoDZtRyXhaxobxZAwr1oRLQsMFUt0xXqeQBEWNIiBybBcyYCM5zCELU0NY0MHkAIogkTAwnEJYSFwTjqBQMIDNoS12kQGdEmc+NfFkYpXU7UQ0l1R9uwwESxLoISnP1F83zRNqapIOr4PClrYzQeRyBYmM/06+aK3UE5VMLVaGqsnzSGGTofHoVKwgEAI0DSSASDnJBlsgPYCrECq+3HDCADEQoBmSZj4JQs3gbxEDIr5AV8PMQimn9IGOhh2PaI4bs8PZ8F8fBVlfSAAFUbAAGS4ABtUlhSZApiio1MWbqE0mhaNpuW6ZBbAAXQAClgXBcG4QkAHppaIHC3g0YNpdkIVMjAGhFxVtWSDAIREDARBaDSaXUe8aXrhuABKDD9GMcAoEjcEcAIYgyGUCYcVTLheH4YRRHEKQZG/RRlFUdQtB0O2TCgOBtitF3CFIchn2iYNvf8NBsSaZxXGxkOChUNRNG0XQwEMe3TAMb7Fx1aRpfesQNcUFgJAYaXEAoduCI0Fq5w4AwYiHgwLEgOIAEk3ZT7N2VzicIU7TB7v5Hc7sLMDyJ7vuJQOZc1gkEmpCTeVFXgA4fzCeUlzPqDrywDcNW9SiU3YCVOm6Pie1rQDoIwSzPF4BtQ0zQUBLlHOwX+6MCz0GYD4cQdceAuEXM1SgSN4SzUMm+TAY8rDj0gMsYaSYCrwGCJ4bi1FIDjxKCNXM2Cv6uQEjQf6IVyIEBLIgZ0FAqCEMQs7dseouwLCPucMgpp7xSFoP/Wi3QZSpWxPvbo0QQKPTCNwJBbAaAUDQY+d6zBdQYE8uoZAq5SAYQAKJcJCj5Wg+isG5n8pg0K2ZGAzCWMgIRngRFYHvOJA40Q9g5GYNjPSSwnE0FTLCGMRUGBIE8AEXAQIsCMPAQSISuIAwU3elpNQupQYOFaDE8+iFCGnjGm45hfBdL6Uwc6XMplLEsjCSlMmK4wTylfFEMA+i4nNUScgNYj4SAGWdrA7w4gulrB3EacSd8GFhRSS1DCCQZnAyGsjSgnhUCjTXvCR+dUfgqGXPGNg/1Anej+HOcetAJRwiJBcqhEplEbwFJ1aALdnRAxvAMDsXYBINBcOJQcnFMSLk8kVKQuwKoDBwvgPA6AxkeQpqeAqBQ4qeT0vLBgGEADyUgqC+HWWCRer5CxkB8iiR+IkXDqHkCaaJsTyAcWdqU4iNk+JMCUN8hGng/hrHhgYimRiFABH+oDWZuT4rmEsHEBFz4KqRMfkoKlcqHzO2+iCGS0QQqAgELqBgX1IWyUQEYKAAA5GMdZNG/wVaeJV3gkF32QA/dVqRPbareHqg1ERMjIH6Bs3anLnmoF8XsPkQ8YgmqrjXMBiAG5DKbmw1u7dO7twALLaAwPYAOFVe6IH7oPYeo8J5Tw9tEHOfw84P2JcvAwXpq3rzwp4Ik6a1hZs+X/be/1uXwgPt4I+roAh7EWAQH4FMH6ngBjNO+/EFTxgSSKk0trQgUGRPOzyD8pK9U+ETMVULm0ZrbXfN+xwvGxkUDfQ6soCxiDimeHRq6gRNsTRwisNp20voCSwdqyA1FUA0ag+EgzhkPzYtIdpK0ZhBLRPlIgXZyKbolJOAOllUD1pndfPxwKFCcqmAEcRnlTx8soGwcSzjBnqhAxxWdnkiTPtqchYd8qd5nKJAhj4RhlkDWKesipfwJbrs3F8AAUvYAAGthzwTzG3eniYkiUyTUzvhPSQURBkhDNAmFIv42oBPel3pougbz2E3JjESIz+Bx7hKIiGfEO0FgyBU1gQdqQ+OezhHh3IswMDkDxv8SA3TsbvR6Gddyuxf7FD+BkQVYR7wivpWCfhy5eVherEir4iBkGiqnasmlUjPP4r8BivUFYipPhJv9PS2Jx1WVZbZsRGaVwzTXrQBaGCCNfH04it846W5WmVF8AI7lR3dcE0TRslAEq3SXoWEsF1bIIkUMiHM+0SDYkne2kGE4+CWrqN9X6nlzhCMNBQAA3BqAEPwK0I3kMTCoTqsAcp3Pan444StGhie0RlvUpVj1ldaryirRDPf+86ucGq3V8B1Z69gNZpAmsgKm5qnRGizuoI+lcmkZ7EhdTJM8Q64FZfbQjg92ajQi03VwFtmbScYGqFLK2EowKmQXDGuNw7m4lmYG3DuXdpZU8PTmztgAkwm9DjsISh8djMJ9O/nNORYAG8vLsJGu+yAABfSnJP2108QAz3ZzPa5RFjY3XAHOk087TVru+ua5xEgLRGgwEAwBGGjXXY38bTeJq58m3nyWbcD3DSPaVk9k6ltnhW+eLjpvGtrVNkl/SFtIgpViaXOXXtTCI3lA0aWm1M3JAGKkr72ZVAL+KWCtnkCfqgwKAAAqrMkut9Zs7EBKXUb0/N8pjKeGwHv0Daj1b/GcJxVtVgMVmesjZEP+zEDKCXkyWuOX5YFfzv1mW9emG1doFAg6KUfvQvwySKmKpfvBOMa5JjyHIsX/0NQqRSMGwUdF+BMUyh8Re6MAw1SPxQmhcv0opPf7X64Ccwshtg/KyKhDRADK1Df554ii37ihSKCgN4gGM4J41h9hATQpoiVhxC4L4KOj8Swpdz+JnKnhkB5A5AYCKb7K1i6pNq/Dg4aAaJoAaAUEaAABq480AZiAA+kkNijYHaOPHwXgePLwXaGYkkOhGPP1OKnFDxj6DxDfmKI0qhkaAcEQI+sBDhEWNxGWDcgsLZMgLQLIMclBENJAKHqnJMEMFqtJhUFwtKFpDQMUMTBQK5kOBsKFMNtgSou+LINwDMOsDQlsGEHMFhkSPvg2KpFPlOB1P0jtPvk0hhEWn9o6gDkusqiDhCGLlqpDh6sVjDkavDuavClahkXCFkcDhkaDuDvkV4LqkUYaj6gMP6n8IGnhMGmfKGvbpGs7gYGoksGuO7sOhbtLEASAf7n0UWiHu7DYfxE4BHs7PWjHnEMPmtt2kodRFMZ2vZtjI5vCGVknqdIwVEOVBTK6D3sOovt9KoqXnwO4btPGPLBTOQopofs0O1p4HAQ4CUHaEgZMQ8dZiROEB9IWMWKWI6PRqPhgRPrEbvpugtHCQON8cmMoR1LmEKskh8JZl9EuExO4jyMMMiF2McBRO0PYPMjCRSSQGYnsHsAHCZs8WuP5tUp5MkmgsFFoqEeiRQoAp9uxFIu8WGBsBLIsDMB/qUrqOwG2LoSMIgBePAFePKl9AJE0YgJKQDhpC4aiANo2BWAKWQOxNjHYfQDBCKfBKsVIoeMyF9GDqCIWKNEaDQCVBvvNhgldFxCfumMgCpKRN8NuDNNIDQhjrqanl8g4hyWFM0LydyfCO4XxjmhxnIasgocwkCIwBpl+u/MFi4GQiQAcEqMSM/BiTpHMDEWpK+nMNkEGexDIWYhKdIp9PEj8BtHJAUjfPqi4a6FXpSi6XccUG0ECN8bSiKngAjAOEojhM8k2kCQgRZJABYuZEpr5IYv0mAqmJetBi0t9CiCqaqmfI/LmRsOOfjPwHgPKJytVgAg8bcSVKkdKukaqVUVxEDg6qqXUa6g0VDs0d6nDo7pAKaiPtZHVidLQMSPOaoYztaEMWkCMc3rgOMVBcyDbnbmaiPrmegIkHQCWe0EATBd6HBQhSbshT6bsXmuhUBZhXROBXhSQJWYRaxjlCRR7mRRiRRbboBcBdiFhXRU/O0LWYKdIExcRfdIhexTsQ8WhX0Y7lXGJfXKRSmtLHAZxQHoWsHiWgseWn5lWmvGsRsV2suMgczNJXscpqInyoiEtgvvwgpHPoykmNcTPqVncelHaU8aeL8SJJkLsFIrvJsiGDJMgD5mBn5s5WECeRWF5TrIwDKfBH2TFQ3n8QCZQmEBeEMuFEmK5W6cctECZZkAAMIDklSGFtSdDeC0AJzujireiFXxU0QDJZjBlIGqXAl/7wSgZXhKS2gGEVjYI1WrIybcAMDLKNLHBIl45oBwI0bAijWJCNJwhpjiySwyzSy2gaD17CgaBKASBpi4nrAwEso2aynHzzKQC0GHJNo+WvzTQolYG3IoqFnz4yFei0n0mMkt7Nn0QEnMQf775TmPzeUNWKRmGdhUGwqIC3rtiiLHWgn3gFQtQq6BzZ5vjnTKKPy2lRjei9XQkVjtD8bRa7Kng4nbpxVDKnDqjrbTqzlfQMkBxniMZ1RZXSm+VhAw0OG1bSgFQI2pKBlb55A54EEkBqK6hHyk1ehY11CqhaiJHICdgIxdRkDFBqJUZC1Eg3W7ASh1SiR6kwZwY1bFSaCyErIuRrLZXZlBJYW0lwFoF47z4A6IQtKgXsAKqgZMpJXChxVs2nJfospG2Pm/YVEvld5vnZG1G5EOmarmkFFNH6rFE+qlEgVc2dU4UQXehtULmiUsXiVKW86Z2qEyUYW8W0Vp30W2364KWjFiDjEF2oXbyyVO5GBV0SXKVrBKBzi5rqURqzFaUzyLFzwrH6UrxR7x7E0Cgt153tzt3fS5o7zdoKJ9p6k9LiKuQ3mgLg5NKKZPG0kEW+1BLoTSylnUQwkspOEtDTKeRi7Oy70+kyE8Ub2uruJoDCKHHgVH4206x0jMZfqH1zBwHf00h12F59mH3APii8mjLwLLhi6V4sYaATHkXAlgECLoL2RzlINZ3qT4V33YMMWT68m0lCXGkiVSLX3tBE77Gno6n91wh2RzQZ06yoE0goVUh4MEV4OMV4PEPBkcKEP/1f3lCshbgBBTBvTVJkBokv3aD2r0ERmDSw5wN+0IDIA+I+AkCB0yrB2qqvkFnh2fmR31Ex2NHQ4tEAVQCI5oiKDYVRjl2CMMDZ3DG51sVt3uSd0tRUWWPI42O4WMMoHtVM6T0uO84z3uMcKAVePWP8XbEcwBOwU52KXBPT1uNz0RNI5RNl0CX4MImBMJPV1IWuMd2pMWPpNHCZNEOWgkMvq5NOOJNjGFOz0eNpNWMo6Kho7jmY4TDY5R1hCK7H2KZq4naOPwXOP1MhMpMeOQAi7M6b2K7gNFClCYOqHFD9OnUVmNgrOCWVO8ObMkAAPlDq5DOV15Ot3jNFNNOB5yUDFBNjPtwCMN50hd0zGaXWH906WVoLzD2x5kI9H8QfjeFv5FI03SbmFjoQijSW3fUAHwYnNT1H3tD7NYqTMBX+W7owSAJ5CQ23p5XISrNa1KYOaiKDbIIf6f0PNCMoby07KoCDpRBiBQG/iqQG1kI+mQLpzSiLodZotJKo7zoaNk2WlhRQpyJ3pKnnBYZ8X3Z6ltQA1ApJjA0+0mjUBZhjTeGCsLTiRLTL6alnzzbMJ3b72xgbK3qyYUACoQZxIGlIYz7r2CsA6ytoma2YkwKYAJieR2vKsSmpg2nqIoJaIVimsniBHrijbyP1ZgSAzNYau3ztZtRn07rZZm00rMpfBYWoBGnb5KIIB7w7R5UVhyhKkHlYAeIHGiI0NuZHTNSaKmh7B0u2Q7SAjAiOmmj2oVtA2xVOv5s+ksmkDes/ZaMewh2A76OqpfnR38Cx2mP/kx4lMtMzptN8thlY6i49Nht4u4DADQB6AiwaC7t641MjN1M13KX3PCiPOTPTPX3U1QqnuZB0ibvbu7saD7vxO1P5PjG3skDnvhOXNN2DGwtsUtRFSaEaD6xGjd1B5jxzHTyezvOR6rEj1oZWWLYmmugp5SSUCvZEgxD6xzgxBcAxAm56x5oxDMnkS7jcoUDYpFsvoUPTrr3QDBv2CFvpUaEKhAiQIBWQAiy0aKlsdECgeIBGhEg2yfBbKpkwS8yFKtGngibiZPiYCICJlnqcpwh/hzC2XgE0skC1T1RH4tjxHGIASokUwRX36hAptTIYCaEcfTp4YZrGJNbTYL7JZL5C17aPgclVsSSV4sIuCkABlqNRDFBt5UBtloImhMt1AUdRB8CDUPiaPPk6Oh16M1EGP2lGMTsmN/mKPw506Ye0rlM4ckcEdEe4ekf64xeUDUeOqIYQi8fAcKiCfCfHNvsSVAf8fNcYB26/vyUAdjF5A0BKydf6wQe92vOwdLG6WfPR6If6VHHWUUqEZBVCswTr1Eg16DckBN46jYCvFgAm7NiiSvFnQ7QKlmgcsScngpeQAcHqBWc2cqpYC7evGnncJD4p4BbifY10MrePwRU8DHdrAoMKSoBbhYAKLLBHBYkzToPHjf4vcDKcLcKWe3f3eOSPeD5ieqPnpAtQpkqYAmmngCDmiVVuh4z5wGqHKeQA9NDaiup+G013dMIUEWhGipgjSkzkxvieVfBzDOwA+MntMzYjlvHtl6TcCKYmgk+K3hC/AaP9tJfrCZFh1pejuGPfnGO/nx1mMzvWBA/GjlMm4iwvsI8G8vouAo/r3M8PfsdPeteHvvtbfDfWdNf6w9fDxXPN0nNGwmzKVbWZBPOB7jfzFvNTcfOj01rrEp7kPzKKZUNv1p0q9NoB8aPIvcrFBSddnFIZeupolEg8MogSjFE3ZkFfBBeeAhL91tQbep/EeGzGzjGCumzbMogt91kkCvUqM2MDTwqVaRGF+d9erJtqeai4xgbwByOJUCip8SgV9DkYDkpokV98TXtJvp7yD0OOKgQhUj5i6TaeAed/SwPhtYCRvOe8kiOagiqpRnhRC1F8CAsxvJeY0BCYvNC3oOXRAn8hSMFP2JfaNleujM8CO2V5jsIc2XHXtOyTrYhr60TQfj/QPp14dY9fX3k3x9Lt9hKmAkhsMwQpoD/eOsVJr1wrgxwDU5pcFngCTih9PY7LXYFwCoDZxw+WUAuGHGLiRwy4hgUgenHUC8F8giAXgm/0yB+paAvBASLjjtgGBSBFwG4GgAADsAAVgEDyD5BAAJhuC0AGAAgAAAxXALgKgrQSQAADMtAAAIx7ALgAgEwVcC0EAAOCwUoAEAXALgBgrQVoI4GSCHYkAEwUYNkEXBZBNwEwRcBME2CVAdAGwQIAUEqDgh8g3wZ1FcEXAGAVwJISQFkECAVB+qaOJ4JUE2DaAsgmwYYJIAXAbBNgrQWgC0EXAhkNwGwSpnkFaCTB2QxwWgHkGBCVBtABktoL2AaD3BpAsocoOyF6CohNwFQU0IuCGCbgDAUYfIICFXB9QAgIXqMK0G0BFhJAcoW4IkFcCWAYwXALwKqoCCgEq2OgLwSdj6AgAA= -->

<!-- internal state end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=yorkie-team/yorkie-js-sdk&utm_content=946):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: codecov[bot]
created_at: 2025-03-05T10:35:55Z
body: ## [Codecov](https://app.codecov.io/gh/yorkie-team/yorkie-js-sdk/pull/946?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 78.50%. Comparing base [(`1111636`)](https://app.codecov.io/gh/yorkie-team/yorkie-js-sdk/commit/11116369ed9fa5678814e0af75440ff9ee9db7ee?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) to head [(`469b9a6`)](https://app.codecov.io/gh/yorkie-team/yorkie-js-sdk/commit/469b9a6a95a31a8fa17d52b03838c4265bd47acc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #946      +/-   ##
==========================================
- Coverage   78.54%   78.50%   -0.04%     
==========================================
  Files          63       63              
  Lines        5421     5421              
  Branches      998      998              
==========================================
- Hits         4258     4256       -2     
  Misses        874      874              
- Partials      289      291       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/yorkie-team/yorkie-js-sdk/pull/946?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE