Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Repository Guidelines

## 项目结构与模块划分

`src/` 是公开 TypeScript API 和 JS 层辅助逻辑;`cpp/`、`android/`、`ios/`、`nitrogen/` 承载 Nitro 原生实现与绑定生成。`__tests__/` 放根目录 Node 语义对齐测试,`__mocks__/react-native-nitro-modules.ts` 用于 Jest 隔离原生依赖。`lib/` 是构建产物,不要手改。`example/` 是独立的 React Native CLI 宿主应用,`example/src/` 放 smoke cases,`example/.maestro/` 放真机/模拟器冒烟流程。

## 构建、测试与开发命令

- `yarn build`:执行 `nitrogen && tsc`,更新生成绑定和 `lib/`。
- `yarn test`:运行根目录 Jest parity 测试。
- `cd example && yarn start --reset-cache`:启动 Metro,验证真实原生实现前先开这个。
- `cd example && yarn ios` / `yarn android`:运行示例宿主应用。
- `cd example && yarn test:e2e`:执行 Maestro smoke flow;多设备时用 `MAESTRO_DEVICE=<id> yarn test:e2e`。
- `cd example && yarn pods`:iOS 依赖或 Podspec 变化后重装 Pods。

## 代码风格与命名

仓库以 TypeScript ES Modules 为主,现有代码使用 4 空格缩进、单引号、无分号风格。导出类和类型用 `PascalCase`,内部辅助函数用 `camelCase`,测试文件命名保持 `*.test.ts` 或 `*.test.tsx`。优先做最小化改动;行为变更先补测试,再改实现。除非执行生成命令,否则不要直接编辑 `lib/` 或 `nitrogen/generated/`。

## 测试指南

根测试基于 Jest + `ts-jest`,新增行为应尽量与 `node:buffer` 做对照断言。兼容性测试放在 `__tests__/`,`it(...)` 描述应直接说明对齐的 Node 语义或回归场景。涉及原生路径、编码边界或端到端行为时,还应在 `example/` 中复现,并按需更新 Maestro smoke 流程。

## 提交与 PR 规范

现有提交历史混合使用祈使句标题和轻量 Conventional Commit,例如 `Fix Buffer.toString...`、`chore: bump version...`。沿用这个模式:标题简短、动词开头,只有版本或工具链改动再用 `chore:`。PR 需要说明行为变化、影响平台、已执行命令(如 `yarn test`、`yarn build`、必要时 `cd example && yarn test:e2e`);仅在示例界面或 Maestro 流程变化时附截图或录屏。

## 配置与原生注意事项

`example/` 要求 Node 20+,仓库使用 Yarn 4 且 `.yarnrc.yml` 配置为 `node-modules` linker。原生相关改动通常需要同时检查 `cpp/`、`android/`、`ios/` 是否一致;依赖或 Podspec 变化后,记得重新执行 `bundle exec pod install --project-directory=ios`。
59 changes: 59 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

react-native-nitro-buffer is a high-performance, Node.js-compatible Buffer implementation for React Native, powered by Nitro Modules and C++. It provides 100x-900x faster operations than alternatives by delegating heavy work (encoding, base64, fill, compare, indexOf) to native C++.

## Architecture

Three-layer design:

1. **TypeScript API** (`src/Buffer.ts`) — `Buffer` class extending `Uint8Array` with Node.js-compatible API. `src/utils.ts` provides `atob`, `btoa`, `isAscii`, `isUtf8`, `transcode`. `src/NitroBuffer.nitro.ts` defines the native module interface.
2. **Nitrogen Bindings** (`nitrogen/generated/`) — Auto-generated platform bindings. **Never edit manually**; regenerated by `nitrogen` CLI.
3. **C++ Implementation** (`cpp/HybridNitroBuffer.cpp/.hpp`) — Core logic: base64 encode/decode, UTF-8/UTF-16/Latin1/ASCII encoding, binary operations (fill, indexOf, compare, swap).

The native module is instantiated via `NitroModules.createHybridObject<NitroBuffer>('NitroBuffer')` and works with `ArrayBuffer` for zero-copy memory sharing between JS and C++.

## Build & Development Commands

```bash
yarn build # nitrogen && tsc — regenerate bindings + compile TS to lib/
yarn test # Jest parity tests (Node environment)
yarn test -- --testPathPattern=buffer.test # Run a single test file

# Example app (React Native CLI)
cd example && yarn start --reset-cache # Start Metro bundler
cd example && yarn ios # Run on iOS simulator
cd example && yarn android # Run on Android emulator
cd example && yarn pods # Reinstall CocoaPods after Podspec changes
cd example && yarn test:e2e # Maestro smoke tests
```

## Testing Strategy

- **Root `__tests__/`** — Jest + ts-jest running in Node environment. Tests assert parity with `node:buffer`. The mock at `__mocks__/react-native-nitro-modules.ts` provides a Node.js Buffer-backed hybrid object so tests run without native deps.
- **`example/src/smokeCases.ts`** — Real device/simulator smoke cases exercised through the example app.
- **`example/.maestro/`** — Maestro automation flows for end-to-end validation.

New behavior should have parity tests against Node.js Buffer. Native-path or encoding-boundary changes also need smoke case coverage in `example/`.

## Code Style

- 4-space indentation, single quotes, no semicolons
- `PascalCase` for exported classes/types, `camelCase` for internal helpers
- ES Modules (`"type": "module"` in package.json)
- TypeScript strict mode, target ES2020

## Key Constraints

- **Do not edit** `lib/` (build output) or `nitrogen/generated/` (auto-generated bindings)
- Native changes in `cpp/` typically require corresponding checks in `android/` and `ios/` for consistency
- After dependency or Podspec changes: `cd example && bundle exec pod install --project-directory=ios`
- Requires Node 20+, Yarn 4 with `node-modules` linker (`.yarnrc.yml`)
- Peer dependency: `react-native-nitro-modules ^0.35.0`

## Commit Conventions

Short imperative title, verb-first. Use `chore:` prefix only for version bumps or toolchain changes. PR descriptions should note: behavior changes, affected platforms, and commands run to verify (`yarn test`, `yarn build`, optionally `cd example && yarn test:e2e`).
42 changes: 36 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A high-performance, Node.js compatible `Buffer` implementation for React Native,
## 🚀 Features

* **⚡️ Blazing Fast**: Implemented in C++ using Nitro Modules for maximum performance.
* **✅ Node.js Compatible**: Drop-in replacement for the standard Node.js `Buffer` API.
* **✅ Node.js-style Buffer API**: Covers the core `Buffer` surface used in React Native apps, with parity tests against Node.js.
* **🔒 Type Safe**: Written in TypeScript with full type definitions.
* **📦 Zero Dependencies**: Lightweight and efficient.
* **📱 Cross Platform**: Works flawlessly on iOS and Android.
Expand Down Expand Up @@ -88,14 +88,45 @@ console.log(base64); // SGVsbG8gV29ybGQ=
const decoded = Buffer.from(base64, 'base64');
console.log(decoded.toString()); // Hello World

const base64url = Buffer.from('hello world').toString('base64url');
console.log(base64url); // aGVsbG8gd29ybGQ

const utf16 = Buffer.from('foo', 'utf16le');
console.log(utf16.toString('hex')); // 66006f006f00

// 4. Binary Manipulation
const buf2 = Buffer.allocUnsafe(4);
buf2.writeUInt8(0x12, 0); // (Note: typed array methods available via standard Uint8Array API)
```

## 🧪 Testing

This repository validates compatibility in two layers:

* **Node parity tests**: Root-level Jest tests compare behavior against `node:buffer`.
* **Real React Native smoke tests**: The [`example/`](./example) app runs the real Nitro-backed implementation, and Maestro verifies the screen state on a simulator / emulator.

Root verification:

```bash
yarn test
yarn build
```

Real app verification:

```bash
cd example
yarn start --reset-cache
yarn ios
MAESTRO_DEVICE=<simulator-id> yarn test:e2e
```

For full setup details, see [`example/README.md`](./example/README.md).

## 🧩 API Support

This library achieves **100% API compatibility** with Node.js `Buffer`.
This library targets the standard Node.js `Buffer` API and is validated with Node parity tests plus a real React Native example app.

### Static Methods
* `Buffer.alloc(size, fill, encoding)`
Expand Down Expand Up @@ -148,16 +179,15 @@ This library achieves **100% API compatibility** with Node.js `Buffer`.

### `toString('ascii')` Behavior

When decoding binary data with non-ASCII bytes (0x80-0xFF), `react-native-nitro-buffer` follows the **Node.js standard** by replacing invalid bytes with the Unicode replacement character (`U+FFFD`, displayed as `�`).
When decoding binary data with non-ASCII bytes (0x80-0xFF), `react-native-nitro-buffer` follows the **Node.js behavior** by clearing the high bit of each byte.

```javascript
const buf = Buffer.from([0x48, 0x69, 0x80, 0xFF, 0x21]); // "Hi" + invalid bytes + "!"
buf.toString('ascii');
// Nitro (Node.js compatible): "Hi��!" (length: 5)
// @craftzdog/react-native-buffer: "Hi!" (length: 5) - incorrectly drops invalid bytes
// Nitro (Node.js compatible): "Hi\u0000\u007f!"
```

This ensures consistent behavior with Node.js when handling binary protocols like WebSocket messages containing mixed text and binary data (e.g., Microsoft TTS audio streams).
This matches Node.js semantics for ASCII decoding and avoids silently dropping bytes.

## 📄 License

Expand Down
42 changes: 36 additions & 6 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## 🚀 特性

* **⚡️ 极速**: 使用 Nitro Modules 和 C++ 实现,性能极致。
* **✅ Node.js 兼容**: 可直接替换标准的 Node.js `Buffer` API
* **✅ Node.js 风格 Buffer API**: 覆盖 React Native 常见场景里的核心 `Buffer` 能力,并通过 Node.js 对照测试验证
* **🔒 类型安全**: 全 TypeScript 编写,提供完整的类型定义。
* **📦 零依赖**: 轻量且高效。
* **📱 跨平台**: 完美支持 iOS 和 Android。
Expand Down Expand Up @@ -88,14 +88,45 @@ console.log(base64); // SGVsbG8gV29ybGQ=
const decoded = Buffer.from(base64, 'base64');
console.log(decoded.toString()); // Hello World

const base64url = Buffer.from('hello world').toString('base64url');
console.log(base64url); // aGVsbG8gd29ybGQ

const utf16 = Buffer.from('foo', 'utf16le');
console.log(utf16.toString('hex')); // 66006f006f00

// 4. 二进制操作
const buf2 = Buffer.allocUnsafe(4);
buf2.writeUInt8(0x12, 0); // (注意:可通过标准 Uint8Array API 使用类型化数组方法)
```

## 🧪 测试

这个仓库现在有两层验证:

* **Node 对照测试**:根目录 Jest 测试会直接和 `node:buffer` 做行为对比。
* **真实 React Native smoke 测试**:[`example/`](./example) 会跑真实 Nitro 原生实现,再由 Maestro 在模拟器 / 真机上校验页面状态。

根目录验证:

```bash
yarn test
yarn build
```

真实 App 验证:

```bash
cd example
yarn start --reset-cache
yarn ios
MAESTRO_DEVICE=<simulator-id> yarn test:e2e
```

完整运行说明见 [`example/README.md`](./example/README.md)。

## 🧩 API 支持

本库实现了 Node.js `Buffer` 的 **100% API 兼容性**
本库以标准 Node.js `Buffer` API 为目标,并通过 Node 对照测试和真实 React Native example 双重验证

### 静态方法 (Static Methods)
* `Buffer.alloc(size, fill, encoding)`
Expand Down Expand Up @@ -148,16 +179,15 @@ buf2.writeUInt8(0x12, 0); // (注意:可通过标准 Uint8Array API 使用类

### `toString('ascii')` 行为差异

当解码包含非 ASCII 字节 (0x80-0xFF) 的二进制数据时,`react-native-nitro-buffer` 遵循 **Node.js 标准**,使用 Unicode 替换字符 (`U+FFFD`,显示为 `�`) 替换无效字节
当解码包含非 ASCII 字节 (0x80-0xFF) 的二进制数据时,`react-native-nitro-buffer` 遵循 **Node.js 行为**,会把每个字节的高位清掉

```javascript
const buf = Buffer.from([0x48, 0x69, 0x80, 0xFF, 0x21]); // "Hi" + 无效字节 + "!"
buf.toString('ascii');
// Nitro (Node.js 兼容): "Hi��!" (length: 5)
// @craftzdog/react-native-buffer: "Hi!" (length: 5) - 错误地丢弃了无效字节
// Nitro (Node.js 兼容): "Hi\u0000\u007f!"
```

这确保了在处理包含混合文本和二进制数据的二进制协议(如包含音频流的微软 TTS WebSocket 消息)时与 Node.js 行为一致
这和 Node.js 的 ASCII 语义一致,也避免了字节被静默丢弃

## 📄 许可

Expand Down
74 changes: 74 additions & 0 deletions __mocks__/react-native-nitro-modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Buffer as NodeBuffer } from 'node:buffer'

const normalizeEncoding = (encoding: string = 'utf8'): BufferEncoding => {
switch (encoding.toLowerCase()) {
case 'utf8':
case 'utf-8':
return 'utf8'
case 'latin1':
case 'binary':
return 'latin1'
case 'ucs2':
case 'utf16le':
case 'utf-16le':
return 'utf16le'
default:
return encoding as BufferEncoding
}
}

const getView = (buffer: ArrayBuffer, offset = 0, length?: number): NodeBuffer => {
const byteLength = length ?? (buffer.byteLength - offset)
return NodeBuffer.from(buffer, offset, byteLength)
}

const hybridObject = {
byteLength(string: string, encoding: string): number {
return NodeBuffer.byteLength(string, normalizeEncoding(encoding))
},
write(buffer: ArrayBuffer, string: string, offset: number, length: number, encoding: string): number {
const target = getView(buffer)
const source = NodeBuffer.from(string, normalizeEncoding(encoding))
const end = Math.min(target.length, offset + length)
const bytesToWrite = Math.max(0, Math.min(source.length, end - offset))

source.copy(target, offset, 0, bytesToWrite)
return bytesToWrite
},
decode(buffer: ArrayBuffer, offset: number, length: number, encoding: string): string {
return getView(buffer, offset, length).toString(normalizeEncoding(encoding))
},
compare(a: ArrayBuffer, aOffset: number, aLength: number, b: ArrayBuffer, bOffset: number, bLength: number): number {
return getView(a, aOffset, aLength).compare(getView(b, bOffset, bLength))
},
fill(buffer: ArrayBuffer, value: number, offset: number, length: number): void {
getView(buffer).fill(value, offset, offset + length)
},
indexOf(buffer: ArrayBuffer, value: number, offset: number, length: number): number {
const view = getView(buffer, offset, length)
const index = view.indexOf(value)
return index === -1 ? -1 : offset + index
},
indexOfBuffer(buffer: ArrayBuffer, needle: ArrayBuffer, offset: number, length: number): number {
const view = getView(buffer, offset, length)
const index = view.indexOf(getView(needle))
return index === -1 ? -1 : offset + index
},
lastIndexOfByte(buffer: ArrayBuffer, value: number, offset: number, length: number): number {
const view = getView(buffer, offset, length)
const index = view.lastIndexOf(value)
return index === -1 ? -1 : offset + index
},
lastIndexOfBuffer(buffer: ArrayBuffer, needle: ArrayBuffer, offset: number, length: number): number {
const view = getView(buffer, offset, length)
const index = view.lastIndexOf(getView(needle))
return index === -1 ? -1 : offset + index
},
fillBuffer(buffer: ArrayBuffer, value: ArrayBuffer, offset: number, length: number): void {
getView(buffer).fill(getView(value), offset, offset + length)
}
}

export const NitroModules = {
createHybridObject: jest.fn(() => hybridObject)
}
Loading