npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@barocss/collaboration-liveblocks

v0.1.2

Published

Liveblocks adapter for Barocss Editor collaborative editing

Readme

@barocss/collaboration-liveblocks

Liveblocks adapter for Barocss Editor collaborative editing. Integrates Barocss DataStore with Liveblocks for managed collaborative editing infrastructure.

Architecture

graph TB
    subgraph "Barocss Editor"
        A["DataStore<br/>emitOperation/onOperation"]
        B["AtomicOperation<br/>create|update|delete|move"]
    end
    
    subgraph "LiveblocksAdapter"
        C["BaseAdapter<br/>Common Logic"]
        D["LiveblocksAdapter<br/>Room Integration"]
    end
    
    subgraph "Liveblocks"
        E["Liveblocks Client<br/>Connection Manager"]
        F["Liveblocks Room<br/>Shared State"]
        G["Liveblocks Server<br/>Managed Infrastructure"]
    end
    
    A --> B
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G
    
    G --> H["Other Clients"]
    
    style A fill:#e1f5ff
    style D fill:#fff4e1
    style E fill:#e8f5e9
    style F fill:#f3e5f5
    style G fill:#fce4ec

Installation

pnpm add @barocss/collaboration-liveblocks @liveblocks/client

Quick Start

import { DataStore } from '@barocss/datastore';
import { LiveblocksAdapter } from '@barocss/collaboration-liveblocks';
import { createClient } from '@liveblocks/client';

// Create Liveblocks client
const client = createClient({
  publicApiKey: 'your-api-key'
});

// Enter room
const room = client.enter('my-room');

// Create adapter
const adapter = new LiveblocksAdapter({
  room,
  config: {
    clientId: 'user-1',
    user: {
      id: 'user-1',
      name: 'John Doe'
    },
    debug: true
  }
});

// Connect to DataStore
const dataStore = new DataStore();
await adapter.connect(dataStore);

Usage

Basic Setup

import { DataStore } from '@barocss/datastore';
import { LiveblocksAdapter } from '@barocss/collaboration-liveblocks';
import { createClient } from '@liveblocks/client';

// 1. Create Liveblocks client
const client = createClient({
  publicApiKey: 'pk_live_...',  // Public API key from Liveblocks dashboard
  // For authentication, use:
  // authEndpoint: '/api/liveblocks-auth'
});

// 2. Enter a room
const room = client.enter('document-id');

// 3. Create adapter
const adapter = new LiveblocksAdapter({
  room,
  config: {
    clientId: 'user-1',
    user: {
      id: 'user-1',
      name: 'John Doe',
      color: '#ff0000',
      avatar: 'https://avatar.url'
    },
    debug: true
  }
});

// 4. Connect to DataStore
const dataStore = new DataStore();
await adapter.connect(dataStore);

Authentication

import { createClient } from '@liveblocks/client';

// With authentication endpoint
const client = createClient({
  authEndpoint: '/api/liveblocks-auth',
  // The auth endpoint should return:
  // { token: '...' }
});

const room = client.enter('document-id');

Room Events

// Listen to room connection status
room.subscribe('connection', (status) => {
  console.log('Connection status:', status);
  // 'open' | 'closed' | 'unavailable'
});

// Listen to room errors
room.subscribe('error', (error) => {
  console.error('Room error:', error);
});

// Listen to other users
room.subscribe('others', (others) => {
  console.log('Other users in room:', others);
});

Presence

Liveblocks provides built-in presence for cursor positions and user information:

// Update local presence
room.updatePresence({
  cursor: { x: 100, y: 200 },
  selection: { start: 0, end: 10 }
});

// Listen to other users' presence
room.subscribe('others', (others) => {
  others.forEach((other) => {
    console.log('User:', other.presence);
    console.log('Cursor:', other.presence?.cursor);
  });
});

API Reference

LiveblocksAdapterOptions

interface LiveblocksAdapterOptions {
  /**
   * Liveblocks Room instance
   * Required: The room that manages shared state
   * Created via client.enter(roomId)
   */
  room: Room;

  /**
   * Adapter configuration
   * See AdapterConfig in @barocss/collaboration
   */
  config?: AdapterConfig;
}

LiveblocksAdapter Methods

class LiveblocksAdapter extends BaseAdapter {
  /**
   * Connect adapter to DataStore
   * Sets up room subscriptions and loads initial state
   */
  connect(dataStore: DataStore): Promise<void>;

  /**
   * Disconnect adapter from DataStore
   * Removes room subscriptions
   */
  disconnect(): Promise<void>;

  /**
   * Check if adapter is connected
   */
  isConnected(): boolean;
}

Advanced Usage

Custom Storage Structure

Liveblocks uses a storage structure. Customize how operations are stored:

// Operations are stored in room storage
room.update((root) => {
  if (!root.operations) {
    root.operations = [];
  }
  
  root.operations.push({
    type: 'create',
    nodeId: 'node-1',
    data: { /* ... */ }
  });
});

Storage Events

// Listen to storage changes
room.subscribe('storage', (storage) => {
  console.log('Storage updated:', storage);
  
  // Access operations
  const operations = storage.root.get('operations');
  console.log('Operations:', operations);
});

Broadcast Events

Use Liveblocks broadcast for custom events:

// Broadcast custom event
room.broadcastEvent({
  type: 'custom-event',
  data: { message: 'Hello' }
});

// Listen to broadcast events
room.subscribe('event', (event) => {
  if (event.type === 'custom-event') {
    console.log('Received:', event.data);
  }
});

History and Undo

Liveblocks provides history for undo/redo:

// Get history
const history = room.getHistory();

// Undo last operation
room.history.undo();

// Redo last operation
room.history.redo();

Error Handling

// Handle connection errors
room.subscribe('error', (error) => {
  console.error('Room error:', error);
  
  if (error.type === 'CONNECTION_ERROR') {
    // Handle reconnection
    setTimeout(() => {
      room.reconnect();
    }, 1000);
  }
});

// Handle storage errors
try {
  room.update((root) => {
    root.operations.push(operation);
  });
} catch (error) {
  console.error('Storage error:', error);
}

Configuration Options

AdapterConfig

interface AdapterConfig {
  /**
   * Unique client identifier
   */
  clientId?: string;

  /**
   * User information for presence
   */
  user?: {
    id: string;
    name?: string;
    color?: string;
    avatar?: string;
  };

  /**
   * Enable debug logging
   */
  debug?: boolean;

  /**
   * Transform operations before applying
   */
  transformOperation?: (op: AtomicOperation) => AtomicOperation;
}

Troubleshooting

Connection Issues

  1. Check API key: Ensure public API key is correct

    const client = createClient({
      publicApiKey: 'pk_live_...'
    });
  2. Verify authentication: If using auth, check auth endpoint

    const client = createClient({
      authEndpoint: '/api/liveblocks-auth'
    });
  3. Check room ID: Ensure room ID is valid

    const room = client.enter('valid-room-id');

Storage Not Updating

  1. Check room connection: Ensure room is connected

    room.subscribe('connection', (status) => {
      console.log('Connection:', status); // Should be 'open'
    });
  2. Verify storage structure: Check storage structure

    room.subscribe('storage', (storage) => {
      console.log('Storage:', storage.root.toJSON());
    });
  3. Check adapter connection: Verify adapter is connected

    console.log('Adapter connected:', adapter.isConnected());

Operations Not Syncing

  1. Check room subscriptions: Ensure subscriptions are active

    room.subscribe('storage', (storage) => {
      // This should be called when storage changes
    });
  2. Verify operation format: Ensure operations match expected format

    room.update((root) => {
      const operations = root.get('operations');
      console.log('Operations:', operations.toJSON());
    });

Best Practices

  1. Use Authentication: Always use authentication for production
  2. Handle Disconnections: Implement reconnection logic
  3. Use Presence: Leverage Liveblocks presence for cursors
  4. Monitor Connection: Track connection status
  5. Error Handling: Implement comprehensive error handling
  6. Clean Up: Leave rooms when done

Example: Full Integration

import { Editor } from '@barocss/editor-core';
import { DataStore } from '@barocss/datastore';
import { LiveblocksAdapter } from '@barocss/collaboration-liveblocks';
import { createClient } from '@liveblocks/client';

class CollaborativeEditor {
  private client: ReturnType<typeof createClient>;
  private room: any;
  private adapter: LiveblocksAdapter;
  private dataStore: DataStore;
  private editor: Editor;

  constructor(roomId: string, userId: string, apiKey: string) {
    // Initialize Liveblocks client
    this.client = createClient({
      publicApiKey: apiKey,
      authEndpoint: '/api/liveblocks-auth'
    });

    // Enter room
    this.room = this.client.enter(roomId);

    // Initialize DataStore
    this.dataStore = new DataStore();

    // Create adapter
    this.adapter = new LiveblocksAdapter({
      room: this.room,
      config: {
        clientId: userId,
        user: {
          id: userId,
          name: `User ${userId}`
        },
        debug: true
      }
    });

    // Initialize editor
    this.editor = new Editor({
      dataStore: this.dataStore
    });
  }

  async connect() {
    // Connect adapter
    await this.adapter.connect(this.dataStore);

    // Set up event handlers
    this.room.subscribe('connection', (status) => {
      console.log('Connection status:', status);
    });

    this.room.subscribe('others', (others) => {
      console.log('Other users:', others);
    });

    // Update presence
    this.room.updatePresence({
      cursor: { x: 0, y: 0 }
    });
  }

  async disconnect() {
    await this.adapter.disconnect();
    this.client.leave(this.room.id);
  }
}

Liveblocks Setup

Getting API Key

  1. Sign up at liveblocks.io
  2. Create a project
  3. Get your public API key from dashboard

Authentication Setup

Create an auth endpoint:

// /api/liveblocks-auth
export default async function handler(req, res) {
  // Verify user authentication
  const user = await verifyUser(req);
  
  // Generate Liveblocks token
  const token = await generateToken(user.id);
  
  res.json({ token });
}

License

MIT