DO NOT EDIT - AUTO-GENERATED FROM docs/src/llms/shared/keepsync/worker-nodejs.md

This file is automatically generated from the documentation.

Edit the source file instead: docs/src/llms/shared/keepsync/worker-nodejs.md

Generated on: 2025-07-17T13:11:10.053Z

DO NOT EDIT - AUTO-GENERATED FROM docs/src/llms/shared/keepsync/worker-nodejs.md

This file is automatically generated from the documentation.

Edit the source file instead: docs/src/llms/shared/keepsync/worker-nodejs.md

Generated on: 2025-07-17T13:11:10.054Z

Basic Usage

1. Set Up the Sync Provider

Initialize the sync engine in your application entry point (or before using any synced stores):

// index.tsx
import { configureSyncEngine } from "@tonk/keepsync";
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs";

const wsAdapter = new BrowserWebSocketClientAdapter("ws://localhost:7777/sync);
const storage = new NodeFSStorageAdapter();

configureSyncEngine({
  url: "http://localhost:7777",
  network: [wsAdapter as any],
  storage,
});

2. Create a Synced Store with the Middleware

Use the sync middleware to create stores that automatically synchronize with other clients:

// stores/counterStore.ts
import { createStore } from 'zustand/vanilla';
import { sync, DocumentId } from '@tonk/keepsync';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

export const counterStore = createStore<CounterState>(
  sync(
    // The store implementation
    (set) => ({
      count: 0,

      // Increment the counter
      increment: () => {
        set((state) => ({ count: state.count + 1 }));
      },

      // Decrement the counter
      decrement: () => {
        set((state) => ({ count: Math.max(0, state.count - 1) }));
      },

      // Reset the counter
      reset: () => {
        set({ count: 0 });
      },
    }),
    // Sync configuration
    { 
      docId: 'counter' as DocumentId,
      // Optional: configure initialization timeout
      initTimeout: 30000,
      // Optional: handle initialization errors
      onInitError: (error) => console.error('Sync initialization error:', error) 
    }
  )
);

3. Manually fetch the state

Because this is a Node project, we need to use zustand in a different way as it is used in React components. Each time you want fresh state you will need to use the getState() function.

  const counterStore = createStore<CounterState>(
    sync(
      // The store implementation
      (set) => ({
        count: 0,

        // Increment the counter
        increment: () => {
          set((state) => ({ count: state.count + 1 }));
        },

        // Decrement the counter
        decrement: () => {
          set((state) => ({ count: Math.max(0, state.count - 1) }));
        },

        // Reset the counter
        reset: () => {
          set({ count: 0 });
        },
      }),
      // Sync configuration
      { 
        docId: 'counter' as DocumentId,
        // Optional: configure initialization timeout
        initTimeout: 30000,
        // Optional: handle initialization errors
        onInitError: (error) => console.error('Sync initialization error:', error) 
      }
    )
  );

  const state = counterStore.getState();

  state.increment(); 
  console.log(`The current count is: ${store.getState().count}`);

Directly reading and writing documents

You can also directly read and write documents and address them using paths similar to a filesystem. This is useful for when you need more fine-grained control over document access and a zustand store is too cumbersome (e.g. when you want each document to have its own space and be directly addressable);

import { readDoc, writeDoc, ls, mkDir, rm, listenToDoc } from "@tonk/keepsync";

/**
 * Reads a document from keepsync
 *
 * This function retrieves a document at the specified path in your sync engine.
 * It returns the document content if found, or undefined if the document doesn't exist.
 *
 * @param path - The path identifying the document to read
 * @returns Promise resolving to the document content or undefined if not found
 * @throws Error if the SyncEngine is not properly initialized
 */
readDoc = async <T>(path: string): Promise<T | undefined>;

/**
 * Writes content to a document to keepsync
 *
 * This function creates or updates a document at the specified path.
 * If the document doesn't exist, it creates a new one.
 * If the document already exists, it updates it with the provided content.
 *
 * @param path - The path identifying the document to write
 * @param content - The content to write to the document
 * @throws Error if the SyncEngine is not properly initialized
 */
writeDoc = async <T>(path: string, content: T);

/**
 * Lists documents at a specified path
 *
 * This function retrieves a list of documents at the specified directory path.
 * It returns an array of document names found at that path.
 *
 * @param path - The directory path to list documents from
 * @returns Promise resolving to an array of document names
 * @throws Error if the SyncEngine is not properly initialized
 */
ls = async (path: string): Promise<string[]>;

/**
 * Creates a directory at the specified path
 *
 * This function creates a new directory at the specified path.
 * If the directory already exists, it does nothing.
 *
 * @param path - The path where the directory should be created
 * @throws Error if the SyncEngine is not properly initialized
 */
mkDir = async (path: string): Promise<void>;

/**
 * Removes a document or directory at the specified path
 *
 * This function deletes a document or directory at the specified path.
 * If removing a directory, it will remove all documents within it.
 *
 * @param path - The path of the document or directory to remove
 * @param recursive - Whether to recursively remove directories (default: false)
 * @throws Error if the SyncEngine is not properly initialized
 */
rm = async (path: string, recursive?: boolean): Promise<void>;

/**
 * Listens for changes to a document
 *
 * This function sets up a listener for changes to a document at the specified path.
 * The callback will be called whenever the document changes with detailed patch information.
 *
 * @param path - The path of the document to listen to
 * @param callback - Function to call when the document changes, receives payload with doc, patches, patchInfo, and handle
 * @returns A function that can be called to stop listening
 * @throws Error if the SyncEngine is not properly initialized
 */
listenToDoc = <T>(path: string, callback: (payload: { doc: T; patches: any[]; patchInfo: any; handle: DocHandle<T> }) => void): Promise<() => void>;

File System Operations Example

Here's an example of how to use the file system operations:

import { ls, mkDir, rm, readDoc, writeDoc, listenToDoc } from "@tonk/keepsync";

// Create a directory structure
await mkDir("/users");

// Write a document
await writeDoc("/users/user1", { name: "Alice", age: 30 });
await writeDoc("/users/user2", { name: "Bob", age: 25 });

// List documents in a directory
const users = await ls("/users");
console.log(users); // ["user1", "user2"]

// Read a document
const user1 = await readDoc<{ name: string, age: number }>("/users/user1");
console.log(user1); // { name: "Alice", age: 30 }

// Listen for changes to a document
const unsubscribe = await listenToDoc<{ name: string, age: number }>("/users/user1", (payload) => {
  const { doc: user, patches, patchInfo, handle } = payload;
  if (user) {
    console.log(`User updated: ${user.name}, ${user.age}`);
    console.log("Patches:", patches);
    console.log("Patch info:", patchInfo);
  }
});

// Update the document (will trigger the listener)
await writeDoc("/users/user1", { name: "Alice", age: 31 });

// Stop listening when done
unsubscribe();

// Remove a document
await rm("/users/user2");

// Remove a directory and all its contents
await rm("/users", true);