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

effect-mdx

v0.2.2

Published

A library for working with MDX using Effect

Readme

effect-mdx

A robust, type-safe, and purely functional library for processing MDX (Markdown with JSX) content, built with the Effect-TS ecosystem. effect-mdx provides a high-level API for parsing, compiling, and manipulating MDX files, ensuring that all operations are handled within Effect's powerful and composable asynchronous runtime.

npm version

# Quick install
npm install effect-mdx

This library is designed for both backend and frontend use:

  • Backend (Node): read MDX from the filesystem, parse frontmatter, and compile to HTML or JS/ESM. Provide NodeFileSystem.layer when using file I/O.
  • Frontend (browser/edge runtimes): parse/compile MDX strings (no FS needed). Use parseMdxFile, compileMdxToHtml, or compileMdx directly on strings.

Use it for static sites, docs platforms, content pipelines, or interactive UIs that render MDX on the client.

Features

  • Purely Functional: Built entirely with Effect-TS for robust error handling and composition.
  • 🧱 Service-Based Architecture: Uses Effect's Service pattern for easy testing and dependency management.
  • 📝 Frontmatter-Aware: First-class support for parsing and validating YAML frontmatter.
  • ⚙️ Extensible Compilation: Leverages the unified ecosystem (remark, rehype) for flexible MDX processing.
  • 🚨 Typed Errors: Custom, typed errors (InvalidMdxFormatError) for predictable failure modes.

Installation

To get started, add effect-mdx to your project using your preferred package manager:

bun add effect-mdx
# or
npm install effect-mdx
# or
yarn add effect-mdx

Core Concepts

The central piece of this library is the MdxService, an Effect Service that encapsulates all MDX processing logic. To use it, you access its methods through the MdxService tag and provide its Live layer to your Effect program's context.

The MdxService depends on @effect/platform-node's NodeFileSystem, so you must provide NodeFileSystem.layer alongside MdxService.Live.

Quick Start: Reading and Compiling an MDX File

Here is a complete example of how to read an MDX file from the filesystem, parse its contents, and compile it to HTML.

import { Effect, Exit } from "effect";
import { NodeFileSystem } from "@effect/platform-node";
import { MdxService, MdxServiceApi } from "effect-mdx";

// 1. Define your program using Effect.gen for a clean, imperative style.
const program = Effect.gen(function* () {
  // 2. Access the MdxService from the context.
  const mdx = yield* MdxService;

  const filePath = "path/to/your/file.mdx";

  // 3. Use the service's methods.
  console.log(`Reading file: ${filePath}`);
  const { attributes, body } = yield* mdx.readMdxAndFrontmatter(filePath);
  console.log("Frontmatter:", attributes);

  console.log("\nCompiling body to HTML...");
  const html = yield* mdx.compileMdxToHtml(body);
  console.log("Compiled HTML:", html);

  return { attributes, html };
});

// 4. Provide the necessary layers to run the program.
const runnable = program.pipe(
  Effect.provide(MdxService.Live),
  Effect.provide(NodeFileSystem.layer)
);

// 5. Execute the effect and handle the result.
Effect.runPromiseExit(runnable).then((exit) => {
  if (Exit.isSuccess(exit)) {
    console.log("\n✅ Program completed successfully!");
  } else {
    console.error("\n❌ Program failed:", exit.cause);
  }
});

API Reference

Frontend Quick Start (no filesystem)

Use the service directly on strings in the browser/edge. You do not need NodeFileSystem.layer when you are not reading from disk.

import { Effect, Exit } from "effect";
import { MdxService } from "effect-mdx";

const mdx = `---\nlayout: demo\n---\n\n# Hello from the browser`;

const program = Effect.gen(function* () {
  const svc = yield* MdxService;
  // Validate and split frontmatter/body first
  const parsed = yield* svc.parseMdxFile(mdx);
  // Compile body to HTML on the client
  const html = yield* svc.compileMdxToHtml(parsed.body);
  return { frontmatter: parsed.attributes, html };
}).pipe(Effect.provide(MdxService.Live));

Effect.runPromiseExit(program).then((exit) => {
  if (Exit.isSuccess(exit)) {
    console.log("frontmatter", exit.value.frontmatter);
    console.log("html", exit.value.html);
  } else {
    console.error(exit.cause);
  }
});

MdxService

The MdxService provides the following methods:

| Method | Description | Returns | | ----------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------- | | readMdxAndFrontmatter(path) | Read file, parse YAML frontmatter and body. | Effect<ReadMdxAndFrontmatter, PlatformError | InvalidMdxFormatError> | | updateMdxContent(content, fm)| Reconstruct content with updated frontmatter. | string | | parseMdxFile(content) | Parse MDX string into attributes and body. | Effect<ParsedMdxAttributes, InvalidMdxFormatError> | | compileMdxToHtml(content) | Compile body to HTML using remark/rehype. | Effect<string, InvalidMdxFormatError> | | compileForLlmUi(content) | Prepare data for LLM UI (raw markdown + sanitized frontmatter). | Effect<CompileForLlmUiResult, InvalidMdxFormatError> | | compileMdx(content, options)| Compile true MDX with @mdx-js/mdx to JS/ESM. | Effect<CompiledMdxResult, InvalidMdxFormatError> | | validateMdxConfig(attrs) | Extract common config fields from attributes. | Effect<MdxConfigValidation, never> | | extractParameters(metadata) | Extract typed parameter definitions from metadata. | Parameters |

Key Types

  • ReadMdxAndFrontmatter
  • ParsedMdxAttributes
  • Frontmatter
  • Metadata
  • Parameters
  • ParameterDefinition
  • CompileForLlmUiResult
  • CompiledMdxResult
  • MdxCompileOptions
  • MdxConfigValidation

True MDX Compilation Example

LLM UI mode (what it’s for)

compileForLlmUi() prepares MDX for use in an "LLM UI"—a simple, typed data shape that frontends can consume to build prompt editors, previews, or playground-style experiences. It returns:

  • rawMarkdown: the MDX body, ready for editors or previews
  • frontmatter: sanitized, JSON-only metadata derived from YAML frontmatter
  • metadata: { llmUiMode: true }: a marker indicating UI-focused usage

This shape avoids bundler/JSX concerns and keeps the payload minimal and safe for client-side rendering.

import { Effect, Exit } from "effect";
import { MdxService } from "effect-mdx";

const mdx = `---\nmodel: gpt-4o\nparameters: { temperature: 0.2 }\n---\n\n# Prompt`;

const program = Effect.gen(function* () {
  const svc = yield* MdxService;
  const out = yield* svc.compileForLlmUi(mdx);
  // { rawMarkdown, frontmatter, metadata: { llmUiMode: true } }
  return out;
}).pipe(Effect.provide(MdxService.Live));

Effect.runPromiseExit(program).then((exit) => {
  if (Exit.isSuccess(exit)) {
    console.log("raw", exit.value.rawMarkdown);
    console.log("fm", exit.value.frontmatter);
  } else {
    console.error(exit.cause);
  }
});

Minimal UI render (insert into DOM):

<div>
  <h3>Frontmatter</h3>
  <pre id="fm"></pre>
  <h3>Markdown</h3>
  <textarea id="editor" rows="6" cols="60"></textarea>
</div>
<script type="module">
  import { Effect } from "effect";
  import { MdxService } from "effect-mdx";

  const mdx = `---\ntitle: Demo\n---\n\n# Hello`;

  const prog = Effect.gen(function* () {
    const svc = yield* MdxService;
    return yield* svc.compileForLlmUi(mdx);
  }).pipe(Effect.provide(MdxService.Live));

  Effect.runPromise(prog).then(({ rawMarkdown, frontmatter }) => {
    document.querySelector("#editor").value = rawMarkdown;
    document.querySelector("#fm").textContent = JSON.stringify(
      frontmatter,
      null,
      2
    );
  });
</script>
import { Effect, Exit } from "effect";
import { NodeFileSystem } from "@effect/platform-node";
import { MdxService } from "effect-mdx";

const content = `---\ntitle: JSX demo\n---\n\nexport const Answer = 42\n\n# Hello <Badge text=\"MDX\" />\n`;

const program = Effect.gen(function* () {
  const svc = yield* MdxService;
  const out = yield* svc.compileMdx(content, {
    format: "mdx",
    outputFormat: "program",
  });
  return out;
}).pipe(Effect.provide(MdxService.Live), Effect.provide(NodeFileSystem.layer));

Effect.runPromiseExit(program).then((exit) => {
  if (Exit.isSuccess(exit)) {
    console.log("code length:", exit.value.code.length);
    console.log("frontmatter:", exit.value.frontmatter);
    console.log("messages:", exit.value.messages);
  } else {
    console.error(exit.cause);
  }
});

Custom Errors

  • InvalidMdxFormatError: A Data.TaggedError raised when frontmatter is malformed or MDX content fails to compile. Contains reason and cause fields.

Providing the Service Layer

To use MdxService, you must provide its live implementation, MdxService.Live, to your Effect context. Since it interacts with the filesystem, it has a dependency on NodeFileSystem.

import { Effect } from "effect";
import { NodeFileSystem } from "@effect/platform-node";
import { MdxService } from "effect-mdx";

const myEffect = Effect.gen(function* () {
  const mdx = yield* MdxService;
  // ... use mdx service
});

// Provide both layers
const executable = myEffect.pipe(
  Effect.provide(MdxService.Live),
  Effect.provide(NodeFileSystem.layer)
);

This modular approach allows you to easily swap the live implementation with a test version in your unit tests, as shown in this project's own test suite.

Pipeline configuration (MdxConfigService)

You can configure the Markdown/MDX pipeline via the MdxConfigService layer. This controls remark/rehype plugins used by compileMdxToHtml() and serves as defaults for compileMdx().

  • Defaults: no extra plugins.
  • Override globally: provide your own layer for MdxConfigService.
  • Per call: pass remarkPlugins/rehypePlugins to compileMdx().

Example: enable slugs, autolinked headings, and sanitization.

import { Effect, Layer } from "effect";
import { NodeFileSystem } from "@effect/platform-node";
import {
  MdxService,
  MdxConfigService,
  type MdxPipelineConfig,
} from "effect-mdx";
import remarkSlug from "remark-slug";
import remarkAutolinkHeadings from "remark-autolink-headings";
import rehypeSanitize from "rehype-sanitize";

const pipeline: MdxPipelineConfig = {
  remarkPlugins: [remarkSlug, [remarkAutolinkHeadings, { behavior: "wrap" }]],
  rehypePlugins: [[rehypeSanitize, {}]],
  sanitize: {},
  slug: true,
  autolinkHeadings: true,
};

const PipelineLayer = Layer.succeed(MdxConfigService, {
  getConfig: () => pipeline,
});

const program = Effect.gen(function* () {
  const svc = yield* MdxService;
  const html = yield* svc.compileMdxToHtml("# Hello");
  return html;
}).pipe(Effect.provide(PipelineLayer), Effect.provide(NodeFileSystem.layer));

// Per-call override for true MDX
const mdxProgram = Effect.gen(function* () {
  const svc = yield* MdxService;
  const out = yield* svc.compileMdx("# Hi <X/>", {
    remarkPlugins: [],
    rehypePlugins: [],
  });
  return out.code;
});

Contributing

Contributions are welcome! This project follows a standard fork-and-pull-request workflow. Please follow these steps to contribute:

  1. Fork the Repository: Create your own fork of the project on GitHub.
  2. Clone Your Fork: Clone your fork to your local machine.
    git clone https://github.com/YOUR_USERNAME/effect-mdx.git
  3. Install Dependencies: This project uses bun for package management.
    bun install
  4. Create a Branch: Create a new branch for your feature or bug fix.
    git checkout -b my-new-feature
  5. Make Changes: Implement your changes and add any necessary tests.
  6. Run Tests: Ensure all tests pass before submitting your changes.
    bun test
  7. Push and Create a Pull Request: Push your branch to your fork and open a pull request against the main effect-mdx repository.

Development

Install dependencies

bun install

Build

bun run build

Run tests

bun test

License

MIT