Skip to content

Latest commit

 

History

History
161 lines (112 loc) · 5.28 KB

File metadata and controls

161 lines (112 loc) · 5.28 KB

Node Bindings - Claude Assistant Context

This file provides context for Claude Code to understand the node bindings crate structure and development workflows.

Crate Overview

This crate (bindings_node) provides Node.js bindings for libxmtp using NAPI-RS. It exposes the core XMTP functionality to JavaScript/TypeScript applications.

Code Organization

The crate follows a domain-driven modular structure:

Core Modules

Module Purpose
client/ Client struct and all its methods, split by concern
conversations/ Conversations collection - listing, creating, streaming
conversation/ Conversation methods, split by concern
content_types/ Content type definitions (text, reaction, reply, etc.)
messages/ Message struct and related types

Key Files

File Contains
lib.rs Crate entry point, ErrorWrapper utility
client/mod.rs Client struct definition, core methods
client/create_client.rs create_client() function, client builder
client/options.rs ClientOptions configuration
conversations/mod.rs Conversations struct, core methods
conversation/mod.rs Conversation struct, core methods
messages/mod.rs Message structs and enums

Pattern: Methods Split by Concern

Large structs like Client and Conversation have their impl blocks split across multiple files:

client/
├── mod.rs           # Client struct + core methods
├── consent_state.rs # impl Client { consent methods }
├── signatures.rs    # impl Client { signature methods }
└── ...

Each file adds methods to the same struct via impl Client { ... } blocks. This keeps files focused and manageable.

Pattern: conversation/ vs conversations/

  • conversation/ = methods on a single Conversation (send message, get members, etc.)
  • conversations/ = methods on the collection (list, create, stream conversations)

Common Tasks

Adding a New Client Method

  1. Identify the concern (consent, signatures, identity, etc.)
  2. Find or create the appropriate file in src/client/
  3. Add the method in an impl Client block with #[napi] attribute
// src/client/my_feature.rs
use napi_derive::napi;
use crate::client::Client;

#[napi]
impl Client {
    #[napi]
    pub async fn my_new_method(&self) -> napi::Result<()> {
        // implementation
        Ok(())
    }
}
  1. Add mod my_feature; to src/client/mod.rs

Adding a New Conversation Method

Same pattern as Client - add to src/conversation/ in appropriate file.

Adding a New Content Type

  1. Create src/content_types/<type_name>.rs
  2. Define the struct with #[napi(object)]
  3. Add pub mod <type_name>; to src/content_types/mod.rs
  4. Add variant to ContentType enum in mod.rs
  5. Update DecodedMessageContentType, DecodedMessageContentInner, and DecodedMessageContent
  6. Add encoder function if content type should be sent by clients
  7. Add function to expose ContentTypeId struct if content type will be read by clients

Adding a Shared Type

Create a root-level file in src/ for types used across multiple modules.

Development Commands

From the repository root, use just recipes (preferred — they use Nix automatically):

just node install       # Install dependencies
just node check         # Build release NAPI bindings
just node lint          # Check TypeScript formatting
just node format        # Format TypeScript files
just node test          # Run tests (requires just backend up)
just node build         # Alias for check

Important Patterns

Error Handling

Use ErrorWrapper to convert Rust errors to NAPI errors:

use crate::ErrorWrapper;

self.inner_client
    .some_method()
    .await
    .map_err(ErrorWrapper::from)?;

NAPI Attributes

  • #[napi] on struct/impl - exports to JavaScript
  • #[napi(object)] - plain object (not a class)
  • #[napi(getter)] - property getter
  • #[napi] on method - exported method

BigInt for Timestamps

Use napi::bindgen_prelude::BigInt for nanosecond timestamps to avoid precision loss.

Cloning Inner Types

Conversation uses a pattern where create_mls_group() creates a new MlsGroup from stored data, since the inner group isn't clonable in a way that makes sense for NAPI.

Testing

Tests are in test/ directory as TypeScript files using Vitest:

  • Client.test.ts
  • Conversation.test.ts
  • Conversations.test.ts

Run tests with yarn test. Requires local XMTP node (start with ./dev/up from repo root).

Key Dependencies

  • napi / napi-derive - Node.js bindings
  • xmtp_mls - Core MLS implementation
  • xmtp_db - Database layer
  • xmtp_proto - Protocol buffer definitions

File Naming Convention

  • mod.rs - Module entry point, contains primary struct
  • <concern>.rs - Additional impl blocks for that concern
  • <type>.rs in content_types/ - Individual content type definitions