11 KiB
Agent Instructions for node-osc
This document provides context and instructions for AI agents (GitHub Copilot, Cursor, and other agentic platforms) working on the node-osc project.
Project Overview
node-osc is a Node.js library for sending and receiving Open Sound Control (OSC) messages over UDP. It provides a simple, no-frills API inspired by pyOSC.
Key Features
- Send and receive OSC messages and bundles
- Dual module support (ESM and CommonJS)
- Both callback and async/await APIs
- TypeScript type definitions generated from JSDoc
- Well-tested with comprehensive test coverage
- Supports Node.js 20, 22, and 24
Architecture
Core Components
-
Server (
lib/Server.mjs) - EventEmitter-based OSC server for receiving messages- Listens on UDP socket
- Emits events:
listening,message,bundle,error, and address-specific events
-
Client (
lib/Client.mjs) - OSC client for sending messages- Sends messages over UDP
- Supports both callbacks and async/await
-
Message (
lib/Message.mjs) - Represents a single OSC message- Contains address (string) and arguments (array)
- Can append additional arguments
-
Bundle (
lib/Bundle.mjs) - Represents a collection of OSC messages- Contains timetag and array of elements (messages or nested bundles)
- Used for sending multiple messages together
-
Low-level encoding/decoding (
lib/osc.mjs,lib/internal/) - Binary OSC protocol implementationencode()- Converts Message/Bundle objects to binary Bufferdecode()- Parses binary Buffer into Message/Bundle objects
Module System
The project uses ESM as the source format but provides dual ESM/CommonJS support:
- Source files:
lib/**/*.mjs(ESM) - Built CommonJS files:
dist/lib/**/*.js(transpiled via Rollup) - TypeScript definitions:
types/index.d.mts(generated from JSDoc)
Important: The single .d.mts type definition file works for both ESM and CommonJS consumers.
Package Exports
{
"exports": {
"types": "./types/index.d.mts",
"require": "./dist/lib/index.js",
"import": "./lib/index.mjs",
"default": "./lib/index.mjs"
}
}
Development Workflow
Essential Commands
# Install dependencies
npm install
# Run linter (ESLint)
npm run lint
# Build the project (clean, transpile to CJS, generate types)
npm run build
# Run all tests (lint + build + ESM tests + CJS tests)
npm test
# Run only ESM tests
npm run test:esm
# Run only CJS tests
npm run test:cjs
# Generate API documentation from JSDoc
npm run docs
# Clean build artifacts
npm run clean
Testing Strategy
- Tests are written in ESM format in
test/test-*.mjs - Tests are run against both ESM source (
lib/) and transpiled CJS (dist/) - Uses
taptest framework - Test utilities in
test/util.mjsprovide helpers likegetPort()for getting available ports - Always run
npm run buildbefore running CJS tests - 100% test coverage is required - All lines, branches, functions, and statements must be covered
Build Process
- Clean: Removes
dist/andtypes/directories - Rollup: Transpiles ESM to CommonJS in
dist/directory - TypeScript: Generates type definitions from JSDoc in
types/directory
The build is automatically run before publishing (prepublishOnly script).
Coding Standards
JavaScript Style
- ES Modules: Use ESM syntax (
import/export) - File extension: Use
.mjsfor ESM files - Linting: Follow ESLint rules in
eslint.config.mjs - Modern JavaScript: Use async/await, arrow functions, destructuring
- Error handling: Always handle errors in async operations
Documentation
- JSDoc comments: All public APIs must have JSDoc comments
- Type annotations: Use JSDoc types for TypeScript generation
- Examples: Include code examples in JSDoc comments
- Auto-generated docs: Run
npm run docsafter changing JSDoc comments
Example JSDoc pattern:
/**
* Sends an OSC message or bundle.
*
* @param {Message|Bundle|string} msg - The message, bundle, or address to send.
* @param {...*} args - Additional arguments (used when first param is a string address).
* @returns {Promise<void>}
*
* @example
* await client.send('/test', 123);
*
* @example
* const message = new Message('/test', 123);
* await client.send(message);
*/
async send(msg, ...args) { ... }
Type System
- TypeScript definitions are generated from JSDoc comments
- Do not manually edit
types/*.d.mtsfiles - Update JSDoc comments in source files instead
- Run
npm run build:typesto regenerate types
Naming Conventions
- Classes: PascalCase (e.g.,
Client,Server,Message,Bundle) - Functions: camelCase (e.g.,
encode,decode,toBuffer) - Private functions: Prefix with underscore (e.g.,
_oscType) - Constants: UPPER_SNAKE_CASE for true constants
- Files: Match class names or use descriptive kebab-case
Dual Module Support Patterns
When writing code that needs to work in both ESM and CJS:
- Imports: Use ESM imports in source (Rollup handles conversion)
- Exports: Use named exports for all public APIs
- Testing: Test both ESM and CJS builds
- Package imports: Use
#decodesubpath import for internal modules (defined inpackage.jsonimports field)
Important Files and Directories
Source Files
lib/- ESM source code (the canonical source)lib/index.mjs- Main entry point, exports all public APIslib/internal/- Internal utilities (decode, encode, helpers)lib/osc.mjs- Low-level encode/decode functions
Build Artifacts
dist/- Transpiled CommonJS files (generated, do not edit)types/- TypeScript type definitions (generated, do not edit)
Tests
test/test-*.mjs- Test files using tap frameworktest/util.mjs- Test utilities and helperstest/fixtures/- Test data and fixtures
Documentation
README.md- Main documentation with quick start guidedocs/API.md- Auto-generated API reference (do not edit manually)docs/GUIDE.md- Best practices, error handling, troubleshootingexamples/- Working example code for various use cases
Configuration
package.json- Package configuration, scripts, exportseslint.config.mjs- ESLint configurationrollup.config.mjs- Rollup build configuration (ESM to CJS)tsconfig.json- TypeScript compiler options for type generationjsdoc.json- JSDoc configuration for documentation generation
Making Changes
Adding a New Feature
- Write ESM source in
lib/ - Add JSDoc comments with types and examples
- Export from
lib/index.mjsif it's a public API - Write tests in
test/test-*.mjs- must achieve 100% coverage (lines, branches, functions, statements) - Run tests:
npm test(tests both ESM and CJS) - Update docs:
npm run docsto regenerate API.md - Update README.md if adding user-facing functionality
Fixing a Bug
- Write a failing test that demonstrates the bug
- Fix the bug in the ESM source files
- Run tests:
npm testto verify fix works in both ESM and CJS - Verify coverage: Ensure 100% test coverage is maintained
- Check no regressions: Ensure all tests pass
Modifying the API
- Update JSDoc in source files
- Regenerate types:
npm run build:types - Update tests to cover new behavior - must maintain 100% coverage
- Regenerate docs:
npm run docs - Update README.md and
docs/GUIDE.mdas appropriate
Common Patterns
Creating a Server
import { Server } from 'node-osc';
const server = new Server(3333, '0.0.0.0');
server.on('message', (msg, rinfo) => {
console.log('Message:', msg);
});
Creating a Client
import { Client } from 'node-osc';
const client = new Client('127.0.0.1', 3333);
await client.send('/test', 123);
await client.close();
Working with Bundles
import { Bundle } from 'node-osc';
const bundle = new Bundle(['/one', 1], ['/two', 2]);
await client.send(bundle);
Low-level Encoding/Decoding
import { Message, encode, decode } from 'node-osc';
const message = new Message('/test', 123);
const buffer = encode(message);
const decoded = decode(buffer);
Troubleshooting
Build Issues
- "Cannot find module": Run
npm installto install dependencies - Type generation fails: Check JSDoc syntax in source files
- CJS tests fail but ESM pass: Run
npm run buildbefore testing
Test Issues
- Port conflicts: Tests use dynamic port allocation via
getPort()utility - Timing issues: Use async/await and proper event handling
- ESM/CJS differences: Ensure code works in both environments
Module Resolution
- Dual package hazard: The package exports both ESM and CJS - don't mix them
- Type imports: TypeScript consumers get types automatically from
types/index.d.mts - Internal imports: Use
#decodesubpath for internal modules
Dependencies
Runtime Dependencies
- None - This is a zero-dependency library for production use
Development Dependencies
- eslint - Code linting
- tap - Test framework
- rollup - Module bundler for ESM → CJS transpilation
- typescript - Type definition generation from JSDoc
- jsdoc - Documentation generation
- globals - ESLint globals configuration
OSC Protocol Knowledge
When working with OSC message encoding/decoding:
- OSC addresses start with
/(e.g.,/oscillator/frequency) - OSC types: integer (i), float (f), string (s), blob (b), time tag (t)
- Messages are null-padded to 4-byte boundaries
- Bundles have time tags (when to execute) and can contain nested bundles
- See OSC Specification for protocol details
Security Considerations
- Always validate input data when decoding OSC messages
- Be careful with buffer operations to avoid out-of-bounds access
- Limit message and bundle sizes to prevent DoS attacks
- Sanitize OSC addresses before using them as event names
- Handle malformed OSC data gracefully (emit errors, don't crash)
License
This project uses the Apache-2.0 license. When contributing code:
- Ensure all new code is compatible with Apache-2.0
- Do not introduce dependencies with incompatible licenses
- Include proper attribution for any third-party code
Getting Help
- API Documentation: See
docs/API.md - Usage Guide: See
docs/GUIDE.md - Examples: See
examples/directory - Issues: Check existing GitHub issues for similar problems
- OSC Protocol: Refer to http://opensoundcontrol.org for protocol details