Skip to content

Releases: alloc/rouzer

v4.0.0

24 May 19:13

Choose a tag to compare

What changed

Rouzer v4 removes the low-level client request descriptor API and makes generated, route-backed clients the only supported client shape.

Breaking changes

  • createClient now requires routes.
    • Use createClient({ baseURL, routes }) and call generated action functions like client.users.get(...).
  • HTTP actions no longer expose nested .request(...) factories.
  • Generated clients no longer expose client.request(...) or client.json(...) helpers.
  • The client metadata property was renamed from config to clientConfig.
    • This allows route trees to define an action named config without being overwritten, so client.config(...) can be a route.

Why

The removed request factory path was ambiguous for nested resources because action-local factories did not carry parent resource paths. Requiring createClient({ routes }) keeps URL construction, validation, response maps, and response plugins on the same generated action path.

Migration

Before:

const client = createClient({ baseURL })
await client.json(routes.getUser.request({ path: { id: '42' } }))

After:

const client = createClient({ baseURL, routes })
await client.getUser({ path: { id: '42' } })

If you read the client options from the returned client, use client.clientConfig instead of client.config.

Full Changelog: v3.2.0...v4.0.0

v3.2.0

24 May 15:05

Choose a tag to compare

Typed response maps.

import { $error, $type } from 'rouzer'
import * as http from 'rouzer/http'

export const getUser = http.get('users/:id', {
  response: {
    200: $type<User>(),
    404: $error<{ code: 'NOT_FOUND'; message: string }>(),
  },
})

Handlers return declared errors with ctx.error:

createRouter().use({ getUser }, {
  getUser(ctx) {
    if (ctx.path.id === 'missing') {
      return ctx.error(404, {
        code: 'NOT_FOUND',
        message: 'User not found',
      })
    }
    return { id: ctx.path.id, name: 'Ada' }
  },
})

Clients get typed tuples instead of thrown errors for declared statuses:

const [error, user, status] = await client.getUser({
  path: { id: 'missing' },
})

Also:

  • Add $error<T>() for declared JSON error responses.
  • Add ctx.success(status, body) for choosing explicit declared success statuses.
  • Support response plugins, including NDJSON, inside response maps.
  • Forward extra RequestInit fields such as signal and credentials from client calls.
  • Add runnable typed error response docs and tests.

Response markers are type contracts. Rouzer does not re-validate handler return values at the server boundary; validate data where it enters your server or client code.

Compare: v3.1.0...v3.2.0

v3.1.0

23 May 02:54

Choose a tag to compare

What's new

  • Added NDJSON response stream support via the new rouzer/ndjson subpath.
    • Define streamed responses with ndjson.$type<T>().
    • Register ndjson.routerPlugin with createRouter(...) and ndjson.clientPlugin with createClient(...).
    • Handlers can return Iterable<T> or AsyncIterable<T> values, which Rouzer serializes as application/x-ndjson.
    • Generated client action functions resolve to an AsyncIterable<T> parsed from the response body.
  • Added response plugin support for non-JSON response codecs, with fast failure when plugin-backed routes are used without the matching router/client plugin.
  • Added a runnable NDJSON streaming example: examples/ndjson-stream.ts.

Notes

  • Streamed NDJSON items are parsed as JSON but are not validated against a Zod schema.
  • NDJSON support is for response streams; request bodies continue to use the existing JSON body schema path.
  • Generated client action functions now follow the documented client.json(...) non-2xx error behavior.

v3.0.0

22 May 20:31

Choose a tag to compare

Update every route map to rouzer/http.

-import { route } from 'rouzer'
+import * as http from 'rouzer/http'

-export const profileRoute = route('profiles/:id', {
-  GET: { response: $type<Profile>() },
-  PATCH: { body: updateProfileSchema, response: $type<Profile>() },
+export const profiles = http.resource('profiles/:id', {
+  get: http.get({ response: $type<Profile>() }),
+  update: http.patch({ body: updateProfileSchema, response: $type<Profile>() }),
 })
 createRouter().use(routes, {
-  profileRoute: {
-    GET(ctx) { ... },
-    PATCH(ctx) { ... },
+  profiles: {
+    get(ctx) { ... },
+    update(ctx) { ... },
   },
 })
-await client.profileRoute.GET({ path: { id: '42' } })
+await client.profiles.get({ path: { id: '42' } })

Also:

  • createClient({ routes }) and createRouter().use(...) now take HTTP action/resource trees.
  • Keep route(...) only for low-level client.request(...) / client.json(...) calls.
  • ALL fallback routes and route-level OPTIONS handlers are removed.
  • @remix-run/route-pattern is upgraded to v0.21.

Docs: https://github.com/alloc/rouzer/blob/v3.0.0/docs/context.md

Compare: v2.0.1...v3.0.0

v2.0.0

19 Mar 18:47

Choose a tag to compare

  • Breaking change: Switch from zod/mini to regular zod