This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Masto.js is a universal Mastodon API client for JavaScript/TypeScript that works in Node.js, browsers, and Deno. The library prioritizes minimal runtime code (targeting <6kB gzipped) and provides three main client types: REST API, Streaming API, and OAuth API.
npm run build # Build both ESM and CJS outputs
npm run build:esm # Build ESM only
npm run build:cjs # Build CJS onlyThe build process uses tsconfig-to-dual-package to generate dual ESM/CJS outputs from separate TypeScript configs.
npm test # Run all tests (unit + e2e)
npm run test:unit # Run unit tests only (uses Vitest)
npm run test:e2e # Run e2e tests only (requires Mastodon instance)Important: E2E tests require a local Mastodon instance running at http://localhost:3000 with admin credentials (admin@localhost / mastodonadmin). The test setup automatically creates OAuth apps and tokens, caching them in node_modules/.cache/masto/.
npm run lint # Run all linters
npm run lint:eslint # Run ESLint only
npm run lint:spellcheck # Run spellcheck onlynpm run docs:build # Generate TypeDoc documentationnpx vitest --project unit src/path/to/file.spec.ts
npx vitest --project e2e tests/path/to/file.spec.tsMasto.js uses a proxy-based architecture to minimize runtime code. API calls are not hardcoded methods but dynamically constructed through JavaScript Proxies.
When you call masto.v1.statuses.create({ status: "Hello" }):
- Proxy Layer (
src/adapters/action/proxy.ts): Intercepts property access and function calls, building up a path (e.g.,/api/v1/statuses) and converting camelCase to snake_case - Action Abstraction: Converts the call into an abstract
Actionobject with{ type, path, data, meta } - ActionDispatcher: Routes the Action to the appropriate implementation (HTTP, WebSocket)
- Serializer: Converts JavaScript objects to/from wire formats (JSON, FormData) and handles case conversion
- Transport Layer: Executes the actual HTTP request or WebSocket subscription
-
src/adapters/- Concrete implementations of interfacesaction/- Proxy system and action dispatchershttp/- HTTP client implementationws/- WebSocket implementationserializers/- JSON/FormData serializationconfig/- Configuration handlinglogger/- Logging utilitiesclients.ts- Factory functions (createRestAPIClient, etc.)
-
src/interfaces/- Abstract interfaces (kept separate from implementations)- Defines contracts for
ActionDispatcher,Http,Serializer, etc.
- Defines contracts for
-
src/mastodon/- Mastodon-specific types and API structureentities/- TypeScript type definitions for Mastodon entitiesrest/v1/,rest/v2/- REST API endpoint definitionsstreaming/- Streaming API typesoauth/- OAuth flow types
-
src/utils/- Shared utility functions -
test-utils/- Testing infrastructureservices/- Session pool and test account managementsetup-files/- Vitest setup (globals, polyfills, sessions)vitest-global-setup.ts- Creates OAuth app and admin token before testsvitest-environment.ts- Custom test environment
src/adapters/clients.ts- Entry point for creating clientssrc/adapters/action/proxy.ts- The Proxy handler that powers the APIsrc/adapters/action/dispatcher-http.ts- HTTP request dispatchersrc/adapters/action/dispatcher-ws.ts- WebSocket dispatchersrc/adapters/action/dispatcher-http-hook-mastodon.ts- Mastodon-specific hooks (e.g., media upload polling)
- Located alongside source files
- Test individual functions and edge cases
- Used for exception handling and logic that's hard to test E2E
- Primary testing approach
- Use real Mastodon server at
localhost:3000 - Leverage
sessionsglobal for test account management:
// Single user test
await using session = await sessions.acquire();
await session.rest.v1.statuses.create({ status: "test" });
// Multi-user interaction test
await using alice = await sessions.acquire();
await using bob = await sessions.acquire();
await alice.rest.v1.statuses.create({
status: `Hello @${bob.account.acct}`,
});The await using syntax (explicit resource management) automatically releases sessions back to the pool when the scope exits.
All Mastodon entity types are defined in src/mastodon/entities/. The library exports these as mastodon.v1.Status, mastodon.v1.Account, etc.
$select(id)- Used to select a specific resource:masto.v1.statuses.$select("123").fetch()- Properties starting with
$are treated specially by the proxy system
The library provides async iterators for paginated endpoints. Paginators are implemented in src/mastodon/paginator.ts and src/adapters/action/paginator-http.ts.
JavaScript uses camelCase, Mastodon API uses snake_case. The change-case library handles conversion in the serializer layer.
Clients and subscriptions implement Symbol.dispose for explicit resource cleanup, compatible with the TC39 explicit resource management proposal.
- Uses TypeScript with separate configs for ESM (
tsconfig.esm.json) and CJS (tsconfig.cjs.json) tsconfig-to-dual-packagegenerates proper package.json files in dist/ directories- Outputs both
dist/esm/anddist/cjs/with correct module types
change-case- Case conversionevents-to-async- Event stream to async iterator conversionisomorphic-ws+ws- WebSocket support for Node.jsts-custom-error- Custom error classes
Keep bundle size minimal - this is a key project goal tracked via size-limit in package.json (4.0 kB per client type).
- Add types to
src/mastodon/entities/if new entities are introduced - Add endpoint signature to appropriate
src/mastodon/rest/v*/file - No runtime code needed - the proxy handles routing automatically
- Add E2E test in
tests/rest/v*/to verify functionality
Set the log option when creating clients:
const masto = createRestAPIClient({
url: "https://mastodon.social",
accessToken: "...",
log: "debug", // "debug" | "info" | "warn" | "error"
});Media uploads use a polling mechanism (see dispatcher-http-hook-mastodon.ts). The mediaTimeout option controls how long to wait for processing (default: 60 seconds).