sdxc

[ Releases ]

Changelog

Every dated release of the collection, newest first, with its notes.

2026.10.15

On GitHub

@sdxc/qr

  • fix: patch jsQR version-23 alignment for round-trips Stock jsQR 1.4.0 lists alignment centre 74 for version 23; ISO/IEC 18004 Annex E requires 78 (cozmo/jsQR#251). The encoder already matches the standard; the round-trip suite now loads a corrected reader so version-23 symbols decode.

  • fix: drop temporary fuzz probe scripts The version-23 jsQR patch and the round-trip regression stay; the one-off isolate/probe scripts do not ship with the package.

@sdxc/spec

  • feat: add Browser Run plugin for CDP-backed browser tools Workers can drive the same browser tool surface as agent-browser by injecting a Browser Run binding through @sdxc/spec/browser-run, so a hosted flow can exercise a real app without a local CLI.

  • fix: kill the process group after cli.stop's leader exits A shell that dies on SIGTERM cancelled the SIGKILL timer while its background children were still disappearing; stop now SIGKILLs the group in finally, and the grandchild assertion waits out the brief kill(pid, 0) race.

@sdxc/ui

  • fix: keep Typeset code blocks from trapping page scroll overflow: auto made every pre a two-axis scrollport, so a vertical wheel over a fence stopped at the block instead of chaining to the document. Scroll long lines on the inline axis only, and contain overscroll there so a horizontal fling stays inside the block.

  • fix: drop Typeset pre scroll comment The overflow utilities already state the contract; the block restated what the declarations do.

@sdxc/workers-cache

  • fix: mark a response that declares no policy as private, no-store Workers Cache applies heuristic freshness to a response without Cache-Control, storing a 200 for two hours under a key that ignores cookies. A route that never called ctx.cache() was therefore cached and replayed to every visitor of its URL, signed-in pages included. The middleware now writes private, no-store on such a GET or HEAD response, unless it set its own Cache-Control or Expires.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.14...v2026.10.15

2026.10.14

On GitHub

@sdxc/activitypub

  • fix: format issue paths through @sdxc/json-pointer

@sdxc/auth

  • fix: read Link headers through @sdxc/link-header

@sdxc/authz

Republished because @sdxc/mcp changed.

@sdxc/feed

  • fix: parse Link headers with the RFC 8288 scanner

@sdxc/idempotency

Republished because @sdxc/problem changed.

@sdxc/json-pointer

  • feat: RFC 6901 parse, stringify, get and set

@sdxc/json-schema

  • fix: format conversion paths through @sdxc/json-pointer

@sdxc/link-header

  • feat: RFC 8288 parse and stringify

@sdxc/mcp

Republished because @sdxc/json-schema changed.

@sdxc/merge-patch

  • fix: format member paths through @sdxc/json-pointer

@sdxc/openapi

  • fix: escape pointer segments through @sdxc/json-pointer

@sdxc/pagination

  • feat: parse Link headers through @sdxc/link-header

@sdxc/problem

  • fix: format issue paths through @sdxc/json-pointer

@sdxc/search

Republished because @sdxc/pagination changed.

@sdxc/ui

  • chore: fix CodeQL polynomial ReDoS and incomplete sanitization alerts (#4)

    <!-- CURSOR_AGENT_PR_BODY_BEGIN -->

    Summary

    Clears the open CodeQL code-scanning findings (API access to the alerts list wasn’t available to this agent, so the same javascript-code-scanning suite was run locally against main).

    • js/polynomial-redos in packages/ui — slide()’s plain-length regex used \d*\.?\d+, which backtracked over long digit runs that weren’t lengths. Replaced with mutually exclusive numeric alternatives and added a linear-time regression test.

    • js/incomplete-sanitization in apps/sdxc — reachedThrough escaped only $ before building a RegExp. Identifiers are now fully escaped and matched with JavaScript identifier edges (so $ names still resolve).

    Verification

    • vp test run on packages/ui/src/animations/transitions.test.ts and apps/sdxc/scripts/catalogue.test.ts — 54 passed

    • bun check:fix — clean

    • Local CodeQL javascript-code-scanning re-run after the fixes — 0 findings outside ignored test/vendor paths

    Commits

    • fix(ui): match slide distances with a linear-time length regex

    • fix(sdxc): escape identifiers before matching them in catalogue reachability

    <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-2a035b48-e974-4664-bc8d-9028556b7224?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-2a035b48-e974-4664-bc8d-9028556b7224&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
  • feat: add YouTubeEmbed, a lazy-loaded 16:9 YouTube player YouTubeEmbed renders an iframe for a video id, optionally starting at a given second, from www.youtube-nocookie.com so visitors receive no YouTube cookies until they press play. The title is a required prop, and a page using it must allow that host in frame-src.

@sdxc/webmention

  • fix: read Link headers through @sdxc/link-header

  • fix: advertise the endpoint through @sdxc/link-header

@sdxc/websub

  • fix: parse Link headers with the RFC 8288 scanner

  • fix: write hub Link advertisements through @sdxc/link-header

@sdxc/well-known

  • fix: format issue paths through @sdxc/json-pointer

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.13...v2026.10.14

2026.10.13

On GitHub

@sdxc/activitypub

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/api-client

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/assets-middleware

  • feat: publish a page's document assets as ctx.assets New package. assets(resolver, { stylesheets, script, nonce }) resolves the stylesheets a document links and its browser entry through the app's asset resolver, before the route runs, and publishes them as ctx.assets for the page to pass to its document. The CSP nonce is read through the callback the first time ctx.assets.nonce is accessed, so a response that renders no document never asks for one.

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/atom

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/attribution

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/auth

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/authz

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/backoff

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/billing

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/bracket-params

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/cache

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/captcha

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/catch-response-middleware

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/cloudflare-mocks

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/cloudflare-pricing

  • feat: define a rate card that prices usage quantities defineRateCard(rates) turns a record of per-unit prices into a frozen card with the resources in the order they were written, zero() for an empty quantities record, and price(quantities) for the total in cents, treating a missing or negative quantity as zero. AnalyticsEngine.billedCentsPerUnit(meter) prices an Analytics Engine meter at zero until Cloudflare starts billing for it.

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/cron

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/crypto

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/csv

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/data-model

  • docs: generate example ids with @sdxc/uuid/v4

@sdxc/data-table-d1

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/data-table-sqlstorage

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/database-middleware

  • feat: publish a request's database as ctx.db New package. database(db) is router middleware that sets a remix/data-table Database on the request context under the exported Database key and as ctx.db, with a typed context transform so a router that derives its context from an inline middleware list sees ctx.db typed. Importing the package declares db on RequestContext, which is the property @sdxc/data-model/router's models() reads.

  • feat: publish the database on jobs through ./jobs @sdxc/database-middleware/jobs exports database(db) as @sdxc/jobs job middleware, setting the database under the same Database key as ctx.db, so a job handler reads it the way a route handler does. It takes the instance or a function opening it, called once per job run, since a dispatcher outlives the tests that swap its database. @sdxc/jobs is an optional peer dependency.

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/dates

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/diagram

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/digest-fields

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/distill

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/doh

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/duration

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/email-address

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/epub

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/expression

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/feed

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/flags

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/flags-engine

  • test: generate KV keys with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/get-client-ip

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/highlight

  • docs: drop crypto.randomUUID from the grammar example

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/honeypot

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/hostname

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/html

  • refactor: compute accessible names in-package, dropping dom-accessibility-api Accessible names now come from an AccName 1.2 / HTML-AAM implementation inside @sdxc/html, built on the package's own role table and hiding rules. Some query names change to match what a browser computes:

    • inline markup no longer splits words (<a>Pro<b>file</b></a> is "Profile")

    • <script> and <style> text no longer counts toward a name built from content
    • an input's placeholder and a figure's <figcaption> now name them

    • a text field's value counts when it sits inside another control's label

    • hidden content inside a hidden aria-labelledby target counts toward the name

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/http

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/http-signatures

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/i18n

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/icalendar

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/icons

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/idempotency

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/ip

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/jobs

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/jsdoc

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/json-feed

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/json-schema

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/jwt

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/lazy-frame

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/lazy-route

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/location

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/logger

  • chore: remove dependencies nothing imports Drops declarations that no file in their workspace references, so a change to one of them no longer republishes the packages that listed it:

    • logger: @cloudflare/workers-types (dev)

    • webmention: remix

    • demo: @sdxc/messageformat, @sdxc/sample (dev)

    The lockfile also drops the entries for the unused declarations already removed from activitypub, billing, idempotency, messaging, newsletter, auth-saas, blog, books and reader.

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/mail

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/markdown

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/math

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/mcp

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/merge-patch

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/messageformat

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/messaging

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/microformats

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/micropub

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/newsletter

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/no-www-middleware

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/openapi

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/opml

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/outbound

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/pagination

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/passkey

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/password-policy

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/problem

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/qr

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/random

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/rate-limit

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/rdap

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/response

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/result

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/robots

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/rss

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/saml

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/sample

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/scim

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/search

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

  • feat: read an order: directive into parsed.order parseQuery reads order:relevance, order:asc and order:desc into a new ParsedQuery.order (a SearchOrder, "relevance" by default) instead of a term. The package carries the direction only; the caller decides which column asc and desc sort. An unknown value, a signed order:, or a declared field or filter named order keeps today's reading.

  • feat: formatQuery writes a parsed query back as canonical text formatQuery(query, { prefix }) writes what parseQuery read as one canonical text: single spaces, OR between alternatives, quotes only where a value needs them, and order: last. Parsing it again yields the same clauses, filters and order, so two spellings of one query write the same text, which an app can store under a unique index.

@sdxc/security-headers

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/semver

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/seo

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/server-timing

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/session-storage-kv

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/sitemap

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/spam

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/spec

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/strings

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/structured-fields

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/trace-context

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/trailing-slash-middleware

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/typeid

  • docs: build example UUIDs with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/types

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/u

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

  • fix: raw colors and shadows pass through, translucent() is see-through, debug(false) and u.if() lists work

    • Color utilities pass hex, named and system colors through as written: bg("#fff"), bg("Canvas") and fg("red") emit those colors instead of throwing or resolving var(--ui-red-fg). Tones and palette paths resolve as before; a tone sharing a CSS color name is written with a suffix ("red.fg").

    • shadow("none") clears the elevation shadow, and a raw CSS shadow such as shadow("0 1px 2px black") applies as written instead of becoming the md shadow.

    • translucent() paints a see-through background (--ui-bg mixed 70% with transparent) where the backdrop blur applies, so the blur is visible; under reduced transparency it keeps the opaque --ui-bg.

    • debug(false) emits nothing, so a flag can switch the outline off; debug() still outlines.

    • u.if(condition, [a, b]) merges the list into one mixin, so it applies as a unit inside a mix list like combine() does.

    • Docs: type("lg")'s line-height fallback is calc(1.75 / 1.125), and backdropDropShadow() states it needs its own backdrop root to show.

@sdxc/ui

  • refactor: generate identifiers with @sdxc/uuid/v4

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

  • feat: larger pointer targets, concentric surfaces and finer typographic detail Every small control now reaches a pointer target of --ui-target-size: 24px by default (WCAG 2.5.8) and 44px under pointer: coarse, through the new hitArea() style. Buttons, toggle and link buttons, tag remove buttons, the Resizable handle, sliders, toast and calendar controls, sidebar actions, drag handles and swatches keep their visible size and grow only the area that receives the pointer. TagGroup.Remove renders as a 1rem glyph again instead of a 40px icon button inside its tag. Breadcrumb links and sortable table headers make their whole visible area clickable.

    Floating panels (menus, list popups, select pickers, navigation menus) derive their radius from --ui-radius-md plus --ui-surface-padding, so rows and panel corners stay concentric under any override (surfaceInset(), surfacePadding(), concentricRadius()). Elevated surfaces draw a layered --ui-shadow-ring instead of a solid border, images get an inset --ui-image-outline, and the reset enables font smoothing.

    Titles wrap with text-wrap: balance and body copy with pretty; counts, readouts, dates, page numbers and key caps use tabular figures, and Table.Column/Table.Cell take numeric for end-aligned tabular columns. Buttons trim the padding beside a leading or trailing icon.

    enterExit() gains blur, separate exit values, exitDuration (75% of duration by default) and exitEasing; zoom() and slide() exit halfway back, and Drawer, Sheet and Modal leave with a short fade and blur. Icon-only button sizes and the checkbox size are now custom properties.

  • docs: trim the README additions for target size, surfaces and motion to one line each

  • fix: count a floating surface's 1px edge in its concentric radius Menus, list popups, select pickers, the command palette, the menubar and navigation panels round their outer corners by the row radius plus the padding plus the surface's 1px edge, so the gap between a highlighted row and the panel corner is even all the way around.

  • fix: give a vertical Slider the same pointer target as a horizontal one A vertical slider's range input now reaches --ui-target-size across the track's width (24px, or 44px on coarse pointers) instead of the 20px thumb width, while the rail and fill render exactly as before.

@sdxc/user-agent

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/uuid

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/validate

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/web-push

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/webhooks

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/webmention

  • chore: remove dependencies nothing imports Drops declarations that no file in their workspace references, so a change to one of them no longer republishes the packages that listed it:

    • logger: @cloudflare/workers-types (dev)

    • webmention: remix

    • demo: @sdxc/messageformat, @sdxc/sample (dev)

    The lockfile also drops the entries for the unused declarations already removed from activitypub, billing, idempotency, messaging, newsletter, auth-saas, blog, books and reader.

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/websub

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/well-known

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/workers-cache

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/xml

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/yaml

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/zip

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

@sdxc/zone-file

  • docs: restructure the README into usage steps, a concise API reference and standalone patterns Usage walks the basics in order, the API reference gives each export one or two sentences under its signature, and the patterns show the package on real tasks, so the README teaches every feature without reading the whole reference. Signatures and behavior are checked against the current source.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.12...v2026.10.13

2026.10.12

On GitHub

@sdxc/activitypub

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/api-client

Republished because @sdxc/trace-context changed.

@sdxc/attribution

Republished because @sdxc/logger changed.

@sdxc/auth

Republished because @sdxc/idempotency changed.

@sdxc/authz

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/billing

Republished because @sdxc/api-client changed.

@sdxc/cache

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/cloudflare-mocks

  • fix: type the SQL cursor's next() as IteratorResult so it satisfies the latest workers-types

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/cron

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/data-model

  • feat: reach the model context from methods, upsert in one statement, and match constraint violations Methods read the model context as this.context: this.context.models reaches a sibling in the same binding and unit of work, and every member the app supplies, such as jobs, is there too. A lazily loaded entry answers db and context before its module loads.

    Type models on the context from an interface of the registry's entries, BoundModels<AppModels>, rather than BoundRegistry<typeof models>: each entry then resolves when it is read, so a method calling a sibling no longer collapses ctx.models to any (TS7022). withContext<Context>() answers a createModel typed with a context of your own, for an app that binds several registries; each registry's bind and middleware require the members its models' context declares.

    Breaking changes:

    • upsert(values, { conflict, update }) replaces upsert(values, { conflictTarget }). It is one INSERT … ON CONFLICT statement with no read before it and runs no model callbacks; update defaults to the values without the conflict and key columns and created_at, and update: {} keeps a conflicting row as it is.

    • A table declaring primaryKey: ["id"] takes its key as the column's value in find, update and delete, as primaryKey: "id" does.

    • A method or scope named like a model member (exists, db, join, context…) is now a type error; ReservedName lists them.

    Also:

    • update(key, {}) touches updated_at and runs the update callbacks.

    • A bulk update from a query takes touch: false and returning.

    • A database constraint refusal is a ConstraintViolation, a ValidationError with kind ("unique", "foreign-key", "not-null", "check") and column.

    • ./testing adds overrideModel(model, build), which replaces members in every binding of a model, per-request ones included, and publishModels(ctx, registry, context).

    • A chained call on a lazy entry whose import fails rejects once, without an unhandled rejection from the intermediate calls.

  • fix: write only the timestamp columns a table has in upsert and empty updates A table declaring timestamps: { createdAt } resolves updatedAt to its default name, which the table may not have; upsert and update(key, {}) wrote it anyway and the statement failed.

  • fix: hand overrideModel's build the original members A replacement that called through to bound.create at call time reached itself and recursed. build now receives a copy holding the original members. Also exports ConstraintKind, and the docs declare a withContext context as an interface, since an intersection type makes the models circular again.

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

  • fix: refuse an upsert outside a model's constraints before writing, take http(s) URLs only, and answer NotFound for a key of the wrong shape upsert on a model with constraints now reads the conflict target first, so a conflicting row of another sub-model is left as it is instead of being overwritten and then refused. field.url() accepts http and https URLs only, on write and on read, so a stored value is safe to render as a link. A meta key or scope named like an Object.prototype member (constructor, proto) is undeclared like any other instead of throwing, and a key of a shape no primary key takes, such as a list, answers null from find and NotFound from update and delete.

  • docs: restructure the README and add patterns for scopes, methods, callbacks, constraints and meta fields Usage walks define, bind, publish and context in order, and the API documents each export in one or two sentences under its signature. Five new standalone patterns show a default scope with composable scopes, custom methods answering queries, results or raw rows, every callback around one write, constraints pinning a model to a slice of a table, and typed meta fields over a key/value table; the post-types pattern now focuses on extend().

@sdxc/data-table-d1

  • fix: bind an upsert's insert values before its conflict update An upsert whose update differs from its values compiled the conflict clause first, so its values were bound to the insert's placeholders and the row was written with the wrong columns, or refused. Values now bind in the order the statement reads them.

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/data-table-sqlstorage

  • fix: bind an upsert's insert values before its conflict update An upsert whose update differs from its values compiled the conflict clause first, so its values were bound to the insert's placeholders and the row was written with the wrong columns, or refused. Values now bind in the order the statement reads them.

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/flags

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/flags-engine

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/hostname

Republished because @sdxc/api-client changed.

@sdxc/i18n

Republished because @sdxc/logger changed.

@sdxc/icons

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/idempotency

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/jobs

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/jsdoc

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/jwt

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/logger

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/mail

Republished because @sdxc/logger changed.

@sdxc/mcp

  • feat: import a tool or resource handler on first use with lazy() Wrap a handler's module in lazy(() => import(...)) when mapping it and a Worker starts without the code behind every tool and resource. A module imports once per isolate and concurrent requests share the import; a failed import answers as an unexpected failure and is retried by the next request.

    tools/call and resources/read import only the handler they reach. Lists import every lazy module they must ask, because available and list live in the module, and a list containing a lazy handler defaults to a private cacheScope.

@sdxc/messaging

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/newsletter

Republished because @sdxc/api-client changed.

@sdxc/rate-limit

Republished because @sdxc/logger changed.

@sdxc/rdap

Republished because @sdxc/cache changed.

@sdxc/sample

Republished because @sdxc/jwt changed.

@sdxc/search

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/seo

Republished because @sdxc/attribution changed.

@sdxc/spec

Republished because @sdxc/sample changed.

@sdxc/trace-context

Republished because @sdxc/logger changed.

@sdxc/typeid

Republished because @sdxc/uuid changed.

@sdxc/ui

Republished because @sdxc/icons changed.

@sdxc/uuid

  • chore: upgrade all dependencies, including vite-plus 0.3.3 and Vitest 4.1.11 Vitest 4.1.11 fixes the @vitest/mocker path traversal advisory (Dependabot alert 2). Vitest is carried by vite-plus, so the bump comes from vite-plus.

@sdxc/web-push

Republished because @sdxc/jwt changed.

@sdxc/workers-cache

Republished because @sdxc/logger changed.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.11...v2026.10.12

2026.10.11

On GitHub

@sdxc/data-model

  • feat: add default scopes and this-bound methods; join whereMeta reads Breaking: methods is now an object whose methods run with this bound to the model, so a method can call the model's other methods with types; a method answering a plain value turns the definition into an InvalidModel. ModelContext keeps only db and get(key): require() is gone, and services a callback reads are declared by augmenting the interface and supplied at binding. models is typed the same way, with BoundRegistry<typeof models>.

    • defaultScope applies to every read and to the row an update or delete targets; unscoped() reads past it.

    • The first whereMeta on a read joins the meta table, so a broad key such as a locale filters any number of rows within D1's parameter limit.

    • An update naming only meta keys touches the table's updatedAt column.

  • fix: build the model context when an app augments ModelContext Binding cast the package's own members to ModelContext, which fails to compile once an app adds a member such as models to the interface.

  • fix: run a lazy model's calls as real promises, awaited or not A call on a model whose module had not loaded returned a thenable that did nothing until something called its then(), so an unawaited write never ran and code testing instanceof Promise never awaited it. The call now starts the import and replays as it is made, behind a real Promise.

  • feat: expose the binding's database on a bound model as db A custom method can run a raw statement the query builder cannot express, such as an INSERT ... ON CONFLICT DO NOTHING, through this.db, which is the transaction when the model runs inside a unit of work.

  • fix: accept a single primary key in its object form A table that declares its key as primaryKey: ["id"] types a key as { id }, so find, update and delete were handed an object they bound as the key's value. A single key now reads as its value or as an object naming its column, the two forms data-table accepts.

@sdxc/typeid

  • docs: import generateUUID from @sdxc/uuid/v7 in examples

@sdxc/uuid

  • feat!: generate v4 and v7 UUIDs from their own entry points @sdxc/uuid/v4 and @sdxc/uuid/v7 each export generateUUID(): UUID, so the import path names the version and switching versions changes only that path. The root entry point keeps the UUID type, isUUID, assertUUID and the error classes, and no longer exports generateUUID or generateUUIDv7: import generateUUID from @sdxc/uuid/v4 for the old generateUUID(), and from @sdxc/uuid/v7 for the old generateUUIDv7().

    The v7 generator is now monotonic: a 12-bit counter in rand_a keeps values from one process in call order within a millisecond, which matters on Cloudflare Workers where Date.now() holds still between I/O.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.10...v2026.10.11

2026.10.10

On GitHub

@sdxc/activitypub

Republished because @sdxc/cache changed.

@sdxc/api-client

Republished because @sdxc/trace-context changed.

@sdxc/atom

Republished because @sdxc/xml changed.

@sdxc/attribution

Republished because @sdxc/logger changed.

@sdxc/auth

Republished because @sdxc/crypto changed.

@sdxc/authz

Republished because @sdxc/billing changed.

@sdxc/billing

Republished because @sdxc/api-client changed.

@sdxc/cache

Republished because @sdxc/logger changed.

@sdxc/crypto

  • fix: decode base32 and PEM text in linear time A long run of = in base32 input, or many unterminated PEM begin lines, took quadratic time to decode. Both decoders now scan the text once, accepting and rejecting exactly the same input as before.

  • feat: export the codecs alone as @sdxc/crypto/encoding @sdxc/crypto/encoding exposes Base32, Base64, Base64Url and Hex through an import graph that stays on Web APIs. Browser code imports from there so a dev server that serves modules unbundled never requests the node:crypto import behind password hashing, which it refuses to load in a browser.

@sdxc/data-model

  • feat: add models over remix/data-table tables A new package for the model layer apps keep writing by hand. createModel takes a data-table table plus scopes, custom methods, async callbacks, constraints and typed meta fields; a definition binds once to a database and a host context, and every member runs there.

    • Scopes chain on real data-table queries, so Pagination, search refinement and eager loading take them unchanged.

    • Writes answer Results: ValidationError for callback, table-hook and constraint failures, NotFound for a missing key.

    • afterCommit callbacks defer to the end of models.transaction() and drop on a throw or a returned Failure, on every adapter; writes roll back only where the database has real transactions.

    • extend() builds sub-models on a discriminator column, and meta fields read and write a key/value companion table as typed values.

    • createModels() registries load entries on demand; ./router and ./jobs publish them as ctx.models, and ./testing adds seeded factories.

@sdxc/diagram

  • fix: escape every regular-expression character in sequence arrows The message pattern escaped only parentheses in arrow tokens; it now escapes every metacharacter, so adding an arrow can never change how messages parse.

  • feat: name a diagram from an alt option and a fence's alt annotation parseDiagram, toSVG and the Diagram component take an alt that becomes the SVG title, the accessible name screen readers announce, over the name a title or accTitle statement gives it. The markdown visitor keeps a mermaid fence's {% alt="…" %} annotation on the diagram tag, so renderDiagram and DiagramTag name the drawing from the markdown.

@sdxc/digest-fields

Republished because @sdxc/crypto changed.

@sdxc/distill

Republished because @sdxc/outbound changed.

@sdxc/doh

Republished because @sdxc/zone-file changed.

@sdxc/email-address

Republished because @sdxc/doh changed.

@sdxc/epub

  • fix: check stylesheet references in linear time A stylesheet full of unclosed comment openers made reference checking quadratic. Comments are now removed with a forward scan that keeps the same results.

  • fix: read stylesheet url() and @import references in linear time A stylesheet with an unclosed url( followed by a long whitespace run, or many repeated @import openers, made reference checking take quadratic time or worse. References are now read by forward scanners that accept the same quoted and bare forms.

@sdxc/expression

  • refactor: read strings and quoted names with a linear scanner Strings, backtick-quoted names and context paths are now read by small scanners instead of regular expressions. Parsing results and error positions are unchanged.

@sdxc/feed

Republished because @sdxc/atom changed.

@sdxc/flags

Republished because @sdxc/jobs changed.

@sdxc/flags-engine

Republished because @sdxc/expression changed.

@sdxc/highlight

  • fix: scan JavaScript regex literals in linear time A backslash or "[" inside a regex literal now reads one way only, as an escape or a character class, so long runs of "[]" or "\" no longer hang the highlighter. A regex with an unclosed "[" is painted as division.

@sdxc/honeypot

  • fix: draw trap field names uniformly from the alphabet Trap names were drawn with a byte modulo 26, which slightly favored some letters; letters are now picked by rejection sampling, so each is equally likely.

@sdxc/hostname

Republished because @sdxc/api-client changed.

@sdxc/http

Republished because @sdxc/crypto changed.

@sdxc/http-signatures

Republished because @sdxc/crypto changed.

@sdxc/i18n

  • fix: read each Accept-Language range on its own parse splits the header on commas and reads each trimmed range with an anchored pattern, so its cost stays linear. A malformed range is now skipped whole instead of leaving fragments behind.

@sdxc/idempotency

Republished because @sdxc/crypto changed.

@sdxc/jobs

Republished because @sdxc/logger changed.

@sdxc/jsdoc

  • fix: read inline links in linear time An unclosed {@link} followed by a long run of whitespace made inlineLinks quadratic. Links are now read with a forward scanner that returns the same targets, labels and offsets.

@sdxc/logger

Republished because @sdxc/expression changed.

@sdxc/mail

  • fix: derive the plain-text part in linear time with no markup left behind htmlToText now drops comments, the doctype, scripts, styles and hidden or skip-in-text elements in a single forward pass. Input with many unclosed comments or tags no longer makes it slow, and removing one region can no longer join the text around it into a new tag or comment.

@sdxc/markdown

  • fix: parse link titles and write raw HTML blocks in linear time A link title full of backslash escapes no longer takes exponential time, and a backslash before the closing quote now always escapes it, as CommonMark specifies. Writing a raw HTML block that ends in a long run of newlines no longer slows down. HTML comment blocks still end only at "-->".

  • feat: load server-rendered regions into a document with a frame tag FRAME_TAG from @sdxc/markdown/plugin/frames registers a frame tag that holds a region a route renders per request: <frame src="/frames/demo">Loading…</frame>. Its src must be a path on the page's own origin, so a frame pointing anywhere else fails the parse at the tag's line. FrameTag from @sdxc/markdown/remix draws it as a Remix <Frame>, with the tag's children as the streamed fallback; a self-closing tag holds the page until the region resolves. toHTML and toPlainText render the fallback, so feeds and search indexes read it in place of the region.

  • docs: say when a frame tag's fallback gives way to its content A frame tag with children streams them first, and the client runtime swaps the region in when it arrives. A self-closing tag holds the page and inlines the region, so it reads the same before any script loads.

@sdxc/math

Republished because @sdxc/markdown changed.

@sdxc/mcp

Republished because @sdxc/logger changed.

@sdxc/messaging

  • fix: build Discord edit URLs in linear time A webhook URL ending in thousands of slashes made DiscordWebhook.update quadratic.

@sdxc/microformats

  • fix: keep page-supplied rel keys from reaching Object.prototype A link whose URL or rel value was __proto__ wrote onto Object.prototype instead of into relUrls/rels; such keys are now ordinary own entries. Trimming text and e-* HTML also runs in linear time on long inner whitespace runs, which previously took quadratic time.

@sdxc/micropub

Republished because @sdxc/microformats changed.

@sdxc/newsletter

Republished because @sdxc/api-client changed.

@sdxc/openapi

Republished because @sdxc/yaml changed.

@sdxc/opml

Republished because @sdxc/xml changed.

@sdxc/outbound

  • fix: strip trailing dots from host names in linear time A host name holding thousands of dots made resolveHost quadratic before the lookup.

@sdxc/pagination

  • feat: refuse a keyset query that already orders its rows Pagination.byKeyset appends its own sort keys, so a query that already carried an orderBy would sort on that column first and seek on the wrong key. It now answers an InvalidOrderingError naming the existing sort instead of returning wrongly paged rows; leave the ordering to the orderBy option.

@sdxc/passkey

Republished because @sdxc/crypto changed.

@sdxc/password-policy

Republished because @sdxc/crypto changed.

@sdxc/rate-limit

Republished because @sdxc/logger changed.

@sdxc/rdap

Republished because @sdxc/cache changed.

@sdxc/robots

  • fix: trim record whitespace in linear time A line padded with long runs of spaces or tabs no longer makes parse backtrack quadratically; only leading and trailing spaces and tabs are trimmed, as before.

@sdxc/rss

Republished because @sdxc/xml changed.

@sdxc/saml

Republished because @sdxc/crypto changed.

@sdxc/sample

Republished because @sdxc/crypto changed.

@sdxc/scim

  • fix: read If-Match and If-None-Match headers in linear time A conditional header holding a long run of characters that form no tag took quadratic time to parse. Tags are now read by a single scan that yields the same tags as before.

@sdxc/search

Republished because @sdxc/pagination changed.

@sdxc/security-headers

Republished because @sdxc/crypto changed.

@sdxc/seo

Republished because @sdxc/attribution changed.

@sdxc/sitemap

Republished because @sdxc/xml changed.

@sdxc/spam

  • fix: scan link markup and trailing punctuation in linear time Submitted text with long runs of "[url=", "<a\t", "[" or "!" made the link-syntax rule and link extraction take quadratic time. They now read the text in a single pass and score exactly the same markup.

@sdxc/spec

  • fix: sanitize artifact names in linear time A test title with long runs of dashes made trimming them quadratic.

@sdxc/strings

  • fix: convert long digit runs with underscore in linear time An identifier with a long run of digits or capitals took quadratic time in underscore(). The acronym boundary is now found with a lookaround, giving the same output.

@sdxc/trace-context

  • fix: trim tracestate members in linear time Long runs of optional whitespace around or inside a member no longer make parse backtrack quadratically; the trimming rules are unchanged.

@sdxc/ui

  • fix: parse hsl() hues with long digit runs in linear time parseColor took quadratic time on an hsl() hue holding thousands of digits. The hue is now matched by an unambiguous pattern that accepts the same values.

  • fix: keep MathML spacing and draw it in a stretchable math font The base reset zeroed the padding the browser gives MathML table cells and fractions, collapsing matrices and cases into touching rows. Everything inside <math> now keeps its user-agent spacing, and <math> names STIX Two Math and Cambria Math ahead of the generic math family so fences, integrals and sums stretch to the content they enclose.

  • fix: server-render subscribing mixins without errors in workerd calendarKeys, rangePreview, gridListKeys, treeKeys, dragReorder, dropZone, commandFilter, messageFollow, resizeHandle and viewTransition subscribed to their model, session or frame while rendering, which on a server hands addEventListener a stand-in signal that workerd rejects. Each render logged "Incorrect type for the 'signal' field on 'AddEventListenerOptions'".

    They now subscribe when their host is inserted, through the new exported whileLive(handle, subscribe) helper, which also detaches a replaced model's listeners when a mixin is re-rendered with a different one. A replaced DragSession or ResizeSession is cancelled at that point too. The Toaster and Announcer examples now subscribe inside handle.queueTask for the same reason.

  • feat: let island components subscribe through whileLive whileLive(handle, subscribe) now also takes a component's Handle. An island subscribes once its first render commits, so its server render never touches a real event target with the server's stand-in signal, and the listeners detach when the island unmounts. The Toaster and Announcer examples use it.

  • docs: show aria-hidden="false" for exposing Skeleton, ColorSwatch and Marker.Icon A boolean false drops the attribute, so the component default keeps the element hidden; the "false" token is what exposes it.

@sdxc/user-agent

  • fix: read Android models in linear time on adversarial user agents A model padded with thousands of whitespace characters made device detection quadratic. The Build/ suffix is now cut with a single search.

@sdxc/web-push

  • fix: read padded base64url keys in linear time A key carrying a long run of = took quadratic time to strip before decoding. Padding is now trimmed with a single scan, so subscribe, subscription parsing and VAPID keys accept the same values as before.

  • fix: load @sdxc/web-push/browser under an unbundled dev server The browser entry reached node:crypto through the @sdxc/crypto barrel. A production build tree-shook it away, but a dev server serving modules one by one failed the import, so an island using subscribe never hydrated in dev. It now imports its base64url codec from @sdxc/crypto/encoding.

@sdxc/webhooks

Republished because @sdxc/crypto changed.

@sdxc/webmention

Republished because @sdxc/microformats changed.

@sdxc/websub

Republished because @sdxc/crypto changed.

@sdxc/well-known

Republished because @sdxc/http changed.

@sdxc/workers-cache

Republished because @sdxc/logger changed.

@sdxc/xml

  • fix: parse hostile XML declarations in linear time An XML declaration padded with long whitespace runs or malformed attribute text could take seconds to parse. The declaration is now read by a linear scan, and the version, encoding and standalone values it returns are unchanged.

@sdxc/yaml

  • fix: stringify strings with many line breaks in linear time Measuring a multi-line string's trailing breaks took quadratic time when the string held long runs of line breaks before its end. The output is unchanged.

@sdxc/zone-file

  • fix: parse names, CAA, RFC 3597 generic data and TTLs in linear time Long runs of backslashes, whitespace or digits no longer make absoluteName, parseRecordData or TTL reading backtrack polynomially (or exponentially for \# generic data). Accepted and rejected inputs are unchanged.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.9...v2026.10.10

2026.10.9

On GitHub

@sdxc/activitypub

  • feat: add an ActivityPub package for federating an app A new private package that holds the protocol logic for ActivityPub. The app keeps its own data and implements four small store interfaces for it.

    • The root reads ActivityStreams 2.0 from untrusted JSON and evens out how Mastodon, Misskey, Pixelfed, GoToSocial and Threads write it. stringify writes it back with one fixed @context.

    • ./keys generates and imports RSA actor keys. ./discovery gives the WebFinger self link and handle lookup. ./response handles content negotiation and serving documents, with 410 for a Tombstone. ./collections builds collections and their pages.

    • ./remote fetches actors, keys and objects with SSRF checks on every redirect, checks the document id against where it came from, signs GETs when given a signer, and caches what it fetched.

    • ./inbox verifies incoming activities during the request, accepting RFC 9421 or draft-cavage signatures with a digest. handle then processes them: follows, undos, creates, deletes, likes, boosts and moves. summarize turns a reply, like or boost into a Webmention-shaped record.

    • ./outbox fans an activity out to one delivery per distinct inbox. Each delivery tries an RFC 9421 signature first, falls back to draft-cavage, and remembers which scheme each server accepted. Servers that have been failing for 7 days are skipped.

    • ./memory and ./conformance give in-memory stores, plus a Vitest suite that checks an app's own stores.

  • fix: close sender-spoofing, foreign-delete and weak-signature paths

    • handle ignores an activity when the actor document fetched for the sender has a different id. Before, a redirecting actor URL could make another account appear as the sender.

    • A Delete whose object no longer resolves is processed only when that object is on the sender's own origin.

    • receive also checks the verified signer's host against blocked, so a key that redirects to a blocked server is refused.

    • deliver always covers @method, @target-uri and content-digest, even when an inbox's Accept-Signature asks for less.

  • fix: confirm handles on the actor's host, and cap Retry-After lookup used to accept any actor whose preferredUsername matched the handle's user, so @alice@evil.com could resolve to the real alice on another server. Now, unless the handle is already preferredUsername@<actor host>, WebFinger on the actor's host must point back at the same actor, as Mastodon requires. parseRetryAfter clamps its value between 0 and 12 hours, so an inbox cannot push its retries arbitrarily far out.

  • refactor: leave queue batch sizing to the job writer fanOut closes a batch at pageSize deliveries alone. Keeping each batch under Cloudflare's 256 KB sendBatch limit is now handled by enqueueMany in @sdxc/jobs.

  • refactor!: serve and process federation through one Federation class new Federation({ actor, keys, stores, cache, userAgent, queue, blocked }) takes the wiring once. It then provides:

    • on(type, handler) to handle incoming activities, with ctx.summary()

    • fetch(request) to serve the actor, inbox, outbox, followers and following

    • respond(request, document) for content negotiation on the app's own pages

    • publish(activity), lookup(handle) and verify(request)

    Background work runs as one job: Federation.MESSAGE describes every queued message, and process(message, { attempts }) handles it. A retryable failure carries the delay to retry with, and the runtime enqueues Accept and Reject replies and drops inboxes that answer 410 on its own.

    ActorKeys and RemoteResolver replace the key and resolver functions. The vocabulary, stringify, the collection builders and actorLink stay plain functions. The subpaths are now the root and ./testing, which holds the in-memory stores and conformance suites.

  • feat: publish @sdxc/activitypub to npm The package is now public and ships in the daily release under the @sdxc scope.

  • feat: approve or reject a pending follower with federation.approve and federation.reject A follower held back by a "pending" Follow decision can now be decided later. approve stores it as accepted and queues the Accept of its stored Follow; reject queues the Reject, then removes it. Both answer not-found for an actor that does not follow the local actor and are safe to retry. The README's job example now returns ctx.retry and ctx.ack.

  • feat: remember processed activities in any @sdxc/cache Cache with CacheSeenActivities A shipped SeenActivities store that keeps claims under activitypub:seen:<id>, holding each for at least the one minute Workers KV accepts.

@sdxc/api-client

Republished because @sdxc/trace-context changed.

@sdxc/attribution

  • feat: record where a visit came from as a first and last touch New package. readTouch reads the utm_* parameters, the ad-network click identifier and the Referer into a touch, normalizes every campaign value to a short slug (an address or a URL in one is dropped), and credits it to a channel. The attribution() middleware keeps the first touch within a window and the latest non-direct one in the session or a signed cookie and publishes them as ctx.attribution; bots, non-navigation requests and direct revisits write nothing, and Global Privacy Control is honored by default. toMetadata and toUtmParams flatten touches for a checkout's metadata or a newsletter provider, and @sdxc/attribution/parameters holds the tracking-parameter list with withoutTracking to strip it from a URL.

  • feat: flatten a touch into plain campaign fields with toCampaign toCampaign(touch, url) answers the touch's source, medium, campaign, term and content, the referring hostname, and the landing path resolved into an absolute URL, leaving out every field the touch lacks. A null touch answers undefined, so ctx.attribution.last ?? ctx.attribution.first passes straight in. The result fits a newsletter or CRM provider that takes campaign fields under plain names.

@sdxc/auth

Republished because @sdxc/crypto changed.

@sdxc/authz

  • feat: add @sdxc/authz, authorization from a typed ability catalog, additive roles and guards A catalog declares every ability once with the context a check of it passes; a policy grants abilities through additive roles and everyone, refuses only through guards that apply to admins too, and writes conditions in a strict @sdxc/expression dialect so a missing or mistyped fact refuses instead of granting. policy.for binds roles, a within ceiling and facts (values, or lazy sources loaded once by access.load) into an access whose checks are synchronous: can, check, authorize, permittedFields, decide and claims, with Decisions typed for hydrated components.

    Adapters ship as their own entries: router access/requireAbility, the job dispatcher's ctx.authz, MCP guard/requireToolAbility, fromFlags and fromEntitlements fact sources, and testAccess/diffPolicies for tests.

  • fix: return plain Middleware from access and requireAbility Both adapters declared a context entry, which turned a controller's context into one TypeScript would no longer pass to helpers typed RequestContext. ctx.access stays typed on every request context through the module augmentation, and ctx.get(ability) reads the loaded context typed through the ability, undefined until requireAbility ran.

  • fix: keep undecidable decisions plain data and answer a whole catalog An undecidable decision now carries each failure as { grant, message, path, missing?, mismatch? } instead of an ExpressionError, so it survives structured cloning across a Durable Object RPC intact. access.decide and access.claims also accept the catalog root, answering every group in one call.

@sdxc/billing

  • feat: read a request's entitlements once with readEntitlements readEntitlements(ctx) runs the billing middleware's entitlements reader at most once per request, caching a null snapshot too, and answers a Result instead of throwing. requireEntitlement reads through it, so other code deciding on the same request (an authorization policy's billing facts) shares the guard's read.

@sdxc/cache

Republished because @sdxc/logger changed.

@sdxc/cron

Republished because @sdxc/dates changed.

@sdxc/crypto

  • feat: encode and decode PEM armor Pem.encode(der, label) wraps DER bytes as an RFC 7468 block, for example an SPKI public key or a PKCS#8 private key. Pem.decode(text, label) reads the first block back. It accepts CRLF line endings, indentation and any line width, and fails when the label does not match.

@sdxc/dates

  • feat: add day ranges, a six-week month grid, and the runtime's time-zone list Adds monthRange, quarterRange, yearToDateRange, lastNDaysRange, dayRangeLength, isWholeMonth and validateDayRange over inclusive DayRange day keys; validateDayRange reports InvalidDayRangeError with a problem of invalid, reversed, tooLate or tooLong. Adds monthGrid for calendars that always draw six weeks, with inMonth on padding days. Adds supportedTimeZones, isSupportedTimeZone and timeZonesByRegion, read from Intl.supportedValuesOf with UTC first, and systemTimeZone.

@sdxc/diagram

  • feat: draw Mermaid sequence, class, state and flowchart diagrams as SVG, with a markdown visitor and a component @sdxc/diagram reads a subset of Mermaid's text syntax — the one GitHub renders in mermaid fences — and draws it as SVG on the server, with text measured from font metrics so no DOM or browser is needed. Sequence diagrams cover participants, actors, every arrow, activations, notes, blocks and autonumber; flowcharts every node shape, link form, chains and nested subgraphs; class diagrams members, generics, annotations, the UML relations with cardinalities, notes and namespaces; state diagrams descriptions, start/end per scope, choice/fork/join, notes and composite states.

    toSVG returns the markup and parseDiagram the JSON tree of SVG elements; DiagramError carries the line and column. Lines and text use currentColor and fills fall back to Canvas, so a diagram follows the page's color scheme. @sdxc/diagram/markdown turns mermaid fences into diagram tags, failing the walk on invalid source unless invalid: "keep", and renderDiagram draws them for toHTML. @sdxc/diagram/ui provides Diagram and DiagramTag for toRemix.

@sdxc/digest-fields

  • feat: add RFC 9530 digest fields and the legacy Digest header A new private package that computes, parses, writes and verifies Content-Digest, Repr-Digest and their Want- preferences, plus the RFC 3230 Digest header that draft-cavage HTTP signatures cover. It supports SHA-256 and SHA-512.

  • fix: reject a legacy Digest that repeats an algorithm SHA-256=<forged>, SHA-256=<valid> used to keep only the last entry, so a forged digest could sit next to a valid one. A repeated algorithm in the RFC 3230 Digest header is now malformed.

  • feat: publish @sdxc/digest-fields to npm The package is now public and ships in the daily release under the @sdxc scope.

@sdxc/distill

Republished because @sdxc/outbound changed.

@sdxc/doh

  • feat: print record data back to presentation form and keep CAA's flags octet formatRecordData is the inverse of parseRecordData: names print absolute, TXT strings and CAA values print quoted with \DDD escapes, and a parsed record always reads back unchanged. A CAA record from a zone file and the same record as a resolver's RFC 3597 generic data now print to one string, so a diff keyed on the value sees one record.

    CAARecord gains flags, the octet as published with its reserved bits, and generic CAA data with an empty tag no longer parses. NameNotFoundError.authenticated reports whether the resolver validated the denial with DNSSEC.

  • feat: decide whether a CA may issue for a name from its CAA records The new @sdxc/doh/caa entry reads CAA records the way a certificate authority does (RFC 8659, RFC 8657). parseCaaProperty reads an issue, issuewild or iodef value under the RFC's grammar, findRelevantCaa climbs from the name toward the TLD to the first RRset it publishes, evaluateCaa decides a request against an RRset with no I/O, so a zone file's records decide the same way, and mayIssue combines the two.

    A failing lookup reads as allowed: false, because a CA refuses on it too; narrow on reason === "lookup-failed" to retry through a second resolver first.

  • feat!: read record data through @sdxc/zone-file The RDATA presentation codec moved to @sdxc/zone-file, which this package now depends on. parseRecordData, formatRecordData and RecordDataError are no longer exported here; import them from @sdxc/zone-file.

    DoH.RecordData and the per-type field interfaces are now ZoneFile types (ZoneFile.RecordData<Type>, ZoneFile.MXData…). DoH.RecordFor<Type> is those fields plus name and ttl, so a resolved record and one read from a zone file share every type-specific field; resolve's answers keep their shape. PTR and DNAME answers now come back typed, with a target, and names in record data come back in canonical escaped form.

@sdxc/email-address

Republished because @sdxc/doh changed.

@sdxc/epub

  • feat: build EPUB 3.3 publications from XHTML chapters A new package for offering long-form writing as an ebook. EPUB.build({ metadata, chapters, cover, styles, resources }) answers Result<EPUB, EpubError>, and epub.stream() writes an application/epub+zip body that e-readers, phone reading apps and Send to Kindle open: mimetype stored first, the package document, a navigation document with a nested table of contents and landmarks, an EPUB 2 toc.ncx (unless legacyNcx: false), an optional cover page, one content document per chapter, and every stylesheet and resource. epub.bytes() collects it, and epub.files lists every file of the container.

    A chapter body is an XHTML string, such as toHTML(document, { syntax: "xhtml" }) from @sdxc/markdown/html. Each body is parsed and re-serialized, which proves it well-formed, keeps its whitespace and turns named entities like &nbsp; into characters. The build refuses what epubcheck reports as an error, with one error subclass per code and the chapter id or path it concerns: invalid-metadata, invalid-content (script, event handlers, forms, duplicate ids, a chapter nobody can reach), missing-resource (a reference or fragment the publication lacks, CSS url() included), remote-resource (images, stylesheets or fonts from the web), unsupported-media and invalid-path. Manifest properties (svg, mathml, nav, cover-image) are derived from the content. Accessibility metadata is written only when given.

@sdxc/expression

  • feat: compare a field against another context path, and test lists with includes, intersects and subsetOf Every comparison built-in (eq, ne, in, notIn, lt, lte, gt, gte) takes path in place of value or values, so a rule can compare two facts the caller supplies, as in { op: "eq", field: "article.authorId", path: "actor.id" }. A node carries one or the other; the schema refuses both and neither. A comparison between two paths is false when either side is missing, null, or of a type the operator cannot compare, and two Dates compare by time value.

    includes asks whether a list field has a member, intersects whether two lists share one, and subsetOf whether every member of the field is in the other list. Each takes a literal or a path.

    evaluate() now accepts any object as its context, so interface-typed records type-check.

  • feat!: write every path in the text form under a ctx. root The text form now spells a context path as ctx followed by its segments, so both sides of a comparison read the same way: ctx.article.authorId == ctx.actor.id. A segment a bare word cannot spell is quoted in backticks on its own (ctx.headers.user-agent). A comparison's right-hand side and a call's right-hand argument fill path when written as ctx.…, as in includes(ctx.list, ctx.x); a path anywhere else fails naming the operator.

    Breaking: text written as plan.tier == "pro" fails with "Paths start with 'ctx.'"; prefix every path with ctx.. stringify() prints the new form. The JSON form is unchanged, so stored expressions need no migration. createLanguage() throws a TypeError for an operator or reference named ctx or another keyword of the text form.

  • feat: add strict languages that fail on missing or mistyped operands, and list the paths an expression reads createLanguage({ strict: true }) fails a leaf, instead of answering false, when a path it reads resolves to nothing (exists excepted), when a comparison between two paths holds null, or when an operand has a type the operator cannot compare. evaluate() then returns Result<boolean, ExpressionError>; the error carries the context path in missing or the operand types in mismatch, and names the failing node by its path, a failure inside a reference reported at the reference node with the inner one as its cause. all, any and not combine failures in three-valued logic, so swapping operands never changes the answer, and evaluation still stops at the first deciding member. Lenient languages, the default, answer exactly as before.

    language.paths(compiled) returns every context path a compiled expression reads, through references, so a caller can load only the facts a rule needs.

@sdxc/feed

Republished because @sdxc/outbound changed.

@sdxc/flags

Republished because @sdxc/jobs changed.

@sdxc/flags-engine

  • fix: keep the condition schema to the operators targeting rules have today The expression language gained includes, intersects and subsetOf. Flag conditions name their built-ins explicitly, so CONDITION_SCHEMA keeps refusing those until targeting rules adopt them.

  • docs: write condition text with the ctx. root and note path comparisons flagConditions.parse() reads paths as ctx.plan.tier, so the README's text-form examples use it. The comparisons from eq to gte also accept path in place of a literal.

@sdxc/highlight

Republished because @sdxc/markdown changed.

@sdxc/honeypot

Republished because @sdxc/crypto changed.

@sdxc/hostname

Republished because @sdxc/api-client changed.

@sdxc/http

Republished because @sdxc/crypto changed.

@sdxc/http-signatures

  • feat: sign and verify requests with RFC 9421 or draft-cavage-12 A new private package. sign adds the signature and body digest a request needs, and verify checks one. Both work with either scheme. verify requires the signature to cover the method, the target, the host, a time and the body digest, and checks the digest and the signature age. The structured Signature-Input, Signature and Accept-Signature fields, and the cavage Signature header, can also be parsed and written on their own.

  • fix: reject replays that add an unsigned created, and malformed covered names A draft-cavage signature now counts created and expires as its time only when it covers (created) and (expires). Before, appending an unsigned created to a captured request reset its age, so it could be replayed indefinitely. A covered name that is not a valid header field name now fails verify as malformed instead of throwing.

  • feat: publish @sdxc/http-signatures to npm The package is now public and ships in the daily release under the @sdxc scope.

@sdxc/i18n

Republished because @sdxc/logger changed.

@sdxc/icalendar

Republished because @sdxc/dates changed.

@sdxc/idempotency

Republished because @sdxc/crypto changed.

@sdxc/jobs

  • fix: split Cloudflare queue writes by size as well as count A Cloudflare Queues sendBatch accepts at most 256 KB across its messages, so an enqueueMany of 100 messages a few KB each was refused whole. The Cloudflare queue now closes a write before its serialized bodies, plus headroom per message, reach that limit, as well as at 100 messages.

@sdxc/jwt

  • feat: store signing keys in Cloudflare R2 with @sdxc/jwt/r2 createR2KeyStorage(bucket) implements KeyStorage over an R2 bucket binding and reads back files with their stored name and type. Bucket types are declared structurally, so importing it needs no platform types.

@sdxc/lazy-frame

  • feat: add LazyFrame, a frame that loads when the reader scrolls near it or opens its dialog Renders its children until then, so a browser without script keeps a working link. It can also keep the address bar on the page being read, hold the reader's place when content lands above them, and turn a link into the opener of the dialog around it. Apps resolve the client entry @sdxc/lazy-frame/ui#LazyFrame in their loadModule.

  • fix: give LazyFrame a written EntryComponent type so its declarations build The publish build emits declarations, and the inferred clientEntry type named internal remix/component types it cannot reference (TS2883).

@sdxc/logger

Republished because @sdxc/expression changed.

@sdxc/mail

  • fix: leave markdown comments out of both parts of an email

@sdxc/markdown

  • feat: write XHTML from toHTML with syntax: "xhtml" toHTML(node, { syntax: "xhtml" }) writes output an XML parser reads: the task-list checkbox is self-closed, and every boolean attribute carries its name as its value (disabled="disabled", an annotation's data-wide="data-wide"). Use it for EPUB content documents and any other consumer that parses the markup as XML. The default, "html", is unchanged, and markup a tag renderer returns is written as the renderer built it.

  • feat: read comments as nodes renderers skip and stringify keeps A comment on its own line is a comment block that may span lines, blank ones included; inside a line it is an inline comment. toHTML, toRemix and toPlainText leave it out, and Markdown.stringify writes it back, so a document an editor parses and writes keeps the author's notes. An unclosed comment is a parse error at the line it opened on, and an annotation above a comment decorates the block after it.

    comment joins Markdown.Block and Markdown.Inline, so an exhaustive switch over node types needs a case for it.

  • feat: take variables, arrays and objects in attribute values, and fill them with plugin/variables A braced attribute value now holds data with variable leaves: src={$cdn}, data={[1, 2, $three]}, options={{ stacked: true, "max-width": $width }}, plus {null} and quoted strings. Tags and annotations share the one reader, a $name reads as the same variable node a text hole produces, and nothing in a value executes. Markdown.Attributes is now Record<string, Markdown.AttributeValue>.

    A tag whose attributes hold a variable skips its schema at parse time. The new @sdxc/markdown/plugin/variables visitor fills text holes and attribute values from one set of values, runs the deferred schema with options.tags, and fails on a missing name unless missing: "keep".

    toHTML writes a structured annotation value into its data- attribute as JSON, and an unfilled variable as its braced source spelling.

  • feat: render allowlisted HTML elements as elements through options.html Raw HTML still renders as escaped text. Markdown.Options.html names the elements a document may opt into and the attributes each may carry, e.g. { details: ["open"], summary: [], sup: [], a: ["href"] }. An allowlisted element parses to a new element node whose children are markdown, and toHTML, toRemix and toPlainText draw it as itself.

    Where an element stands comes from the element: details, div and other containers are blocks holding blocks, summary/figcaption/dt hold a line of inline content, phrasing elements like sup, kbd and a are inline wherever they are written, and br, img, wbr and hr are void with an optional closing slash. Elements markdown already spells and raw-text or script elements cannot be allowlisted.

    An unlisted attribute is a parse error at the opening tag, on… handlers are always refused, and URL attributes take only relative, http, https, mailto and tel URLs. plugin/variables takes html to check a URL a variable is filled with, and both renderers drop an unsafe attribute from a hand-built tree.

    element joins Markdown.Block and Markdown.Inline. A tag's children that open with a comment are now written back as the inline content they are.

  • feat: read dotted variable paths like $plan.price and $items.0 A variable may continue as a dotted path in text and in attribute values: {% $plan.price %}, {$user.name}, {[$items.0, $a.b.c]}. Each segment is a name or an array index, and Markdown.Variable.name keeps the path exactly as written, so stringify round-trips it unchanged.

    plugin/variables fills a path one segment at a time, reading only own properties of objects and numeric indexes of arrays. A path that runs off its data is a missing variable, and the failure names the full path (No value for $plan.price). $a.b[0], $a.b(), a trailing dot or an empty segment are not variables.

  • feat: add headings, links, typography, embeds and lint plugins Five new entry points under @sdxc/markdown/plugin/, each a visitor factory for Markdown.walk that hands back untouched nodes as the same object:

    • headings: GitHub-compatible heading ids, numbered -1, -2 when repeated, keeping and reserving every id an author wrote; tableOfContents() reads them back nested by level.

    • links: resolves relative hrefs and srcs of links, images and allowlisted elements against a base URL, with a rewrite hook run on every URL.

    • typography: curly quotes decided across a whole paragraph, en and em dashes, and ellipses, in text nodes only; code, kbd, samp and var elements keep their ASCII.

    • embeds: a URL alone on its line becomes a block tag through URL-only providers, with youtube(), vimeo(), gist() and x() built in.

    • lint: lint(document) returns problems with positions for missing code languages, skipped heading levels, repeated h1s, missing alt text, empty links, broken #anchors and duplicate ids, plus custom rules.

  • feat: merge visitors with Markdown.compose Markdown.compose(...visitors) returns one visitor for a single walk. Handlers for the same node type run in the order the visitors were given, each on the node the previous one returned, so two plugins that both handle headings — variables() and headings(), say — each get their turn where a spread keeps only the last. A handler that removes the node, splices it or changes its type ends the chain for that node, and the merged visitor is asynchronous only when one of its handlers is.

@sdxc/math

  • feat: convert TeX math to MathML Core, with a markdown visitor and a component @sdxc/math parses a practical TeX subset — fractions, roots, scripts, Greek letters and symbols, big operators with limits, \left/\right delimiters, \text, font commands, function names, accents, spacing and matrix/cases environments — into a JSON tree of MathML Core elements. toMathML writes it as a <math> string with the original TeX kept in an annotation; MathError carries the line and column.

    @sdxc/math/markdown turns ```math fences and GitHub's $x$ inline form into math tags, failing the walk on invalid TeX unless invalid: "keep", and renderMath draws the tag for toHTML. @sdxc/math/ui provides MathFormula for direct use and MathTag for toRemix's components option.

@sdxc/mcp

Republished because @sdxc/logger changed.

@sdxc/messaging

  • feat: send one portable message to chat, paging and webhook destinations A message is plain JSON data (title, Markdown text, severity, fields, link buttons, an incident key and state) that every provider renders natively and sends with one fetch, answering a Result with a MessagingError whose code says whether to retry, stop (gone), or fix the configuration.

    Providers, each behind its own subpath: SlackWebhook, SlackBot, DiscordWebhook, TeamsWorkflow, GoogleChatWebhook, TelegramBot, WhatsAppCloud (approved templates), Ntfy, Pushover, PagerDuty (resolves by key), Opsgenie (closes by key) and a Standard Webhooks signed Webhook whose render and sign a subclass can replace. Slack bot, Discord and Telegram can edit a sent message or reply under it, and supports() narrows those methods.

    User-supplied URLs pass a host allowlist or the public-internet policy, a redirect is never followed, answers are read within 64 KiB, and no error or log names a webhook URL or token. MemoryDestination and the describeDestination conformance suite cover an app's tests. Private until two apps use it.

  • feat: publish @sdxc/messaging to npm The package is now public and ships in the daily release under the @sdxc scope.

  • fix: stop documenting SendOptions.id as Opsgenie's request id Opsgenie's Alert API takes no client-supplied request identifier: it generates the requestId it answers, and deduplicates alerts by alias, which comes from the message's key. SendOptions.id is read only by the signed webhook, as its webhook-id.

  • fix: leave markdown comments out of platform text

  • feat: send a message to a browser through Web Push New @sdxc/messaging/web-push entry with a BrowserPush destination, built from a WebPush sender and one browser subscription from @sdxc/web-push.

    It sends a small JSON payload for the app's service worker to draw: title, a plain-text body holding the text and fields, the first link as url, the key as tag, severity and timestamp. The text is cut so the payload always fits one push. The key is also the push Topic, so an offline browser receives only the latest message about it, and critical messages go out with high urgency. Web Push failures map to MessagingError codes: an unsubscribed browser is gone, a bad subscription is invalid-destination, unusable VAPID keys are unauthorized.

  • feat: install @sdxc/web-push only when sending browser push @sdxc/web-push is now an optional peer dependency instead of a dependency, so an app that never imports @sdxc/messaging/web-push no longer installs the Web Push, JWT and crypto stack. Apps using BrowserPush add @sdxc/web-push themselves. The @sdxc/crypto dependency is gone too.

  • fix: write the text of markdown HTML elements and tags that open with a comment

@sdxc/newsletter

  • feat: add vendor-neutral newsletter lists with Buttondown and Kit providers One contract for a newsletter's subscriber list — subscribe, find, list, tags, update, unsubscribe — answering every call as a Result with a NewsletterError whose code is normalized (invalid_address and suppressed are the refusals a visitor can act on). Subscribing an address the list already holds, in any status, succeeds with created: false and leaves the record untouched.

    • @sdxc/newsletter/buttondown pins Buttondown's API version 2026-04-01 and verifies X-Buttondown-Signature deliveries.

    • @sdxc/newsletter/kit subscribes through a Kit form, resolves tag names to ids, pages by cursor and verifies timestamped X-Kit-Signature deliveries, accepting any rotated secret.

    • @sdxc/newsletter/memory implements the whole contract in memory, with seeding, scripted faults and signed deliveries for app tests.

    • NewsletterWebhook verifies a delivery, skips event ids a ReplayStore has seen, and answers 503 when a handler throws so the platform redelivers.

    • @sdxc/newsletter/middleware publishes the provider as context.newsletter, and @sdxc/newsletter/conformance holds any provider to the contract.

  • feat: take the visitor IP as an IP from @sdxc/ip SubscribeInput.ipAddress?: string | null is now ip?: IP | null, the type ctx.ip already carries, so a provider only ever sends a valid address in its canonical spelling. MemoryNewsletter.ipAddress(email) is renamed ip(email) and answers the recorded IP.

  • feat: accept the result of IP.parse as the visitor IP SubscribeInput.ip takes an IP or the Result<IP, IP.Error> that IP.parse answers, so a caller passes IP.parse(header) without unwrapping it. A failed parse records no address rather than refusing the sign-up.

  • docs: credit a campaign through @sdxc/attribution's toCampaign The README gains a pattern passing toCampaign(touch, url) as attribution and toMetadata as metadata, and ADR-114 shows the same call in place of a hand-written mapping of a touch.

  • feat: publish @sdxc/newsletter to npm The package is now public and ships in the daily release under the @sdxc scope.

@sdxc/no-www-middleware

  • feat: redirect www. hostnames to the apex domain with noWWW()

@sdxc/outbound

Republished because @sdxc/doh changed.

@sdxc/pagination

Republished because @sdxc/crypto changed.

@sdxc/passkey

Republished because @sdxc/crypto changed.

@sdxc/password-policy

Republished because @sdxc/crypto changed.

@sdxc/qr

  • feat: add a QR Code encoder with SVG path data and a remix/component renderer A new package for drawing QR codes on server-rendered pages. encodeQr(data, options) encodes text or bytes as a QR Code Model 2 symbol and answers a Result: it splits text into numeric, alphanumeric and UTF-8 byte segments with the fewest bits, picks the smallest version that holds them, raises the error correction level while that version still fits, and chooses the mask with the lowest penalty. level, minVersion, maxVersion, mask and boostLevel override each choice; a too-long error reports the bits needed and available. The symbol exposes isDark(x, y), which answers false past the edge.

    svgPath(symbol, { margin }) returns the d and viewBox for one SVG path, and QrCode from @sdxc/qr/component renders it as a labelled <svg role="img"> in fixed dark-on-light colors that hold under dark themes and forced colors. The main entry point depends only on @sdxc/result and runs in Workers, Bun, Node and the browser.

  • feat!: encode through QR.encode QR.encode(data, options) replaces encodeQr, with the same options and the same Result<QrSymbol, QrError>. Replace each encodeQr(...) call with QR.encode(...) and import QR from @sdxc/qr.

  • feat!: build SVG path data through QR.toSVGPath QR.toSVGPath(symbol, { margin }) replaces the standalone svgPath export, with the same options and the same { d, viewBox, size } result. Replace each svgPath(...) call with QR.toSVGPath(...).

  • feat!: return a QR instance from QR.encode QR.encode now answers Result<QR, QrError>, where the QR instance carries version, level, mask, size, isDark(x, y) and toSVGPath({ margin }). The QrSymbol type and the static QR.toSVGPath are gone: type an encoded value as QR, and replace QR.toSVGPath(symbol, options) with symbol.toSVGPath(options). QrCode's symbol prop takes the instance.

  • feat!: render QrCode from serializable path data QrCode now takes path, the plain { d, viewBox, size } object qr.toSVGPath() returns, in place of the symbol instance, so its props survive serialization into a hydrated component. The margin prop is gone: pass it to toSVGPath({ margin }) instead. Replace <QrCode symbol={qr} margin={2} … /> with <QrCode path={qr.toSVGPath({ margin: 2 })} … />.

  • feat!: move QrCode to @sdxc/qr/ui The remix/component renderer now lives at @sdxc/qr/ui, the subpath every package uses for its UI. Replace import { QrCode } from "@sdxc/qr/component" with import { QrCode } from "@sdxc/qr/ui".

  • feat!: narrow QR.encode failures to QrTooLongError and QrOptionsError QR.encode now fails with QrTooLongError or QrOptionsError, both subclasses of QrError, so checking error.code === "too-long" narrows the error and error.bits is always present: drop the non-null assertion on bits. instanceof QrError still matches every failure.

@sdxc/rate-limit

  • feat: key a budget on a client's network with addressKey addressKey(ip) widens IPv6 to its /64, keeps IPv4 whole, and sends a missing address to the shared "unknown" bucket. It takes any parsed address with network({ v4, v6 }), such as the IP from @sdxc/get-client-ip.

@sdxc/rdap

  • feat: add an RDAP client for domain registration data New package. new RDAP({ cache, userAgent }) finds a domain's registry through the IANA bootstrap file and rdap.domain(name) answers its expiry, registration and last-changed dates, EPP statuses, registrar (name, IANA id, abuse email), nameservers and DNSSEC flag as plain camelCase data.

    Every request goes through @sdxc/outbound, so a redirect cannot reach a private address. The bootstrap file is cached, refreshed after bootstrapTtl, and an expired copy is served when IANA is down. A lookup makes one request chain and never retries: failures are an RDAPError with a code, retryable and, for a 429, retryAfter in milliseconds. { related: true } also asks the registrar's server on thin registries such as .com.

@sdxc/saml

Republished because @sdxc/crypto changed.

@sdxc/sample

Republished because @sdxc/crypto changed.

@sdxc/search

Republished because @sdxc/pagination changed.

@sdxc/security-headers

Republished because @sdxc/crypto changed.

@sdxc/seo

  • feat: leave campaign parameters and click identifiers out of canonical URLs seo.canonical removes utm_* parameters and click identifiers such as gclid and fbclid (the list @sdxc/attribution keeps) before building the canonical URL, so a page reached from any campaign link names its clean URL and search engines consolidate on one address. Every other query parameter is kept verbatim.

@sdxc/spam

Republished because @sdxc/doh changed.

@sdxc/spec

  • fix: click a control whose centre sits below the viewport's edge browser.click and browser.click_selector click an element's centre. A control straddling the bottom of the viewport has its centre off the visible page, so the click landed on the document instead and a form button never submitted, with no error. Such a control is now scrolled to the middle of the viewport before the click. A control whose centre is already visible is clicked in place, and the page keeps its scroll position.

  • feat: point a missed lookup at the field that reaches a role-less control A password input carries a label but exposes no ARIA role, so a lookup like textbox "Password" can never reach it. The miss used to list only other textboxes, which gave no hint why the labelled field wasn't found. A lookup that matches nothing now also reports any role-less control carrying that name and the field lookup that reaches it, in html and browser alike: That name labels a control with no role, reached as field "password".

    The README's sign-in example now fills the password by field, and it documents how a click treats a control at the viewport's edge.

  • fix: click controls a scrolling container clips browser.click scrolled its target into view only when the target's centre sat outside the viewport. A control inside a modal taller than the window can have its centre on screen yet clipped by the modal's own scroll box, so the click landed on the page behind it. A click now hit-tests that point and scrolls the control into the middle of every scrolling ancestor whenever the point does not reach it.

@sdxc/trace-context

Republished because @sdxc/crypto changed.

@sdxc/ui

  • feat: add keymap(), key-combo helpers, ShortcutList and an Announcer hold keymap()/bindKeymap() bind page-wide shortcuts that stand down in fields, IME composition and foreign dialogs; parseKeyCombo/matchesKeyCombo/keyComboGlyphs read one combo spelling for both matching and the printed hint, with mod as Command on Apple keyboards and Control elsewhere; ShortcutList renders a shortcuts panel's key list; new Announcer({ hold }) moves past each message on its own. hotkey(): ctrl and cmd now name only their own key instead of acting as aliases for mod.

@sdxc/user-agent

  • feat: tell a crawler, preview fetcher or script from a browser with isBot isBot(header) answers whether a User-Agent names automated software: search crawlers, link-preview fetchers (facebookexternalhit, Slackbot, WhatsApp and the like), uptime monitors, headless browsers and HTTP libraries. An empty header counts as a bot, since every browser sends one.

@sdxc/web-push

  • feat: send Web Push notifications with VAPID signing and subscription checks New package. WebPush encrypts a payload for one browser subscription (RFC 8291, aes128gcm), signs an RFC 8292 VAPID token per push service origin and caches it for twelve hours, and sends one POST, answering a Result whose WebPushError code says what to do with the stored subscription: gone to delete it, unauthorized to fix the sender's keys, rate-limited/unavailable/timeout/network to retry.

    Malformed subscriptions, bad VAPID keys, invalid options and payloads over 3,993 bytes are refused before any request. SUBSCRIPTION_SCHEMA applies the same checks at registration: a public https: endpoint, a P-256 point on the curve and a 16-byte auth secret. previous keeps rotated-out key pairs signing for the subscriptions made under them.

    @sdxc/web-push/browser adds subscribe() and isSupported(): it asks for permission inside the caller's click, reuses a subscription made under the page's key and replaces one made under another.

  • docs: state that no push provider account is needed The README named Firebase where it meant Chrome's push service, which read like a dependency. It now says each browser picks its vendor's push service and the sender is identified only by its own VAPID key pair.

@sdxc/webhooks

Republished because @sdxc/crypto changed.

@sdxc/webmention

Republished because @sdxc/outbound changed.

@sdxc/websub

Republished because @sdxc/crypto changed.

@sdxc/well-known

  • feat: read and write NodeInfo 2.0/2.1 and its discovery links @sdxc/well-known/nodeinfo adds two formats: nodeInfoLinks, the /.well-known/nodeinfo document pointing at a server's NodeInfo, and nodeInfo, the document itself. Both work with respond like the other well-known documents. Reading accepts 2.0 and 2.1 and keeps protocols and services the schema does not register; writing always produces 2.1.

@sdxc/workers-cache

Republished because @sdxc/logger changed.

@sdxc/zip

  • feat: add a streaming ZIP writer with CRC-32 A new package for downloads that bundle several files. new Zip() collects entries with zip.add(path, source, { modified, comment }), where the source is a string (written as UTF-8), a Uint8Array or a ReadableStream<Uint8Array>, and answers a Result: a name that is empty, absolute, holds a backslash, a control character or an empty, . or .. segment, or repeats one already added, fails with invalid-entry; the 65,536th entry fails with too-large. zip.stream() writes the entries in order as a ReadableStream ready for a Response, and zip.bytes() collects it into a Result<Uint8Array, ZipError>.

    Every entry is stored uncompressed, with its CRC-32 and sizes in the local header, so every unarchiver and streaming reader opens the archive; text-heavy archives are three to four times the size a compressed one would be. A stream entry is read whole when the archive reaches it, and a source that errors fails the output with source-failed, carrying the source's error as cause. Timestamps default to 1980-01-01, so the same entries produce the same bytes. crc32(bytes, previous?) is exported for other formats that need it.

@sdxc/zone-file

  • feat: read and write RFC 1035 zone files New package. parse(text, { origin }) reads the whole master-file grammar — $ORIGIN, $TTL, $INCLUDE through an include option, parentheses, blank owners, escaped names, BIND TTL units, any class and any type — and returns the records beside every entry it could not use, each with its line and a reason. Its only failure is input past maxBytes.

    stringify(zone, { relative }) writes records back, absolute by default or relative to the origin, and parse(stringify(zone)) yields the same records.

    The package also owns the RDATA presentation codec: parseRecordData and formatRecordData, now with typed PTR and DNAME, canonical escaped names, and BIND units in SOA timers.

  • test: pin the public surface a namespace import reaches The README's normalize example sorts a copy with spread and sort, which every supported runtime has.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.8...v2026.10.9

2026.10.8

On GitHub

@sdxc/auth

Republished because @sdxc/well-known changed.

@sdxc/cloudflare-mocks

  • fix: keep a CREATE TRIGGER body in one D1 statement The D1 mock split scripts and checked prepared statements on every top-level semicolon, so a trigger's body was cut at its first inner statement and a migration declaring a trigger failed against the mock while D1 applies it. Semicolons inside a CREATE TRIGGER ... BEGIN ... END body (CASE blocks included) now stay with the trigger, and an unclosed quote, comment or body is reported with the line it opened on.

@sdxc/distill

  • feat: read a page's excerpt and summarize pages without an article Every distilled article now carries an excerpt: the summary the page shares links under (og:description, description, twitter:description), else its first paragraph of prose, cut at the last whole word within EXCERPT_LENGTH (300) characters.

    summaryFrom(source, url) answers the title, excerpt and canonical address of any page, including landing and video pages that carry no article, so a link can be shown with a headline and a line of text.

@sdxc/http

  • fix: declare UTF-8 on text, HTML, CSS, JavaScript, XML and Markdown responses The text response builders sent a bare MIME type, so a client fell back to its own default encoding and showed non-ASCII text as mojibake ("Xalambrí" read as "Xalambrí" in a text/plain llms.txt). A string body is always sent as UTF-8, so each now declares charset=utf-8, as csv() already did.

@sdxc/search

  • feat: add @sdxc/search for FTS5 and LIKE search over app-declared tables A new private package. parseQuery turns search box text into terms (phrases, exclusions, prefix rules, length and term limits) and never lets user text reach FTS5 as syntax or LIKE as a wildcard. defineSearch describes a table the app already declares, with per-column weights and an optional FTS5 table, and its query() is an OffsetQuery and KeysetQuery that @sdxc/pagination pages, ranked by bm25() or by weighted LIKE hits. predicate() embeds the match in an app's own statement, reindex() fills an index in idempotent batches, and highlight()/excerpt() (also under @sdxc/search/query, free of database code) return segments to render as <mark>. The README carries the recommended contentless-delete FTS5 schema and triggers; the package creates no tables.

  • fix: recommend triggers that keep the index current through an upsert The recommended FTS5 triggers wrote with INSERT OR REPLACE. A trigger's statements take the conflict policy of the statement that fired it, so an upsert (INSERT … ON CONFLICT DO UPDATE) on the source table turned that write into a plain insert: the row's new terms were indexed beside its old ones, and a search kept finding text the row no longer held. Checked against SQLite 3.53 and against D1 inside workerd.

    The insert and update triggers in the README's Storage section now delete the row's rowid from the index and then insert it, which holds under every conflict policy and stays idempotent beside a batched reindex. An app that copied the earlier triggers should replace them in a new migration and run reindex from the start.

  • docs: show search over the table that already holds the text The README's examples searched a dedicated post_search table, which read as if the package needed one. They now define the search on an ordinary articles table, say that LIKE needs no schema change at all, state FTS5's two requirements (searched columns on one row, an integer key the rowid mirrors), and describe an integer column or a search document table only as the way to fit a table that meets neither. Tests and the schema fixture follow the README.

  • feat: publish @sdxc/search to npm The package is now public. Its README is written for npm readers: installation with its published companions, short usage for parsing, LIKE and FTS5 search and highlighting, the API, and patterns for storing an FTS5 index, reindexing from a job and narrowing a list's own order. The schema fixture the tests share moved out of src so it never ships.

  • feat: read a Lucene-style query syntax, with fields, filters and OR parseQuery now reads uppercase OR (joining the terms on each side), NOT and AND, a leading +, and declared field:value and filter:value qualifiers, falling back to plain AND wherever an operator cannot apply, so no input is a syntax error. A ParsedQuery is now clauses (OR alternatives that must match, or one excluded term) and filters (exact values for the caller to apply as where), replacing the flat terms array. defineSearch columns take a field name, listed in search.fields; scoped terms compile to FTS5 column filters or a single LIKE column, a filter-only query matches every row not excluded with rank 0, and highlight/excerpt take the field they render. See ADR-117.

@sdxc/ui

  • feat: mark search matches inside text with Highlight <Highlight segments={[{ text, match }]} /> renders text split where a query matched, each match a native <mark> tinted through a semantic color role (brand by default). The mark keeps the surrounding text's color and weight, so a match inside a link still reads as the link, and its tint carries across line breaks. The segments are plain { text, match } objects, so any matcher can produce them, and every stretch stays escaped text.

  • fix: draw the theme's focus ring on keyboard focus by default The reset now gives any element in keyboard focus the theme's ring (2px of --ui-ring, 2px offset) in its base layer, so a plain <summary>, link or control that no component styles shows the same ring as the components do instead of the browser's default. Component styles still take precedence.

  • fix: show a loaded avatar picture instead of its fallback initials An Avatar or ImagePlaceholder fallback hid itself behind an unbroken image with display: none, which competed with its own display: flex. Every utility lands in a cascade layer declared in the order a page first uses it, so on a page that used the centering utility after the hiding rule, the initials covered every loaded picture. The fallback now stands down through visibility, which no other rule on it sets.

@sdxc/well-known

Republished because @sdxc/http changed.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.6...v2026.10.8

2026.10.6

On GitHub

@sdxc/api-client

Republished because @sdxc/trace-context changed.

@sdxc/atom

Republished because @sdxc/result changed.

@sdxc/auth

Republished because @sdxc/crypto changed.

@sdxc/backoff

  • feat: add retry delay schedules createBackoff answers how long to wait after the Nth failure, counting from 1: exponential, linear or constant growth from a base, or a hand-written table of steps, with an optional ceiling, free attempts and jitter (a ±fraction or "full"). Jitter draws from an injectable Random, so a test passing a seeded stream asserts exact delays. Options are validated once and delay/at never fail.

  • docs: read Retry-After as seconds or an HTTP date The Retry-After pattern answered NaN for a header written as a date; it now reads both forms and treats anything else as no answer.

@sdxc/billing

Republished because @sdxc/api-client changed.

@sdxc/bracket-params

Republished because @sdxc/result changed.

@sdxc/cache

Republished because @sdxc/duration changed.

@sdxc/captcha

  • fix: pass only a parsed client address to the provider The middleware's default remoteIp now reads CF-Connecting-IP through @sdxc/get-client-ip, so the provider receives the address in canonical text, and no address at all when the header is not one.

@sdxc/cron

  • test: draw the fuzz corpus from @sdxc/random The corpus generator replaces its hand-written mulberry32 source and between helper with a seeded createRandom stream. The corpus is still fixed by CORPUS_SEED, though the expressions it draws are new.

@sdxc/crypto

Republished because @sdxc/result changed.

@sdxc/csv

Republished because @sdxc/result changed.

@sdxc/dates

Republished because @sdxc/duration changed.

@sdxc/distill

  • refactor!: fetch through @sdxc/outbound and remove the ./retrieve export @sdxc/distill/retrieve is gone; use @sdxc/outbound's checkUrl, follow and readText for a bounded fetch on its own. addressable and distill now also refuse URLs carrying credentials and the reserved names .internal, .intranet, .lan, .corp, .private, home.arpa, .test, .invalid and .example. A caller's signal is now combined with timeoutMs rather than replacing it, so the deadline always applies.

@sdxc/doh

Republished because @sdxc/result changed.

@sdxc/duration

Republished because @sdxc/result changed.

@sdxc/email-address

Republished because @sdxc/doh changed.

@sdxc/expression

  • feat: add a typed condition language with dialects and custom operators createLanguage defines a dialect once: the built-in operators it keeps, the operators it adds with defineOperator, and how it spells a reference to a shared condition. The result carries a Standard Schema for the JSON form, compile (validation, v-flag patterns, memoized references with cycle detection) and a pure evaluate. A missing field is false for every operator but exists, and no comparison coerces across types.

  • feat: parse and print expressions in a text form Every language gains parse and stringify. The text form writes and/or/not with not over and over or, infix comparisons, and call syntax for every other operator, extensions and references included; values are JSON literals. parse validates what it builds and reports failures with a line and column, and stringify prints canonical text that parses back to the same JSON.

@sdxc/feed

  • feat: refuse feed URLs and redirects that leave the public internet Every URL a fetch or discovery asks, including each redirect hop, must now be HTTP(S) with no credentials and point to a public host. A private address, a reserved name such as localhost or .local, or a name with no dot is refused as a FeedFetchError before a request goes out. Pass hosts: "any" to fetch from your own network on purpose. Requests no longer send credentials, and the reported url is now the normalized URL.

@sdxc/flags

Republished because @sdxc/jobs changed.

@sdxc/flags-engine

  • refactor: evaluate targeting conditions with @sdxc/expression Conditions are now the flagConditions dialect: every built-in, semver, and references spelled segment. Stored flag sets parse exactly as before, and flagConditions is exported so an editor can compile and evaluate a rule the way parseFlagSet does.

  • docs: edit a targeting condition as text in the preview pattern

@sdxc/get-client-ip

  • feat!: answer a parsed IP and publish it as ctx.ip getClientIP(request) now answers an IP from @sdxc/ip instead of the raw header text, and null when CF-Connecting-IP is missing or is not one address, so a malformed header can no longer become a rate limit key or a stored address. Call .toString() for the canonical text, or .network({ v4: 32, v6: 64 }).toString() to key a limit on the client's network.

    The new @sdxc/get-client-ip/middleware default export parses the header once per request and publishes it as ctx.ip: IP | null, with ClientIP as its context key.

  • docs: log the address on the request's wide event

@sdxc/highlight

Republished because @sdxc/markdown changed.

@sdxc/honeypot

Republished because @sdxc/crypto changed.

@sdxc/hostname

Republished because @sdxc/api-client changed.

@sdxc/html

Republished because @sdxc/result changed.

@sdxc/http

Republished because @sdxc/crypto changed.

@sdxc/i18n

Republished because @sdxc/logger changed.

@sdxc/icalendar

Republished because @sdxc/dates changed.

@sdxc/idempotency

Republished because @sdxc/crypto changed.

@sdxc/ip

  • feat: add IPv4 and IPv6 address and range value objects A new package for reading, classifying and keying IP addresses. IP.parse(text) answers a Result<IP, IP.Error>, accepting a bracketed IPv6 host and only canonical dotted-decimal IPv4. An IP reports its classification against one table of the IANA IPv4 and IPv6 special-purpose registries, isPublic, the embeddedIPv4 inside an IPv4-mapped, NAT64 or 6to4 address (which classification judges by), network({ v4, v6 }) for a rate limit key, value equals, and RFC 5952 canonical text through toString() and toJSON().

    IP.Range.parse("10.0.0.0/8") gives a network with contains, refusing a range whose address has host bits set.

@sdxc/jobs

  • fix: hold a retry past 12 hours for the longest Cloudflare Queues allows A retry delay longer than the platform holds a message, such as a backoff ceiling plus jitter or a remote Retry-After, now waits 12 hours instead of being passed through as a delay the queue cannot honor.

@sdxc/jsdoc

  • fix: read a header written directly above an import as the module's comment An import carries no documentation of its own, so a file whose header sits on the line above its first import now reports that header as the module's comment instead of dropping it.

  • fix: keep a class's generics and heritage when its namespace is declared first A class merged with a namespace written above it reported no type parameters, no extends and no implements; the merged symbol now carries the class's own.

@sdxc/json-feed

Republished because @sdxc/result changed.

@sdxc/json-schema

Republished because @sdxc/result changed.

@sdxc/jwt

Republished because @sdxc/duration changed.

@sdxc/logger

  • refactor: draw the sampling decision from @sdxc/random The tail sampler draws against rate from a Web Crypto stream instead of Math.random, and its tests run on a seeded stream.

  • feat: accept a condition as the sampling keep exemption Sample.Options.keep takes a condition in the @sdxc/expression JSON form as well as a function, so an exemption can come from configuration. It compiles once at createLogger, reads a namespaced field by its dotted path, and a condition that does not compile keeps every log.

@sdxc/mail

Republished because @sdxc/crypto changed.

@sdxc/markdown

Republished because @sdxc/result changed.

@sdxc/mcp

Republished because @sdxc/json-schema changed.

@sdxc/merge-patch

Republished because @sdxc/problem changed.

@sdxc/messageformat

Republished because @sdxc/result changed.

@sdxc/microformats

Republished because @sdxc/html changed.

@sdxc/micropub

Republished because @sdxc/microformats changed.

@sdxc/openapi

Republished because @sdxc/json-schema changed.

@sdxc/opml

Republished because @sdxc/result changed.

@sdxc/outbound

  • feat: check, follow and read URLs a stranger chose checkUrl refuses a URL before any request: a scheme other than HTTP(S), embedded credentials, a disallowed port, a non-public address literal or a reserved name. follow walks the redirect chain by hand, checking every hop with the same policy, releasing each redirect's body, and holding the chain and the body read after it to one deadline; resolve: true also refuses a hop whose DNS answers are not public. readText, readBytes and limitBody cap any Request or Response, whatever runtime produced it. Every failure is an OutboundError with a code, the url it concerns, and whether a retry can help.

  • fix: keep the response URL on a body bound to a deadline or cap follow and limitBody hand back a response with a new body, which used to leave response.url empty, so a failed read reported "the body" instead of the URL. The response now keeps the URL it was fetched from.

@sdxc/pagination

Republished because @sdxc/crypto changed.

@sdxc/passkey

Republished because @sdxc/crypto changed.

@sdxc/password-policy

Republished because @sdxc/crypto changed.

@sdxc/problem

Republished because @sdxc/result changed.

@sdxc/random

  • feat: add seeded and system random streams with resumable state A new package for drawing random values. createRandom(seed) opens a seeded stream that replays the same sequence on any machine, derives independent child streams by label, and snapshots its position with state() so restoreRandom resumes it exactly. systemRandom() draws from crypto.getRandomValues for jitter and sampling, systemSeed() gives a fresh seed to log, and RANDOM_STATE_SCHEMA validates a stored snapshot. Every stream offers next, int, float, bool, pick and shuffle.

  • feat!: move RANDOM_STATE_SCHEMA to @sdxc/random/schema The main entry point now has no dependencies, so a library can accept a Random without installing remix. Import RANDOM_STATE_SCHEMA from @sdxc/random/schema; remix is an optional peer dependency needed only there.

  • docs: report a fuzz seed in the suite name and refuse a bad save without throwing

@sdxc/rate-limit

  • docs: key anonymous limits on the client's parsed network The examples read CF-Connecting-IP through @sdxc/get-client-ip and key on the address's /64 for IPv6, so a malformed header shares the unknown bucket and an IPv6 client cannot mint budgets by rotating addresses.

@sdxc/result

  • feat: accept a function of the attempt as retry's delay retry's delay takes milliseconds or (attempt) => number, where attempt counts failures from 1. A function delay ignores backoff, so a schedule with a ceiling and jitter, such as one from @sdxc/backoff, plugs in without @sdxc/result depending on it.

@sdxc/rss

Republished because @sdxc/result changed.

@sdxc/saml

Republished because @sdxc/crypto changed.

@sdxc/sample

  • feat!: draw from @sdxc/random and drop createRandom and systemSeed createRandom, systemSeed, Random and Seed are no longer exported; import them from @sdxc/random, which this package now depends on. The generator and seed hashing are unchanged, so every seed produces the values it produced before. createSample({ seed }) takes a seed or an open SeededRandom.

  • docs: report a run's seed in the suite name

@sdxc/scim

Republished because @sdxc/result changed.

@sdxc/security-headers

Republished because @sdxc/crypto changed.

@sdxc/semver

Republished because @sdxc/result changed.

@sdxc/session-storage-kv

Republished because @sdxc/duration changed.

@sdxc/sitemap

Republished because @sdxc/result changed.

@sdxc/spam

Republished because @sdxc/doh changed.

@sdxc/spec

  • refactor: draw seeds and streams from @sdxc/random Plugins receive a SeededRandom as context.random, and workers re-exports Random, Seed and SeededRandom from @sdxc/random. Run ids draw their suffix from Web Crypto instead of Math.random. Seeded values are unchanged.

  • docs: document context.random for plugin authors

@sdxc/structured-fields

Republished because @sdxc/result changed.

@sdxc/trace-context

Republished because @sdxc/crypto changed.

@sdxc/validate

Republished because @sdxc/result changed.

@sdxc/webhooks

Republished because @sdxc/crypto changed.

@sdxc/webmention

  • refactor: fetch sources and endpoints through @sdxc/outbound The package no longer depends on @sdxc/distill. Every source, endpoint and redirect hop must now be HTTP(S) with no credentials and outside the reserved names (.internal, .test, .example, home.arpa and the like), as well as the loopback, .local, address-literal and single-label refusals it already had. WebmentionFetchError.retryable follows the shared rule: a timeout or a body that breaks off can be retried, while an overlong redirect chain, an oversized body and a refused host are final.

@sdxc/websub

  • refactor: cap delivery bodies with @sdxc/outbound Signed deliveries are read through the shared bounded reader. A delivery past maxBytes is still refused as too-large, including one that declares a length over the cap, and a body that breaks off is still a mismatch.

@sdxc/well-known

Republished because @sdxc/http changed.

@sdxc/workers-cache

Republished because @sdxc/logger changed.

@sdxc/xml

Republished because @sdxc/result changed.

@sdxc/yaml

Republished because @sdxc/result changed.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.3...v2026.10.6

2026.10.3

On GitHub

@sdxc/spec

  • fix: find elements inside a display: contents wrapper in the browser A wrapper styled display: contents draws no box of its own, so the browser reported it as unrendered and every lookup treated what it holds as hidden. Headings, form fields and modals inside such a wrapper are now addressable.

  • feat: start long-lived programs with cli.start and run them in a chosen directory cli.start launches a program and returns while it keeps running, so a setup hook can boot a dev server; cli.output reads what it printed and asserts with contains inside eventually; cli.stop ends it and reports its exit code and output. Anything still running stops when the run ends, and stopping signals the whole process group. cli.run and cli.start take in <directory> to run outside the workspace, gated by --allow-host-fs.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.2...v2026.10.3

2026.10.2

On GitHub

@sdxc/api-client

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/atom

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/auth

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/billing

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/bracket-params

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/cache

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/captcha

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/catch-response-middleware

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/cloudflare-mocks

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/cloudflare-pricing

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/cron

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/crypto

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/csv

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/data-table-d1

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/data-table-sqlstorage

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/dates

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/distill

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/doh

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/duration

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/email-address

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/feed

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/flags

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/flags-engine

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/get-client-ip

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/highlight

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/honeypot

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/hostname

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/html

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/http

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/i18n

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/icalendar

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/icons

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/idempotency

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/jobs

  • docs: enqueue examples carry a complete payload Both examples passed a partial payload for a job whose schema needs more fields, so copying one produced code that fails the type check the examples are there to advertise.

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/jsdoc

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/json-feed

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/json-schema

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/jwt

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/lazy-route

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/location

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/logger

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/mail

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/markdown

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/mcp

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/merge-patch

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/messageformat

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/microformats

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/micropub

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/openapi

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/opml

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/pagination

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/passkey

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/password-policy

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/problem

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/rate-limit

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/response

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/result

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/robots

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/rss

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/saml

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/sample

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/scim

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/security-headers

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/semver

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/seo

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/server-timing

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/session-storage-kv

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/sitemap

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/spam

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/spec

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/strings

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/structured-fields

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/trace-context

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/trailing-slash-middleware

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/typeid

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/types

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/u

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/ui

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/user-agent

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/uuid

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/validate

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/webhooks

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/webmention

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/websub

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/well-known

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/workers-cache

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/xml

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

@sdxc/yaml

  • chore: update Remix to 3.0.0 Remix 3 is stable. Every workspace now pins remix@3.0.0, and @remix-run/data-schema moves to ^1.0.0.

    The component runtime moved from remix/ui to remix/component, along with its subpaths (remix/component/server, remix/component/test, the JSX runtimes), so every import and every jsxImportSource now points there.

    The headless primitives (anchor, combobox, listbox, tabs) are no longer part of remix. They ship in the standalone @remix-run/ui package, pinned at 0.12.1, which @sdxc/ui and the sdxc app now depend on directly.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.1...v2026.10.2

2026.10.1

On GitHub

@sdxc/auth

Republished because @sdxc/jwt changed.

@sdxc/billing

  • fix: renew a month-end subscription on the next month's last day The memory provider advanced a billing period with setUTCMonth, so a subscription bought on January 31st renewed on March 3rd, and a yearly one bought on February 29th renewed on March 1st. Month and year periods now clamp to the end of the target month.

@sdxc/bracket-params

  • feat: read and write nested query strings and form data with bracket syntax parse(source, schema) reads bracket keys (filter[status][]=open, items[0][quantity]=2) from a query string, URLSearchParams, URL, @sdxc/location Location or FormData into nested objects and arrays, validates them against a synchronous Standard Schema, and returns a Result. Files in a form arrive as values unchanged. Depth and entry-count limits fail the parse rather than truncating it.

    stringify(value) writes a nested object back as a query string and toFormData(value) as a form, files included, in the form parse reads. fieldName(path) writes one path as its field name, for naming inputs and placing a schema issue on the input it belongs to.

  • feat: make the package public

  • docs: follow the public package README structure Lead with the smallest example, give each export one or two sentences, list every exported type, show the URLSearchParams calls stringify replaces, and add a pattern that places each schema issue on the input it belongs to.

@sdxc/cron

  • refactor: resolve schedule wall clocks through @sdxc/dates

@sdxc/dates

  • feat: add month and quarter boundaries, datetime-local values, Unix seconds and zone validation

    • startOfMonth, endOfMonth, startOfQuarter, endOfQuarter and addMonths work on a stated zone's calendar; addMonths keeps the wall-clock time and clamps to the target month's last day, so January 31st plus one month is February 28th or 29th.

    • daysInMonth and, on @sdxc/dates/zone, shiftCalendarMonth do the same on plain calendar days.

    • toDateTimeLocal and parseDateTimeLocal convert between an instant and an <input type="datetime-local"> value read in the reader's zone; parsing reports an InvalidDateTimeLocalError for a value naming a day or time that does not exist.

    • toUnixSeconds and fromUnixSeconds convert to and from whole epoch seconds, as JWT iat and exp claims carry them.

    • isValidTimeZone checks an untrusted zone name before any zone-taking function throws Intl's RangeError on it.

  • fix: resolve a repeated wall-clock hour east of UTC to its earlier instant instantFromParts returned the later of the two instants a fall-back hour names in zones at or east of UTC, so Madrid's 02:30 on 2026-10-25 read as 01:30Z instead of 00:30Z. Every zone now resolves a repeated hour to its first occurrence, which also corrects addMonths and parseDateTimeLocal landing on such an hour.

@sdxc/flags

Republished because @sdxc/jobs changed.

@sdxc/flags-engine

Republished because @sdxc/flags changed.

@sdxc/icalendar

Republished because @sdxc/dates changed.

@sdxc/jobs

Republished because @sdxc/cron changed.

@sdxc/jwt

  • fix: read issuedAt and notBefore as the dates their claims name The iat and nbf claims hold seconds since the epoch, and the two getters passed them straight to new Date(), which reads milliseconds, so a token issued in 2026 reported an issue date in January 1970. Both now convert the seconds the way expiresAt already did, so they read back the date their setters wrote.

@sdxc/sample

Republished because @sdxc/dates changed.

@sdxc/spec

Republished because @sdxc/sample changed.

@sdxc/ui

  • fix: compile in a Cloudflare Worker, whose runtime types redeclare Element A Worker's generated runtime types declare a global Element of their own, HTMLRewriter's, whose append, prepend, before and after take string, stream or Response content. The declaration merges into the DOM's Element and hides the node-taking methods it inherits from ParentNode and ChildNode, so an app type-checking @sdxc/ui alongside those types failed in the select's mirrorSelectedOption mixin. The mixin now copies the selected option's nodes with appendChild, which the Workers types leave alone, and the package type-checks against the same Element declaration, so a call that only compiles against the DOM fails here rather than in an app.

  • fix: hide the mobile nav drawer while it is closed Sidebar.MobileNav laid its <dialog> out as flex whether or not it was open, which overrode the browser's hiding of a closed dialog. A closed drawer stayed rendered just off the left edge, and its shadow showed as a grey band down the side of the page. The drawer now lays out only while open, and still slides in and out as before.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.30...v2026.10.1

2026.9.30

On GitHub

@sdxc/api-client

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/auth

Republished because @sdxc/idempotency changed.

@sdxc/billing

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/captcha

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/csv

  • feat: read and write RFC 4180 CSV, with a streaming writer parse reads text into records keyed by the header row, or into rows of fields, and returns every field as a string so rows are typed by the caller's schema. It accepts CRLF, LF and CR, drops a leading BOM, skips blank lines, and fails with a line number on an unterminated quote, text after a closing quote, a ragged record or a duplicate header.

    stringify writes objects in a declared column order under a header, or arrays of cells as bare records. streamify writes the same text as a UTF-8 byte stream while an iterable or async iterable yields rows, for downloads too large to hold as one string. Both quote only what RFC 4180 requires, accept ,, ; or tab as the delimiter, can prefix a BOM for Excel, and neutralize string cells a spreadsheet would run as formulas.

  • feat: make the package public Its README is written for npm, it carries an MIT license, and it is no longer private, so the daily release publishes it under a dated version once its first bootstrap publish exists on npm.

@sdxc/distill

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/email-address

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/flags

Republished because @sdxc/jobs changed.

@sdxc/flags-engine

Republished because @sdxc/flags changed.

@sdxc/highlight

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/honeypot

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/hostname

Republished because @sdxc/api-client changed.

@sdxc/http

  • feat: stream CSV responses as UTF-8 and encode download filenames per RFC 8187 csv() takes a ReadableStream<Uint8Array> as well as a string and sends text/csv; charset=utf-8, so a CSV writer's stream can be the body of a download without buffering it.

    The new attachment(filename) builds a Content-Disposition value with an ASCII filename fallback and an RFC 8187 filename* for names outside ASCII. file() uses it, so a quote in a download's name no longer breaks the header and accented or non-Latin names reach the browser intact.

@sdxc/idempotency

Republished because @sdxc/problem changed.

@sdxc/jobs

  • feat: add a router middleware publishing ctx.jobs for enqueuing from route handlers @sdxc/jobs/router exports jobEnqueuer(queue), a remix/router middleware that publishes ctx.jobs (and the Jobs context key) with enqueue and enqueueMany. It writes through the queue alone, so the dispatcher, its middleware and every job handler stay out of the request path's module graph. Both methods take what the dispatcher's own take, raise the backend's JobQueueError, and carry the request's trace on each message.

@sdxc/json-schema

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/mail

Republished because @sdxc/highlight changed.

@sdxc/mcp

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

  • feat!: declare tool schemas with @sdxc/json-schema A tool's input and output are now @sdxc/json-schema schemas whose parse yields an object, replacing the hand-written JSON Schema literal. The same schema is published in tools/list (input side, named schemas inline, no $schema), parses every tools/call, and types ctx.input as its parse output, so defaults, transforms, refinements and coercion all apply. tool() throws at declaration when a schema has no JSON Schema form or its root is not an object.

    Arguments keep the model-friendly rules: unknown keys are dropped, every issue is reported at once, and null counts as absent for a property whose schema does not accept it. Issue messages now use data-schema's wording (limit: Expected integer), and a missing required argument reads Required.

    Breaking: ObjectSchema, PropertySchema, FromSchema, FromObjectSchema and the other subset types are removed in favour of ToolSchema; Tool.inputSchema is now Tool.input; validateArguments takes the tool instead of a schema.

@sdxc/merge-patch

Republished because @sdxc/problem changed.

@sdxc/openapi

Republished because @sdxc/json-schema changed.

@sdxc/passkey

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/password-policy

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/problem

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/saml

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/security-headers

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/spam

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/trace-context

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

@sdxc/webmention

Republished because @sdxc/distill changed.

@sdxc/well-known

  • docs: shorten the package description The description npm and the documentation site show now fits in three lines, keeping what the package does and dropping the detail its README covers.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.29...v2026.9.30

2026.9.29

On GitHub

@sdxc/api-client

Republished because @sdxc/trace-context changed.

@sdxc/auth

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/billing

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/cache

Republished because @sdxc/logger changed.

@sdxc/captcha

  • feat: add @sdxc/captcha, provider-neutral CAPTCHA verification A Captcha interface every provider implements, with Turnstile, hCaptcha and reCAPTCHA (v2 and v3, with a minimum score) classes and a remix/ui widget for each. The captcha() router middleware reads the provider's form field, verifies it, publishes the outcome as ctx.captcha and answers a failure with a 403 unless onFailure decides otherwise; it can also hold a verification to an expected action and hostname. Failures use shared codes (missing-token, rejected, expired, low-score, unavailable, action-mismatch, hostname-mismatch) with the provider's own codes kept alongside. MemoryCaptcha lets a test queue passes and failures, fix a token's answer and inspect every call.

  • feat: publish @sdxc/captcha to npm The package is public: it ships in the daily release under the @sdxc scope.

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/catch-response-middleware

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/cloudflare-mocks

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/cloudflare-pricing

  • feat: add @sdxc/cloudflare-pricing, Cloudflare list prices per service One module per service (workers, durable-objects, d1, kv, queues, analytics-engine, email-service, r2) holds each meter's Workers Paid price as Cloudflare publishes it, the monthly included allowance and the Free plan limit, plus the docs URL the prices were taken from and the date they were verified. centsPerUnit() derives the per-unit cost in cents, and centsPerGbDay() accepts only GB-month storage meters, so a storage price cannot be converted at the wrong scale.

  • feat: publish @sdxc/cloudflare-pricing to npm The package is public: it ships in the daily release under the @sdxc scope.

@sdxc/data-table-d1

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/data-table-sqlstorage

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/doh

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/email-address

  • feat: add @sdxc/email-address, parsing, disposable-domain and mail-server checks parseEmailAddress() validates a dot-atom address within RFC 5321 length limits, NFKC-normalizes it and ASCII-encodes an international domain, returning the address as entered and a canonical form for comparisons. Separate entry points check a bundled disposable-domain list (disposable-email-domains, CC0-1.0), whether a domain can receive mail over DNS over HTTPS (null MX, implicit MX fallback, NXDOMAIN and resolver failures told apart), role accounts such as admin@, and likely typos of large providers.

  • fix: keep the disposable-domain list out of the type declarations DISPOSABLE_DOMAINS is declared as string, so its declaration file no longer repeats the whole list as a string-literal type (585 B instead of 140 kB), and the refresh script writes the same annotation.

  • feat: publish @sdxc/email-address to npm The package is public: it ships in the daily release under the @sdxc scope.

@sdxc/flags

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/flags-engine

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/highlight

Republished because @sdxc/markdown changed.

@sdxc/honeypot

  • feat: add honeypot form fields that refuse bot submissions before the handler New public package. Honeypot issues a random trap field name and an HMAC-signed token recording when the form was rendered and which field is the trap, and verifies them: a filled trap, a missing or forged token, and the optional minSeconds and maxAge limits each fail with a code. A list of secrets signs with the first and verifies with all, so rotation keeps open forms valid.

    The honeypot() middleware verifies on the route that accepts the form and publishes ctx.honeypot with the signed render time; HoneypotFields renders the token and a trap kept off-screen, inert, out of the tab order and ignored by autofill.

  • docs: write the success-answer example's onFailure as a method with early returns

  • feat: publish the honeypot on ctx.honeypot and verify only submissions The middleware now publishes its Honeypot as ctx.honeypot (and HoneypotKey) on every request, so the handler that renders a form issues fields without declaring a context property of its own. It verifies only on methods other than GET, HEAD and OPTIONS, so one installation covers a route that renders a form and accepts it.

    Breaking: the verification moved from ctx.honeypot to ctx.honeypotOutcome, and its context key from HoneypotKey to HoneypotOutcomeKey.

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/hostname

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/http

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/i18n

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/icons

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/idempotency

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/jobs

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/json-schema

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/lazy-route

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/logger

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/mail

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/markdown

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/mcp

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/merge-patch

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/microformats

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/micropub

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/openapi

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/pagination

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/passkey

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/password-policy

  • feat: add @sdxc/password-policy, password acceptance checks checkPassword() applies the NIST SP 800-63B checks: length in code points after NFKC (15 by default, 8 when a second factor is required), a bundled list of common passwords (SecLists' copy of the NCSC top 100,000, MIT), the account's identifiers and caller-supplied denied terms, and, when enabled, the Have I Been Pwned range API, which receives only a five-character SHA-1 prefix. checkPasswordHistory() refuses a password matching one of the account's stored hashes. Every refusal is a PasswordPolicyError whose issue names the rule, and a lookup that could not run is reported apart from a refusal, so the caller decides whether to fail open.

  • fix: keep the common-password list out of the type declarations COMMON_PASSWORDS and COMMON_PASSWORDS_NOTICE are declared as string, so the declaration file no longer repeats the whole list as a string-literal type (under 1 kB instead of 502 kB), and the refresh script writes the same annotation.

  • feat: publish @sdxc/password-policy to npm The package is public: it ships in the daily release under the @sdxc scope.

@sdxc/problem

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/rate-limit

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/scim

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/security-headers

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/seo

  • fix: pass JSON-LD through unsafeHTML for remix rc.4

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/session-storage-kv

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/spam

  • feat: add a spam filter that scores submissions and explains its verdicts New public package. createSpamFilter adds the weighted signals of every check into a ham, unsure or spam verdict and keeps each signal as a reason. Local rules run first; remote checks run in parallel under a timeout only while the score is below the spam threshold; escalation checks run only in the unsure band. A check that fails or times out adds nothing and is listed in failures.

    Ships default rules for timing, link count and syntax, link targets, Unicode obfuscation, contact bait, shouting, script mismatch and author names, plus author-email, StopForumSpam, DNS link blocklists, Akismet, a trainable naive Bayes classifier with memory and data-table stores, a Workers AI escalation check, and a scripted MemoryCheck for tests.

  • docs: show refusing bots with a honeypot before scoring The README now pairs the filter with @sdxc/honeypot: the middleware refuses a filled trap or a forged token before any check runs, and its signed render time feeds the timing rule.

  • docs: read the honeypot's render time from ctx.honeypotOutcome

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/structured-fields

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/trace-context

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/trailing-slash-middleware

  • feat: add @sdxc/trailing-slash-middleware, one canonical trailing-slash form per path trailingSlash() redirects every path to one form: without a trailing slash by default, or with one under { mode: "always" }, where a path whose last segment contains a dot (/robots.txt) is treated as a file and left alone. The root path never redirects, a run of trailing slashes resolves in one hop, and the query string is kept. Redirects are 308, so a non-GET request is repeated with its method and body.

  • feat: publish @sdxc/trailing-slash-middleware to npm The package is public: it ships in the daily release under the @sdxc scope.

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/u

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/ui

  • fix: apply the theme's sans font stack at the document root --ui-font-sans was defined but nothing ever applied it anywhere, so any page relying on the reset alone (no explicit u.font() call on its root) rendered in the browser's own default font instead. The reset now sets it once on html/:host, at zero specificity, so any element's own explicit font-family (u.font(), a component's mono/serif override) still wins as before.

  • fix: keep an open modal Dialog fixed to the viewport The panel carried position: relative, which beat the user agent's dialog:modal { position: fixed }. An element in the top layer whose position is static or relative resolves to absolute, so with no positioned ancestor the panel was laid out against the document and drifted by the scroll offset — a dialog opened on a scrolled page sat above the viewport and scrolled away.

    Gates a fixed override on :modal, emitted after the relative rule so the generated layer order resolves in its favour. The flow-state relative stays, since a non-modal dialog needs it as the containing block Dialog.Close pins to.

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/user-agent

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/validate

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/webhooks

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/webmention

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/well-known

  • chore: upgrade remix to 3.0.0-rc.4

@sdxc/workers-cache

  • chore: upgrade remix to 3.0.0-rc.4

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.28...v2026.9.29

2026.9.28

On GitHub

@sdxc/api-client

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: carry the running invocation's trace on every request APIClient.fetch() now writes the W3C Trace Context headers of the running invocation into every request: traceparent, naming that invocation's span as the parent, and tracestate when upstream sent vendor entries. Injection runs before before(), so a subclass can still read, replace or delete the headers, and a traceparent passed in init is kept. Outside a traced invocation nothing is added, so existing clients send exactly what they sent before.

    A subclass narrows what it forwards with the new propagateTrace field: "traceparent" for a service that should not receive upstream vendor state, "none" for one that should see no trace at all. It defaults to "all".

@sdxc/auth

  • feat: revoke a linked identity's refresh token at the provider on unlink Adds RelyingParty.revoke() (RFC 7009 revocation, packages/auth), reusing the token endpoint's own client-authentication pattern and the already-established AuthErrorCode.RevocationFailed. unlinkIdentity now surrenders an OIDC identity's stored refresh token at the provider before dropping the local row, best-effort: a provider that publishes no revocation endpoint, refuses the call, or is unreachable never blocks the local unlink, since the tenant's own directory row is the source of truth for whether the identity is linked. The outcome is recorded on the identity.unlinked audit event as providerRevoked.

  • feat: export managementProblems, the management API's problem catalog The management API's RFC 9457 problem types are now declared once, as a @sdxc/problem catalog exported from @sdxc/auth/management-client, with one stable title per type. ManagementProblem is decoded through it: a missing title or type takes its RFC default and status comes from the status line. ManagementProblemDetail is now an alias of ProblemIssue.

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: read an OAuth-only issuer from its RFC 8414 metadata Issuer takes discovery: "oauth" to read /.well-known/oauth-authorization-server instead of the OpenID Connect discovery document. For an issuer carrying a path, the suffix is inserted before the path as RFC 8414 requires (https://as.example/tenant reads https://as.example/.well-known/oauth-authorization-server/tenant). The default stays "openid", so existing issuers read the same document as before, and Issuer.for keeps one instance per discovery kind.

  • feat: write and read WWW-Authenticate Bearer challenges @sdxc/auth/bearer-challenge writes the challenge a bearer-token API answers a refusal with and reads it back on the client. stringify takes the RFC 6750 §3 parameters (realm, scope, error, error_description, error_uri) plus RFC 9728 §5.1's resource_metadata, quoting and escaping every value. parse reads a header that may list several schemes (Basic realm="x", Bearer error="invalid_token") and returns the Bearer challenges as a Result, with unknown parameters kept in extensions and a malformed header reported as a ChallengeParseError.

  • feat: publish RFC 9728 protected resource metadata and discover APIs from it ResourceServer takes a resource identifier. With it, metadataUrl says where the API's metadata lives, metadata() builds the document to serve (naming the issuer as the authorization server), and challenge() writes the WWW-Authenticate value for a refusal with the resource_metadata pointer and an optional realm. A server given a resource and no audience accepts tokens bound to that resource; existing audience-only servers behave as before.

    @sdxc/auth/protected-resource is the client side: ProtectedResource.discover(url) and ProtectedResource.fromChallenge(response, url) fetch the metadata and hold it to the resource asked about (exact match by default, match: "prefix" to accept a parent path), issuer() hands out the Issuer for a listed authorization server through RFC 8414 with an OpenID Connect fallback, and requiredScopes(response) reads the scopes a 403 asks for.

    In @sdxc/auth/remix/schemes, bearerScheme's rejection now carries the server's challenge, and bearerFailure(api) is a requireAuth({ onFailure }) handler that answers a request with no token with a 401 pointing at the metadata.

  • feat: discover the management API's token endpoint from its URL ManagementClient.discover(api, credentials) reads the API's RFC 9728 protected-resource metadata, then the listed authorization server's RFC 8414 metadata, and returns a client whose service client is granted tokens at the token endpoint found there.

    Issuer.Metadata's authorization_endpoint is now optional: RFC 8414 lets a server answering only client credentials leave it out, and such a document now passes discovery. authorizationEndpoint() fails with endpoint_unsupported for a server that publishes none.

  • feat: send the management client's updates as JSON Merge Patch updateTenantSubject and updateTenantClient send application/merge-patch+json. updateTenantClient takes any subset of the client record, so a caller names only what changes, and now sends PATCH, the method the management API answers on, where it used to send PUT. UpdateTenantSubjectInput's attributes are typed as attribute values, where null removes the attribute.

  • feat: send an Idempotency-Key from the management client's create calls createTenantSubject, registerTenantClient, rotateTenantClientSecret and inviteTenantMember take an optional { idempotencyKey }, sent as the Idempotency-Key header's quoted sf-string, so a caller retrying an operation with the same key gets the first attempt's response instead of a second resource. managementProblems gains the four Idempotency-Key refusals (missing, invalid, in use, reused).

  • feat: describe the management problem catalog's extensions as JSON Schema validationFailed's extension schema implements Standard JSON Schema beside validating, so an OpenAPI document built from managementProblems describes the errors member every validation failure carries instead of failing to convert the entry.

  • fix: call the management API's real routes for secret rotation, secret revocation and session revocation ManagementClient called several routes the management API never serves, so those methods always failed against a real server. Each now calls the route the server maps:

    • rotateTenantClientSecret: POST /clients/:clientId/rotate-secret (was POST /clients/:clientId/secrets/rotate)

    • revokeTenantClientSecret: POST /clients/:clientId/secrets/:secretId/revoke (was DELETE /clients/:clientId/secrets/:secretId)

    • revokeTenantSubjectSession: DELETE /subjects/:subjectId/sessions/:sessionId?reason=… (was POST …/sessions/:sessionId/revoke with a JSON body)

    • revokeTenantGrant: POST /subjects/:subjectId/grants/:clientId/revoke (was DELETE /subjects/:subjectId/grants/:clientId)

    • verifyTenantSubjectIdentifier: POST /subjects/identifiers/verify (was POST /identifiers/verify)

    • revokeAllTenantSubjectSessions: input.reason is now required, since the server rejects a body without one

    A route-table test now asserts every tenant-scoped method's HTTP method and path against the server's route patterns.

  • chore: drop the unused @sdxc/result from robots and declare idempotency and merge-patch in auth @sdxc/robots never imported @sdxc/result, so installing it pulled in a package it does not use. @sdxc/auth declares @sdxc/idempotency and @sdxc/merge-patch so its management client can reuse their Idempotency-Key and merge-patch definitions.

  • refactor: take the Idempotency-Key problems and header format from @sdxc/idempotency The management client and its problem catalog kept private copies of the four Idempotency-Key refusals, the header serializer and the merge-patch media type. They now come from @sdxc/idempotency and @sdxc/merge-patch, so the slugs, titles and header encoding match every other API using those packages. The public API is unchanged; a key the header cannot carry still fails with the same ManagementError, now with the serializer's error as its cause. inviteTenantMember's documentation now points to the server's separate email invitation endpoint.

@sdxc/billing

Republished because @sdxc/api-client changed.

@sdxc/crypto

  • feat: import/export foundation — hash recognition, run tracking, pre-hashed import (ADR-037 pass 1) password.recognizes() exposes the platform's own hash-format test as a standalone predicate, separate from verify() (needs a plaintext) and needsRehash() (conflates foreign with merely stale) — the exact "would this platform accept this stored value" check a bulk import needs, built on the same parser verify/needsRehash already use internally.

    tenant_import_runs is a new control-plane table tracking one bulk-import run's progress: which source file it reads, which report it writes failures to, its running counts, and a cursor position so a job that paces itself across many separate invocations (rather than one long-running call) always knows where the previous tick left off. TenantImportRun follows the same active-record model shape as this session's own AttackSignalAlert.

    importPasswordHash writes an already-hashed password directly to a subject's row with no plaintext ever seen on this path, skipping every plaintext-only policy check (length, common-password, reuse) that has nothing to check an imported hash against, while still refusing anything password.recognizes() doesn't accept. An imported row participates in the existing upgrade-on-login mechanism automatically, since signInWithPassword already calls needsRehash on whatever hash it finds with no regard for how the row was written.

    Foundation only — no job, RPC method, R2 wiring, or storage-ceiling logic yet; those build on this in later passes.

@sdxc/dates

  • feat: publish the zone math as @sdxc/dates/zone The wall-clock conversions every calendar operation is built on are now importable from @sdxc/dates/zone: zonedParts, instantFromParts, offsetMsAt, utcFromParts, the calendar-day helpers and DAY_MS. instantFromParts resolves a repeated hour to its earlier instant and reads a skipped one with the offset before the gap, which is the rule RFC 5545 sets for local times, so a calendar format reader can use it directly.

@sdxc/distill

  • fix: refuse everything when robots.txt is unreachable fetchRobots now answers Distill.Robots, a status beside the source to pass as options.robots, and reads the response the way RFC 9309 does: a 2xx is found, any 4xx is missing and permits everything, and a 5xx, a network failure or a chain that cannot be followed is unreachable and yields a document disallowing every path. The status lets a caller cache an unreachable answer briefly instead of refusing the origin for a full cache period.

  • feat: export the bounded retrieval as @sdxc/distill/retrieve The fetch that distill runs every page through is now importable on its own: addressable and isAddressableHost refuse a non-HTTP(S) URL or a private host before any request, follow walks a redirect chain re-checking every hop and answers the last response whatever its status, retrieve answers only a 2xx, readWithin reads a body within a byte cap, and release cancels a body left unread. Any client fetching a URL a stranger chose, such as a Webmention receiver or sender, gets the same bounds distill applies.

  • chore: declare the new standards packages where the apps adopt them Apps and packages now list the standards packages (SCIM, merge patch, idempotency, OpenAPI, JSON Schema, DoH, security headers, trace context, well-known, WebSub, robots, microformats, Webmention, structured fields, iCalendar) they are about to adopt. @sdxc/scim no longer depends on @sdxc/problem, and @sdxc/icalendar no longer depends on remix; neither imported them.

  • refactor!: take the robots.txt decision from @sdxc/robots options.robots now takes the RobotsFetch.Outcome that fetchRobots from @sdxc/robots/fetch answers, and distill evaluates it with isAllowedBy. The package's own fetchRobots, isAllowed, productToken, robotsUrl and the Distill.Robots type are removed; import them from @sdxc/robots instead.

    Behavior changes that come with the RFC 9309 parser:

    • an origin answering its robots.txt with 429 now refuses every path until the outcome expires, where it used to permit everything like any other 4xx

    • mayCache reads X-Robots-Tag per agent, so a noarchive scoped to another crawler no longer stops the article being kept

    • rules match after percent-encoding normalization, and /robots.txt is always allowed

@sdxc/doh

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: resolve DNS records over DoH with typed answers and failures New package. resolve(name, type, options) queries a DNS-over-HTTPS JSON resolver (Cloudflare by default, Google, or a custom { url, format }) with the global fetch and returns a Result. Records are typed per query type (A, AAAA, CNAME, TXT with joined text, MX, NS, CAA, SOA, SRV, and raw data for any other type); the answer also carries the CNAME chain, the smallest TTL, the AD and TC flags, and records whose data did not parse. NXDOMAIN (NameNotFoundError, with the SOA negative TTL), SERVFAIL (ServerFailureError), other response codes (ResponseCodeError) and network, timeout, non-2xx or malformed responses (TransportError) are distinct failure classes, while a name with no records of the type is a success with none.

    parseRecordData reads presentation-format RDATA, including RFC 3597 generic data, for callers holding zone-file records. verifyTxtRecord matches a verification token even when split into several character-strings or escaped, and checkCname follows an alias chain to a target; both read NXDOMAIN as not verified.

  • feat: make the package public @sdxc/doh now publishes to npm. It resolves names through a DNS over HTTPS JSON resolver (Cloudflare by default, Google, or your own), returns records typed per query type, tells NXDOMAIN, SERVFAIL and transport failures apart by class, and checks TXT and CNAME records for domain verification.

@sdxc/feed

  • feat: pair a hub with its topic through Feed.selectSubscription Feed.selectSubscription(header, document) returns { hub, topic, source }: the hub chosen exactly as Feed.selectHub chooses it, and the rel=self declared in the same place as that hub, falling back to the document's when the Link header named a hub alone. topic is null when neither declares one. A subscriber no longer pairs a header-advertised hub with a topic only the document declared, which could subscribe under the wrong topic. Feed.selectHub is unchanged.

@sdxc/flags

Republished because @sdxc/jobs changed.

@sdxc/flags-engine

Republished because @sdxc/flags changed.

@sdxc/highlight

Republished because @sdxc/markdown changed.

@sdxc/hostname

Republished because @sdxc/api-client changed.

@sdxc/http

Republished because @sdxc/crypto changed.

@sdxc/i18n

  • feat!: translate MessageFormat 2 messages without i18next Messages are Unicode MessageFormat 2. createI18n builds a synchronous per-language translator; the middleware publishes context.intl in place of context.i18next and takes resources directly; IntlProvider, setIntl and Trans take an I18n, and Trans renders MF2 markup ({#name}…{/name}) into components. TFunction remains as a deprecated alias of Translate.

  • docs: drop the remix-i18next lineage from the README

  • refactor!: make createTranslator synchronous and remove the TFunction alias A Translator now returns its Translation directly instead of a Promise; drop the await at call sites. The deprecated TFunction type is gone; type a t parameter with Translate, typed by a bundle (Translate<typeof en>) or untyped.

  • style: stringify a part's unknown value without a lint suppression bun typecheck reports the type-aware rule even with its disable comment in place; typing the value as unknown states the intent and clears the warning.

@sdxc/icalendar

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: read and write iCalendar with recurrence expansion and zones @sdxc/icalendar parses and writes RFC 5545 calendars: typed VEVENT, VALARM and VTIMEZONE over a generic component and property layer, so VTODO, VJOURNAL, VFREEBUSY and X- properties survive a round trip verbatim. parse and parseAll fail on structural errors with their line and report missing UID/DTSTAMP or unreadable values as warnings; stringify folds at 75 octets without splitting a character, escapes TEXT, caret-encodes parameters (RFC 6868) and writes the RFC 7986 feed fields with their X-WR- aliases. toInstant, utc and calendarResponse (a text/calendar response, optionally a download) complete the entry point.

    @sdxc/icalendar/rrule reads and writes RRULE values and expands an event's recurrence set (DTSTART, RRULE, RDATE minus EXDATE) into the occurrences overlapping a window, always bounded by that window and a limit; every example in RFC 5545 §3.8.5.3 is part of the test suite. @sdxc/icalendar/timezone builds a VTIMEZONE for an IANA zone from Intl, one observance per offset change in a span, and TZIDs resolve through a calendar's own VTIMEZONE rules before Intl.

  • chore: declare the new standards packages where the apps adopt them Apps and packages now list the standards packages (SCIM, merge patch, idempotency, OpenAPI, JSON Schema, DoH, security headers, trace context, well-known, WebSub, robots, microformats, Webmention, structured fields, iCalendar) they are about to adopt. @sdxc/scim no longer depends on @sdxc/problem, and @sdxc/icalendar no longer depends on remix; neither imported them.

  • feat: build and read iTIP invitations, cancellations and replies The new @sdxc/icalendar/itip subpath implements RFC 5546 scheduling for events: request invites attendees (defaulting unanswered ones to PARTSTAT=NEEDS-ACTION with RSVP), cancel cancels the whole event or uninvites some attendees with SEQUENCE bumped by one, reply answers for one attendee echoing the request's UID, SEQUENCE and RECURRENCE-ID, and readReply reads each attendee's PARTSTAT from a REPLY. nextSequence applies the §2.1.4 rule for when a revision bumps SEQUENCE, and calendarPart returns the { method, content, filename? } shape a mailer sends as a text/calendar; method=… part.

    The reader no longer warns about a VEVENT without DTSTART in a REPLY, CANCEL, REFRESH or DECLINECOUNTER, which RFC 5546 lets omit it.

  • feat: make the package public @sdxc/icalendar now publishes to npm. It reads and writes RFC 5545 calendars with typed events, alarms and time zones over a lossless generic component layer, parses, writes and expands recurrence rules within a bounded window, builds VTIMEZONEs from Intl, serves text/calendar responses for feeds and downloads, and builds and reads RFC 5546 iTIP requests, cancellations and replies.

@sdxc/idempotency

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: replay Idempotency-Key requests and refuse conflicting reuse @sdxc/idempotency implements the IETF Idempotency-Key draft on the server. The idempotency() middleware (./middleware) runs the first POST or PATCH carrying a key once, stores its outcome, and answers every retry with that stored response. It refuses a missing key (400, when required), a malformed one (400), a retry while the first request still runs (409 with Retry-After), and a key reused with a different payload (422), all as problem details built from the app's own catalog through IDEMPOTENCY_PROBLEM_ENTRIES.

    Records live behind the IdempotencyStore contract. ./data-table claims a key with a single conditional upsert, so exactly one of two concurrent requests wins on D1 and on Durable Object SQLite without a transaction; it ships the table definition, IDEMPOTENCY_KEYS_SCHEMA_SQL for the host's migration, and purgeExpired for a scheduled sweep. ./memory is an in-process store for tests. ./client mints keys (generateIdempotencyKey, deriveIdempotencyKey) and sets the header on a RequestInit or Request without replacing a key the caller already set.

  • feat: make the package public @sdxc/idempotency now publishes to npm. Its router middleware runs a POST or PATCH once per Idempotency-Key, replays the stored response to every retry, and refuses a key reused with a different payload or still in flight. It ships an atomic remix/data-table store, an in-memory store, and client helpers that mint, derive and attach keys.

@sdxc/jobs

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: carry the enqueuing invocation's trace to the job that runs it Every message now carries the W3C Trace Context of the invocation that enqueued it: enqueue(), enqueueMany() and messageBody() add traceparent (and tracestate when upstream sent vendor entries) to the envelope whenever a trace is bound. The job that runs the message continues that trace with a span of its own, exposed as ctx.trace, bound for the whole run, and stamped on the job's log as trace_id, span_id, parent_span_id and trace_flags, so a request and the jobs it caused share one trace_id.

    A cron tick and a queue batch each start a root trace and stamp it on their own log; every message one tick enqueues carries the tick's trace. An envelope without a trace, or with an invalid one, runs under a new root, so messages written by an earlier deploy dispatch unchanged, and a consumer from an earlier deploy ignores the new members.

    JobContextInit gains an optional trace, defaulting to the current trace or a new root. Tests that compare a tick's enqueued envelopes by exact equality now see a traceparent member beside job.

@sdxc/jsdoc

  • feat: add @sdxc/jsdoc, JSDoc read into a JSON documentation model extract(source, options) reads one file's text into a DocModule: the header comment, every exported symbol with its signature and members, and the exports it forwards to other modules. It is a pure function of the string it is given — no file system, no imports followed, no type checker — so the caller chooses which files a documentation site covers and the same input always produces the same output.

    Types are reported as the annotations their authors wrote, and a JavaScript file's @param {T} and @returns {T} tags fill the same fields. A barrel's export … from lines arrive as reExports for the caller to resolve.

    parseComment, findTag, findTags and inlineLinks are exported on their own for anything that needs one comment rather than a whole module.

  • fix: keep the value half of a merged symbol and its static members A namespace and the function it names merge into one symbol, and the merge kept the namespace's kind and comment. The function's description and examples were dropped, which is the documentation a caller reads. The merged node now takes the value's kind and comment, and the namespace contributes the types declared inside it.

    Static property assignments were not captured at all, being expression statements rather than declarations. They now attach as children of the exported symbol they extend, so the parts a component composes from are documented alongside it. An assignment whose owner is not exported is still ignored.

  • fix: match {@link} tags wrapped across lines

  • feat: make the package public Rewrite the README as an npm landing page, drop private: true, and mark it public in the root package table.

@sdxc/json-schema

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: build data-schema schemas that describe themselves as JSON Schema 2020-12 @sdxc/json-schema offers the remix/data-schema combinators under the same names and signatures. Each validates by delegating to data-schema, so behavior is identical, and also carries the JSON Schema 2020-12 keywords that describe what it accepts. Every schema implements Standard JSON Schema (~standard.jsonSchema).

    • toJSONSchema(schema, { direction, refs }) converts any Standard JSON Schema into a standalone document, collecting schemas named with meta({ id }) under $defs; a nested schema that cannot describe itself fails with the path where it sits

    • withJSONSchema(schema, json) pairs a schema built with data-schema directly with a hand-written JSON Schema

    • lazy(get, { id }) documents recursion through $ref, variant emits oneOf with an OpenAPI discriminator, and transform(fn, output) describes both sides

    • ./checks adds keyword-carrying minLength, maxLength, min, max, email, url, pattern, minItems and maxItems; ./coerce documents the text a query string may carry

  • feat: nest any data-schema schema that implements Standard JSON Schema Combinators now accept a DescribedSchema as a nested schema: any remix/data-schema schema that also implements Standard JSON Schema, not only schemas built by this package. A schema that carries its own ~standard.jsonSchema converter, such as a shared extension schema from another package, composes into s.object({ ... }) and is described through its converter.

  • feat: make the package public @sdxc/json-schema now publishes to npm. Its schema builders take the same arguments as remix/data-schema and validate identically, and every schema also describes itself as JSON Schema 2020-12 through Standard JSON Schema, with checks and coercions that document their keywords. @sdxc/openapi, which builds its documents from these schemas, depends on it.

@sdxc/jwt

  • feat: type JWK.toJSON entries with the kty every published key carries JWK.toJSON now returns JWK.PublishedKeySet, whose entries type kty as a string rather than optional, so its output is accepted wherever a JWK Set with required kty is expected.

  • docs: describe JWK.PublishedKey by the member it guarantees

@sdxc/mail

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: write one-click unsubscribe and List-Id headers from message options Messages and Email classes accept unsubscribe ({ url, mailto?, oneClick? }) and list ({ id, name? }). The mailer writes List-Unsubscribe, List-Unsubscribe-Post: List-Unsubscribe=One-Click and List-Id from them, and fails the send with a MailError when the URL is not https:, the mailto names no address, the list id is not an RFC 2919 list-id, or the same header is also set by hand. NormalizedMessage carries both options (or null). Messages that set neither option are unchanged.

    The new @sdxc/mail/unsubscribe subpath serves the receiving endpoint: signUnsubscribeToken and verifyUnsubscribeToken sign opaque subject and list ids with HMAC-SHA-256 into one URL path segment, with no expiry unless expiresAt is given and a purpose prefix for key separation, and isOneClickUnsubscribe tells the mailbox provider's RFC 8058 POST from a person's form submission.

  • feat: send a text/calendar part for calendar invitations A message or email now takes calendar: { method, content, filename? }. The MIME builder writes it as the last, most preferred, multipart/alternative part with Content-Type: text/calendar; method=<method>; charset=UTF-8, which is what makes Gmail, Outlook and Apple Mail show an iTIP invitation with accept and decline buttons. Setting filename also attaches the calendar as application/ics inside multipart/mixed.

    The Cloudflare transport, whose binding has no alternative-part field, sends the calendar as one attachment typed text/calendar; method=… (named by filename or invite.ics). A method that is not a token, or a filename with quotes, slashes or non-ASCII characters, fails the send with a MailError.

@sdxc/markdown

  • fix: stop a raw block gaining a blank line on every write A raw HTML block kept the newline its source ended on, and the block separator adds its own, so writing a document back put a blank line after every raw block that the document before it did not have. Reading and writing a file therefore never settled: each pass grew the gap, and a corpus checked against the repository's own formatter failed on any document opening a custom block tag.

    The newline is dropped where the block is written, since the separator is what supplies whatever follows it.

@sdxc/merge-patch

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: apply, diff and read RFC 7396 JSON merge patches New package. apply(target, patch) merges a patch per RFC 7396 without mutating either input; diff(source, target) returns the smallest patch between two values, and fails with UnrepresentableChangeError (carrying a JSON Pointer) when the target holds a literal null a merge patch cannot write. parse/stringify read and write patch documents, MEDIA_TYPE names application/merge-patch+json, and MergePatchOf<T> types the patches valid for a resource. applyValidated(target, patch, schema) applies a patch and validates the result with a Standard Schema, so one schema serves create and update.

    The ./request entry point checks a Request's media type before reading its body (isMergePatch, readMergePatch with alsoAccept for endpoints that keep taking application/json), and mergePatchProblem answers a refusal with a 415 carrying Accept-Patch or a 400. Every RFC 7396 Appendix A example runs as a test.

  • feat: make the package public @sdxc/merge-patch now publishes to npm. It applies and diffs RFC 7396 JSON Merge Patch documents, validates a patched resource against a Standard Schema, types the patches a resource accepts with MergePatchOf, and reads application/merge-patch+json requests, answering refusals as problem documents.

@sdxc/messageformat

  • feat: add @sdxc/messageformat, a Unicode MessageFormat 2 ponyfill of Intl.MessageFormat Parses and formats MF2 messages through the API the TC39 Intl.MessageFormat proposal defines (format, formatToParts, custom functions), with :string, :number and :integer built in, verified against the MessageFormat working group's conformance suite.

@sdxc/microformats

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: parse pages into canonical microformats2 JSON and read it back parse(source, baseUrl) and fromDocument(tree, baseUrl) run the microformats2 parsing algorithm over the tree @sdxc/html builds: h-* roots, p-/u-/dt-/e- properties, nested items with their value, children, implied name/photo/url, the value-class pattern for dates, rels and rel-urls, and URL resolution honoring <base href>. Classic roots (hentry, hfeed, vcard, hreview, hnews and the rest of the specification's table) are read when no mf2 root is present, unless backcompat: false.

    stringify and parseJSON write and read the canonical JSON with its wire name rel-urls (relUrls in code). ITEM_SCHEMA validates one mf2 item, the shape of a Micropub JSON create body, filling the value of { html } content and of a nested item written without one. findItem and values look up items and read property values as strings.

    The official microformats test suite (CC0) is vendored under docs/vendor/microformats-tests and runs fixture by fixture; each departure is named with its reason.

  • feat: read IndieWeb vocabularies and write microformats from remix/ui @sdxc/microformats/vocabulary reads an item as a typed Entry, Card or Feed (readEntry, readCard, readFeed), or through any Standard Schema with single-valued properties unwrapped (readItem). A response property holding a nested h-cite or a bare URL both become a Cite, and dt-* values carry the instant they name. It also runs the IndieWeb living algorithms: authorOf (entry, then feed, then rel=author on a permalink, ending at { url } when another page must be fetched), representativeCard, postType (Post Type Discovery with bookmark) and responseTo, which finds the entry replying to, liking, reposting, bookmarking or mentioning a URL.

    @sdxc/microformats/ui gives remix/ui templates typed class names: mf() is a mixin that appends microformats tokens beside css() in mix and reaches through components forwarding mix, classes() returns the same tokens as a string, and MicroTime renders a <time> with a dt-* class and an ISO datetime. A misspelled or mistyped class name (p-in-reply-to, a classic hentry) fails to compile.

  • feat: make the package public @sdxc/microformats now publishes to npm. It parses HTML (classic microformats included) into canonical microformats2 JSON, reads and writes that JSON, validates a single item, gives typed views of h-entry, h-card, h-feed and h-cite with the IndieWeb authorship, representative h-card, Post Type Discovery and response algorithms, and provides typed class-name mixins for remix/ui templates.

  • style: open the entry module with its header comment Two type imports sat above the module's JSDoc header, which every module in the repo opens with.

@sdxc/micropub

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: read Micropub requests into typed operations and answer them parseOperation decodes a form-encoded, multipart or JSON POST into a typed create, update (replace, add, and delete by property or by value), delete or undelete, and returns the access token from the Authorization header or the form's access_token. Forms are reshaped into the JSON shape first and every create's values pass ITEM_SCHEMA from @sdxc/microformats, so both encodings yield the same operation; mp-slug, mp-syndicate-to and post-status arrive as typed commands. parseQuery reads q=config, q=source, q=syndicate-to and q=category, passing any other query through. requiredScopes names the scopes that authorize an operation, and created, accepted, updated, deleted, error, config, syndicateTo, source and categories build the responses the specification defines. @sdxc/micropub/media reads a media endpoint upload's file part, checked for size and type, and answers it with uploaded. Every example of the W3C Recommendation and every micropub.rocks server test is covered by the test suite.

  • feat: make the package public @sdxc/micropub now publishes to npm. It decodes Micropub POSTs (form-encoded, multipart and JSON) into typed create, update, delete and undelete operations and GETs into typed queries, returns the access token from wherever it was sent, names the scopes each operation needs, builds every response the specification defines, and reads and answers media endpoint uploads.

@sdxc/openapi

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: build, serve and check OpenAPI 3.1 documents from typed operations @sdxc/openapi describes an API from the declarations its handlers already run.

    • defineOperation(name, route, spec) binds a route-map route to its params, query, body, responses, problem names and security; operation.parse(request, params) parses all three inputs into typed values, failing with an OperationInputError that names the part and carries the schema's issues. A params schema must name exactly the pattern's variables, at compile time and at build time

    • createDocument(options).add(...operations).build() assembles the document: :param becomes {param}, named schemas hoist into components.schemas, a problem catalog becomes a response per entry, and problems sharing a status merge with oneOf. scopes() lists every required scope per scheme

    • parse and stringify read and write documents as JSON or YAML

    • ./security builds bearer, API key, OAuth 2.0 and OpenID Connect schemes

    • ./router serves the document as JSON or YAML with a strong ETag and 304 revalidation, building it on the first request

    • ./testing checks responses against the document: checkResponse reports undocumented operations, statuses, media types and problem types, missing headers and body mismatches, and createConformanceRecorder collects them across a router's exchanges and lists the declared statuses no test produced

  • feat: make the package public @sdxc/openapi now publishes to npm. It binds a route to the schemas of its params, query and body, its responses, problem types and security, parses requests through that declaration, assembles an OpenAPI 3.1 document from the same operations, serves it as JSON or YAML with ETag revalidation, and checks responses against it in tests.

@sdxc/pagination

Republished because @sdxc/crypto changed.

@sdxc/passkey

Republished because @sdxc/crypto changed.

@sdxc/problem

  • feat: add @sdxc/problem, RFC 9457 problem details and catalogs Build a problem+json Response from a status alone or a full document, and read one back into a Result with the RFC's defaults, the status line winning over the body, and extensions validated by a Standard Schema. defineProblems declares an API's problem types once and gives a typed builder per type plus a parser that names the entry a response belongs to. validationProblem and issuesFrom turn schema issues into a 422 with JSON Pointer errors.

  • feat: list each catalog entry's extensions schema and describe ISSUES_SCHEMA as JSON Schema entries() now includes extensions, the schema an entry was declared with, on the entries that have one, so a tool rendering an error reference or an OpenAPI document can describe each problem type's extension members. Entries without a schema are listed as before.

    ISSUES_SCHEMA also implements Standard JSON Schema (~standard.jsonSchema), describing the errors extension's pointer, code and message members. It validates exactly as before and stays a remix/data-schema schema.

@sdxc/rate-limit

  • chore: declare the new standards packages where the apps adopt them Apps and packages now list the standards packages (SCIM, merge patch, idempotency, OpenAPI, JSON Schema, DoH, security headers, trace context, well-known, WebSub, robots, microformats, Webmention, structured fields, iCalendar) they are about to adopt. @sdxc/scim no longer depends on @sdxc/problem, and @sdxc/icalendar no longer depends on remix; neither imported them.

  • refactor: write the RateLimit fields as RFC 9651 structured fields rateLimitHeaders now serializes RateLimit (a Dictionary) and RateLimit-Policy (an Item with a w parameter) through @sdxc/structured-fields, so quoting and number rules come from one checked implementation. Output for every existing policy is byte-for-byte the same; the fields still follow draft-ietf-httpapi-ratelimit-headers-07.

    A field holding an Integer beyond 15 digits, which RFC 9651 cannot represent, is now left out instead of being written as an invalid value.

@sdxc/robots

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: parse, write, evaluate and fetch robots.txt per RFC 9309, and read robots directives @sdxc/robots parses any text as robots.txt without failing, keeping groups, sitemaps, Crawl-delay, typed Content-Signal records and any other record, and stringify writes a document that parses back to itself. isAllowed picks the agent's groups by product token (merging every group that names it, falling back to *), normalizes percent-encoding in both pattern and path, and applies the longest matching rule with * and $ wildcards, Allow winning ties and /robots.txt always allowed. crawlDelay, productToken and robotsUrl complete the root export.

    @sdxc/robots/fetch retrieves an origin's robots.txt with RFC 9309's status rules: a 2xx is parsed, a 4xx other than 429 allows everything, and a 5xx, 429, network failure, timeout or a chain past five redirects disallows everything. Each outcome is plain data carrying the lifetime a cache should give it (24 hours, or 1 hour for an unreachable origin), and isAllowedBy evaluates it.

    @sdxc/robots/directives reads and writes X-Robots-Tag and robots meta values: bot-scoped sets, max-snippet, max-image-preview, max-video-preview and unavailable_after, with unknown directives kept. directivesFor merges a response's headers into the most restrictive set for one bot.

  • feat: make the package public @sdxc/robots now publishes to npm. It parses, writes and evaluates robots.txt per RFC 9309, fetches an origin's file and turns every HTTP outcome into the decision the RFC assigns it, and reads and writes the per-page robots meta tag and X-Robots-Tag directives. The published build leaves out the conformance fixtures ported from Google's robots.txt test suite.

  • chore: drop the unused @sdxc/result from robots and declare idempotency and merge-patch in auth @sdxc/robots never imported @sdxc/result, so installing it pulled in a package it does not use. @sdxc/auth declares @sdxc/idempotency and @sdxc/merge-patch so its management client can reuse their Idempotency-Key and merge-patch definitions.

@sdxc/saml

  • feat!: stop publishing the test fixtures @sdxc/saml/testing is removed; build keys and assertions in your own tests.

@sdxc/sample

  • style: split strings into code points with Array.from Same code-point split as the spread it replaces, without the no-misused-spread suppression that bun typecheck ignores.

@sdxc/scim

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: parse, filter, patch and describe SCIM 2.0 resources @sdxc/scim holds the standard half of a SCIM 2.0 service provider (RFC 7643 and RFC 7644), as plain functions over plain data and Response objects:

    • "@sdxc/scim": User, Group and Enterprise User types; parseUser with typed, schema-validated extensions and parseGroup (attribute names matched case-insensitively, null read as unassigned); userResource/groupResource; weak entity-tag versions and If-Match/If-None-Match checks; list queries and search bodies with the RFC's clamping; ListResponse, the SCIM error document, application/scim+json responses; attribute projection.

    • "@sdxc/scim/filter": the complete RFC 7644 filter grammar parsed to an AST, written back to text, and compiled to a predicate that honors caseExact, date-time instants and multi-valued matching, restricted by an allow list.

    • "@sdxc/scim/data-table": a filter translated to a remix/data-table predicate, failing with UntranslatableFilterError when only in-memory evaluation is faithful.

    • "@sdxc/scim/patch": PatchOp parsing and all-or-nothing application, including the shapes Okta and Entra ID send.

    • "@sdxc/scim/discovery": RFC 7643 attribute definitions and the ServiceProviderConfig, ResourceTypes and Schemas documents.

  • chore: declare the new standards packages where the apps adopt them Apps and packages now list the standards packages (SCIM, merge patch, idempotency, OpenAPI, JSON Schema, DoH, security headers, trace context, well-known, WebSub, robots, microformats, Webmention, structured fields, iCalendar) they are about to adopt. @sdxc/scim no longer depends on @sdxc/problem, and @sdxc/icalendar no longer depends on remix; neither imported them.

  • feat: make the package public @sdxc/scim now publishes to npm. It reads and writes SCIM 2.0 User, Group and Enterprise User resources, parses and evaluates the complete filter grammar (and translates filters into remix/data-table predicates), applies PATCH operations, reads list queries, projects attributes, and builds the SCIM error, list and discovery documents.

@sdxc/security-headers

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: apply typed CSP, Permissions-Policy and security header policies with a per-request nonce A policy set is one typed object covering Content-Security-Policy (enforced and Report-Only), Strict-Transport-Security, Referrer-Policy, Permissions-Policy, COOP, COEP (and its Report-Only variant), CORP, nosniff and Reporting-Endpoints. entries and apply turn it into headers, deriving X-Frame-Options from frameAncestors and writing HSTS only on https: requests; a header the response set itself wins.

    ./csp writes CSP Level 3 directives with keywords, nonces and hashes quoted, substitutes a "nonce" placeholder (writing 'none' for a list left empty), parses a header holding several policies, and merges an override where null removes a directive. ./permissions-policy writes and reads the header through the RFC 9651 serializer. ./reports reads CSP violation reports in both the Reporting API and the legacy application/csp-report formats.

    ./middleware exposes securityHeaders(policy), which publishes ctx.securityHeaders with a nonce generated on first read (a response that never reads it advertises none), and securityHeadersOverride(patch) for a route's own exceptions. Rendering <ImportMap nonce={ctx.securityHeaders.nonce}> carries the nonce to the import maps the remix/ui client runtime appends.

  • chore: lock @sdxc/idempotency's dependencies and drop unused ones Records the dependencies @sdxc/idempotency declared, and removes remix from @sdxc/websub and @sdxc/http from @sdxc/security-headers, which neither imports.

  • feat: make the package public @sdxc/security-headers now publishes to npm. It models Content-Security-Policy, Permissions-Policy, Strict-Transport-Security, Referrer-Policy, the cross-origin policies and Reporting-Endpoints as one typed policy, writes them with a per-response CSP nonce from router middleware, parses CSP and Permissions-Policy back, and reads CSP violation reports in both formats.

@sdxc/seo

  • chore: declare the new standards packages where the apps adopt them Apps and packages now list the standards packages (SCIM, merge patch, idempotency, OpenAPI, JSON Schema, DoH, security headers, trace context, well-known, WebSub, robots, microformats, Webmention, structured fields, iCalendar) they are about to adopt. @sdxc/scim no longer depends on @sdxc/problem, and @sdxc/icalendar no longer depends on remix; neither imported them.

  • refactor: write robots meta content through @sdxc/robots robotsDirectives (behind seo.robotsTag) now delegates to @sdxc/robots' stringifyDirectives with explicit defaults, so the robots meta tag and X-Robots-Tag share one grammar. The emitted content is byte-for-byte the same: "index, follow", "noindex, follow", "index, nofollow" or "noindex, nofollow".

@sdxc/server-timing

  • fix: escape quotes and backslashes in a timing description A description containing " or \ ended the quoted desc parameter early and made the whole Server-Timing header unparseable. Both characters are now backslash-escaped, so any description reaches the browser intact.

@sdxc/spec

Republished because @sdxc/sample changed.

@sdxc/structured-fields

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: parse and serialize RFC 9651 structured field values parse(text, type) reads a List, Dictionary or Item into a plain value model and returns a Result that never carries a partial value, as RFC 9651 requires. stringify(value, type) writes the canonical text, enforcing the Integer and Decimal ranges, the String and Token character sets, the key grammar and whole-second Dates, and reports the path to any value with no representation. Token, Decimal and DisplayString keep the types a JavaScript primitive cannot tell apart through a round trip.

    getField and setField read and write a field on Headers: an absent field reads as null, and an empty List or Dictionary deletes the field. Passing a synchronous Standard Schema as the last argument to parse or getField returns the schema's typed output or a ValidationError.

    The httpwg structured-field-tests suite is vendored and runs in full.

  • feat: declare a field's shape with sf.* data-schema helpers @sdxc/structured-fields/schema exports sf, a catalog of remix/data-schema schemas over the Structured Field model: integer, decimal, token (optionally narrowed to a list of literals), displayString, bytes and date for bare items, and item, value and innerList for members. They unwrap Token, Decimal and DisplayString to primitives, compose with s.object, s.array, s.union and checks, and report issues at their path inside the field, so getField(headers, "Priority", "dictionary", schema) returns typed values.

  • docs: document the API and record how the package was built The README covers parse, stringify, getField, setField, the bare item wrappers, the error classes and the sf.* schema helpers, with patterns for Cache-Status, Idempotency-Key and writing only the members a caller has.

    ADR-079 is accepted, and its notes record where the build differs from the proposal: ValidationError is re-exported from remix/data-schema, base64 uses the platform's atob/btoa, and schema arguments are typed SF.SyncSchema<Output> so remix/data-schema's own schemas are accepted and an async schema is a type error.

  • feat: make the package public @sdxc/structured-fields now publishes to npm. It parses and serializes RFC 9651 structured HTTP field values (Lists, Dictionaries and Items), reads and writes them on Headers, and validates them into typed values through remix/data-schema builders. @sdxc/idempotency and @sdxc/security-headers, which read and write their fields through it, depend on it.

@sdxc/trace-context

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: parse, bind and propagate W3C Trace Context A new package for W3C Trace Context. ./traceparent and ./tracestate parse and write both headers to the specification: future versions read by the version-00 rules, version ff and all-zero ids refused with a code naming which, the Level 2 random-trace-id flag, the tracestate key and value grammar, the 32-entry cap, duplicate keys, the move-left rule for TraceState.set(), and truncation that drops entries over 128 characters first.

    The root export binds a TraceContext to the running invocation: startTrace(), continueTrace(), extract() from incoming headers (never failing, restarting on an invalid parent), inject() into outgoing ones, withTrace() for forwarding a request to a Durable Object, toTraceParent() for RPC arguments, currentTrace() / runWithTrace(), and traceFields(), the OpenTelemetry-named fields a wide event carries.

    ./middleware exports trace(), which continues or starts the request's trace, publishes it as ctx.trace, binds it for currentTrace() and stamps the request's log. Mount it directly after log(logger).

  • feat: make the package public @sdxc/trace-context now publishes to npm. It parses and writes the W3C traceparent and tracestate headers, binds one trace to each invocation, and provides a router middleware that continues the caller's trace and stamps its ids on the request's log. @sdxc/jobs and @sdxc/api-client, which propagate the trace, depend on it.

@sdxc/ui

  • fix: render a path-less Form issue instead of dropping it silently Form.Issue.path is documented as optional — "omitted for a form-level issue" — but groupIssuesByField only ever grouped issues that named a field, and nothing else in Form read the rest. A server-side refusal decided before any single field was at fault (auth-saas's own addIdentifier/setPassword failures, and its newer Turnstile refusals on sign-up and reset) genuinely refused the request at 400, but the message explaining why never reached the page: every field-level component only ever asks Form.Context for its own field's issues, and a path-less issue was never stored anywhere a lookup could find it.

    Form now separates path-less issues from the ones grouped by field, renders them as its own leading Alert when there are any, and exposes them through Form.Context.getFormIssues() too, for a caller that wants to render them somewhere other than Form's own default slot. No call site needs to change: every existing { message } issue with no path, in auth-saas or anywhere else this component is used, starts rendering without touching whoever passed it in.

  • feat: add a hover step between each tone's border and its strong one Every tone gains --ui-{tone}-border-hover, one shade along from the resting --ui-{tone}-border. The strong weight the contract reserves for focus, validity and the high-contrast promotion is several steps away, so a control that darkened its border on hover had to jump the whole distance and read as a state change rather than as a nudge. High contrast promotes the new step alongside the resting one, so the two never invert.

  • fix: draw a button's border as the hairline every other control uses Buttons were the only bordered controls in the library at 2px, and their outline variants rested on the strong border weight, so an outline button beside a field carried an edge several shades darker and twice as thick. All four now draw a 1px hairline, step to the hover weight under the pointer, and state their row measure outright instead of inheriting it from the border they happened to have — which is what kept them aligned with a field before, and stopped as soon as the border changed. A glyph inside one is measured against the label beside it rather than at whatever size an icon set drew it.

  • fix: show a field's focus as a ring standing off it A field promoted its border to the strong weight on focus and sat a ring flush against it, and the two read as one thick slab — the field looked like it had changed shape rather than taken focus. Focus is now drawn the way every button in the library draws it, as a ring standing off the control, with the border stepping only to its hover weight so nothing jumps. Hovering steps the border to that same weight instead of jumping to the strong one.

    The rule suppressing the platform's own outline is scoped to pointer focus alone. It is a shorthand, and from a later cascade layer it was resetting the keyboard ring's colour, width and style together — leaving a field with no visible focus at all.

  • fix: keep a customized select showing the option it has selected A customized select renders its current value into <selectedcontent>, which the browser fills and a re-render empties — so the trigger went blank after the first update. mirrorSelectedOption() puts the selected option's content back whenever the slot arrives empty, and Select.Value carries it.

    value and defaultValue are now rejected by the prop type. A <select> takes its selection from the option carrying selected and reads neither attribute, so passing one went unread and the field quietly showed its first option instead. Focus and hover follow the field treatment: a ring standing off the control, and a border stepping to its hover weight.

  • fix: join a group's controls into one shape A group left every child fully rounded and drew a ring around the whole cluster on focus, so a row of controls read as separate boxes that lit up together. The cluster now keeps the rounding on its own outer corners and collapses the seam two controls share, and focus is shown by whichever control holds it — a ring around the group says one of them is focused without saying which. The focused control is raised so its ring crosses the seam rather than being painted over by its neighbour.

  • fix: reveal a tree branch when it opens An open branch stayed empty: the collapsed block-size: 0 and the open auto were written as a value and an override, and the override landed in an earlier cascade layer than the value it was meant to beat. They are now selectors that exclude each other, so layer order cannot decide it.

    A leaf now clears the same gutter its siblings' expand controls occupy, so files at one depth line up with the folders beside them instead of sitting where their parent's name starts. treeKeys() drives the native [open] state rather than tracking its own.

  • fix: let a tooltip open A popover in hint mode was hidden outright while it was not open, which the CSS reveal a tooltip relies on had no way past — so a tooltip never appeared. Hint mode is now exempt from that rule, leaving the surface free to be revealed by the hover-driven styling it carries.

  • fix: give a search field one clear control, sized and placed to its row A search field drew its own clear control beside the one the platform renders, so a field with text in it showed two crosses side by side. The platform's is now suppressed and SearchField.Clear is the single control, centred on the field's own axis rather than hanging off the text baseline. It carries the plain, quiet chrome a control inside a field wants instead of a full button's hover fill.

    clearField() hides the control while the field is empty and shows it once there is something to clear, so the field is not permanently offering an action that would do nothing.

  • fix: choose a day or a row by pointer, not only by key Both behaviours moved the active cell from the keyboard and left a click to do nothing, so a calendar or a grid list built on them answered arrow keys but not the pointer — the two inputs disagreed about what was selected. A press now moves the active cell the same way a key does, and the tests cover both paths.

  • fix: keep a table's sort indicator square The three sort indicators were squashed to roughly 13 by 14 by the header cell they sit in, which read as a broken glyph rather than a direction. They hold their measure now.

  • fix: align a sidebar's gutters and stack a menu item holding a sub-menu The sidebar's inline gutters did not match each other or the block ones, leaving a wide band of empty space down the leading edge that the trailing edge had nothing to answer. Every gutter is now the same measure.

    A menu item holding a sub-menu laid its rows out as one line, so a nested list ran along beside its parent's label instead of under it. An item with a sub-menu now stacks and stretches; one without keeps the centred row it had.

  • fix: keep a hover card reachable across the gap to its trigger The card rests off its trigger's edge, and a pointer travelling between the two left both, so the card closed before it could be reached and nothing inside it was ever clickable. The card's hit area now reaches back across that offset, and the pair reads as one surface for as long as the pointer is on either.

    An open card takes pointer events through an important declaration, because the resting state's own rule can land in a later cascade layer than the open one.

  • fix: show which row the keyboard is on in a listbox Walking a listbox with the arrow keys moved something invisible: nothing marked the row the next Enter would land on, so the choice was only visible once it had been made. The row the keyboard is on now carries a tint, stopping short of a row that is already selected so it does not lose the stronger fill while being walked past.

    ArrowUp at the top of the list wraps to the last row, which it did not do while ArrowDown wrapped to the first.

  • fix: follow a message to its end as it streams in The log followed new messages but not a message growing in place, so text arriving a word at a time scrolled out of view under the reader. Character-level changes now count as growth, and a pinned log follows them without animating each step — a smooth scroll per word never finishes before the next arrives.

    The jump-to-latest control listens from the frame rather than from the node it is drawn into, so it survives the log re-rendering underneath it, and it carries an opaque surface of its own so it stays legible over the conversation it points at.

  • fix: show a grid list's selection and hold its glyphs to one measure A selected row was tinted with a colour the same lightness as the list behind it, so a selection was invisible — the count said two rows were selected and nothing on screen agreed. It now carries a tint well clear of the surface, and the pointer states are written to exclude the selected one so hovering a selected row cannot repaint it as unselected.

    A glyph leading a row was a flex item free to shrink, so every row squeezed its icon by however much that row's own text overran and no two came out the same width.

  • fix: show the active row in a command palette and centre its empty state The active row was tinted with a colour the same lightness as the panel behind it, so walking the list with the arrow keys moved a cursor nobody could see. It now carries a tint clear of the surface, with the pointer states written to exclude it.

    The list spent its block padding even with every row filtered out, so that padding stacked above the empty-state message with nothing to balance it below and the message read as pushed down. It spends that padding only when it has a row to space. A glyph anywhere in a row is held to the row's own text measure.

  • fix: draw a keyboard hint as the key it names, inline with its sentence A hint was plain muted text, so a shortcut written mid-sentence read as prose rather than as a key. It is drawn as a cap now: tinted, bordered, and held to a measure that reads beside the row's own text whether it carries one glyph or a chord.

    It centres that glyph without composing the centring pattern, which brings a block display along with it — every cap stood on its own line and stretched to the width of whatever held it, breaking the sentence apart.

  • fix!: leave the calendar to the platform in DatePicker and DateRangePicker Both components shipped a trigger and a popover calendar alongside a native date field, so the field offered the browser's picker and the trigger offered a composed one — two calendars on one control, writing to the same value and disagreeing about what it was. Suppressing the platform's proved browser-dependent and unreliable, so the composed one goes instead: the field is the whole control, and its picker, keyboard handling and locale all come from the browser and work before any script loads.

    BREAKING CHANGE: DatePicker.Button, DatePicker.Dialog, DateRangePicker.Button and DateRangePicker.Dialog are removed, along with their prop types. Calendar and RangeCalendar remain as standalone components.

    DatePicker.Group keeps its place and does more: it joins two date fields into one control, collapsing the seam they share, showing focus on the field that holds it, and drawing validity once around the pair rather than twice at the seam — a range is one value, and half of one is what makes it invalid.

  • fix: draw a side drawer against the full edge it docks to A drawer docked left or right pinned both block insets and stopped there, and a <dialog> sizes to its content, so the panel sat at the top of the viewport at whatever height its contents came to rather than filling the edge. Each placement now gives the edge it docks to a depth and tells the other axis to fill — which the top and bottom placements already did.

  • fix: widen a dialog to the form it usually carries A dialog capped at 28rem left a 400px column once its padding was taken, which a field, its help text and a row of actions do not read well in. It is now 32rem, the measure AlertDialog had already widened to for a title and one sentence — so the general surface was narrower than the specialised one built on it. AlertDialog inherits that measure now rather than restating it, and the two cannot drift apart.

  • fix: draw a drop indicator only at the gap the drop lands in The bar painted the same colour whether or not it was the target, because the rule meant to colour the active one landed in an earlier cascade layer than the resting value it was overriding — so a list carrying one bar per gap showed a permanent rule in every gap and nothing changed while a drag was in flight. The two states are now selectors that exclude each other, the resting bar paints nothing while keeping its place in the layout, and the active one fades in.

  • fix: lay out an empty state's actions as a row The action slot was a bare block, so two buttons sat flush against each other with nothing between them. It is a centred row now, gapped and wrapping, since two buttons side by side outgrow a narrow panel.

  • fix: stop a disabled disclosure from toggling, and inset its panel A row marked aria-disabled was dimmed and fully operable — a <summary> has no disabled, and the component left blocking the toggle to the consumer. It now leaves the tab order and stops taking pointer events, which is the whole of what disabled would do and holds before any script runs.

    The panel had no padding at all while the trigger above it was inset, so a section's content ran to the frame's edge while its label sat well inside. It takes the trigger's inset and clears the lower edge. Accordion.Content releases that inline inset, since an accordion row runs edge to edge the way its trigger does.

    A trigger's indicator glyph is measured against the label beside it, and the section turns it over while it is open — the rule Accordion already had and Disclosure did not.

  • fix: fill a colour picker's trigger with the swatch it previews The trigger centred nothing and passed its size to the swatch as a mix, which the swatch's own size rule outranked — so the preview sat at its default measure in a corner of the button with slack on two sides. The size now reaches the swatch through the measure the swatch itself reads. Hover steps the preview's edge one shade darker instead of laying a second ring outside an edge it already draws, and the group leaves focus to whichever control holds it.

    A circular wheel held its measure on one axis and shrank on the other as a flex item, which is an ellipse. A slider insisted on a 10rem floor, which does not make a track usable inside a narrower panel — it makes the panel overflow and the track run off the edge.

  • fix: move a carousel to the slide a control names --ui-goto measured the target slide through offsetLeft, which reports a distance from whichever ancestor happens to be positioned — usually the page — so every dot scrolled by however far the carousel sat from that ancestor rather than to the slide it named. The target is measured against the viewport's own box now, and given as a position rather than a distance so a command arriving while another is still animating retargets instead of compounding a stale delta.

    A slide no longer forces the scroll to stop at it. scroll-snap-stop: always clamps every scroll to a single snap point, programmatic ones included, so no control could move more than one slide however far it asked to go.

    carouselControls() marks the --ui-goto invoker naming the slide in view, so a row of dots reads as a position rather than as identical buttons.

  • fix: sit a bubble's reactions on the line its text sits on The reaction row was pulled up by a negative margin into the last line of the message it belongs to, and inset barely at all while the content beside it was inset well inside the frame — so the chips crowded the text and hugged the bubble's edge. It takes the content's inset and clears the frame's lower edge.

    The frame no longer declares its own containment, which was scoping container queries written against the conversation column to each bubble instead.

  • fix: centre a calendar day in the cell that holds it A day sat hard against the top of its cell, so the selected range's fill ran through the middle of the numbers rather than around them. Table cells centre their content through alignment now, and a row laid out as a flex box centres it the other way — the two are written together and each answers one case, since a flex box here would take every day out of its row and stack the month into a column.

  • fix: centre an overlay arrow's point under the trigger The host spans the whole edge it attaches to, and the glyph sat wherever the box put it, so the point landed away from the trigger the surface hangs off. The glyph is centred inside that host now, which is what puts the point under the trigger. A side placement sits past the surface's own inline edge, where there is no space to grow into, so its glyph is sized from its own content instead.

  • fix: give each engine its own rule for a meter's fill and scope a slider's orientation A vendor pseudo-element an engine does not recognise invalidates every selector written beside it, so naming Chrome's, Safari's and Firefox's meter fills in one list left every engine with no fill at all. Each engine gets a rule of its own.

    A slider matched its vertical rules on the orientation attribute alone, which answers to any ancestor that happens to carry one — a scroll viewport, a toolbar — and laid a horizontal slider out as a vertical one. The rules are scoped to the slider's own slot.

  • fix: hold a menu row's glyph to the row's own text measure A glyph in a row was drawn at whatever size the icon set gave it and was free to shrink, so it neither matched the text beside it nor stayed put while a long label wrapped.

  • feat: let a gated loop settle visible on a host carrying its own content A gated loop rests hidden outside its gate, which suits an element that exists only as a loading cue and leaves one carrying content of its own blank once the loop settles — an attachment's title vanished the moment its upload finished. shimmer() takes outsideGate, and Attachment.Title asks to stay visible.

  • fix: open a card written as content alone with its own top padding The body meets whatever sits above it with no gap, because a header already carries that space — so a card written as content alone started hard against its own top edge. It takes that padding back when it is the first thing in the card.

  • fix: let an item row wrap so its actions can take a line Wrapping was asked for under the row's own container query, which can never apply — a container query cannot change the layout of the container it measures. The row held nowrap while its actions claimed a full line, leaving the content with nothing. Wrapping is allowed at every width, and the actions claiming that line is what decides whether it happens.

  • fix: stand a placeholder's fallback down once the image loads Both layers fill the host and the fallback is written after the image, so it covered a picture that had loaded fine. It stands down while an image is present and unbroken, and takes over once the image reports that it failed.

  • fix: drop the inline chip padding from a fenced block's code A fenced block's code is an inline box, so the chip padding carried over from inline code applied at the start of the box alone and indented the block's first line past every line under it. The block's padding is the surrounding pre's.

  • fix: keep a context menu open after the press that asked for it The menu opened and closed again in the same gesture. A popover="auto" surface is dismissed by a press beginning outside it, and the press asking for the menu began before the menu existed — so the browser paired that pointerdown with the pointerup still to come and read the pair as a click away. A pointer's menu now opens on the release, leaving the surface to the next press rather than the one that summoned it. A menu asked for from the keyboard reports no button and has no release to wait for, so it opens where it stands.

  • fix: copy what a field holds, not the markup around it The mixin read its target's text content, and a form control keeps what it shows in its value and has no text of its own — so the pairing a copy button is most often written for, a read-only field beside a button, copied an empty string. An input or a textarea is read from its value.

  • fix: watch a hotkey on the document its host was inserted into The combination was registered on the global document as the mixin was set up, which runs wherever the mixin is evaluated — including where there is no document to watch. It is registered from the host's insertion instead, on that host's own document, so the mixin stays with the one document it belongs to and is inert until it has one.

  • fix: accept a schema whose input is not already a string validate() typed its schema as taking a string, so a schema that parses a field's value into something else — a number, a date, a union — would not type-check against a field whose value is always a string. The input type is open now; what a schema produces is still its own.

  • fix: close the mobile nav drawer with a press outside it The drawer is a modal dialog, which closes on Escape and nothing else, and it renders no control of its own — so on a touch device, where there is no Escape to press, it opened and could not be dismissed. A press outside it now closes it, which is the gesture a drawer is dismissed with. A consumer wanting the drawer held open passes its own closedby.

  • fix: keep the sidebar's header and footer opaque Both slots were transparent, so a rail that holds one of them against the viewport showed the scrolling nav straight through it. Each slot now paints the background it inherits, which is the rail's own while it sits in the docked rail and the drawer's while it sits in the drawer, so the row reads the same either way and nothing passes behind it in view.

@sdxc/user-agent

  • feat: request middleware and predicate helpers Reading the user agent was every caller's own job: parse the header, then match on the parsed shape. Both halves now live in the package.

    userAgent() publishes the parsed agent as ctx.userAgent, declaring the RequestContext augmentation from an imported module so installing the middleware is all a project needs to have the property typed. Its key carries an all-null default, so a surface reached before the middleware runs reads the same keys rather than throwing.

    isApplePlatform, isAndroid, isMobile, isTablet, isDesktop and isTouch each take a parsed agent, a request context, or nothing — reading the current request through the async context in the last case, and answering against the unknown agent outside a request so a script or a test gets an answer rather than an exception.

@sdxc/validate

  • fix: read any +json media type as a JSON body validate() now reads a Request body as JSON when its Content-Type is application/json or carries the RFC 6839 +json structured-syntax suffix, such as application/merge-patch+json or application/problem+json. The media type is compared case-insensitively with parameters ignored. Previously a merge patch request was refused with "Unsupported content-type".

@sdxc/webhooks

Republished because @sdxc/crypto changed.

@sdxc/webmention

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: receive, verify, discover and send Webmentions @sdxc/webmention/receiver reads an endpoint request from already-parsed form data, checks it against an app-supplied accepts predicate and answers 202 or 400 without fetching anything; verify then fetches the source under bounds and reports it as linked (with a sanitized, display-ready mention read from its microformats), gone or unlinked, or as a fetch error saying whether a retry can help. @sdxc/webmention/discover finds a page's endpoint from its Link header or markup, passing all 23 webmention.rocks discovery tests, refuses endpoints on private hosts, and writes the values that advertise your own. @sdxc/webmention/sender lists an entry's outbound links, plans which targets an update or delete notifies (removed links included), and delivers one mention.

  • feat: make the package public @sdxc/webmention now publishes to npm. It reads and answers a Webmention endpoint's request, verifies a source under bounded fetches and summarizes it from its microformats, discovers a target's endpoint and advertises your own, and plans and sends the mentions a post's links call for. The published build leaves out the webmention.rocks discovery fixtures.

@sdxc/websub

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: subscribe to WebSub hubs, verify deliveries and ping hubs as a publisher @sdxc/websub/subscriber builds and sends subscribe and unsubscribe requests (https: hubs only, secrets under 200 bytes), reads the hub's verification of intent for subscribe, unsubscribe and denied, and answers it with acknowledge, refuse or gone (410, which ends the hub's retries). verifyDelivery checks X-Hub-Signature over the raw body for sha1, sha256, sha384 and sha512 in constant time, reading at most 1 MiB by default and reporting why a delivery was refused while the caller answers received() either way. renewalAt computes when to resubscribe from the lease the hub granted.

    @sdxc/websub/publisher writes the Link header that advertises a topic's hubs and sends the hub.mode=publish ping public hubs accept, one hub.url per changed topic. The root export holds the shared SignatureAlgorithm type and the WebSubRequestError, WebSubVerificationError and WebSubSignatureError classes.

  • chore: lock @sdxc/idempotency's dependencies and drop unused ones Records the dependencies @sdxc/idempotency declared, and removes remix from @sdxc/websub and @sdxc/http from @sdxc/security-headers, which neither imports.

  • feat: make the package public @sdxc/websub now publishes to npm. It builds and sends WebSub subscription requests, answers a hub's intent verification, verifies signed deliveries in constant time, schedules lease renewal, and on the publisher side advertises hubs in a Link header and pings them.

@sdxc/well-known

  • chore: scaffold the workspaces ADR-079 to ADR-095 introduce Creates the sixteen private packages with their workspace dependencies, and wires @sdxc/trace-context into api-client and jobs, @sdxc/well-known into auth and @sdxc/crypto into mail, so the packages can be built in parallel without contending over the lockfile.

  • feat: read, write and serve the documents under /.well-known/ Adds typed readers and writers for security.txt (RFC 9116), WebFinger (RFC 7033), OAuth authorization server metadata (RFC 8414), OpenID Connect provider metadata, OAuth protected resource metadata (RFC 9728), JWK Sets, passkey endpoints and the change-password redirect, one subpath each. Fields are camelCase and URL-typed; every parse returns a Result whose WellKnownParseError lists each issue with a JSON Pointer or line number. Discovery parsers check the published issuer or resource against the one requested, and jwks.stringify refuses private key material.

    wellKnownUrl builds a document's URL with the placement its specification fixes, respond() answers with the media type, a cache policy, an ETag, CORS where required and a 304 for a current copy, and the wellKnown() middleware mounts several names on a router. The document subpaths depend only on @sdxc/result, @remix-run/data-schema and @standard-schema/spec.

  • chore: let @sdxc/well-known type-check JWK output in its tests

  • feat: make the package public @sdxc/well-known now publishes to npm. It reads and writes the documents served under /.well-known/ (security.txt, WebFinger, OAuth authorization server and protected resource metadata, OpenID Connect discovery, JWKS, change-password and passkey endpoints), answers them as cacheable responses, and mounts several on a router. @sdxc/auth, which reads discovery documents through it, depends on it.

  • fix: accept interface-typed keys in a JWK Set Jwk lists the members RFC 7517, RFC 7518 and RFC 8037 register instead of an index signature, so a key typed by an interface, such as the output of JWK.toJSON, can be passed to stringify. Unregistered members still pass through at runtime.

  • fix: write REQUIRED metadata lists even when they are empty stringify dropped every empty list, so an RFC 8414 document with responseTypesSupported: [] omitted response_types_supported, and an OpenID provider with an empty subject_types_supported or id_token_signing_alg_values_supported omitted those. Each is REQUIRED, and parse refuses a document missing one, so stringify produced output its own reader rejected. Members the specifications mark REQUIRED are now always written; optional empty lists are still left out.

@sdxc/workers-cache

  • chore: declare the new standards packages where the apps adopt them Apps and packages now list the standards packages (SCIM, merge patch, idempotency, OpenAPI, JSON Schema, DoH, security headers, trace context, well-known, WebSub, robots, microformats, Webmention, structured fields, iCalendar) they are about to adopt. @sdxc/scim no longer depends on @sdxc/problem, and @sdxc/icalendar no longer depends on remix; neither imported them.

  • feat: read RFC 9211 Cache-Status hops with cacheHops cacheHops(response) parses the standard Cache-Status field through @sdxc/structured-fields and returns one CacheHop per cache the response passed through, origin side first, with hit, fwd, fwdStatus, ttl, stored, collapsed, key and detail as that cache sent them. It sits beside cacheStatus, which keeps reading Cloudflare's cf-cache-status. An absent or invalid field reads as an empty list, as RFC 9211 and RFC 9651 have recipients ignore an invalid field.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.23...v2026.9.28

2026.9.23

On GitHub

@sdxc/atom

Republished because @sdxc/xml changed.

@sdxc/auth

  • feat: support a non-JWT access token in RelyingParty OAuth2 states no shape for an access token at all — RFC 9068 is this package's own issuer's choice, not a rule every issuer follows — so a third-party provider's access token is commonly an opaque string, not a JWT. RelyingParty.callback and AuthSession.accessToken both assumed otherwise, unconditionally and uncaught: decoding a real Google, Microsoft, GitLab, LinkedIn or Apple access token this way throws JWTInvalid, since none of them issue one that decodes as a JWT for the scopes an ordinary sign-in requests.

    AccessToken.tryDecode(raw) answers the decoded token or null instead of throwing, for a caller that doesn't already know it's holding a JWT. decode and verify stay strict, since a caller naming either already knows what it has (this package's own resource-server and service-client roles, reading a token this package minted for itself).

    Grant.accessToken and AuthSession#accessToken are now AccessToken | null. Grant gains accessTokenRaw (the token exactly as the issuer answered it, whatever its shape) and expiresAt (from the token response's own expires_in, seconds since the epoch) — both answer the same way whether or not the access token happens to be a JWT, so a caller that only needs to hold the token and know when it expires never has to touch the decoded form. AuthSession's own memoization was fixed alongside this: null now means "decoded and absent," not "not yet decoded," so an opaque access token doesn't re-attempt decoding on every read.

    Updates auth-saas's connection-sign-in.ts (ADR-027) to use the new, non-throwing path: the try/catch that previously turned a JWTInvalid crash into a clean refusal is gone, since there's nothing to catch anymore, and the sealed connection identity's token expiry now reads grant.expiresAt rather than reaching for a JWT claim that may not exist. Real sign-in against every catalog provider — Google, Apple, Microsoft, GitLab and LinkedIn's real, opaque access tokens included — no longer depends on the provider happening to issue a JWT.

  • feat: tenant-scoped management API surface on ManagementClient Extends ManagementClient with a parallel, tenant-scoped surface for a multi-tenant provider's own management API — auth-saas's, whose design names this exact class, configured with a baseUrl and resources for the API's own origin, as its administrative role. The existing single-tenant fetchSubjectById/Subject/SUBJECT_SCHEMA is untouched.

    Every new method calls one of three shared helpers instead of repeating fetch/parse boilerplate: #send (token, request, non-2xx classification), #decode (JSON body against a schema), #call and #page (the whole-response and keyset-list wrappers over both). A non-2xx response declaring application/problem+json decodes into a new ManagementProblem carrying every RFC 9457 field (type, title, status, detail, instance, errors); anything else falls back to the existing flat ManagementError. Options gains apiVersion, sent as X-API-Version on every call, and apiVersionReceived echoes back what the response carried.

    Covers subjects (create, read, update, block, unblock, delete, add/verify/ remove identifier), credentials and sessions (list, revoke one or all, revoke a passkey, force a password reset, reset a second factor), clients (list, register, update, rotate/revoke secret, disable, delete), grants (list, revoke), audit events (a filtered page), and tenants/members/domains (read, list/invite/change-role/remove a member, list/attach a domain, read its verification, update the MFA policy) — every wire shape cross-checked against the real operation it fronts rather than guessed, catching a few real mismatches along the way: verifying an identifier takes no subject id (the ticket alone resolves it), grants and their revocation are subject-scoped rather than tenant-wide, and membership/domain listing answers a plain array since neither is paginated server-side.

    Deliberately not built, because nothing on the other side exists to call: listing subjects, defining a scope or assigning a role, API keys, webhook endpoints, and import/export runs.

    Exports parseLinkHeader/serializeLinkHeader/LinkValue from @sdxc/pagination's public entry (previously internal to link.ts) so this package can read a keyset list's Link header without a second RFC 8288 implementation; @sdxc/auth takes it as a new dependency.

@sdxc/billing

Republished because @sdxc/crypto changed.

@sdxc/crypto

  • feat: TOTP second factor and recovery codes, mechanism (ADR-026 pass 1) The tenant-object side of a second factor, independent of the mailbox that already recovers a password: enrolment, activation, recovery codes, removal, an administrator reset, and trusted devices. Nothing yet demands this factor during sign-in or at the authorization endpoint — that's a second pass, once this lands.

    database/totp.ts holds five tables (totp_enrolments, totp_factors, totp_claims, recovery_codes, trusted_devices) and the operations over them. beginTotpEnrolment mints and seals a secret, answering the otpauth:// URI and setup key once — nothing afterwards ever returns the secret again. activateTotpFactor spends the enrolment unconditionally (a wrong code is a failed setup, not a retryable one) and only on a verified code writes the factor and mints ten recovery codes, replacing any prior factor and clearing every trusted device, since a second authenticator is a second scan of the same QR code rather than a second credential. Recovery codes are single-use, found by digest the way a session token already is; regenerating replaces the whole set rather than leaving a half-old one nobody can reason about. removeTotpFactor requires proof first and refuses outright under a tenant policy of required. resetSecondFactor is the administrator path: strips everything, revokes every session, marks the subject as owing a fresh enrolment, and answers the address to notify — mailing it is left to a Worker-side caller, since a tenant object has no mail client of its own.

    The secret is sealed at rest under an AES-GCM key imported once per isolate and cached, the same lazy-import pattern the DAU cap's own enforcement read already uses. Adds a Base32 codec to @sdxc/crypto (mirroring its existing Base64/Base64Url/Hex classes) since recovery codes need the same alphabet TOTP secrets already use and nothing exported it.

    The replay guard (totp_claims) and the two flows that actually demand this factor mid-sign-in — completeSecondFactor and the authorization endpoint's step-up outcome — are the next pass's job; this one only builds what they'll call.

@sdxc/feed

Republished because @sdxc/atom changed.

@sdxc/http

Republished because @sdxc/crypto changed.

@sdxc/opml

Republished because @sdxc/xml changed.

@sdxc/pagination

  • feat: tenant-scoped management API surface on ManagementClient Extends ManagementClient with a parallel, tenant-scoped surface for a multi-tenant provider's own management API — auth-saas's, whose design names this exact class, configured with a baseUrl and resources for the API's own origin, as its administrative role. The existing single-tenant fetchSubjectById/Subject/SUBJECT_SCHEMA is untouched.

    Every new method calls one of three shared helpers instead of repeating fetch/parse boilerplate: #send (token, request, non-2xx classification), #decode (JSON body against a schema), #call and #page (the whole-response and keyset-list wrappers over both). A non-2xx response declaring application/problem+json decodes into a new ManagementProblem carrying every RFC 9457 field (type, title, status, detail, instance, errors); anything else falls back to the existing flat ManagementError. Options gains apiVersion, sent as X-API-Version on every call, and apiVersionReceived echoes back what the response carried.

    Covers subjects (create, read, update, block, unblock, delete, add/verify/ remove identifier), credentials and sessions (list, revoke one or all, revoke a passkey, force a password reset, reset a second factor), clients (list, register, update, rotate/revoke secret, disable, delete), grants (list, revoke), audit events (a filtered page), and tenants/members/domains (read, list/invite/change-role/remove a member, list/attach a domain, read its verification, update the MFA policy) — every wire shape cross-checked against the real operation it fronts rather than guessed, catching a few real mismatches along the way: verifying an identifier takes no subject id (the ticket alone resolves it), grants and their revocation are subject-scoped rather than tenant-wide, and membership/domain listing answers a plain array since neither is paginated server-side.

    Deliberately not built, because nothing on the other side exists to call: listing subjects, defining a scope or assigning a role, API keys, webhook endpoints, and import/export runs.

    Exports parseLinkHeader/serializeLinkHeader/LinkValue from @sdxc/pagination's public entry (previously internal to link.ts) so this package can read a keyset list's Link header without a second RFC 8288 implementation; @sdxc/auth takes it as a new dependency.

@sdxc/passkey

  • feat: add @sdxc/passkey, WebAuthn for the browser and the server Two entry points over one implementation of WebAuthn Level 3.

    @sdxc/passkey/client reduces a ceremony to one call: Passkey.register, .authenticate and .autofill take the server's JSON options verbatim and return the response as JSON, reporting every outcome as a Result so a dismissed prompt is a value to branch on rather than an exception to catch. Only one ceremony is ever open, so an autofill prompt and a button no longer cancel each other by accident.

    @sdxc/passkey/server is a stateless RelyingParty: register() and authenticate() hand back the challenge to persist alongside the options to send, and the matching verify* call takes the response — or the Request carrying it — and returns the row to store. Each refusal is its own error class, so a caller can tell a replayed challenge from a failed signature.

    Verification runs on WebCrypto alone, so the same code holds on Workers, Node and Deno. Ceremonies are checked against challenge, origin, relying party id, user presence and, when required, user verification; assertions additionally against the stored key and a signature counter that has to advance.

    Safe by default: a ceremony run inside a cross-origin frame is refused unless allowFramed is set, a credential must use an algorithm the relying party offered, and an attestation statement is either verified or refused. A none attestation must carry no statement; a packed self-attestation is verified against the credential's own key, which proves possession at enrollment. A statement carrying a certificate chain is refused rather than accepted unchecked, because a chain means nothing without trust anchors this package does not ship.

    Tests drive both ceremonies with a software authenticator built on WebCrypto, emitting real CBOR and real DER, and cover the refusals each check produces.

@sdxc/rss

Republished because @sdxc/xml changed.

@sdxc/saml

  • feat: SAML 2.0 service provider on Web Crypto Verifies what an identity provider posts, builds the request that asked for it, and reads and writes the metadata the two sides exchange — with no DOM and no Node crypto, so one implementation serves every runtime.

    The shape of the API is the security property. An XML signature covers whatever its reference resolves to, and a reader then goes looking for an assertion by name; every wrapping attack is a document where those are not the same element. So there is no parseResponse, the tree is never returned, and the Assertion constructor is not exported: a verification answers with an assertion built from the element the signature covered, and that is the only place claims are readable from.

    verifyResponse takes every option and defaults none, so a call that forgets the audience does not typecheck and the replay check is always present. Exclusive canonicalization stays unexported beside the verifier, since publishing it would hand a caller the two primitives whose recombination is the attack. SHA-1, inclusive canonicalization, XPath transforms, whole-document references and a second signature are each refused by name, so a tenant reads which part of a provider's configuration to change.

  • feat: publish to npm Drops the private flag, so the next dated release ships the package. The README is rewritten for a reader who can reach only what npm serves: installation, the calls in order of size, every export, and the patterns they combine on.

    @sdxc/saml/testing is now a surface of its own, exporting the fixture builders an integration test needs — a response built and signed as a provider builds one, and an assertion encrypted as a provider encrypts one. buildResponse answers plain tree data and signDocument signs whatever it is handed, so a test can plant a second assertion beside the signed one, or give two elements one id, and still be signing genuinely.

@sdxc/sample

Republished because @sdxc/crypto changed.

@sdxc/sitemap

Republished because @sdxc/xml changed.

@sdxc/spec

Republished because @sdxc/sample changed.

@sdxc/typeid

Republished because @sdxc/uuid changed.

@sdxc/user-agent

  • feat: read a User-Agent string into browser, engine, OS and device One parse(userAgent) answers with the browser and the version it reports, the rendering engine behind it, the operating system in its own numbering, and the device type, vendor and model. Each name is a string union, so a switch over one is exhaustive and an editor lists the options.

    The read is total: anything the rules do not recognize — a crawler, a script, an empty header — comes back as null in that field rather than as an error, since every string is a legal User-Agent and there is nothing to recover from.

@sdxc/uuid

  • fix: add generateUUIDv7, use it for control-plane ids ADR-003 states control-plane ids are "TypeIDs over UUIDv7... they sort by creation because the UUID behind them does," but @sdxc/uuid only ever exported generateUUID (crypto.randomUUID(), version 4 — random in every position, including when it was created). Every control-plane model (customer/tenant/domain/membership) called that one, so none of the ids actually sorted by creation despite the ADR's explicit claim and the schema's PRIMARY KEY (id) ordering depending on it.

    Adds generateUUIDv7 (RFC 9562 §5.7: 48-bit millisecond timestamp, 74 random bits) to @sdxc/uuid and switches the four control-plane models to it. generateUUID stays for ids that must not reveal their creation time, such as ADR-006's subject ids.

@sdxc/webhooks

Republished because @sdxc/crypto changed.

@sdxc/xml

  • feat: parse with the source's own whitespace preserved XML.parse takes an options object whose whitespace mode decides whether whitespace-only text and CDATA reach the tree. The collapsing default is unchanged, so traversal still steps over indentation; preserve keeps every run, which a caller digesting a document needs, since a byte of indentation changes the hash.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.17...v2026.9.23

2026.9.17

On GitHub

@sdxc/auth

Republished because @sdxc/crypto changed.

@sdxc/billing

Republished because @sdxc/crypto changed.

@sdxc/crypto

  • feat: join byte runs without a local helper Every protocol framing a record — a header, an info string, a length prefix — concatenates bytes, and each caller writing that loop again is another place the offset arithmetic can be wrong.

    Parts come in as BinaryLike, so a label spelled as text joins the binary around it and the caller keeps no TextEncoder of its own.

@sdxc/distill

  • refactor: rename @sdxc/readability to say what it does Readability is the name of a measurement — how hard a text is to read — and this package does not measure anything. It finds the article in a page and throws the furniture away, which is what distilling is, and what Chrome's own reader mode has been called all along.

    extract and extractFrom become distill and distillFrom, and the error and namespace names follow them. The "extracted" outcome keeps its spelling: it is a value the article cache has already written, and renaming it would expire live entries to no purpose.

  • chore: publish distill It was marked private, so the bootstrap filtered it out before it ever looked at npm. Nothing holds it back: it reaches only public packages, and finding the article in a page is useful well outside the reader that needed it first.

    It gains the license file every published package carries.

  • docs: write the README for npm rather than for the repo A published README is read by somebody who can reach only the one page npm serves them. It gains how to install it, how versions are numbered and what depending on one promises, and the license and author every other published package states.

    The tips become two patterns that run: caching what comes back, and deciding the article was worth fetching. Related Packages goes, having described a shelf the reader cannot see.

@sdxc/feed

  • feat: bound what a fetch will read and follow A feed address comes from whoever pasted it, so every fetch is untrusted network input. Two bounds were missing: a publisher serving a gigabyte could exhaust an isolate's memory before the parser saw a byte of it, and a redirect chain could spend a caller's whole budget without ever answering.

    The body is now read off a stream and refused the moment it passes the cap, and a Content-Length already past it is refused before a single read. Redirects are walked here rather than by the runtime, counted, and each Location resolved against the URL it came from — which also makes the address a response finally came from a fact this package tracked rather than one the caller infers.

    Both bounds belong here rather than in a caller, because every caller wants them and a cap applied by one is a cap the next one forgets. A refusal carries its own error type, so a caller can tell a document it declined to read from an origin it could not reach.

  • feat: expose the links a document and its response declare One list folding an Atom link element, an RSS atom:link, a JSON Feed's hubs and the response's Link header, so a caller asking which hub a feed names asks once. selectHub ranks the header over the document and takes only an https one.

@sdxc/html

  • feat: extract an article from a page Scores a document's blocks to find the one that carries the article, serializes it back to markup, and reads the metadata a page declares about itself. Sanitization moves into @sdxc/html, where the parser it needs already lives.

  • feat: sanitize against an allowlist that names every element's attributes No attribute is global: each element states the ones it may keep and the schemes each URL among them may use. A style attribute is named nowhere, so a publisher's positioning and background images are gone before any policy has to refuse them.

  • refactor: rename @sdxc/readability to say what it does Readability is the name of a measurement — how hard a text is to read — and this package does not measure anything. It finds the article in a page and throws the furniture away, which is what distilling is, and what Chrome's own reader mode has been called all along.

    extract and extractFrom become distill and distillFrom, and the error and namespace names follow them. The "extracted" outcome keeps its spelling: it is a value the article cache has already written, and renaming it would expire live entries to no purpose.

@sdxc/http

Republished because @sdxc/crypto changed.

@sdxc/mcp

  • feat: let a resource live under a scheme of its own The route matcher takes http and https, so a reader:// address could not be expressed at all. It is carried as the host of an https address while matching and restored when the href is built, which leaves the template a client reads unchanged.

@sdxc/opml

  • feat: read and write the folder a feed sits in An outline nested under another carries its nearest enclosing folder on read, and a document written back groups each folder's feeds under one outline with the unfiled ones after them.

  • chore: publish opml It was marked private, which is why the bootstrap answered that there was nothing else to publish: the filter drops a private package before npm is ever asked about it. It reaches only public packages, and a subscription list is a format other people read and write too.

@sdxc/pagination

Republished because @sdxc/crypto changed.

@sdxc/sample

Republished because @sdxc/crypto changed.

@sdxc/spec

Republished because @sdxc/html changed.

@sdxc/webhooks

Republished because @sdxc/crypto changed.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.16...v2026.9.17

2026.9.16

On GitHub

@sdxc/auth

Republished because @sdxc/crypto changed.

@sdxc/billing

Republished because @sdxc/crypto changed.

@sdxc/cloudflare-mocks

  • fix: refuse the transactions the platform refuses A Durable Object rejects BEGIN, COMMIT, ROLLBACK, SAVEPOINT and RELEASE, and offers atomicity through coalescing every write a turn makes instead. The mock ran all of them, being plain SQLite, so code that could not execute passed its tests — which is how a broken transaction reached a running app.

    The mock now throws the runtime's own message, matching on each statement's leading keyword past comments and quoted text so a migration naming a column "begin" still runs. The driver no longer emits SQL the platform rejects: the token-based protocol cannot be bridged to the synchronous transactionSync the platform offers, so it refuses rather than handing out a scope that silently never rolls back, and it stops advertising savepoints and transactional DDL.

    Both READMEs document what a Durable Object actually gives, and where the turn-scoped guarantee ends.

@sdxc/crypto

  • docs: stop calling scrypt a Web Crypto primitive Password hashing reaches for node:crypto, because scrypt has no Web Crypto equivalent — which the README states plainly and the description contradicted. The description and the package table now carry the exception too.

@sdxc/data-table-sqlstorage

  • fix: refuse the transactions the platform refuses A Durable Object rejects BEGIN, COMMIT, ROLLBACK, SAVEPOINT and RELEASE, and offers atomicity through coalescing every write a turn makes instead. The mock ran all of them, being plain SQLite, so code that could not execute passed its tests — which is how a broken transaction reached a running app.

    The mock now throws the runtime's own message, matching on each statement's leading keyword past comments and quoted text so a migration naming a column "begin" still runs. The driver no longer emits SQL the platform rejects: the token-based protocol cannot be bridged to the synchronous transactionSync the platform offers, so it refuses rather than handing out a scope that silently never rolls back, and it stops advertising savepoints and transactional DDL.

    Both READMEs document what a Durable Object actually gives, and where the turn-scoped guarantee ends.

  • fix: split a script on terminators, not on every semicolon executeScript cut on every ;, so one inside a string literal, a quoted identifier, a line comment or a block comment corrupted the script. A migration adding a trigger half-applied, since a trigger body's own semicolons ended the statement early — at deploy time, against a real database, with an error that named neither the migration nor the cause.

    The scanner tracks quoting, both comment forms and trigger bodies, where CASE nests and END unnests, so a body's semicolons stay inert. A trigger's BEGIN is therefore never read as the transaction control this driver refuses.

    An unterminated literal, identifier, comment or trigger body now throws before anything runs, naming the problem and the line it opened on, rather than applying whichever prefix happened to parse.

@sdxc/feed

  • feat: read JSON Feed alongside RSS and Atom Text that opens a JSON object is parsed as JSON Feed and normalized into the same shape, so a reader handed a feed.json URL needs to know nothing new. Items gain contentText, which holds a plain-text body as plain text, and discovery follows application/feed+json links, preferring them over application/json.

@sdxc/flags

  • docs: correct what ready() returns The README claimed ready() and shutdown() both answer with a Result, but ready() is declared Promise<void> — as the Flags interface printed a few lines above it already showed. setProvider initializes the provider it registers and hands its caller that outcome, so ready() has nothing left to report and awaits the same memoized answer.

@sdxc/flags-engine

Republished because @sdxc/flags changed.

@sdxc/html

  • docs: name fetch in the package description HTML.fetch is a first-class export that performs the HTTP call itself, but the description covered parsing alone. The README heading already said "fetch or parse"; the description and the package table now agree with it.

@sdxc/http

Republished because @sdxc/crypto changed.

@sdxc/jobs

Republished because @sdxc/validate changed.

@sdxc/json-feed

  • feat: read and write JSON Feed 1.1 documents A builder and parser for the JSON syndication format, named as the format names its own fields. Extension objects round-trip untouched, a parsed 1.0 document writes back out as 1.0, and reading is lenient the way the format asks: a field typed the wrong way is skipped, and only an item without an id is discarded.

@sdxc/lazy-route

  • test: cover a stand-in inside a controller A route map whose actions live in different modules can name a loader per action, and createController() takes those as readily as a plain object does, keeping the route map's typing on each one. The chain still runs in order: the controller's own middleware answers before any action's module is imported.

@sdxc/pagination

Republished because @sdxc/crypto changed.

@sdxc/rate-limit

  • docs: give every README example the key it requires key has no default, and the README says so, but the login and fail-closed examples both omitted it and would not have typechecked. Both surfaces are anonymous, so they key on the connecting address the way the earlier example in the same README does.

@sdxc/sample

Republished because @sdxc/crypto changed.

@sdxc/spec

  • docs: correct the workers entry point's capability list src/workers.ts exports seven plugin factories and its own header comment lists all seven; the README named four. The createHttpPlugin example also imported from /workers while the prose around it described the root entry, which both entries export.

@sdxc/validate

  • docs: rewrite the README for the package it documents Two behaviors were missing. input also takes any JSON value, not only a plain object, and a schema that validates the raw FormData/URLSearchParams source rather than a flattened object — remix/data-schema/form-data's object() — has its rejection retried against that source, so it passes through the same call. A reader working from the README alone would not know either worked.

    It was also the last README still written in the internal style, against a guide that asks a published package for the npm reader's version: no route module paths or other vocabulary that means nothing outside a checkout, exports described in a sentence each instead of Parameters/Returns scaffolding, and the Versioning, License and Author trailer every sibling carries.

@sdxc/webhooks

Republished because @sdxc/crypto changed.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.15...v2026.9.16

2026.9.15

On GitHub

@sdxc/atom

Republished because @sdxc/result changed.

@sdxc/auth

Republished because @sdxc/crypto changed.

@sdxc/billing

Republished because @sdxc/crypto changed.

@sdxc/cache

Republished because @sdxc/duration changed.

@sdxc/cron

Republished because @sdxc/duration changed.

@sdxc/crypto

Republished because @sdxc/result changed.

@sdxc/dates

Republished because @sdxc/duration changed.

@sdxc/duration

Republished because @sdxc/result changed.

@sdxc/feed

Republished because @sdxc/atom changed.

@sdxc/flags

  • feat: implement the OpenFeature specification (ADR-059) Add @sdxc/flags, an implementation of the OpenFeature specification v0.9.0 for the dynamic-context paradigm, so a behavior can change without a deploy and the flag system stays a constructor argument.

    The evaluation API is createFlags() rather than a global singleton, which is the specification's own Requirement 1.8 and keeps a Worker isolate from sharing flag state across concurrent requests. Nothing on the evaluation path throws: a failed evaluation answers with the default value it was handed and carries the reason and error code on its detailed form, so a provider outage is distinguishable from a flag being off. Object flags take a schema and are validated rather than cast.

    Two providers ship. NoopProvider answers defaults, and InMemoryProvider is a real provider with lifecycle and events that the specification's own Gherkin suites evaluate against. A conformance suite exports from /conformance so a provider written anywhere runs the same assertions as the two in the box.

    Compliance is a ledger rather than a claim: a test over the vendored specification.json asserts every MUST-class requirement is either covered by a test named for it or declined with the condition that excuses it, so a specification bump fails with the list of new requirements.

    Both middleware subpaths publish one client to a shared context key, so a job handler reads ctx.flags exactly as a route handler does.

    No app adopts the package yet; that waits on a provider against a real flag system.

  • fix: declare the jobs peer as a workspace range The peer was pinned to * because the release pipeline rewrote dependencies alone, so a workspace: range in peerDependencies reached the manifest check and failed the publish. Peers pin the same as dependencies now, so the range is written the way every other workspace range in the repo is and resolves to the dated version at publish time.

@sdxc/flags-engine

  • feat: add the flag evaluation engine (ADR-060) Decides what a flag is worth. @sdxc/flags shipped the API an application evaluates through and the Provider contract a flag system implements, but nothing that resolves a real flag; this is the half that holds the rules, reads the context a request arrives with, and works out which variant a subject gets.

    A flag is variants, an optional default variant, and an ordered rule list where the first match wins. Conditions are a typed union rather than an expression language, so an editor completes the operators and a write can be validated against the same schema the engine parses with. Every operator compares within one type, and a field the caller did not send matches nothing but exists.

    A split buckets on MurmurHash3 of the subject, scaled onto the weights' own sum, so a percentage means the same thing here as in the reference engine: the same subject lands in the same arm on every request and in every isolate, and two flags at one percentage cover different subjects unless they share a seed.

    Evaluation is pure and synchronous, which is what lets a provider, an HTTP endpoint and an admin preview call one function. It never throws and never logs. Every reason the specification names has exactly one cause here, so a consumer reading reason alone tells a flag that is off from a flag system that is broken.

    Definitions arrive through FlagStore, one method answering with the whole set. An in-memory store and a Cloudflare KV store ship, and an application wanting a row per flag writes that store against its own schema and runs the same conformance suite the two shipped ones do. Parsing is per flag, so a mistyped rule fails the flag someone just edited and leaves the rest resolving.

    EngineProvider is the adapter onto @sdxc/flags, and it is the only module here that knows OpenFeature.

@sdxc/highlight

Republished because @sdxc/markdown changed.

@sdxc/html

Republished because @sdxc/result changed.

@sdxc/http

Republished because @sdxc/crypto changed.

@sdxc/jobs

  • refactor!: let the Cloudflare worker own its dead-letter queue deadLetterQueue and onInvalid were dispatcher options, but neither meant anything to a backend that is pulled rather than pushed: one is matched against the queue name a batch arrived on, which only a push backend reports, and the other exists because this platform reaches a dead-letter queue by exhausting retries rather than by being asked, so a refused body has to be written there.

    Both move to cloudflare.worker(dispatcher, options), the consume side that already knows which queue a batch came from. The dispatcher keeps what is its own: it still answers dead-letter for a message it refuses, and still records a dead-lettered batch as a job log that ended dead_letter. It is now told that a batch is dead-lettered instead of deciding it from a name, and apply may return a promise, since forwarding a body is a write that must land before the message is acked.

    The { invalid: … } envelope becomes the contract it always was, at @sdxc/jobs/queue: the adapter writes it, the dispatcher reads it back.

@sdxc/jwt

Republished because @sdxc/duration changed.

@sdxc/mail

Republished because @sdxc/highlight changed.

@sdxc/markdown

Republished because @sdxc/result changed.

@sdxc/mcp

Republished because @sdxc/result changed.

@sdxc/pagination

Republished because @sdxc/crypto changed.

@sdxc/rate-limit

Republished because @sdxc/duration changed.

@sdxc/result

Republished because @sdxc/types changed.

@sdxc/rss

Republished because @sdxc/result changed.

@sdxc/sample

Republished because @sdxc/crypto changed.

@sdxc/semver

  • feat: add SemVer 2.0.0 parsing, ordering and comparisons Answers the version questions this repository kept asking in two private copies: whether one version stands in a named relation to another, and which of a list of versions is the newest.

    parse() is the single grammar gate, accepting SemVer 2.0.0 plus the leading v a git tag or a user agent carries, and reporting anything else as a Result failure naming the text.

    compare() is total, so a list whose entries come straight from a registry sorts in one call: text that is not a version ranks below every version and ties with other such text, collecting those entries at the front.

    satisfies() covers the eight comparisons =, !=, <, <=, >, >=, ~ and ^ without the range grammar. A prerelease takes part by precedence alone, so 1.2.4-rc.1 satisfies ^ 1.2.3; a release channel is expressed by comparing against the prerelease itself.

@sdxc/session-storage-kv

Republished because @sdxc/duration changed.

@sdxc/sitemap

Republished because @sdxc/result changed.

@sdxc/spec

  • fix: read a browser assertion's grammar before spawning the CLI browser.cookie, text, url, path, title, query and fragment parsed their assertion inside observeValue, after the agent-browser call, so a malformed call reported a missing binary instead of the grammar mistake wherever the CLI is not installed.

@sdxc/types

  • feat: add JSONPrimitive, the scalar leaf of JSONValue Names the half of a JSON boundary that holds no other value, so an API that compares, indexes or keys by what it is handed can say so in its signature instead of taking JSONValue and rejecting structures at runtime.

    JSONValue is now written in terms of it, which keeps the two in step: a primitive is a value by construction rather than by a union that repeats the four scalars in both places.

@sdxc/validate

Republished because @sdxc/result changed.

@sdxc/webhooks

Republished because @sdxc/crypto changed.

@sdxc/workers-cache

Republished because @sdxc/result changed.

@sdxc/xml

Republished because @sdxc/result changed.

@sdxc/yaml

Republished because @sdxc/result changed.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.14...v2026.9.15

2026.9.14

On GitHub

@sdxc/api-client

  • chore: make the package public, with a README written for npm

@sdxc/billing

  • chore: make the package public, with a README written for npm

  • fix: write out the exported context key type The declaration emit cannot name ContextKey, which remix/router returns from createContextKey but does not re-export, so the publish build failed with TS2883. ContextKey is structural, so the annotation writes its shape out.

@sdxc/cache

  • chore: make the package public, with a README written for npm

  • fix: declare the result dependency its sources import The index, both adapters and the conformance entry import @sdxc/result, which the manifest omitted. An installed consumer would have failed to resolve it.

@sdxc/cloudflare-mocks

  • chore: make the package public, with a README written for npm

@sdxc/data-table-d1

  • chore: make the package public, with a README written for npm

@sdxc/data-table-sqlstorage

  • chore: make the package public, with a README written for npm Corrects the documented default for savepoints, which the driver enables.

@sdxc/get-client-ip

  • chore: make the package public, with a README written for npm

@sdxc/highlight

  • feat: paint a parsed markdown document through a walk visitor @sdxc/highlight/markdown exports highlight, a Markdown.walk visitor holding one code handler, so a document paints in the pass that walks it. The handler resolves the language the block names, tokenizes its body, and returns a copy of the node carrying both; a block naming no language, as an indented one never does, is painted as plain.

    The tokens field belongs here rather than to the format: the entry declares it on Markdown.Code through module augmentation, so @sdxc/markdown has no dependency on this package and no field of its own that knows fences can be painted.

    Visitors are values and merge by spread, so one walk can paint and rewrite at once. Every handler is synchronous, which keeps a painting pass inside a render path free of an await.

    @sdxc/highlight/markdoc is removed with the parser it adapted to, and the Markdoc dependency goes with it.

  • chore: make the package public, with a README written for npm

  • chore: drop the unused remix dependency Nothing under src imports remix, so the dependency only added install weight for a consumer.

@sdxc/hostname

  • chore: make the package public, with a README written for npm

  • refactor: build requests through @sdxc/api-client The client held its own header, URL-joining and request helpers. It now composes an APIClient that carries the zone token in its before hook. The public API is unchanged.

@sdxc/http

  • chore: make the package public, with a README written for npm The previous description named Request factories the package does not export, and the status-code and content-type references omitted roughly thirty real exports.

@sdxc/icons

  • docs: point the related-packages row at the markdown renderer entry

  • chore: make the package public, with a README written for npm

  • fix: scope the package tsconfig to src The release build compiles a package with src as the rootDir, so the codegen script the include pattern reached failed the build with TS6059. The script keeps its coverage under its own tsconfig in the directory it lives in.

@sdxc/mail

  • refactor: render a markdown document the caller already parsed @sdxc/mail/markdown's Markdown component takes a document rather than a source string, so a caller holding a parsed document pays for no parser at all and the same document can render as a page and as an email.

    Conversion is a switch on node.type the compiler proves exhaustive, which replaces the shape-sniffing the untyped tree needed. That reach extends the coverage an inbox gets: strikethrough, alerts, tables, footnotes, thematic breaks and inline code all render now, and a code block arrives already painted when the caller ran the highlighter.

    Raw HTML renders as escaped text, so markup an author did not vet never reaches an inbox. The package no longer depends on a markdown parser of any kind.

  • chore: make the package public, with a README written for npm

@sdxc/markdown

  • feat: parse and write GitHub Flavored Markdown over a typed AST @sdxc/markdown reads and writes markdown itself, over a first-party AST. Every node is a plain JSON-serializable object with a type discriminator and a position, so a parsed document caches in KV, travels in a payload, diffs in a test, and narrows in the compiler where content used to arrive as unknown.

    Markdown.parse reads the frontmatter block and the body in one traversal, and Markdown.frontmatter stops after the block so an index over a hundred posts reads a hundred titles without parsing a hundred bodies. Markdown.stringify writes a document back, normalized, so a round trip is idempotent and the output is a fixed point of the repository's formatter. Markdown.walk is the one transform mechanism: a visitor keyed by node type, which can be asynchronous, and which turns a handler's throw into a failure carrying the node's position.

    The dialect is everything in CommonMark plus GFM's tables, task lists, strikethrough and literal autolinks, plus GitHub's alerts and footnotes. Two additions sit on top: {% key="value" %} annotations that decorate a block, and registered elements whose children parse as markdown rather than as text.

    Four entry points, each named for what it produces. The root is the format; /plain gives text, /html gives static HTML carrying md- classes to style, and /remix gives remix/ui nodes. @sdxc/markdown/server and @sdxc/markdown/client are gone, and so is MarkdownView — a view calls toRemix and owns the markup around it. The class is a namespace now: its constructor is private and every operation is static, so per-app configuration is an options object the app hoists rather than an instance it builds.

    Conformance is asserted as a floor that can only rise: 648 of 652 CommonMark examples and 658 of 672 GFM ones, the remainder being the divergences ADR-058 records. The format entry weighs 66.2 KB minified against the 181.1 KB of the entry it replaces.

  • chore: make the package public, with a README written for npm Corrects a schema example that called a checks helper the schema package does not export.

@sdxc/mcp

  • chore: make the package public, with a README written for npm

  • fix: write out the exported context key types The declaration emit cannot name ContextKey, which remix/router returns from createContextKey but does not re-export, so the publish build failed with TS2883. ContextKey is structural, so the annotations write its shape out.

@sdxc/pagination

  • chore: make the package public, with a README written for npm

@sdxc/response

  • chore: make the package public, with a README written for npm

@sdxc/seo

  • chore: make the package public, with a README written for npm

@sdxc/server-timing

  • chore: make the package public, with a README written for npm

@sdxc/session-storage-kv

  • chore: make the package public, with a README written for npm Corrects the session middleware example, which named a signature the middleware does not take.

@sdxc/strings

  • chore: make the package public, with a README written for npm

@sdxc/typeid

  • chore: make the package public, with a README written for npm

  • fix: correct the UUID and suffix in the documented examples The encode, decode and fromUUID examples paired a UUID with a suffix that is not its encoding, in both directions. They now use the TypeID specification's own vector.

@sdxc/u

  • chore: make the package public, with a README written for npm

  • fix: name the mixin type the exported functions return The declaration emit wrote the return type of the two functions that return a raw() mixin as a relative path into the u package source, which the release build rejects as reaching outside dist/. The type ships from the u entrypoint now, and the functions that return one write it out.

@sdxc/ui

  • chore: make the package public, with a README written for npm The previous README documented three exports that do not exist, the wrong theme variable contract and an invented radius scale; all three are corrected against the source.

  • fix: name the mixin type the exported functions return The declaration emit wrote the return type of the two functions that return a raw() mixin as a relative path into the u package source, which the release build rejects as reaching outside dist/. The type ships from the u entrypoint now, and the functions that return one write it out.

@sdxc/uuid

  • chore: make the package public, with a README written for npm

@sdxc/webhooks

  • chore: make the package public, with a README written for npm

@sdxc/workers-cache

  • chore: make the package public, with a README written for npm Corrects the documented purge return type, selector field names and policy value.

@sdxc/yaml

  • chore: make the package public, with a README written for npm

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.12...v2026.9.14

2026.9.12

On GitHub

@sdxc/html

  • feat: query a served page by role and accessible name Parse a response body into a document, then address it by role and accessible name, by field name, by table position or by definition term — no browser, and the answers describe the page as served rather than the page after hydration.

    Names match exactly on the whitespace-normalized accessible name, several matches is a failure carrying every candidate with its position, and visibility is markup-level: hidden, aria-hidden, a <template> and an inline display/visibility declaration hide an element, while a stylesheet stays unread.

  • feat: fetch a page, and let every match scope its own lookups HTML.fetch asks for text/html, parses the body when that is what arrived, and reports a rejected request, an error status or another content type as an HTMLFetchError naming what came back — so a login redirect or a JSON error page is reported rather than parsed.

    Every match now carries the same five lookups over its own subtree, so a caller narrows to a form or a panel and reads inside it, where a name only has to be unique within that section. A scoped miss names what the scope holds.

  • chore: make the package public Removes private, adds the description and the license, and marks the row in the root package table, so the daily release publishes @sdxc/html.

  • feat: let a flow read the page it just fetched html, str and spec join the namespaces a flow may use, so a check can assert on the markup a server returned, compose a value into a request, and give each run an identity no earlier run produced. All three compute from their arguments or from the run's own identity, so none reaches the network and none needs a grant or a place in the request budget.

    @sdxc/html describes the nodes it handles in a vocabulary of its own rather than through the ambient DOM globals. The package ships TypeScript, so a consumer compiles it under the consumer's global scope, and a Worker's generated types declare an Element of their own for HTMLRewriter that merges with the DOM's. A declaration file compiles the package under exactly those hostile globals, so the next consumer to differ finds out here.

@sdxc/sitemap

  • feat: read the protocol the package already writes Sitemap.parse takes a parsed XML document and Sitemap.fetch retrieves one, both answering with the same instance append builds, so a consumer that wants the list of pages a site publishes reads it through one import instead of rediscovering which root elements count, that <loc> is absolute, that <lastmod> is W3C Datetime and that <priority> is a closed range.

    The root element decides whether a sitemap arrived, which is also the content check: a not-found template served under a 200 fails naming the root it found, rather than arriving as an empty entry set. A row that carries no usable <loc> is skipped and the document is kept, and a <lastmod>, <changefreq> or <priority> the protocol refuses leaves that field undefined, so a bad field costs a caller the field rather than the other 49,999 rows.

    A <sitemapindex> reads into the same class under a kind of "index", and toString() writes back the document the instance carries, so an index that is read, filtered and re-serialized stays an index.

  • chore: make the package public Removes private, adds the description, and marks the row in the root package table, so the daily release publishes @sdxc/sitemap. Both packages it depends on are already public.

    Rewrites the README for the npm landing page it becomes: a stranger reaching only what npm serves gets the installation line, the two directions as focused examples, the parsing rules the package owns, and every export. The Remix controller walkthrough, the repository links and the tips are gone, and the patterns that stayed are written to stand on their own.

@sdxc/spec

  • feat: implement ADR-018, what a real browser E2E suite needs Commands absorb fixtures, so fixture leaves the language and the keyword stays reserved to name its replacement. A bare identifier handed to a tool now resolves against the tool's descriptor rather than always becoming a word, and the zero-argument rule holds in every expression position, so an identity value composes where a spec actually writes it.

    str.format gives the language string composition as a tool; spec.run_id, spec.attempt and spec.nonce give a suite the one value that must not reproduce across runs, while every sample draw stays deterministic. html reads a served page, and shares one addressing vocabulary with browser so a document is addressed the same way whether or not a browser is needed.

    Named bases resolve relative targets, db takes its own grant and named connections, and the runner gains setup/teardown, skip, retries with a flaky count, and an artifacts directory.

    Three of the ADR's capability questions are answered against the real tools, and two of them came back needing compensation: agent-browser fill moves no range input and fires no change, so browser.fill drives one itself, and a closed <dialog> is hidden by a stylesheet markup cannot see, so a browser lookup reads visibility from the rendered page.

  • docs: record what ADR-018 settled across the spec ADR suite ADR-018 §7 answers the question ADR-008 left open — how execution environments are defined and selected — with the bases key, on "name", and a composition with ADR-007 that keeps configuration from ever implying authority. Both sides now say so.

    ADR-007 gains the db family and the host-fs grant a tool now demands, ADR-013 gains the two config keys that resolve without a grant, ADR-010's absolute-URL and snapshot-ref decisions are marked superseded, ADR-012's DATABASE_URL gating likewise, and ADR-017 records that the zero-argument reading now holds in every expression position. Every original decision stands as the record.

    teardown, a successful setup, and --artifacts gain the acceptance coverage they lacked, and the addressing vocabulary is exported so a third-party plugin can spread the parameter fragments the plugin guide tells it to.

  • fix: follow the spec language through ADR-018 The flow checker walks a spec's AST to derive the hosts a run may reach, so it tracked two language changes. fixture-call is gone with the construct, and a string literal inside an array literal is reachable now, which a URL sitting in one needs or the run is denied at a host the spec plainly names.

  • feat: let a flow read the page it just fetched html, str and spec join the namespaces a flow may use, so a check can assert on the markup a server returned, compose a value into a request, and give each run an identity no earlier run produced. All three compute from their arguments or from the run's own identity, so none reaches the network and none needs a grant or a place in the request budget.

    @sdxc/html describes the nodes it handles in a vocabulary of its own rather than through the ambient DOM globals. The package ships TypeScript, so a consumer compiles it under the consumer's global scope, and a Worker's generated types declare an Element of their own for HTMLRewriter that merges with the DOM's. A declaration file compiles the package under exactly those hostile globals, so the next consumer to differ finds out here.

  • perf: read one document once however many times a test asks A test asserts many times over one response, and parsing is the expensive part of answering, so re-reading the markup per assertion made a page's size cost what the assertions multiplied it to. The plugin keeps the few documents it most recently parsed, keyed by the source that produced them; a lookup never mutates a document, so callers share one safely.

    This matters most where flows are other people's: an uptime check spends one request from its budget and could spend the CPU of twenty parses.

  • perf: hold one parsed document, not several A parsed document runs about twenty-five times the size of its source, so holding four of them could raise a run's peak memory above what a 128 MB isolate has — while holding one never does: the alternative parses the same markup again, which allocates the same document anyway. One entry keeps the whole win for the case that matters, a test asserting many times over one response, and costs nothing in the worst case.

  • feat: let a host cap how much of a response body http reads A parsed document runs about twenty-five times the size of its source, so one oversized body can exhaust a 128 MB isolate. createHttpPlugin now takes the most bytes it will read: a declared content-length past the cap is refused before a byte arrives, and anything else is counted as it streams and cancelled the moment it passes. Measuring a body already read would come too late, since the memory is spent by then.

    The cap is set where the plugin is constructed, so it is the host's policy and no spec can raise it. spec run sets none. An uptime flow reads at most a mebibyte, which covers every page a server renders and leaves the isolate room to parse it.

    The refusal carries its own error type, so a host recognising it reads a field rather than the wording of a message it does not own.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.11...v2026.9.12

2026.9.11

On GitHub

@sdxc/atom

  • feat: open the package for publishing

@sdxc/auth

  • refactor: cache through @sdxc/cache The three issuers construct WorkerKVCache in place of Cache.KVStore. The blog's MCP cache holds one instance for both its helpers, and its JSON.stringify/JSON.parse bracketing goes with the unchecked casts it existed to make: cached() hands its loader straight to fetch, and the tool middleware reads a CallToolResult as one.

    packages/auth asserts Issuer.CacheStore against the Cache interface itself rather than a concrete store, which is the claim that actually matters.

  • feat!: Issuer.CacheStore answers with a Result The cache tier a shared Issuer and ServiceClient read through now returns Result<_, Error> from all three methods, following @sdxc/cache. The error is Error rather than a store's own type, so a store answering with a narrower one still satisfies it and this package depends on no cache.

    No observable behavior changes: a store that fails costs a read of the provider, and a document that cannot be fetched still throws the AuthError it always did, rethrown from the failure's cause.

  • feat!: open the package for publishing, with a keyed login limit The OAuth 2.0 and OpenID Connect client is published to npm as @sdxc/auth, with a LICENSE.md and a README written for a reader who can reach only the npm page. Every package it depends on is published alongside it: @sdxc/catch-response-middleware, @sdxc/location, @sdxc/logger and @sdxc/rate-limit, over the already-published @sdxc/crypto, @sdxc/duration, @sdxc/jwt and @sdxc/result.

    RelyingParty.Options.rateLimit is now a RateLimit, pairing the adapter with a required key(request). It read a Cloudflare header before, which is the one place the client stopped being runtime-neutral: on any other runtime every attempt collapsed into a single shared budget. Only the app knows what a login budget belongs to — the connecting address where the platform reports one, a tenant, a submitted username — so it now says. ServiceClient keeps a bare adapter, since it counts against its own client id.

    BREAKING CHANGE: rateLimit: adapter becomes rateLimit: { adapter, key: (request) => string | Promise<string> }.

  • chore: upgrade Remix to 3.0.0-rc.2 The router now answers a method mismatch with 405 and an Allow header instead of falling through to the default handler, so the HEAD probes against POST-only routes assert 405 and the cross-origin POST to /api/subjects/:subjectId reads as a method refusal.

@sdxc/catch-response-middleware

  • docs: state the throw redirect affordance directly

  • feat: open the package for publishing The middleware that turns a thrown Response into the request's response is published to npm as @sdxc/catch-response-middleware. Its only dependency is remix itself, and its README is rewritten for a reader who can reach only the npm page.

  • chore: upgrade Remix to 3.0.0-rc.2 The router now answers a method mismatch with 405 and an Allow header instead of falling through to the default handler, so the HEAD probes against POST-only routes assert 405 and the cross-origin POST to /api/subjects/:subjectId reads as a method refusal.

@sdxc/cron

  • feat: open the package for publishing Cron schedules, their zone-aware occurrences and their descriptors are published to npm as @sdxc/cron. It reaches only @sdxc/duration and @sdxc/result, both already in the release set, so the set grows by one.

    Its README gains an installation section, and its cross-package links point at npm rather than at repository paths, which resolve to nothing for a reader who arrived at the package page.

@sdxc/crypto

Republished because @sdxc/result changed.

@sdxc/dates

Republished because @sdxc/duration changed.

@sdxc/duration

Republished because @sdxc/result changed.

@sdxc/feed

  • feat: open the package for publishing

@sdxc/i18n

  • chore: upgrade Remix to 3.0.0-rc.2 The router now answers a method mismatch with 405 and an Allow header instead of falling through to the default handler, so the HEAD probes against POST-only routes assert 405 and the cross-origin POST to /api/subjects/:subjectId reads as a method refusal.

@sdxc/jobs

  • feat!: run over a queue backend of your choosing The package read as platform-neutral — nothing in src/ imported a binding — but it was not: the lifecycle ended a delivery by calling ack() and retry() on a Cloudflare message, the dispatcher was entered through a MessageBatch, and cron existed only because the platform fired scheduled with an expression to match. ADR-054 records the design; this is it.

    The lifecycle now decides an ending and answers with a Settlement for someone else to apply, so deliver() and deliverBatch() name no platform and every backend becomes a translation outside that seam. deliverBatch takes an apply callback rather than answering with an array, because a batch settles each delivery as that one finishes and one job crashing must still leave its batch mates acked. JobQueue is the port both adapters implement: @sdxc/jobs/cloudflare for Queues, @sdxc/jobs/memory for tests and for anything running without a platform, and @sdxc/jobs/conformance is the suite that says what a queue is, so a third adapter can prove itself.

    A message now carries { job, body } instead of the job's name mixed into the payload as type. A backend that wants to index, count or route by job can, and input stops being restricted to object schemas. Deliveries are read in both shapes for one deploy, so messages enqueued by the one before it still run; the fallback comes out next.

    Reporting leaves the lifecycle. onEnd(ctx, status) runs once an ending is decided and before the delivery is settled — the barrier a report that must reach a service needs — and whatever it throws is recorded as job.hook_failed rather than sinking work that succeeded. That replaces an instanceof branch deciding on the app's behalf which reporting failures were worth a redelivery. The ping itself is @sdxc/jobs/uptime, a client answering with a Result, wired to nothing.

    monitorId becomes meta, unconstrained and inferred as written, and ctx.of(job) reads a delivery as one job — its parsed input and its meta, or null for another job's delivery — which is what gives a dispatcher-level hook the types a handler already has.

    BREAKING CHANGE: createJobDispatcher takes queue rather than send; dispatcher.queue and dispatcher.scheduled become deliverBatch and tick, reached through cloudflare.worker(dispatcher); job() takes meta rather than monitorId; the uptime option and ctx.monitorId are gone; and the wire format carries an envelope.

  • chore: upgrade Remix to 3.0.0-rc.2 The router now answers a method mismatch with 405 and an Allow header instead of falling through to the default handler, so the HEAD probes against POST-only routes assert 405 and the cross-origin POST to /api/subjects/:subjectId reads as a method refusal.

@sdxc/jwt

Republished because @sdxc/duration changed.

@sdxc/lazy-route

  • chore: upgrade Remix to 3.0.0-rc.2 The router now answers a method mismatch with 405 and an Allow header instead of falling through to the default handler, so the HEAD probes against POST-only routes assert 405 and the cross-origin POST to /api/subjects/:subjectId reads as a method refusal.

@sdxc/location

  • docs: build URLs with typed route helpers in a Remix v3 action

  • feat: open the package for publishing The Location class and its safe-redirect helpers are published to npm as @sdxc/location. It has no dependencies of its own, and its README is rewritten for a reader who can reach only the npm page.

@sdxc/logger

  • feat: open the package for publishing The wide-event logger and its router middleware are published to npm as @sdxc/logger. Its README is rewritten for a reader who can reach only the npm page.

    The exported CurrentLog context key now states its type. remix/router re-exports createContextKey without the ContextKey type it returns, so the inferred type could not be named in a declaration file and the publish build failed with TS2883. Neither bun run typecheck nor the tests catch that, because neither emits declarations.

  • feat: declare remix as an optional peer remix moves from a dependency to an optional peer, matching what @sdxc/auth settled on. Only the ./middleware export reaches remix/router at runtime, so a consumer of the logger alone no longer installs a Remix release candidate to get it, and one that does use the middleware states the version it is on.

    It stays a dev dependency, so the package's own tests and typecheck resolve it exactly as before.

  • chore: upgrade Remix to 3.0.0-rc.2 The router now answers a method mismatch with 405 and an Allow header instead of falling through to the default handler, so the HEAD probes against POST-only routes assert 405 and the cross-origin POST to /api/subjects/:subjectId reads as a method refusal.

@sdxc/rate-limit

  • feat!: require a key, and answer denials from this package key is now required on every registration. There is no default, because what a budget belongs to is the policy: too broad a key lets one caller spend another's budget, and a key the caller controls lets it mint fresh ones. The old default read a Cloudflare header, which quietly collapsed every caller into one bucket on any other runtime.

    tooManyRequests(decision, window, body?, init?) replaces the borrowed JSON helper and is exported. It fixes the status and writes the quota fields the decision supports, and adds no media type of its own, so a limited page answers HTML where a limited API answers JSON. A closed failure policy still refuses with a 429 carrying no quota fields, since an outage made no decision to report.

    Both changes drop a dependency: the package now needs only @sdxc/duration, @sdxc/logger and @sdxc/result. It is published to npm as @sdxc/rate-limit.

    BREAKING CHANGE: rateLimit({ ... }) requires key; pass the derivation the endpoint should count against, returning one shared bucket for a caller it cannot identify.

  • chore: upgrade Remix to 3.0.0-rc.2 The router now answers a method mismatch with 405 and an Allow header instead of falling through to the default handler, so the HEAD probes against POST-only routes assert 405 and the cross-origin POST to /api/subjects/:subjectId reads as a method refusal.

@sdxc/result

Republished because @sdxc/types changed.

@sdxc/rss

  • feat: open the package for publishing

@sdxc/sample

Republished because @sdxc/crypto changed.

@sdxc/spec

Republished because @sdxc/duration changed.

@sdxc/types

  • feat: add JSONSerialized, the shape a value reads back as Types the read side of a JSON boundary in terms of what was written, instead of widening to JSONValue and casting back: it applies toJSON, drops a property JSON cannot write, writes an unwritable array element as null, and keeps a tuple's length.

    It describes the shape rather than the value, so a cycle, NaN, and an inherited property remain the caller's business.

  • feat: add a cache contract with memory and Worker KV adapters Four methods over string keys, with the value's type taken per call, so one instance serves a namespace whatever mix of types goes into it. Values are constrained to JSONSerializable and read back as JSONSerialized<T>, so a hit and a miss answer with the same type and a Date is typed as the string it serializes to.

    MemoryCache holds serialized text against an injectable clock, so a test caches without a KV namespace and expires an entry without waiting for one. WorkerKVCache defers a put to waitUntil when given one, answering from the instance until it lands, so a write is readable as soon as it resolves and two writes to one key land in order.

    Both run the same conformance suite from @sdxc/cache/conformance, the KV one twice -- awaiting each write and deferring it -- so the two modes are held to answering identically. A store that cannot answer reads as a miss and warns on the invocation's log; what a loader throws and a value JSON cannot write reach the caller.

@sdxc/validate

  • docs: validate inside Remix v3 actions and controllers

  • feat: open the package for publishing Standard Schema validation is published to npm as @sdxc/validate. It reaches only @sdxc/result and @sdxc/types, both already in the release set.

    Its installation section now names the package itself before the schema library installed alongside it, and its cross-package links point at npm rather than at repository paths.

  • chore: upgrade Remix to 3.0.0-rc.2 The router now answers a method mismatch with 405 and an Allow header instead of falling through to the default handler, so the HEAD probes against POST-only routes assert 405 and the cross-origin POST to /api/subjects/:subjectId reads as a method refusal.

@sdxc/xml

  • feat: open the package for publishing

  • fix: serialize a whole document through stringify

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.7...v2026.9.11

2026.9.7

On GitHub

@sdxc/lazy-route

  • feat: defer a route's module until a request reaches it lazy(() => import("./bookmarks")) maps a route without importing its controller, so a cold start loads only the modules the routes it actually serves need. The stand-in keeps the module's own type, so params, request context and route/module mismatches are still checked at the map call.

    Works for both handler shapes, since the router picks between them from the map target rather than the module: a single route reads handler, a route map reads actions. The middleware an action or controller declares is assembled after the module loads and runs ahead of the handler, in the order the router would have run it.

    Validating a controller's actions against its route map moves from startup to the first request to that route, since the actions are not there any earlier. See ADR-049.

  • feat: take guards to run ahead of the module's own A stand-in is an object rather than a function, so it cannot be the handler of an outer action object. A composition root that declares a route group's guards at the map call had no way to keep them there.

    lazy(load, middleware) runs those guards before whatever the module declares. They are typed as middleware with no context transform, so a middleware that publishes a context value is rejected: the loaded handler's type is the module's own and cannot grow to know a value declared at the map call.

  • feat: publish to npm Drops private: true, which is what makes a package public, and adds the metadata the guard requires of one: a description, a LICENSE.md, and the ✅ row in the root README package table.

    The README is rewritten to the public-package structure. It had been written for a reader inside the monorepo, with app-relative controller paths and a Tips section; an npm reader can open none of that, so the examples now use generic subjects and the sections follow Installation / Usage / API / Patterns / Versioning.

    No internal dependencies, so nothing else has to open to ship it.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.5...v2026.9.7

2026.9.5

On GitHub

@sdxc/crypto

  • docs: rewrite the README for npm readers Adds installation and license sections, expands each wrapper into the raw WebCrypto call it stands in for, and drops the repository links and the unpublished package the reference pointed at.

  • docs: explain the dated release versioning States that a version is its publish date, that any release may change an export, and that a dependent should pin one exact date.

  • feat: hash passwords with scrypt password.hash now derives with scrypt through node:crypto at ln=15, r=8, p=3 — 32 MiB of scratch memory — and writes $scrypt$ln=15,r=8,p=3$<salt>$<key>. scrypt is memory-hard, so an attacker pays for memory as well as time, which an iteration count alone never buys.

    Hashes in the previous $pbkdf2-sha256$ format no longer verify: verify returns UnsupportedAlgorithmError for them and needsRehash reports true, so a stored value from an earlier release has to be reset.

    Password hashing is the one part of this package that reaches past Web Crypto, which has no memory-hard derivation on any runtime. Node, Bun and Cloudflare Workers each implement node:crypto scrypt natively and produce identical bytes for the same parameters.

@sdxc/dates

  • docs: rewrite the README for npm readers Adds installation and license sections and expands the formatters into the Intl calls they wrap. Corrects a stale endOfDay output and the range the day-bounded query pattern describes, which is closed rather than half-open.

  • docs: explain the dated release versioning States that a version is its publish date, that any release may change an export, and that a dependent should pin one exact date.

@sdxc/duration

  • docs: rewrite the README for npm readers Adds installation and license sections, shows the millisecond arithmetic toMs and toSeconds replace, and documents that an amount may be zero or negative.

  • docs: explain the dated release versioning States that a version is its publish date, that any release may change an export, and that a dependent should pin one exact date.

@sdxc/i18n

  • feat: publish the package to npm The package drops private: true and gains the description npm renders on its page, so a release run builds it and ships it as @sdxc/i18n.

  • docs: rewrite the README for npm readers Adds installation and license sections, names which export comes from which of the three entry points, and gives every pattern its own imports so each one stands alone.

  • docs: explain the dated release versioning States that a version is its publish date, that any release may change an export, and that a dependent should pin one exact date.

@sdxc/jwt

  • docs: rewrite the README for npm readers Adds installation, versioning, license and author sections, documents the KeyStorage contract so a stranger can implement it, and drops the repository links and the unpublished package the reference pointed at.

@sdxc/result

  • docs: rewrite the README for npm readers Adds installation, versioning, license and author sections and expands each helper into the code it replaces. Corrects RetryError, which is returned inside a Failure rather than thrown, and drops the section that imported an unpublished package.

@sdxc/sample

  • docs: rewrite the README for npm readers Adds installation, versioning, license and author sections and compresses the fifteen namespaces to a description and a method list each. Corrects the PersonRecord field list and two generated example outputs against the source.

@sdxc/spec

  • docs: rewrite the README for npm readers Replaces the checkout-local CLI instructions with what an installed consumer runs, and adds installation, versioning, license and author sections. Keeps the language, capability and permission references, and states that the command runs on Bun.

@sdxc/types

  • docs: rewrite the README for npm readers Adds installation, versioning, license and author sections, replaces the internal examples with generic ones, and shows each type beside the longhand it stands in for. Documents JSONValue as a generic bound, which keeps the caller's shape while rejecting what JSON cannot carry.

  • feat: add JSONSerializable for the write side of a JSON boundary JSONValue names what JSON.parse hands back, so it rejects a Date — the round trip returns a string. That left no type for the other direction, where an object standing in for itself through toJSON is exactly what stringify accepts.

    JSONSerializable adds that branch and nothing else, so an API takes it where a value is written and keeps JSONValue where one is read back.

    Splits the package into one module per type, each with its own header and a type-level test beside it. Those tests assert through expectTypeOf, so the typecheck is what enforces them.

Compare: https://github.com/sergiodxa/monorepo/compare/v2026.9.4...v2026.9.5

2026.9.4

On GitHub

@sdxc/crypto

First release.

@sdxc/dates

First release.

@sdxc/duration

First release.

@sdxc/jwt

First release.

@sdxc/result

First release.

@sdxc/sample

First release.

@sdxc/spec

First release.

@sdxc/types

First release.

Written by Sergio Xalambrí. Follow @sergiodxa for new packages, or sponsor the work.