npm package: @mzebley/mark-down
Description: mark↓ core runtime and shared utilities
Latest version: 1.2.4

# mark↓ Core Runtime

_(published as `@mzebley/mark-down`)_

This package provides the framework-agnostic `SnippetClient` and supporting types used to fetch, cache, and render Markdown snippets at runtime. For a monorepo overview visit the [root README](../../README.md).

## Table of contents

1. [Installation](#installation)
2. [Quick start](#quick-start)
3. [Client options](#client-options)
4. [Working with snippets](#working-with-snippets)
5. [SSR and custom fetch functions](#ssr-and-custom-fetch-functions)
6. [Static sites / CDN usage](#static-sites--cdn-usage)
7. [Zero-build inline mode](#zero-build-inline-mode)
8. [Testing & type safety](#testing--type-safety)
9. [Related packages](#related-packages)
10. [Roadmap](#roadmap)

## Installation

Install the runtime alongside your application code:

```bash
npm install @mzebley/mark-down
```

You will also need a manifest file generated by the CLI. See the [CLI documentation](../cli/README.md) for steps to build `snippets-index.json`.

## Quick start

```ts
import { SnippetClient } from "@mzebley/mark-down";

const client = new SnippetClient({
  manifest: "/snippets-index.json",
});

const hero = await client.get("getting-started-welcome");
const components = await client.listByType("component");

console.log(hero.title);
```

The client lazily loads the manifest when first needed, then fetches Markdown files on demand. Results are cached for the lifetime of the client instance.

## Client options

`SnippetClient` accepts a single configuration object:

- **`manifest`** (`string | SnippetMeta[] | () => Promise<SnippetMeta[]>`) – where to load the manifest. Provide a URL, an in-memory array, or an async factory.
- **`base`** (`string`) – optional base path prepended to relative snippet paths. The client infers the directory from the manifest URL when omitted.
- **`fetch`** (`(url: string) => Promise<Response | string>`) – inject a custom fetch implementation. Use this for SSR, testing, or advanced caching.
- **`frontMatter`** (`boolean`, default `true`) – toggle YAML front-matter parsing.
- **`cache`** (`boolean`, default `true`) – enable or disable per-snippet and manifest memoisation.
- **`verbose`** (`boolean`) – log helpful warnings (for example, slug mismatches) during development.
- **`render`** (`(markdown: string) => string | Promise<string>`) – override the default `marked` renderer when you need custom HTML output.
- **`sanitize`** (`{ policy?: "default" | "strict" | "permissive"; config?: SanitizeHtmlOptions }`) – optionally sanitize rendered HTML before it is returned.

All options are optional except `manifest`. Results are rendered with `marked` by default; override at the application level if you need a different Markdown pipeline.

`sanitize` is opt-in and uses [`sanitize-html`](https://github.com/apostrophecms/sanitize-html) under the hood. Use the `policy` presets for common Markdown output or override the `config` to tweak allowed tags/attributes.

Security note: mark↓ intentionally allows unsanitized output when `sanitize` is omitted. This is a supported feature for trusted pipelines, but it is unsafe for untrusted content. Enable `sanitize` whenever snippets can be user-generated or externally sourced.

## Working with snippets

Commonly used APIs:

- `client.get(slug)` – fetch a single snippet. Throws `SnippetNotFoundError` if the manifest does not include the slug.
- `client.listAll()` – return a copy of every manifest entry.
- `client.listByType(type)` / `client.listByGroup(group)` – targeted manifest filters.
- `client.search({ type, group, tags, tagsMode })` – multi-field search helper with tag matching.
- `client.getHtml(slug)` – convenience wrapper that resolves directly to HTML.
- `client.invalidate()` / `client.invalidateSlug(slug)` – clear caches to force refetching.

Metadata is preserved exactly as declared in front matter. Standard keys (`slug`, `title`, etc.) are copied onto `SnippetMeta`, additional properties live inside `extra`, and the resolved `Snippet` includes both rendered HTML and an optional `raw` Markdown string (without front matter) for advanced use cases.

## SSR and custom fetch functions

The runtime runs in browsers, Node.js, or edge runtimes. For server-side rendering:

```ts
import fetch from "node-fetch";
import { SnippetClient } from "@mzebley/mark-down";

const client = new SnippetClient({
  manifest: () => import("./snippets-index.json"),
  fetch: (url) =>
    fetch(url).then((response) => {
      if (!response.ok) {
        throw new Error(`Request failed with status ${response.status}`);
      }
      return response;
    }),
});
```

You can also pre-seed snippets by passing an array to `manifest` to avoid network requests entirely. When `cache` is disabled the client re-fetches both manifest and snippet payloads on every request.

## Static sites / CDN usage

Need to run mark↓ inside a plain `<script type="module">` context? Use the pre-bundled browser build published at `dist/browser.js`. It automatically polyfills the minimum `Buffer` APIs required by the runtime.

```html
<script type="module">
  import { SnippetClient } from "https://cdn.jsdelivr.net/npm/@mzebley/mark-down/dist/browser.js";

  const client = new SnippetClient({ manifest: "./snippets-index.json" });
  const hero = await client.get("marketing-hero");
  document.querySelector("#hero").innerHTML = hero.html;
</script>
```

As long as you host `snippets-index.json` (generated by the CLI) alongside your static assets, this bundle works without any additional tooling or manual Buffer shims.

## Zero-build inline mode

### Introduction

Need Markdown inside plain HTML without a manifest, build step, or Node runtime? mark↓ ships a lightweight inline helper that scans the DOM for raw Markdown, renders it with the same `marked` pipeline used by `SnippetClient`, and progressively enhances the page. Content stays inside the HTML you ship, so crawlers and no-JS users can still read the raw Markdown even before JavaScript runs.

### Installation

Install the core package as usual:

```bash
npm install @mzebley/mark-down
```

- **Bundlers / ESM:** import the inline helper directly.

  ```ts
  import { enhanceInlineMarkdown } from "@mzebley/mark-down/inline";
  ```

- **Static `<script>` usage:** include the prebuilt UMD bundle that exposes `window.markDownInline`.

  ```html
  <script src="/node_modules/@mzebley/mark-down/dist/mark-down-inline.umd.js"></script>
  <script>
    document.addEventListener("DOMContentLoaded", () => {
      window.markDownInline.enhanceInlineMarkdown();
    });
  </script>
  ```

Host the UMD file yourself or load it from a CDN—no build tooling required.

### Basic usage (happy path)

Write plain Markdown directly in your HTML. The helper finds `[data-markdown]` blocks by default.

```html
<div data-markdown># Hello This is inline markdown with no build step.</div>

<script src="path/to/mark-down-inline.umd.js"></script>
<script>
  document.addEventListener("DOMContentLoaded", function () {
    window.markDownInline.enhanceInlineMarkdown();
  });
</script>
```

The Markdown text remains in the DOM for SEO while JavaScript enhances it to semantic HTML for users.

### Advanced usage (power path with front matter)

You can optionally prepend YAML front matter to provide metadata for each block. Fields are parsed with the same utilities used by the manifest flow.

```html
<div data-markdown>
  --- slug: intro title: Introduction tags: [hero] variant: lead --- #
  Introduction This block has metadata.
</div>
```

- `slug` → sets `id="intro"` if no ID exists and `data-slug="intro"`, allowing deep links.
- `title` → stored in `data-title` for TOC/index generation.
- `tags` → serialized to `data-tags="hero"` (comma-separated when multiple) for light-weight client-side filtering.
- `variant` → adds a class like `md-block--lead` so you can theme specific sections.

You still get fully rendered HTML, but additional attributes unlock progressive enhancement such as TOCs or custom styling.

### Options

Configure the helper per page:

```ts
enhanceInlineMarkdown({
  selector?: string; // defaults to "[data-markdown]"
  processFrontMatter?: boolean; // defaults to true
  applyMetaToDom?: boolean; // defaults to true
  sanitize?: { policy?: "default" | "strict" | "permissive"; config?: SanitizeHtmlOptions };
});
```

- Use `selector` to target different attributes or classes.
- Disable `processFrontMatter` when the block contains literal `---` fences that should stay visible.
- Set `applyMetaToDom` to `false` if you only care about rendered HTML and not DOM metadata.

### SEO & progressive enhancement

- Raw Markdown lives directly in your HTML, so crawlers can index the text before scripts execute.
- Users without JavaScript still see readable Markdown; JavaScript simply upgrades it to semantic HTML, adds IDs, and decorates DOM attributes for richer UX.
- This inline mode is ideal for small/medium static pages or marketing sites. Larger doc sites should continue using the manifest-driven snippet system for better caching and composition.

## Testing & type safety

- Use `SnippetMeta` and `Snippet` TypeScript types to describe props and state in your application.
- Mock the client by providing a manifest array or by stubbing the `fetch` option.
- Run the workspace tests with `npm run test -- core` to exercise Vitest suites that cover caching, filtering, and Markdown conversion.

## Related packages

- [CLI](../cli/README.md) – generate and watch manifests during development.
- [Angular adapter](../angular/README.md) – DI-aware provider, service, and component abstractions.
- [React adapter](../react/README.md) – Hooks and components for React and Next.js applications.

Looking for a step-by-step walkthrough? Start from the [Quick start guide in the root README](../../README.md#quick-start).

## Roadmap

- **Tag-aware helpers** – add `listByTag` and `listByExtra` filters for common metadata structures.
- **Prefetch & hydration APIs** – expose utilities for bundling snippets with static builds or preloading ahead of navigation.
- **Search integration** – optional fuzzy search index built from manifest data.
- **Cache policies** – allow LRU or time-based eviction strategies to better suit long-lived sessions.