sdxc

sdxc

Take one, or take the set. There are 121 of them, written for Remix on Cloudflare Workers: Request and Response in, typed values out.

npm add -g @sdxc/kit
pnpm add -g @sdxc/kit
yarn global add @sdxc/kit
bun add -g @sdxc/kit
Terminal
sdxc create blog
cd blog && bun install

sdxc make controller posts/show
sdxc make job digests/send --cron "0 8 * * *"
sdxc ui add dialog

sdxc run about
121packages under one scope
48RFCs the packages follow
103components in @sdxc/ui
297styling utilities in @sdxc/u
5apps running on them

Everything between the Request and the Response.

Validation, auth, caching, jobs, feeds and the UI on top: 121 packages that already agree on how to fail, so the tenth one you install reads like the first.

Packages that agree with each other

Every package answers the same way, so the second one you pick up already reads like the first. Each example here composes a few of them and never throws.

  • Request and Response in and out, never a framework's own objects

  • Every fallible entry point answers with a Result

  • Standard Schema at every boundary that reads untrusted input

  • Subpath exports, so what you skip stays out of the bundle

app/controllers/board.ts
import { redirect } from "@sdxc/http/response";
import { isFailure } from "@sdxc/result";
import { validate } from "@sdxc/validate";
import { createAction } from "remix/router";

export default createAction(routes.board.create, async (ctx) => {
	let form = await validate(ctx.formData, PostingSchema);
	if (isFailure(form)) return renderBoard(ctx, form.error);

	let posting = await Job.publish(ctx.db, form.data);
	await cache.delete(LISTING_KEY);
	await ctx.jobs.enqueue(jobs.confirm, { id: posting.id });

	ctx.log.set({ posting: { id: posting.id } });
	return redirect(routes.board.index.href(), { status: 303 });
});
app/mcp/cache.ts
import type { ToolMiddleware } from "@sdxc/mcp";

import { WorkerKVCache } from "@sdxc/cache/worker-kv";
import { isSuccess } from "@sdxc/result";

export function cacheToolResults(): ToolMiddleware {
	return async (ctx, next) => {
		let key = await keyFor(ctx.tool.name, ctx.input);
		if (key === null) return next();

		let cache = new WorkerKVCache(env.CACHE, { waitUntil });
		let hit = await cache.read<CallToolResult>(key);
		if (isSuccess(hit) && hit.data !== null) return hit.data;

		let result = await next();
		if (!result.isError) {
			await cache.write(key, result, { ttl: "5 minutes" });
		}
		return result;
	};
}
app/jobs/send-confirmation.ts
import { createJobHandler } from "@sdxc/jobs";
import { isFailure } from "@sdxc/result";

export default createJobHandler(jobs.confirm, async (ctx) => {
	let posting = await Job.find(ctx.db, ctx.input.id);
	if (!posting) {
		ctx.log.note("posting.missing");
		return;
	}

	let sent = await mailer.send(new PublishedEmail(posting));
	if (isFailure(sent)) {
		ctx.log.fail(sent.error);
		return;
	}

	ctx.log.set({ confirmation: { posting: posting.id } });
});
app/controllers/auth.tsx
import { contextOf } from "@sdxc/auth/remix/context";
import { redirect } from "@sdxc/http/response";
import { Location } from "@sdxc/location";
import { isFailure, wrap } from "@sdxc/result";
import { createAction } from "remix/router";

export default createAction(routes.auth.callback, async (ctx) => {
	let party = relyingParty(ctx.url);
	let grant = await wrap(() => party.callback(contextOf(ctx)));
	if (isFailure(grant)) {
		return ctx.render(<LoginView error={grant.error} />);
	}

	let user = await User.fromProfile(ctx.db, grant.data.profile);
	ctx.log.set({ user: { id: user.id } }).note("auth.signed_in");

	let returnTo = Location.safe(grant.data.returnTo, {
		fallback: routes.dashboard.href(),
	});
	return redirect(returnTo, { status: 303 });
});
app/webhooks/invoices.ts
import { ok, unauthorized } from "@sdxc/http/response/json";
import { isFailure } from "@sdxc/result";
import * as Webhooks from "@sdxc/webhooks";
import * as s from "remix/data-schema";
import { createAction } from "remix/router";

let InvoiceEvent = s.object({
	type: s.string(),
	amount: s.number(),
});

export default createAction(routes.webhook, async (ctx) => {
	let delivery = await Webhooks.verify(ctx.request, {
		secret: env.WEBHOOK_SECRET,
		schema: InvoiceEvent,
	});
	if (isFailure(delivery)) {
		return unauthorized({ error: "invalid_signature" });
	}

	let { payload } = delivery.data;
	await ctx.jobs.enqueue(jobs.recordInvoice, payload);
	return ok({ received: true });
});
app/controllers/rss.ts
import { xml } from "@sdxc/http/response";
import { RSS } from "@sdxc/rss";
import { links } from "@sdxc/websub/publisher";
import { createAction } from "remix/router";

export default createAction(routes.rss, async (ctx) => {
	let self = new URL("/rss.xml", ctx.url).href;
	let rss = new RSS({
		title: "Articles",
		description: "Everything published here.",
		link: new URL("/articles", ctx.url).href,
		atomLink: [
			{ rel: "self", href: self },
			{ rel: "hub", href: HUB },
		],
	});

	for (let article of await Article.published(ctx.db)) {
		rss.addItem({
			guid: article.id,
			title: article.title,
			description: article.excerpt,
			link: article.url,
			pubDate: article.publishedAt.toUTCString(),
		});
	}

	let headers = { link: links({ hubs: [HUB], self }) };
	return xml(rss.toString(), { headers });
});
app/services/rate-limit.ts
import type { Adapter } from "@sdxc/rate-limit";

import { currentLog } from "@sdxc/logger";
import { tooManyRequests } from "@sdxc/rate-limit";
import { isFailure } from "@sdxc/result";

export async function spend(adapter: Adapter, key: string) {
	let result = await adapter.consume(key);
	if (isFailure(result)) {
		currentLog()?.warn("rate_limit.unavailable", { key });
		return null;
	}
	if (result.data.allowed) return null;

	return tooManyRequests(result.data, adapter.window, JSON.stringify({ error: "rate_limited" }), {
		headers: { "Content-Type": "application/json" },
	});
}

[ Principles ]

What holds the set together

Web standards first

Request, Response, Web Crypto, Intl and Standard Schema — the platform, not a shim over it.

What these are

Errors are values

Every fallible entry point answers with a Result, so nothing throws past you and the failure carries where it happened.

Result everywhere

Nothing you didn't ask for

Subpath exports keep what you skip out of your bundle, and 53 packages pull in nothing from outside the collection at all.

Subpath exports

Built for Remix, not bound to it

64 target Remix directly, because that is what they are written for. The other 57 depend on no framework and run anywhere fetch does.

Your first handler

[ Packages ]

The packages, by the problem they solve

Package inspector121 packages

Three worth the detour

Each of these does something you cannot get by reaching for another package: specs you can run, components that work before any JavaScript loads, and a markdown parser this very page is rendered through.

  • @sdxc/spec runs specifications under permissions you grant by name

  • @sdxc/ui builds on <dialog>, the Popover API and Invoker Commands

  • @sdxc/markdown gives you a typed tree to walk, transform and write back

spec/greeting.spec
use fs
use cli

test "the script prints its greeting" {
	given {
		write "index.js" "console.log(\"hello from the workspace\")"
	}
	when {
		let result = run "node" "index.js"
	}
	then {
		expect result.exit_code 0
		expect result.stdout "hello from the workspace\n"
	}
}
invite-teammate.tsx
import { Button, Dialog, TextField } from "@sdxc/ui";

<Button commandfor="invite" command="show-modal">
	Invite a teammate
</Button>

<Dialog id="invite" aria-labelledby="invite-title">
	<form method="post" action="/team/invites">
		<Dialog.Header>
			<Dialog.Title id="invite-title">
				Invite a teammate
			</Dialog.Title>
			<Dialog.Description>
				They get an email with a link to join.
			</Dialog.Description>
		</Dialog.Header>

		<TextField
			label="Email"
			type="email"
			name="email"
			required
		/>

		<Dialog.Footer>
			<Button
				type="button"
				commandfor="invite"
				command="close"
				variant="outline"
			>
				Cancel
			</Button>
			<Button type="submit">Send invite</Button>
		</Dialog.Footer>
	</form>
</Dialog>;
app/posts/render.ts
import { highlight } from "@sdxc/highlight/markdown";
import { Markdown } from "@sdxc/markdown";
import { toHTML } from "@sdxc/markdown/html";
import { isFailure } from "@sdxc/result";
import * as s from "remix/data-schema";
import * as coerce from "remix/data-schema/coerce";

let Frontmatter = s.object({
	title: s.string(),
	publishedAt: coerce.date(),
});

let parsed = Markdown.parse(source, { frontmatter: Frontmatter });
if (isFailure(parsed)) return parsed;

let walked = Markdown.walk(parsed.data.document, {
	...highlight,
	link(node) {
		if (!node.href.startsWith("/")) return;
		return { ...node, href: new URL(node.href, origin).href };
	},
});
if (isFailure(walked)) return walked;

return { ...parsed.data.frontmatter, html: toHTML(walked.data) };

Versions are dates

A release published on the 21st of September 2026 is 2026.9.21. There is at most one a day, and a later date means a later release and nothing more: it promises no compatibility with the one before it.

So pin exactly, and move when you are ready to read what changed.

package.json
{
	"dependencies": {
		"@sdxc/result": "2026.9.21"
	}
}

Take one, or take the set.

Start with the guides, or go straight to the package you came for.