sdxc

Type to search, or start from one of these:

Your first handler

Three packages composed into a request handler that never throws, and what each line of it demonstrates.

Last updated 2026-09-21

Here is a handler that accepts a markdown document, validates its frontmatter, and answers with either the frontmatter or the line the document went wrong on. It uses three packages and has no try in it.

npm add @sdxc/markdown @sdxc/response @sdxc/result
import { Markdown } from "@sdxc/markdown";
import { badRequest, methodNotAllowed, ok } from "@sdxc/response";
import { isFailure } from "@sdxc/result";
import * as s from "remix/data-schema";

let Frontmatter = s.object({
	title: s.string(),
	tags: s.optional(s.array(s.string())),
});

export default {
	async fetch(request: Request): Promise<Response> {
		if (request.method !== "POST") return methodNotAllowed({ error: "Send a POST" });

		let parsed = Markdown.parse(await request.text(), { frontmatter: Frontmatter });

		if (isFailure(parsed)) {
			return badRequest({
				error: parsed.error.message,
				line: parsed.error.position?.start.line,
			});
		}

		return ok({ frontmatter: parsed.data.frontmatter });
	},
};

What each part is doing

Markdown.parse returns a Result. It does not throw on malformed input and it does not return null. isFailure narrows the union, so the compiler will not let you reach parsed.data on the branch where there is none. This is the shape every fallible entry point in the collection has — see Result everywhere.

The schema is a Standard Schema. s.object here comes from remix/data-schema, but the option accepts any library implementing the same interface, and the parsed frontmatter is typed from the schema you passed. See Standard Schema validation.

The error carries a position. parsed.error.position points at where in the document the parse gave up, so the response tells the client which line to fix instead of that something, somewhere, was wrong.

The response helpers name the status. ok, badRequest and methodNotAllowed are @sdxc/response: each writes its status, its status text and its JSON content type, and merges an ok field into the body so a client has one field to branch on.

Running it

The handler is a plain object with a fetch method, which is what a Cloudflare Worker, Deno, Bun and Node's own server all accept. Nothing in it is framework-specific, and nothing in it is platform-specific either.

Where to go next

  • Result everywhere — the shape, and how to work with it without writing isFailure on every line.

  • Subpath exports — why some of these imports have a second segment.