@resolute/rio
v0.3.2
Published
Responsive Image Optimizer
Maintainers
Readme
Rio
Rio uploads image bytes to an image service and requests resized variants. The client package provides a standalone transform function and an Astro image service. The included server uses Sharp and Google Cloud Storage on Cloud Run.
Standalone client
npm install @resolute/rioimport { transform } from "@resolute/rio/transform";
const response = await fetch("https://your-site.example/photo.jpg");
const source = new Uint8Array(await response.arrayBuffer());
const result = await transform(
source,
{ width: 800, format: "webp", quality: 82 },
{ endpoint: "https://your-rio-service.example" }
);
// result.data: Uint8Array
// result.format: format reported by the serverThe input can be a Uint8Array, Node Buffer, ArrayBuffer, or another
ArrayBufferView. Fetch URLs or read files before calling transform. Options
are width, height, format, quality, fit, and position. Requesting
format: "svg" returns the input bytes unchanged.
The client hashes the source, checks it with HEAD /{hash}, uploads it with
POST /{hash} when the check is unsuccessful, then requests
GET /{hash}?width=800&format=webp&quality=82. The server verifies uploaded
bytes and caches sources and generated variants in Google Cloud Storage.
Hashes and upload promises are cached by Uint8Array object identity. Treat
inputs as immutable and use separate input objects for separate endpoints. There
is no client filesystem cache.
The npm package targets Node 22+ and the standalone client works in Cloudflare
Workers. A Workers bundler must select the workerd export of xxhash-wasm and
include its Wasm module. The client hashing code does not need Buffer. The
Cloud Run server itself is Node-specific.
Astro
// astro.config.ts
import { defineConfig } from "astro/config";
import rio from "@resolute/rio/astro";
export default defineConfig({
integrations: [
rio({ rio: { endpoint: "https://your-rio-service.example" } }),
],
});This installs @resolute/rio/astro/service as Astro's image service. Use
@resolute/rio/transform for scripts that run outside Astro/Vite.
Development
Use Node 24+ and install both packages to run the complete test suite.
npm ci
npm ci --prefix server
npm run build
npm test
npx tsc --noEmitnpm install and npm ci automatically enable the pre-commit hook in a
repository checkout. If installation scripts are disabled, run
npm run hooks:install manually. The hook runs npm run check and blocks the
commit if formatting fails. Run npm run format, review and stage the changes,
then commit again. The check covers the working tree, including unstaged files.
To start the server:
cd server
BUCKET=your-bucket npm startConfigure Google Cloud credentials before starting the server. It listens on
PORT (default 8080) using cleartext HTTP/2. Run npm run deploy from
server/ only after updating deploy.sh and .env.yaml for your deployment.
The server currently explicitly converts output to AVIF or WebP. Other raster format requests leave Sharp's output format at its default. SVG uploads are rejected.
End-to-end tests
Use Node 24+ and install both packages:
npm ci
npm ci --prefix server
npm run test:e2eTests in test/e2e/*.test.ts start the real Rio HTTP/2 server with in-memory
storage and a local HTTP bridge for the Fetch client. Ports are assigned
automatically and servers are closed afterward. Inputs are small generated PNGs;
Sharp decodes the results and checks their dimensions and format. No Google
credentials are needed for this mode.
To run the same tests against an existing service:
npm run test:e2e:productionThis mode uploads a new synthetic image and creates WebP and AVIF variants in
the service's storage. Those objects are left in its cache. The endpoint can
also point to a staging service using
RIO_E2E_ENDPOINT=https://your-staging-service.example npm run test:e2e. Remote
mode does not start a local server.
See ARCHITECTURE.md for the dependency and packaging rules.
