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

rsc-tape

v0.1.0

Published

Intercept React Server Actions and generate MSW mock handlers

Readme

📼 rsc-tape

Record React Server Actions. Replay them with MSW.

npm version license node TypeScript MSW api-tape

Capture every Server Action from Next.js, Waku, Parcel, or any RSC server — zero config, zero framework hooks.


✨ Why rsc-tape?

Testing Server Actions is painful. You need real responses to write meaningful tests, but manually crafting RSC payloads is tedious and error-prone.

rsc-tape solves this by recording real interactions from your dev server and generating MSW handlers you can drop straight into your test suite.

Dev Server → rsc-tape records → JSON fixtures → MSW handlers → Your tests

🚀 Quick start

npm install rsc-tape --save-dev
npx rsctape init          # Generate config + framework entry point
# ... start dev server, use your app ...
npx rsctape mock -o ./src/mocks/handlers.ts   # Generate MSW handlers

That's it. Your test suite now has real Server Action mocks.


📦 Framework setup

Add to instrumentation.ts (created by rsctape init):

export async function register() {
  if (process.env.NODE_ENV === 'development') {
    const { register } = await import('rsc-tape');
    register();
  }
}

Add to your entry point:

if (process.env.NODE_ENV === 'development') {
  const { register } = await import('rsc-tape');
  register();
}
import { register } from 'rsc-tape';
register();
// ... your http.createServer() call

💡 rsc-tape only activates when NODE_ENV=development or RSCTAPE_ENABLED=true. Zero overhead in production.


🛠 CLI

| Command | Description | |:--------|:------------| | rsctape init | 🔍 Detect framework, generate config and entry point | | rsctape list | 📋 List all captured fixtures | | rsctape mock -o file.ts | ⚡ Generate MSW handlers | | rsctape mock -o file.ts --watch | 👀 Auto-regenerate on fixture changes | | rsctape diff <id1> <id2> | 🔄 Compare two fixtures (input fields) | | rsctape diff <id1> <id2> --full | 📝 Include RSC Payload line-by-line diff | | rsctape types | 🏷️ Generate TypeScript types from fixtures | | rsctape types --jsdoc | 📄 Generate JSDoc types instead | | rsctape delete <id> | 🗑️ Delete a fixture |

Common flags

-d, --dir <path>       Override fixture directory
-o, --output <path>    Output file (mock)
--actions <ids...>     Filter by action IDs (mock)
--full                 Full output diff (diff)
--jsdoc                JSDoc output (types)
-w, --watch            Watch mode (mock)

⚙️ How it works

┌─────────────┐     ┌──────────────┐     ┌──────────────┐
│  Browser     │────▶│  Node.js     │────▶│  Your App    │
│  Client      │     │  HTTP Server │     │  Handler     │
└─────────────┘     └──────┬───────┘     └──────────────┘
                           │
                    ┌──────▼───────┐
                    │  rsc-tape    │  ← monkey-patches http.createServer
                    │  interceptor │
                    └──────┬───────┘
                           │
              ┌────────────▼────────────┐
              │  Next-Action header?    │
              │  Yes → buffer & save    │
              │  No  → pass through     │
              └─────────────────────────┘
  1. register() patches http.createServer to wrap your request handler
  2. Checks each request for the Next-Action header (RSC protocol standard)
  3. Buffers request body + response chunks without modifying them
  4. After res.end(), asynchronously parses FormData and saves fixtures
  5. rsctape mock reads fixtures and generates MSW handlers

The interceptor is purely observational — it never modifies request or response data.


📁 Configuration

rsctape.config.json:

{
  "fixtureDir": "./fixtures/actions",
  "ignore": ["**/internal-*"]
}

| Field | Default | Description | |:------|:--------|:------------| | fixtureDir | ./fixtures/actions | Where fixtures are saved | | ignore | [] | Glob patterns for action IDs to skip |

Environment variables

| Variable | Effect | |:---------|:-------| | NODE_ENV=development | Enable interception (default) | | RSCTAPE_ENABLED=true | Force enable regardless of NODE_ENV | | RSCTAPE_VERBOSE=true | Log each captured action to console |


📼 Fixture format

Each Server Action produces two files:

{actionId}.json

{
  "input": {
    "username": "alice",
    "profile": {
      "age": 25,
      "city": "Taipei"
    }
  },
  "output": "0:{\"result\":\"ok\"}\n"
}

{actionId}.meta.json

{
  "actionId": "abc123def",
  "url": "/",
  "method": "POST",
  "statusCode": 200,
  "contentType": "text/x-component",
  "timestamp": "2024-01-15T10:30:00Z",
  "formDataMetadata": {
    "invocationType": "form",
    "frameworkHint": "next"
  }
}

⚡ Generated MSW handlers

import { http, HttpResponse } from 'msw';

/** Handler for action: abc123 */
export const handle_abc123 = http.post('*', ({ request }) => {
  if (request.headers.get('Next-Action') !== 'abc123') return;
  return new HttpResponse(`0:{"result":"ok"}\n`, {
    headers: { 'Content-Type': 'text/x-component' },
  });
});

export const handlers = [handle_abc123];

Handlers use http.post('*') with header matching because Server Action URLs vary by framework — the Next-Action header is the stable identifier.


🏷️ Type generation

rsctape types infers types from fixture data:

| Invocation type | Output | |:----------------|:-------| | Form submission | TypeScript interface from form fields | | Programmatic call | TypeScript tuple from serialized args |

// Form submission → interface
export interface CreateUserInput {
  username: string;
  age: number;
}

// Programmatic call → tuple
export type UpdateProfileInput = [string, Record<string, unknown>];

🔍 FormData parsing

rsc-tape handles the full complexity of Server Action FormData:

| Pattern | Result | |:--------|:-------| | user[name] | { user: { name: "..." } } | | tags[] | ["a", "b"] | | items[0], items[1] | ["first", "second"] | | Duplicate keys | Collected as arrays | | File fields | { __type: "file", name, type, size } | | JSON string values | Auto-parsed | | $ACTION_ID_, $ACTION_REF_ | Separated into metadata | | 1_$ACTION_ID_xxx | Ordered args array |


🔄 Diff

Action IDs change on HMR/recompile, but old fixtures stay on disk:

rsctape diff abc123 def456          # Input structure diff
rsctape diff abc123 def456 --full   # + RSC Payload line diff

💻 Programmatic API

import { register, createHandler, generateHandlers, detectFramework } from 'rsc-tape';

register({ fixtureDir: './my-fixtures', verbose: true });

const code = createHandler('actionId', fixture);

const module = await generateHandlers({
  fixtureDir: './fixtures/actions',
  outputPath: './handlers.ts',
});

const framework = await detectFramework(); // 'next' | 'waku' | 'parcel' | 'unknown'

🤝 Relationship to api-tape

rsc-tape is the RSC companion to api-tape. It reuses api-tape's core utilities (diff, type inference, sanitization) and adds Server Action-specific logic: HTTP interception, FormData parsing, and Next-Action header-based MSW handlers.


📖 More

  • Guide — step-by-step walkthrough with troubleshooting
  • Changelog — release history

📄 License

MIT