[ Releases ]
Changelog
Every dated release of the collection, newest first, with its notes.
2026.10.15
@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: automade everyprea 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
@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-scanningsuite was run locally againstmain).js/polynomial-redosinpackages/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-sanitizationinapps/sdxc—reachedThroughescaped only$before building a RegExp. Identifiers are now fully escaped and matched with JavaScript identifier edges (so$names still resolve).
Verification
vp test runonpackages/ui/src/animations/transitions.test.tsandapps/sdxc/scripts/catalogue.test.ts— 54 passedbun check:fix— cleanLocal CodeQL
javascript-code-scanningre-run after the fixes — 0 findings outside ignored test/vendor paths
Commits
fix(ui): match slide distances with a linear-time length regexfix(sdxc): escape identifiers before matching them in catalogue reachability
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
@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 asctx.assetsfor the page to pass to its document. The CSP nonce is read through the callback the first timectx.assets.nonceis 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, andprice(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 aremix/data-tableDatabase on the request context under the exportedDatabasekey and asctx.db, with a typed context transform so a router that derives its context from an inline middleware list seesctx.dbtyped. Importing the package declaresdbon RequestContext, which is the property@sdxc/data-model/router'smodels()reads.feat: publish the database on jobs through ./jobs
@sdxc/database-middleware/jobsexportsdatabase(db)as@sdxc/jobsjob middleware, setting the database under the sameDatabasekey asctx.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/jobsis 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
parseQueryreadsorder:relevance,order:ascandorder:descinto a newParsedQuery.order(aSearchOrder,"relevance"by default) instead of a term. The package carries the direction only; the caller decides which columnascanddescsort. An unknown value, a signedorder:, or a declared field or filter namedorderkeeps today's reading.feat: formatQuery writes a parsed query back as canonical text
formatQuery(query, { prefix })writes whatparseQueryread as one canonical text: single spaces,ORbetween alternatives, quotes only where a value needs them, andorder: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")andfg("red")emit those colors instead of throwing or resolvingvar(--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 asshadow("0 1px 2px black")applies as written instead of becoming the md shadow.translucent()paints a see-through background (--ui-bgmixed 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 amixlist likecombine()does.Docs:
type("lg")'s line-height fallback iscalc(1.75 / 1.125), andbackdropDropShadow()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 underpointer: coarse, through the newhitArea()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.Removerenders 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-mdplus--ui-surface-padding, so rows and panel corners stay concentric under any override (surfaceInset(),surfacePadding(),concentricRadius()). Elevated surfaces draw a layered--ui-shadow-ringinstead of a solid border, images get an inset--ui-image-outline, and the reset enables font smoothing.Titles wrap with
text-wrap: balanceand body copy withpretty; counts, readouts, dates, page numbers and key caps use tabular figures, andTable.Column/Table.Celltakenumericfor end-aligned tabular columns. Buttons trim the padding beside a leading or trailing icon.enterExit()gainsblur, separateexitvalues,exitDuration(75% ofdurationby default) andexitEasing;zoom()andslide()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-sizeacross 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
@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.modelsreaches a sibling in the same binding and unit of work, and every member the app supplies, such asjobs, is there too. A lazily loaded entry answersdbandcontextbefore its module loads.Type
modelson the context from an interface of the registry's entries,BoundModels<AppModels>, rather thanBoundRegistry<typeof models>: each entry then resolves when it is read, so a method calling a sibling no longer collapsesctx.modelstoany(TS7022).withContext<Context>()answers acreateModeltyped with a context of your own, for an app that binds several registries; each registry'sbindand middleware require the members its models' context declares.Breaking changes:
upsert(values, { conflict, update })replacesupsert(values, { conflictTarget }). It is oneINSERT … ON CONFLICTstatement with no read before it and runs no model callbacks;updatedefaults to the values without the conflict and key columns andcreated_at, andupdate: {}keeps a conflicting row as it is.A table declaring
primaryKey: ["id"]takes its key as the column's value infind,updateanddelete, asprimaryKey: "id"does.A method or scope named like a model member (
exists,db,join,context…) is now a type error;ReservedNamelists them.
Also:
update(key, {})touchesupdated_atand runs the update callbacks.A bulk
updatefrom a query takestouch: falseandreturning.A database constraint refusal is a
ConstraintViolation, aValidationErrorwithkind("unique", "foreign-key", "not-null", "check") andcolumn../testingaddsoverrideModel(model, build), which replaces members in every binding of a model, per-request ones included, andpublishModels(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 }resolvesupdatedAtto its default name, which the table may not have;upsertandupdate(key, {})wrote it anyway and the statement failed.fix: hand overrideModel's build the original members A replacement that called through to
bound.createat call time reached itself and recursed.buildnow receives a copy holding the original members. Also exportsConstraintKind, and the docs declare awithContextcontext 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
updatediffers 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
updatediffers 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
@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/v4and@sdxc/uuid/v7each exportgenerateUUID(): UUID, so the import path names the version and switching versions changes only that path. The root entry point keeps theUUIDtype,isUUID,assertUUIDand the error classes, and no longer exportsgenerateUUIDorgenerateUUIDv7: importgenerateUUIDfrom@sdxc/uuid/v4for the oldgenerateUUID(), and from@sdxc/uuid/v7for the oldgenerateUUIDv7().The v7 generator is now monotonic: a 12-bit counter in
rand_akeeps values from one process in call order within a millisecond, which matters on Cloudflare Workers whereDate.now()holds still between I/O.
Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.10...v2026.10.11
2026.10.10
@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/encodingexposes 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 thenode:cryptoimport 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
altthat becomes the SVGtitle, the accessible name screen readers announce, over the name atitleoraccTitlestatement gives it. The markdown visitor keeps a mermaid fence's{% alt="…" %}annotation on thediagramtag, 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_TAGfrom@sdxc/markdown/plugin/framesregisters aframetag that holds a region a route renders per request:<frame src="/frames/demo">Loading…</frame>. Itssrcmust be a path on the page's own origin, so a frame pointing anywhere else fails the parse at the tag's line.FrameTagfrom@sdxc/markdown/remixdraws 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.toHTMLandtoPlainTextrender 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 intorelUrls/rels; such keys are now ordinary own entries. Trimming text ande-*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
falsedrops 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:cryptothrough 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 usingsubscribenever 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
@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.
stringifywrites it back with one fixed@context../keysgenerates and imports RSA actor keys../discoverygives the WebFinger self link and handle lookup../responsehandles content negotiation and serving documents, with 410 for a Tombstone../collectionsbuilds collections and their pages../remotefetches 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../inboxverifies incoming activities during the request, accepting RFC 9421 or draft-cavage signatures with a digest.handlethen processes them: follows, undos, creates, deletes, likes, boosts and moves.summarizeturns a reply, like or boost into a Webmention-shaped record../outboxfans 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../memoryand./conformancegive in-memory stores, plus a Vitest suite that checks an app's own stores.
fix: close sender-spoofing, foreign-delete and weak-signature paths
handleignores 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.
receivealso checks the verified signer's host againstblocked, so a key that redirects to a blocked server is refused.deliveralways covers@method,@target-uriandcontent-digest, even when an inbox'sAccept-Signatureasks for less.
fix: confirm handles on the actor's host, and cap Retry-After
lookupused to accept any actor whosepreferredUsernamematched the handle's user, so@alice@evil.comcould resolve to the real alice on another server. Now, unless the handle is alreadypreferredUsername@<actor host>, WebFinger on the actor's host must point back at the same actor, as Mastodon requires.parseRetryAfterclamps 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
fanOutcloses a batch atpageSizedeliveries alone. Keeping each batch under Cloudflare's 256 KBsendBatchlimit is now handled byenqueueManyin@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, withctx.summary()fetch(request)to serve the actor, inbox, outbox, followers and followingrespond(request, document)for content negotiation on the app's own pagespublish(activity),lookup(handle)andverify(request)
Background work runs as one job:
Federation.MESSAGEdescribes every queued message, andprocess(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.ActorKeysandRemoteResolverreplace the key and resolver functions. The vocabulary,stringify, the collection builders andactorLinkstay 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-Digestand theirWant-preferences, plus the RFC 3230Digestheader 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 3230Digestheader is nowmalformed.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
formatRecordDatais the inverse ofparseRecordData: names print absolute, TXT strings and CAA values print quoted with\DDDescapes, 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.CAARecordgainsflags, the octet as published with its reserved bits, and generic CAA data with an empty tag no longer parses.NameNotFoundError.authenticatedreports 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/caaentry reads CAA records the way a certificate authority does (RFC 8659, RFC 8657).parseCaaPropertyreads anissue,issuewildoriodefvalue under the RFC's grammar,findRelevantCaaclimbs from the name toward the TLD to the first RRset it publishes,evaluateCaadecides a request against an RRset with no I/O, so a zone file's records decide the same way, andmayIssuecombines the two.A failing lookup reads as
allowed: false, because a CA refuses on it too; narrow onreason === "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,formatRecordDataandRecordDataErrorare no longer exported here; import them from@sdxc/zone-file.DoH.RecordDataand the per-type field interfaces are nowZoneFiletypes (ZoneFile.RecordData<Type>,ZoneFile.MXData…).DoH.RecordFor<Type>is those fields plusnameandttl, 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 atarget, 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 })answersResult<EPUB, EpubError>, andepub.stream()writes anapplication/epub+zipbody that e-readers, phone reading apps and Send to Kindle open:mimetypestored first, the package document, a navigation document with a nested table of contents and landmarks, an EPUB 2toc.ncx(unlesslegacyNcx: false), an optional cover page, one content document per chapter, and every stylesheet and resource.epub.bytes()collects it, andepub.fileslists 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 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, CSSurl()included),remote-resource(images, stylesheets or fonts from the web),unsupported-mediaandinvalid-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
pathin place ofvalueorvalues, 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 fillpathwhen 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
missingor the operand types inmismatch, 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
pathin 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.
signadds the signature and body digest a request needs, andverifychecks one. Both work with either scheme.verifyrequires 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 structuredSignature-Input,SignatureandAccept-Signaturefields, and the cavageSignatureheader, 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
createdandexpiresas its time only when it covers(created)and(expires). Before, appending an unsignedcreatedto a captured request reset its age, so it could be replayed indefinitely. A covered name that is not a valid header field name now failsverifyasmalformedinstead 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'sdata-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
commentblock that may span lines, blank ones included; inside a line it is an inlinecomment. 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.commentjoinsMarkdown.BlockandMarkdown.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$namereads as the samevariablenode a text hole produces, and nothing in a value executes.Markdown.Attributesis nowRecord<string, Markdown.AttributeValue>.A tag whose attributes hold a variable skips its schema at parse time. The new
@sdxc/markdown/plugin/variablesvisitor fills text holes and attribute values from one set of values, runs the deferred schema withoptions.tags, and fails on a missing name unlessmissing: "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.htmlnames 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 newelementnode whose children are markdown, and toHTML, toRemix and toPlainText draw it as itself.Where an element stands comes from the element:
details,divand other containers are blocks holding blocks,summary/figcaption/dthold a line of inline content, phrasing elements likesup,kbdandaare inline wherever they are written, andbr,img,wbrandhrare 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 takeshtmlto check a URL a variable is filled with, and both renderers drop an unsafe attribute from a hand-built tree.elementjoinsMarkdown.BlockandMarkdown.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, andMarkdown.Variable.namekeeps 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 intomathtags, 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-pushentry with aBrowserPushdestination, built from aWebPushsender 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-textbodyholding the text and fields, the first link asurl, thekeyastag,severityandtimestamp. The text is cut so the payload always fits one push. Thekeyis 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 isgone, a bad subscription isinvalid-destination, unusable VAPID keys areunauthorized.feat: install @sdxc/web-push only when sending browser push
@sdxc/web-pushis now an optional peer dependency instead of a dependency, so an app that never imports@sdxc/messaging/web-pushno longer installs the Web Push, JWT and crypto stack. Apps usingBrowserPushadd@sdxc/web-pushthemselves. The@sdxc/cryptodependency 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_addressandsuppressedare the refusals a visitor can act on). Subscribing an address the list already holds, in any status, succeeds withcreated: falseand leaves the record untouched.@sdxc/newsletter/buttondownpins Buttondown's API version 2026-04-01 and verifiesX-Buttondown-Signaturedeliveries.@sdxc/newsletter/kitsubscribes through a Kit form, resolves tag names to ids, pages by cursor and verifies timestampedX-Kit-Signaturedeliveries, accepting any rotated secret.@sdxc/newsletter/memoryimplements the whole contract in memory, with seeding, scripted faults and signed deliveries for app tests.NewsletterWebhookverifies a delivery, skips event ids a ReplayStore has seen, and answers 503 when a handler throws so the platform redelivers.@sdxc/newsletter/middlewarepublishes the provider ascontext.newsletter, and@sdxc/newsletter/conformanceholds any provider to the contract.
feat: take the visitor IP as an IP from @sdxc/ip
SubscribeInput.ipAddress?: string | nullis nowip?: IP | null, the typectx.ipalready carries, so a provider only ever sends a valid address in its canonical spelling.MemoryNewsletter.ipAddress(email)is renamedip(email)and answers the recordedIP.feat: accept the result of IP.parse as the visitor IP
SubscribeInput.iptakes anIPor theResult<IP, IP.Error>thatIP.parseanswers, so a caller passesIP.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)asattributionandtoMetadataasmetadata, 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 aResult: 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,maskandboostLeveloverride each choice; atoo-longerror reports the bits needed and available. The symbol exposesisDark(x, y), which answersfalsepast the edge.svgPath(symbol, { margin })returns thedandviewBoxfor one SVG path, andQrCodefrom@sdxc/qr/componentrenders 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/resultand runs in Workers, Bun, Node and the browser.feat!: encode through QR.encode
QR.encode(data, options)replacesencodeQr, with the same options and the sameResult<QrSymbol, QrError>. Replace eachencodeQr(...)call withQR.encode(...)and importQRfrom@sdxc/qr.feat!: build SVG path data through QR.toSVGPath
QR.toSVGPath(symbol, { margin })replaces the standalonesvgPathexport, with the same options and the same{ d, viewBox, size }result. Replace eachsvgPath(...)call withQR.toSVGPath(...).feat!: return a QR instance from QR.encode
QR.encodenow answersResult<QR, QrError>, where theQRinstance carriesversion,level,mask,size,isDark(x, y)andtoSVGPath({ margin }). TheQrSymboltype and the staticQR.toSVGPathare gone: type an encoded value asQR, and replaceQR.toSVGPath(symbol, options)withsymbol.toSVGPath(options).QrCode'ssymbolprop takes the instance.feat!: render QrCode from serializable path data
QrCodenow takespath, the plain{ d, viewBox, size }objectqr.toSVGPath()returns, in place of thesymbolinstance, so its props survive serialization into a hydrated component. Themarginprop is gone: pass it totoSVGPath({ margin })instead. Replace<QrCode symbol={qr} margin={2} … />with<QrCode path={qr.toSVGPath({ margin: 2 })} … />.feat!: move QrCode to @sdxc/qr/ui The
remix/componentrenderer now lives at@sdxc/qr/ui, the subpath every package uses for its UI. Replaceimport { QrCode } from "@sdxc/qr/component"withimport { QrCode } from "@sdxc/qr/ui".feat!: narrow QR.encode failures to QrTooLongError and QrOptionsError
QR.encodenow fails withQrTooLongErrororQrOptionsError, both subclasses ofQrError, so checkingerror.code === "too-long"narrows the error anderror.bitsis always present: drop the non-null assertion onbits.instanceof QrErrorstill 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 andrdap.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 anRDAPErrorwith acode,retryableand, for a 429,retryAfterin 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.
WebPushencrypts 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:goneto delete it,unauthorizedto fix the sender's keys,rate-limited/unavailable/timeout/networkto retry.Malformed subscriptions, bad VAPID keys, invalid options and payloads over 3,993 bytes are refused before any request.
SUBSCRIPTION_SCHEMAapplies the same checks at registration: a public https: endpoint, a P-256 point on the curve and a 16-byte auth secret.previouskeeps rotated-out key pairs signing for the subscriptions made under them.@sdxc/web-push/browseraddssubscribe()andisSupported(): 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/nodeinfoadds two formats:nodeInfoLinks, the/.well-known/nodeinfodocument pointing at a server's NodeInfo, andnodeInfo, the document itself. Both work withrespondlike 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 withzip.add(path, source, { modified, comment }), where the source is a string (written as UTF-8), aUint8Arrayor aReadableStream<Uint8Array>, and answers aResult: a name that is empty, absolute, holds a backslash, a control character or an empty,.or..segment, or repeats one already added, fails withinvalid-entry; the 65,536th entry fails withtoo-large.zip.stream()writes the entries in order as aReadableStreamready for aResponse, andzip.bytes()collects it into aResult<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 ascause. 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,$INCLUDEthrough anincludeoption, 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 pastmaxBytes.stringify(zone, { relative })writes records back, absolute by default or relative to the origin, andparse(stringify(zone))yields the same records.The package also owns the RDATA presentation codec:
parseRecordDataandformatRecordData, 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
@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 (brandby 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
@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
createBackoffanswers 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 injectableRandom, so a test passing a seeded stream asserts exact delays. Options are validated once anddelay/atnever 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
remoteIpnow readsCF-Connecting-IPthrough@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
betweenhelper with a seededcreateRandomstream. The corpus is still fixed byCORPUS_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/retrieveis gone; use @sdxc/outbound'scheckUrl,followandreadTextfor a bounded fetch on its own.addressableanddistillnow also refuse URLs carrying credentials and the reserved names .internal, .intranet, .lan, .corp, .private, home.arpa, .test, .invalid and .example. A caller'ssignalis now combined withtimeoutMsrather 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
localhostor.local, or a name with no dot is refused as aFeedFetchErrorbefore a request goes out. Passhosts: "any"to fetch from your own network on purpose. Requests no longer send credentials, and the reportedurlis 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 anIPfrom@sdxc/ipinstead of the raw header text, andnullwhenCF-Connecting-IPis 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/middlewaredefault export parses the header once per request and publishes it asctx.ip: IP | null, withClientIPas 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 aResult<IP, IP.Error>, accepting a bracketed IPv6 host and only canonical dotted-decimal IPv4. AnIPreports itsclassificationagainst one table of the IANA IPv4 and IPv6 special-purpose registries,isPublic, theembeddedIPv4inside an IPv4-mapped, NAT64 or 6to4 address (which classification judges by),network({ v4, v6 })for a rate limit key, valueequals, and RFC 5952 canonical text throughtoString()andtoJSON().IP.Range.parse("10.0.0.0/8")gives a network withcontains, 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
extendsand noimplements; 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
ratefrom a Web Crypto stream instead ofMath.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
checkUrlrefuses 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.followwalks 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: truealso refuses a hop whose DNS answers are not public.readText,readBytesandlimitBodycap anyRequestorResponse, whatever runtime produced it. Every failure is anOutboundErrorwith acode, theurlit concerns, and whether a retry can help.fix: keep the response URL on a body bound to a deadline or cap
followandlimitBodyhand back a response with a new body, which used to leaveresponse.urlempty, 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 withstate()sorestoreRandomresumes it exactly.systemRandom()draws fromcrypto.getRandomValuesfor jitter and sampling,systemSeed()gives a fresh seed to log, andRANDOM_STATE_SCHEMAvalidates a stored snapshot. Every stream offersnext,int,float,bool,pickandshuffle.feat!: move RANDOM_STATE_SCHEMA to @sdxc/random/schema The main entry point now has no dependencies, so a library can accept a
Randomwithout installingremix. ImportRANDOM_STATE_SCHEMAfrom@sdxc/random/schema;remixis 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'sdelaytakes milliseconds or(attempt) => number, whereattemptcounts failures from 1. A function delay ignoresbackoff, so a schedule with a ceiling and jitter, such as one from@sdxc/backoff, plugs in without@sdxc/resultdepending 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,RandomandSeedare 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 openSeededRandom.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
SeededRandomascontext.random, andworkersre-exportsRandom,SeedandSeededRandomfrom@sdxc/random. Run ids draw their suffix from Web Crypto instead ofMath.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.retryablefollows 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
maxBytesis still refused astoo-large, including one that declares a length over the cap, and a body that breaks off is still amismatch.
@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
@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
@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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand 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-schemamoves to^1.0.0.The component runtime moved from
remix/uitoremix/component, along with its subpaths (remix/component/server,remix/component/test, the JSX runtimes), so every import and everyjsxImportSourcenow points there.The headless primitives (
anchor,combobox,listbox,tabs) are no longer part ofremix. They ship in the standalone@remix-run/uipackage, pinned at 0.12.1, which@sdxc/uiand the sdxc app now depend on directly.
Compare: https://github.com/sergiodxa/monorepo/compare/v2026.10.1...v2026.10.2
2026.10.1
@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/locationLocationorFormDatainto nested objects and arrays, validates them against a synchronous Standard Schema, and returns aResult. 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 andtoFormData(value)as a form, files included, in the formparsereads.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,endOfQuarterandaddMonthswork on a stated zone's calendar;addMonthskeeps the wall-clock time and clamps to the target month's last day, so January 31st plus one month is February 28th or 29th.daysInMonthand, on@sdxc/dates/zone,shiftCalendarMonthdo the same on plain calendar days.toDateTimeLocalandparseDateTimeLocalconvert between an instant and an<input type="datetime-local">value read in the reader's zone; parsing reports anInvalidDateTimeLocalErrorfor a value naming a day or time that does not exist.toUnixSecondsandfromUnixSecondsconvert to and from whole epoch seconds, as JWTiatandexpclaims carry them.isValidTimeZonechecks an untrusted zone name before any zone-taking function throwsIntl'sRangeErroron it.
fix: resolve a repeated wall-clock hour east of UTC to its earlier instant
instantFromPartsreturned 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 correctsaddMonthsandparseDateTimeLocallanding 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
issuedAtandnotBeforeas the dates their claims name Theiatandnbfclaims hold seconds since the epoch, and the two getters passed them straight tonew Date(), which reads milliseconds, so a token issued in 2026 reported an issue date in January 1970. Both now convert the seconds the wayexpiresAtalready 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
Elementof their own, HTMLRewriter's, whoseappend,prepend,beforeandaftertake string, stream or Response content. The declaration merges into the DOM'sElementand hides the node-taking methods it inherits fromParentNodeandChildNode, so an app type-checking@sdxc/uialongside those types failed in the select'smirrorSelectedOptionmixin. The mixin now copies the selected option's nodes withappendChild, which the Workers types leave alone, and the package type-checks against the sameElementdeclaration, 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.MobileNavlaid 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
@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
parsereads 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.stringifywrites objects in a declared column order under a header, or arrays of cells as bare records.streamifywrites 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 aReadableStream<Uint8Array>as well as a string and sendstext/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 aContent-Dispositionvalue with an ASCIIfilenamefallback and an RFC 8187filename*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/routerexportsjobEnqueuer(queue), aremix/routermiddleware that publishesctx.jobs(and theJobscontext key) withenqueueandenqueueMany. 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'sJobQueueError, 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
inputandoutputare now@sdxc/json-schemaschemas whose parse yields an object, replacing the hand-written JSON Schema literal. The same schema is published intools/list(input side, named schemas inline, no$schema), parses everytools/call, and typesctx.inputas 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
nullcounts 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 readsRequired.Breaking:
ObjectSchema,PropertySchema,FromSchema,FromObjectSchemaand the other subset types are removed in favour ofToolSchema;Tool.inputSchemais nowTool.input;validateArgumentstakes 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
@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
@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, andtracestatewhen upstream sent vendor entries. Injection runs beforebefore(), so a subclass can still read, replace or delete the headers, and atraceparentpassed ininitis 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
propagateTracefield:"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
Issuertakesdiscovery: "oauth"to read/.well-known/oauth-authorization-serverinstead 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/tenantreadshttps://as.example/.well-known/oauth-authorization-server/tenant). The default stays"openid", so existing issuers read the same document as before, andIssuer.forkeeps one instance per discovery kind.feat: write and read WWW-Authenticate Bearer challenges
@sdxc/auth/bearer-challengewrites the challenge a bearer-token API answers a refusal with and reads it back on the client.stringifytakes the RFC 6750 §3 parameters (realm,scope,error,error_description,error_uri) plus RFC 9728 §5.1'sresource_metadata, quoting and escaping every value.parsereads a header that may list several schemes (Basic realm="x", Bearer error="invalid_token") and returns the Bearer challenges as aResult, with unknown parameters kept inextensionsand a malformed header reported as aChallengeParseError.feat: publish RFC 9728 protected resource metadata and discover APIs from it
ResourceServertakes aresourceidentifier. With it,metadataUrlsays where the API's metadata lives,metadata()builds the document to serve (naming the issuer as the authorization server), andchallenge()writes theWWW-Authenticatevalue for a refusal with theresource_metadatapointer and an optionalrealm. A server given aresourceand noaudienceaccepts tokens bound to that resource; existingaudience-only servers behave as before.@sdxc/auth/protected-resourceis the client side:ProtectedResource.discover(url)andProtectedResource.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 theIssuerfor a listed authorization server through RFC 8414 with an OpenID Connect fallback, andrequiredScopes(response)reads the scopes a403asks for.In
@sdxc/auth/remix/schemes,bearerScheme's rejection now carries the server's challenge, andbearerFailure(api)is arequireAuth({ onFailure })handler that answers a request with no token with a401pointing 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.reasonis 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 andDAY_MS.instantFromPartsresolves 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.robotsnow takes theRobotsFetch.OutcomethatfetchRobotsfrom@sdxc/robots/fetchanswers, and distill evaluates it withisAllowedBy. The package's ownfetchRobots,isAllowed,productToken,robotsUrland theDistill.Robotstype are removed; import them from@sdxc/robotsinstead.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
mayCachereadsX-Robots-Tagper agent, so anoarchivescoped to another crawler no longer stops the article being keptrules match after percent-encoding normalization, and
/robots.txtis 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/icalendarparses and writes RFC 5545 calendars: typedVEVENT,VALARMandVTIMEZONEover a generic component and property layer, soVTODO,VJOURNAL,VFREEBUSYandX-properties survive a round trip verbatim.parseandparseAllfail on structural errors with their line and report missingUID/DTSTAMPor unreadable values as warnings;stringifyfolds at 75 octets without splitting a character, escapes TEXT, caret-encodes parameters (RFC 6868) and writes the RFC 7986 feed fields with theirX-WR-aliases.toInstant,utcandcalendarResponse(atext/calendarresponse, optionally a download) complete the entry point.@sdxc/icalendar/rrulereads 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/timezonebuilds aVTIMEZONEfor an IANA zone fromIntl, one observance per offset change in a span, andTZIDs resolve through a calendar's ownVTIMEZONErules beforeIntl.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/itipsubpath implements RFC 5546 scheduling for events:requestinvites attendees (defaulting unanswered ones to PARTSTAT=NEEDS-ACTION with RSVP),cancelcancels the whole event or uninvites some attendees with SEQUENCE bumped by one,replyanswers for one attendee echoing the request's UID, SEQUENCE and RECURRENCE-ID, andreadReplyreads each attendee's PARTSTAT from a REPLY.nextSequenceapplies the §2.1.4 rule for when a revision bumps SEQUENCE, andcalendarPartreturns the{ method, content, filename? }shape a mailer sends as atext/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/idempotencyimplements the IETF Idempotency-Key draft on the server. Theidempotency()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, whenrequired), a malformed one (400), a retry while the first request still runs (409 withRetry-After), and a key reused with a different payload (422), all as problem details built from the app's own catalog throughIDEMPOTENCY_PROBLEM_ENTRIES.Records live behind the
IdempotencyStorecontract../data-tableclaims 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_SQLfor the host's migration, andpurgeExpiredfor a scheduled sweep../memoryis an in-process store for tests../clientmints keys (generateIdempotencyKey,deriveIdempotencyKey) and sets the header on aRequestInitorRequestwithout 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()andmessageBody()addtraceparent(andtracestatewhen 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 asctx.trace, bound for the whole run, and stamped on the job's log astrace_id,span_id,parent_span_idandtrace_flags, so a request and the jobs it caused share onetrace_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.
JobContextInitgains an optionaltrace, defaulting to the current trace or a new root. Tests that compare a tick's enqueued envelopes by exact equality now see atraceparentmember besidejob.
@sdxc/jsdoc
feat: add @sdxc/jsdoc, JSDoc read into a JSON documentation model
extract(source, options)reads one file's text into aDocModule: 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'sexport … fromlines arrive asreExportsfor the caller to resolve.parseComment,findTag,findTagsandinlineLinksare 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-schemaoffers theremix/data-schemacombinators 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 withmeta({ id })under$defs; a nested schema that cannot describe itself fails with the path where it sitswithJSONSchema(schema, json)pairs a schema built with data-schema directly with a hand-written JSON Schemalazy(get, { id })documents recursion through$ref,variantemitsoneOfwith an OpenAPIdiscriminator, andtransform(fn, output)describes both sides./checksadds keyword-carryingminLength,maxLength,min,max,email,url,pattern,minItemsandmaxItems;./coercedocuments the text a query string may carry
feat: nest any data-schema schema that implements Standard JSON Schema Combinators now accept a
DescribedSchemaas a nested schema: anyremix/data-schemaschema that also implements Standard JSON Schema, not only schemas built by this package. A schema that carries its own~standard.jsonSchemaconverter, such as a shared extension schema from another package, composes intos.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? }) andlist({ 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/alternativepart withContent-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. Settingfilenamealso attaches the calendar asapplication/icsinsidemultipart/mixed.The Cloudflare transport, whose binding has no alternative-part field, sends the calendar as one attachment typed
text/calendar; method=…(named byfilenameorinvite.ics). A method that is not a token, or a filename with quotes, slashes or non-ASCII characters, fails the send with aMailError.
@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/openapidescribes 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 anOperationInputErrorthat 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 timecreateDocument(options).add(...operations).build()assembles the document::parambecomes{param}, named schemas hoist intocomponents.schemas, a problem catalog becomes a response per entry, and problems sharing a status merge withoneOf.scopes()lists every required scope per schemeparseandstringifyread and write documents as JSON or YAML./securitybuilds bearer, API key, OAuth 2.0 and OpenID Connect schemes./routerserves the document as JSON or YAML with a strong ETag and304revalidation, building it on the first request./testingchecks responses against the document:checkResponsereports undocumented operations, statuses, media types and problem types, missing headers and body mismatches, andcreateConformanceRecordercollects 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 includesextensions, 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_SCHEMAalso implements Standard JSON Schema (~standard.jsonSchema), describing theerrorsextension'spointer,codeandmessagemembers. It validates exactly as before and stays aremix/data-schemaschema.
@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/testingis 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.
entriesandapplyturn it into headers, deriving X-Frame-Options fromframeAncestorsand writing HSTS only on https: requests; a header the response set itself wins../cspwrites 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 wherenullremoves a directive../permissions-policywrites and reads the header through the RFC 9651 serializer../reportsreads CSP violation reports in both the Reporting API and the legacyapplication/csp-reportformats../middlewareexposessecurityHeaders(policy), which publishesctx.securityHeaderswith a nonce generated on first read (a response that never reads it advertises none), andsecurityHeadersOverride(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 quoteddescparameter 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.getFieldandsetFieldread and write a field onHeaders: an absent field reads as null, and an empty List or Dictionary deletes the field. Passing a synchronous Standard Schema as the last argument toparseorgetFieldreturns 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/schemaexportssf, a catalog of remix/data-schema schemas over the Structured Field model:integer,decimal,token(optionally narrowed to a list of literals),displayString,bytesanddatefor bare items, anditem,valueandinnerListfor members. They unwrap Token, Decimal and DisplayString to primitives, compose withs.object,s.array,s.unionand checks, and report issues at their path inside the field, sogetField(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 thesf.*schema helpers, with patterns forCache-Status,Idempotency-Keyand writing only the members a caller has.ADR-079 is accepted, and its notes record where the build differs from the proposal:
ValidationErroris re-exported from remix/data-schema, base64 uses the platform's atob/btoa, and schema arguments are typedSF.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.
./traceparentand./tracestateparse and write both headers to the specification: future versions read by the version-00 rules, versionffand 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 forTraceState.set(), and truncation that drops entries over 128 characters first.The root export binds a
TraceContextto 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(), andtraceFields(), the OpenTelemetry-named fields a wide event carries../middlewareexportstrace(), which continues or starts the request's trace, publishes it asctx.trace, binds it forcurrentTrace()and stamps the request's log. Mount it directly afterlog(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, andSelect.Valuecarries it.valueanddefaultValueare now rejected by the prop type. A<select>takes its selection from the option carryingselectedand 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: 0and the openautowere 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.Clearis 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.ButtonandDateRangePicker.Dialogare removed, along with their prop types.CalendarandRangeCalendarremain as standalone components.DatePicker.Groupkeeps 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
AlertDialoghad already widened to for a title and one sentence — so the general surface was narrower than the specialised one built on it.AlertDialoginherits 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-disabledwas dimmed and fully operable — a<summary>has nodisabled, 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 whatdisabledwould 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.Contentreleases 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
Accordionalready had andDisclosuredid 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-gotomeasured the target slide throughoffsetLeft, 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: alwaysclamps 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-gotoinvoker 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()takesoutsideGate, andAttachment.Titleasks 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
nowrapwhile 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
codeis 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 surroundingpre'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
valueand 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 asctx.userAgent, declaring theRequestContextaugmentation 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,isDesktopandisToucheach 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
@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/clientreduces a ceremony to one call:Passkey.register,.authenticateand.autofilltake the server's JSON options verbatim and return the response as JSON, reporting every outcome as aResultso 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/serveris a statelessRelyingParty:register()andauthenticate()hand back the challenge to persist alongside the options to send, and the matchingverify*call takes the response — or theRequestcarrying 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
allowFramedis set, a credential must use an algorithm the relying party offered, and an attestation statement is either verified or refused. Anoneattestation must carry no statement; apackedself-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/testingis 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.buildResponseanswers plain tree data andsignDocumentsigns 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 aswitchover 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
nullin 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
@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
infostring, 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 noTextEncoderof 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.
extractandextractFrombecomedistillanddistillFrom, 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.
selectHubranks 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.
extractandextractFrombecomedistillanddistillFrom, 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
@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()andshutdown()both answer with aResult, butready()is declaredPromise<void>— as theFlagsinterface printed a few lines above it already showed.setProviderinitializes the provider it registers and hands its caller that outcome, soready()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.fetchis 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
keyhas 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.tsexports seven plugin factories and its own header comment lists all seven; the README named four. ThecreateHttpPluginexample also imported from/workerswhile 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.
inputalso takes any JSON value, not only a plain object, and a schema that validates the rawFormData/URLSearchParamssource rather than a flattened object —remix/data-schema/form-data'sobject()— 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
@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 rewrotedependenciesalone, so aworkspace:range inpeerDependenciesreached 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/flagsshipped the API an application evaluates through and theProvidercontract 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
reasonalone 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.EngineProvideris 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
deadLetterQueueandonInvalidwere 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 answersdead-letterfor a message it refuses, and still records a dead-lettered batch as a job log that endeddead_letter. It is now told that a batch is dead-lettered instead of deciding it from a name, andapplymay 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 leadingva git tag or a user agent carries, and reporting anything else as aResultfailure 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, so1.2.4-rc.1satisfies^ 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,queryandfragmentparsed their assertion insideobserveValue, after theagent-browsercall, 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
@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/markdownexportshighlight, aMarkdown.walkvisitor holding onecodehandler, 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
tokensfield belongs here rather than to the format: the entry declares it onMarkdown.Codethrough module augmentation, so@sdxc/markdownhas 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/markdocis 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'sMarkdowncomponent takes adocumentrather 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
switchonnode.typethe 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/markdownreads and writes markdown itself, over a first-party AST. Every node is a plain JSON-serializable object with atypediscriminator and aposition, 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 asunknown.Markdown.parsereads the frontmatter block and the body in one traversal, andMarkdown.frontmatterstops after the block so an index over a hundred posts reads a hundred titles without parsing a hundred bodies.Markdown.stringifywrites a document back, normalized, so a round trip is idempotent and the output is a fixed point of the repository's formatter.Markdown.walkis 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;
/plaingives text,/htmlgives static HTML carryingmd-classes to style, and/remixgivesremix/uinodes.@sdxc/markdown/serverand@sdxc/markdown/clientare gone, and so isMarkdownView— a view callstoRemixand 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
@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 inlinedisplay/visibilitydeclaration hide an element, while a stylesheet stays unread.feat: fetch a page, and let every match scope its own lookups
HTML.fetchasks fortext/html, parses the body when that is what arrived, and reports a rejected request, an error status or another content type as anHTMLFetchErrornaming 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,strandspecjoin 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/htmldescribes 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 anElementof 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.parsetakes a parsed XML document andSitemap.fetchretrieves one, both answering with the same instanceappendbuilds, 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
200fails 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 akindof"index", andtoString()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
fixtureleaves 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.formatgives the language string composition as a tool;spec.run_id,spec.attemptandspec.noncegive a suite the one value that must not reproduce across runs, while everysampledraw stays deterministic.htmlreads a served page, and shares one addressing vocabulary withbrowserso a document is addressed the same way whether or not a browser is needed.Named bases resolve relative targets,
dbtakes its own grant and named connections, and the runner gainssetup/teardown,skip, retries with aflakycount, 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 fillmoves no range input and fires nochange, sobrowser.filldrives 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
baseskey,on "name", and a composition with ADR-007 that keeps configuration from ever implying authority. Both sides now say so.ADR-007 gains the
dbfamily 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'sDATABASE_URLgating 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 successfulsetup, and--artifactsgain 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-callis 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,strandspecjoin 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/htmldescribes 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 anElementof 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.
createHttpPluginnow 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 runsets 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
@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 aLICENSE.mdand 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/loggerand@sdxc/rate-limit, over the already-published@sdxc/crypto,@sdxc/duration,@sdxc/jwtand@sdxc/result.RelyingParty.Options.rateLimitis now aRateLimit, pairing the adapter with a requiredkey(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.ServiceClientkeeps a bare adapter, since it counts against its own client id.BREAKING CHANGE:
rateLimit: adapterbecomesrateLimit: { 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
Responseinto the request's response is published to npm as@sdxc/catch-response-middleware. Its only dependency isremixitself, 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/durationand@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 callingack()andretry()on a Cloudflare message, the dispatcher was entered through aMessageBatch, and cron existed only because the platform firedscheduledwith an expression to match. ADR-054 records the design; this is it.The lifecycle now decides an ending and answers with a
Settlementfor someone else to apply, sodeliver()anddeliverBatch()name no platform and every backend becomes a translation outside that seam.deliverBatchtakes anapplycallback 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.JobQueueis the port both adapters implement:@sdxc/jobs/cloudflarefor Queues,@sdxc/jobs/memoryfor tests and for anything running without a platform, and@sdxc/jobs/conformanceis 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 astype. A backend that wants to index, count or route by job can, andinputstops 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 asjob.hook_failedrather than sinking work that succeeded. That replaces aninstanceofbranch deciding on the app's behalf which reporting failures were worth a redelivery. The ping itself is@sdxc/jobs/uptime, a client answering with aResult, wired to nothing.monitorIdbecomesmeta, unconstrained and inferred as written, andctx.of(job)reads a delivery as one job — its parsed input and its meta, ornullfor another job's delivery — which is what gives a dispatcher-level hook the types a handler already has.BREAKING CHANGE:
createJobDispatchertakesqueuerather thansend;dispatcher.queueanddispatcher.scheduledbecomedeliverBatchandtick, reached throughcloudflare.worker(dispatcher);job()takesmetarather thanmonitorId; theuptimeoption andctx.monitorIdare 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
Locationclass 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
CurrentLogcontext key now states its type.remix/routerre-exportscreateContextKeywithout theContextKeytype it returns, so the inferred type could not be named in a declaration file and the publish build failed with TS2883. Neitherbun run typechecknor the tests catch that, because neither emits declarations.feat: declare remix as an optional peer
remixmoves from a dependency to an optional peer, matching what@sdxc/authsettled on. Only the./middlewareexport reachesremix/routerat 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
keyis 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 a429carrying no quota fields, since an outage made no decision to report.Both changes drop a dependency: the package now needs only
@sdxc/duration,@sdxc/loggerand@sdxc/result. It is published to npm as@sdxc/rate-limit.BREAKING CHANGE:
rateLimit({ ... })requireskey; 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/resultand@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
@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 readsactions. 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
handlerof 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
@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.hashnow derives with scrypt throughnode:cryptoat 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:verifyreturnsUnsupportedAlgorithmErrorfor them andneedsRehashreports 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:cryptoscrypt 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: trueand 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
@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.