rouzer 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # Rouzer
2
2
 
3
- Rouzer lets you declare a route once and share its TypeScript types and Zod
4
- validation between a Hattip-compatible server and a typed fetch client.
3
+ Rouzer lets you declare an HTTP route tree once and share its TypeScript types
4
+ and Zod validation between a Hattip-compatible server and a typed fetch client.
5
5
 
6
6
  ## What it does
7
7
 
8
- A Rouzer route declaration defines a URL pattern, method schemas, and optional
9
- response type once, then reuses that contract to:
8
+ A Rouzer HTTP route tree defines URL patterns, named actions, method schemas, and
9
+ optional response types once, then reuses that contract to:
10
10
 
11
11
  - validate client arguments before `fetch`
12
12
  - match and validate server requests before handlers run
13
13
  - type handler context from path, query/body, headers, and middleware
14
- - attach typed client shorthand methods such as `client.helloRoute.GET(...)`
14
+ - attach typed client action functions such as `client.profiles.get(...)`
15
15
 
16
16
  Rouzer optimizes for shared TypeScript route modules over language-agnostic API
17
17
  schemas or generated SDKs.
@@ -20,10 +20,10 @@ schemas or generated SDKs.
20
20
 
21
21
  Use Rouzer if:
22
22
 
23
- - your server and client can import the same TypeScript route declarations
23
+ - your server and client can import the same TypeScript route tree
24
24
  - you want Zod request validation on both sides of an HTTP boundary
25
25
  - a Hattip-compatible handler fits your server runtime
26
- - you prefer a small routing/client contract over a full web framework
26
+ - you prefer named resource/action functions over a generated client class
27
27
 
28
28
  Consider something else if:
29
29
 
@@ -41,7 +41,7 @@ Consider something else if:
41
41
  - Zod v4 or newer
42
42
  - a Hattip adapter when using `createRouter(...)`
43
43
  - a Fetch API implementation when using `createClient(...)`
44
- - an absolute `baseURL` for pathname route patterns
44
+ - an absolute `baseURL` for generated client URLs
45
45
 
46
46
  ## Installation
47
47
 
@@ -49,41 +49,40 @@ Consider something else if:
49
49
  pnpm add rouzer zod
50
50
  ```
51
51
 
52
- Import the public API from the root package:
52
+ Import the primary API from the root package and declare routes through the HTTP
53
+ subpath:
53
54
 
54
55
  ```ts
55
- import { $type, chain, createClient, createRouter, route } from 'rouzer'
56
+ import { $type, chain, createClient, createRouter } from 'rouzer'
57
+ import * as http from 'rouzer/http'
56
58
  ```
57
59
 
58
60
  `chain` is re-exported from `alien-middleware` for typed server middleware.
59
61
 
60
62
  ## Quick example
61
63
 
62
- This example shows the core loop: one route contract defines validation, server
63
- handler types, and the typed client call.
64
+ This example shows the core loop: one HTTP action contract defines validation,
65
+ server handler types, and the typed client call.
64
66
 
65
67
  ```ts
66
68
  import * as z from 'zod'
67
- import { $type, createClient, createRouter, route } from 'rouzer'
68
-
69
- export const helloRoute = route('hello/:name', {
70
- GET: {
71
- query: z.object({
72
- excited: z.optional(z.boolean()),
73
- }),
74
- response: $type<{ message: string }>(),
75
- },
69
+ import { $type, createClient, createRouter } from 'rouzer'
70
+ import * as http from 'rouzer/http'
71
+
72
+ export const hello = http.get('hello/:name', {
73
+ query: z.object({
74
+ excited: z.optional(z.boolean()),
75
+ }),
76
+ response: $type<{ message: string }>(),
76
77
  })
77
78
 
78
- export const routes = { helloRoute }
79
+ export const routes = { hello }
79
80
 
80
81
  export const handler = createRouter({ basePath: 'api/' }).use(routes, {
81
- helloRoute: {
82
- GET(ctx) {
83
- return {
84
- message: `Hello, ${ctx.path.name}${ctx.query.excited ? '!' : '.'}`,
85
- }
86
- },
82
+ hello(ctx) {
83
+ return {
84
+ message: `Hello, ${ctx.path.name}${ctx.query.excited ? '!' : '.'}`,
85
+ }
87
86
  },
88
87
  })
89
88
 
@@ -92,20 +91,20 @@ const client = createClient({
92
91
  routes,
93
92
  })
94
93
 
95
- const { message } = await client.helloRoute.GET({
94
+ const { message } = await client.hello({
96
95
  path: { name: 'world' },
97
96
  query: { excited: true },
98
97
  })
99
98
  ```
100
99
 
101
- `handler` can be mounted with any Hattip adapter. Client calls validate route
102
- arguments before `fetch`; server handlers validate matched path, query, headers,
103
- and JSON bodies before your handler runs.
100
+ `handler` can be mounted with any Hattip adapter. Client action calls validate
101
+ route arguments before `fetch`; server handlers validate matched path, query,
102
+ headers, and JSON bodies before your handler runs.
104
103
 
105
104
  ## Documentation
106
105
 
107
- - [Concepts and API selection](docs/context.md)
106
+ - [Concepts, API selection, and v2.0.1 migration notes](docs/context.md)
108
107
  - [Runnable shared-route example](examples/basic-usage.ts)
109
108
  - Generated declarations in the published package provide the exact signatures
110
- for every public export.
109
+ for every public export, including the `rouzer/http` subpath.
111
110
  - Public TSDoc in `src/` owns symbol-level behavior and option details.
@@ -1,18 +1,18 @@
1
1
  import { Promisable } from '../common.js';
2
- import { Route } from '../route.js';
2
+ import type { HttpAction, HttpResource, HttpRouteTree } from '../http.js';
3
3
  import type { InferRouteResponse, RouteArgs, RouteRequest, RouteSchema } from '../types.js';
4
- /** Client type inferred from a route map passed to `createClient`. */
5
- export type RouzerClient<TRoutes extends Record<string, Route> = Record<string, never>> = ReturnType<typeof createClient<TRoutes>>;
4
+ /** Client type inferred from an HTTP route tree passed to `createClient`. */
5
+ export type RouzerClient<TRoutes extends HttpRouteTree = Record<string, never>> = ReturnType<typeof createClient<TRoutes>>;
6
6
  /**
7
- * Create a typed fetch client for Rouzer route declarations.
7
+ * Create a typed fetch client for an HTTP route tree.
8
8
  *
9
9
  * @remarks The returned client always includes `request(...)` for raw responses
10
- * and `json(...)` for parsed JSON. Passing `routes` also attaches shorthand
11
- * methods such as `client.helloRoute.GET(...)`.
10
+ * and `json(...)` for parsed JSON. Passing `routes` also mirrors the resource
11
+ * tree and attaches direct action functions such as `client.users.list(...)`.
12
12
  */
13
- export declare function createClient<TRoutes extends Record<string, Route> = Record<string, never>>(config: {
13
+ export declare function createClient<TRoutes extends HttpRouteTree = Record<string, never>>(config: {
14
14
  /**
15
- * Absolute base URL used for pathname route patterns.
15
+ * Absolute base URL used for generated request URLs.
16
16
  *
17
17
  * @remarks A trailing slash is added when missing. In browsers, derive a
18
18
  * relative API path with `new URL('/api/', window.location.origin).href`.
@@ -26,12 +26,12 @@ export declare function createClient<TRoutes extends Record<string, Route> = Rec
26
26
  */
27
27
  headers?: Record<string, string>;
28
28
  /**
29
- * Route map to attach as shorthand methods on the client.
29
+ * HTTP route tree to attach as direct client action functions.
30
30
  *
31
31
  * @example
32
32
  * ```ts
33
33
  * const client = createClient({ baseURL: 'https://example.com/api/', routes })
34
- * await client.helloRoute.GET({ path: { name: 'world' } })
34
+ * await client.users.list({ query: { page: 1 } })
35
35
  * ```
36
36
  */
37
37
  routes?: TRoutes;
@@ -40,16 +40,14 @@ export declare function createClient<TRoutes extends Record<string, Route> = Rec
40
40
  *
41
41
  * @remarks When provided, the return value is returned from `.json()` as-is;
42
42
  * Rouzer does not automatically parse a `Response` returned by this hook.
43
- * Without this hook, `.json()` throws an `Error` and copies JSON error-body
44
- * properties onto it when the response has a JSON content type.
45
43
  */
46
44
  onJsonError?: (response: Response) => Promisable<Response>;
47
45
  /** Custom `fetch` implementation to use for requests. */
48
46
  fetch?: typeof globalThis.fetch;
49
- }): { [K in keyof TRoutes]: TRoutes[K]["methods"] extends infer TMethods ? { [M in keyof TMethods]: RouteFunction<Extract<TMethods[M], RouteSchema>, TRoutes[K]["path"]["source"]>; } : never; } & {
47
+ }): ClientTree<TRoutes, ""> & {
50
48
  config: {
51
49
  /**
52
- * Absolute base URL used for pathname route patterns.
50
+ * Absolute base URL used for generated request URLs.
53
51
  *
54
52
  * @remarks A trailing slash is added when missing. In browsers, derive a
55
53
  * relative API path with `new URL('/api/', window.location.origin).href`.
@@ -61,41 +59,44 @@ export declare function createClient<TRoutes extends Record<string, Route> = Rec
61
59
  * @remarks Per-request headers are merged on top of these values. Undefined
62
60
  * per-request headers are removed before `fetch`.
63
61
  */
64
- headers?: Record<string, string> | undefined;
62
+ headers?: Record<string, string>;
65
63
  /**
66
- * Route map to attach as shorthand methods on the client.
64
+ * HTTP route tree to attach as direct client action functions.
67
65
  *
68
66
  * @example
69
67
  * ```ts
70
68
  * const client = createClient({ baseURL: 'https://example.com/api/', routes })
71
- * await client.helloRoute.GET({ path: { name: 'world' } })
69
+ * await client.users.list({ query: { page: 1 } })
72
70
  * ```
73
71
  */
74
- routes?: TRoutes | undefined;
72
+ routes?: TRoutes;
75
73
  /**
76
74
  * Custom handler for non-2xx responses from `.json()`.
77
75
  *
78
76
  * @remarks When provided, the return value is returned from `.json()` as-is;
79
77
  * Rouzer does not automatically parse a `Response` returned by this hook.
80
- * Without this hook, `.json()` throws an `Error` and copies JSON error-body
81
- * properties onto it when the response has a JSON content type.
82
78
  */
83
- onJsonError?: ((response: Response) => Promisable<Response>) | undefined;
79
+ onJsonError?: (response: Response) => Promisable<Response>;
84
80
  /** Custom `fetch` implementation to use for requests. */
85
- fetch?: typeof globalThis.fetch | undefined;
81
+ fetch?: typeof globalThis.fetch;
86
82
  };
87
83
  request: <T extends RouteRequest>({ path: pathBuilder, method, args: { path, query, body, headers }, schema, }: T) => Promise<Response & {
88
- json(): Promise<T["$result"]>;
84
+ json(): Promise<T['$result']>;
89
85
  }>;
90
- json: <T extends RouteRequest>(props: T) => Promise<T["$result"]>;
86
+ json: <T extends RouteRequest>(props: T) => Promise<T['$result']>;
87
+ };
88
+ type Join<A extends string, B extends string> = A extends '' ? B : B extends '' ? A : `${A}/${B}`;
89
+ /** Client object shape produced from an HTTP route tree. */
90
+ export type ClientTree<T extends HttpRouteTree, TPrefix extends string = ''> = {
91
+ [K in keyof T]: T[K] extends HttpResource<infer P, infer C> ? ClientTree<C, Join<TPrefix, P>> : T[K] extends HttpAction<infer P, infer S, any> ? RouteFunction<S, Join<TPrefix, P>> : never;
91
92
  };
92
93
  /**
93
- * Shorthand client method attached for each route method when `routes` is passed
94
- * to `createClient`.
94
+ * Client action function attached for each HTTP action leaf.
95
95
  *
96
- * @remarks Methods whose schema has `response: $type<T>()` return parsed JSON as
97
- * `T`. Methods without a response marker return the raw `Response`.
96
+ * @remarks Actions whose schema has `response: $type<T>()` return parsed JSON
97
+ * as `T`. Actions without a response marker return the raw `Response`.
98
98
  */
99
99
  export type RouteFunction<T extends RouteSchema, P extends string> = (...p: RouteArgs<T, P> extends infer TArgs ? {} extends TArgs ? [args?: TArgs] : [args: TArgs] : never) => Promise<T extends {
100
100
  response: any;
101
101
  } ? InferRouteResponse<T> : Response>;
102
+ export {};
@@ -1,10 +1,12 @@
1
- import { mapValues, shake } from '../common.js';
1
+ import { RoutePattern } from '@remix-run/route-pattern';
2
+ import { createHref } from '@remix-run/route-pattern/href';
3
+ import { shake } from '../common.js';
2
4
  /**
3
- * Create a typed fetch client for Rouzer route declarations.
5
+ * Create a typed fetch client for an HTTP route tree.
4
6
  *
5
7
  * @remarks The returned client always includes `request(...)` for raw responses
6
- * and `json(...)` for parsed JSON. Passing `routes` also attaches shorthand
7
- * methods such as `client.helloRoute.GET(...)`.
8
+ * and `json(...)` for parsed JSON. Passing `routes` also mirrors the resource
9
+ * tree and attaches direct action functions such as `client.users.list(...)`.
8
10
  */
9
11
  export function createClient(config) {
10
12
  const baseURL = config.baseURL.replace(/\/?$/, '/');
@@ -15,13 +17,13 @@ export function createClient(config) {
15
17
  path = schema.path.parse(path);
16
18
  }
17
19
  let url;
18
- const href = pathBuilder.href(path);
20
+ const href = createHref(pathBuilder, path);
19
21
  if (href[0] === '/') {
20
22
  url = new URL(baseURL);
21
23
  url.pathname += href.slice(1);
22
24
  }
23
25
  else {
24
- url = new URL(href);
26
+ url = new URL(href, baseURL);
25
27
  }
26
28
  if (schema.query) {
27
29
  query = schema.query.parse(query ?? {});
@@ -57,7 +59,7 @@ export function createClient(config) {
57
59
  if (config.onJsonError) {
58
60
  return config.onJsonError(response);
59
61
  }
60
- const error = new Error(`Request to ${props.method} ${props.path.href(props.args.path)} failed with status ${response.status}`);
62
+ const error = new Error(`Request to ${props.method} ${createHref(props.path, props.args.path)} failed with status ${response.status}`);
61
63
  const contentType = response.headers.get('content-type');
62
64
  if (contentType?.includes('application/json')) {
63
65
  Object.assign(error, await response.json());
@@ -68,19 +70,35 @@ export function createClient(config) {
68
70
  }
69
71
  return {
70
72
  ...(config.routes
71
- ? mapValues(config.routes, route => connectRoute(route, request, json))
73
+ ? connectTree(config.routes, '', request, json)
72
74
  : null),
73
75
  config,
74
76
  request,
75
77
  json,
76
78
  };
77
79
  }
78
- function connectRoute(route, request, json) {
79
- return {
80
- ...route,
81
- ...mapValues(route.methods, (schema, key) => {
82
- const fetch = schema.response ? json : request;
83
- return (args) => fetch(route[key](args));
84
- }),
85
- };
80
+ function connectTree(tree, prefix, request, json) {
81
+ return Object.fromEntries(Object.entries(tree).map(([key, node]) => {
82
+ if (node.kind === 'resource') {
83
+ return [
84
+ key,
85
+ connectTree(node.children, joinPaths(prefix, node.path.source), request, json),
86
+ ];
87
+ }
88
+ const path = RoutePattern.parse(joinPaths(prefix, node.path?.source ?? ''));
89
+ const fetch = node.schema.response ? json : request;
90
+ return [
91
+ key,
92
+ (args = {}) => fetch({
93
+ schema: node.schema,
94
+ path,
95
+ method: node.method,
96
+ args,
97
+ $result: undefined,
98
+ }),
99
+ ];
100
+ }));
101
+ }
102
+ function joinPaths(left, right) {
103
+ return [left, right].filter(Boolean).join('/').replace(/\/+/g, '/');
86
104
  }
package/dist/http.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { RoutePattern } from '@remix-run/route-pattern';
2
+ import type { RouteRequestFactory, RouteSchema } from './types.js';
3
+ /** HTTP methods supported by Rouzer action declarations. */
4
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
5
+ /**
6
+ * Callable endpoint leaf in an HTTP route tree.
7
+ *
8
+ * @remarks Actions declare one HTTP operation. Their property name becomes the
9
+ * client/handler name, while their optional `path` contributes URL segments.
10
+ */
11
+ export type HttpAction<P extends string = string, T extends RouteSchema = RouteSchema, M extends HttpMethod = HttpMethod> = {
12
+ /** Discriminator used internally when traversing route trees. */
13
+ kind: 'action';
14
+ /** Optional action-local path segment appended after parent resources. */
15
+ path?: RoutePattern<P>;
16
+ /** HTTP method used when the client sends this action. */
17
+ method: M;
18
+ /** Request validation and optional response type schema. */
19
+ schema: T;
20
+ /** Low-level request descriptor factory for this action. */
21
+ request: RouteRequestFactory<T, P>;
22
+ };
23
+ /**
24
+ * Path-scoped namespace in an HTTP route tree.
25
+ *
26
+ * @remarks Resources contribute URL path segments and contain child resources or
27
+ * actions. They do not have handlers of their own.
28
+ */
29
+ export type HttpResource<P extends string = string, TChildren extends HttpRouteTree = HttpRouteTree> = {
30
+ /** Discriminator used internally when traversing route trees. */
31
+ kind: 'resource';
32
+ /** Path segment contributed by this resource. */
33
+ path: RoutePattern<P>;
34
+ /** Child resources and actions exposed below this resource. */
35
+ children: TChildren;
36
+ };
37
+ /** Node type accepted inside an HTTP route tree. */
38
+ export type HttpNode = HttpAction | HttpResource;
39
+ /** Route tree accepted by HTTP clients and routers. */
40
+ export type HttpRouteTree = {
41
+ [key: string]: HttpNode;
42
+ };
43
+ /**
44
+ * Declare an HTTP resource namespace.
45
+ *
46
+ * @remarks The resource `path` is joined with any parent resource path. Child
47
+ * property names are API names only; they do not affect the URL unless the child
48
+ * is another resource or an action with an explicit path.
49
+ */
50
+ export declare function resource<const P extends string, const TChildren extends HttpRouteTree>(path: P, children: TChildren): HttpResource<P, TChildren>;
51
+ /** Declare a GET action, optionally with an action-local path segment. */
52
+ export declare function get<const P extends string, const T extends RouteSchema>(path: P, schema: T): HttpAction<P, T, 'GET'>;
53
+ export declare function get<const T extends RouteSchema>(schema: T): HttpAction<'', T, 'GET'>;
54
+ /** Declare a POST action, optionally with an action-local path segment. */
55
+ export declare function post<const P extends string, const T extends RouteSchema>(path: P, schema: T): HttpAction<P, T, 'POST'>;
56
+ export declare function post<const T extends RouteSchema>(schema: T): HttpAction<'', T, 'POST'>;
57
+ /** Declare a PUT action, optionally with an action-local path segment. */
58
+ export declare function put<const P extends string, const T extends RouteSchema>(path: P, schema: T): HttpAction<P, T, 'PUT'>;
59
+ export declare function put<const T extends RouteSchema>(schema: T): HttpAction<'', T, 'PUT'>;
60
+ /** Declare a PATCH action, optionally with an action-local path segment. */
61
+ export declare function patch<const P extends string, const T extends RouteSchema>(path: P, schema: T): HttpAction<P, T, 'PATCH'>;
62
+ export declare function patch<const T extends RouteSchema>(schema: T): HttpAction<'', T, 'PATCH'>;
63
+ /** Declare a DELETE action, optionally with an action-local path segment. */
64
+ export declare function del<const P extends string, const T extends RouteSchema>(path: P, schema: T): HttpAction<P, T, 'DELETE'>;
65
+ export declare function del<const T extends RouteSchema>(schema: T): HttpAction<'', T, 'DELETE'>;
66
+ /** Alias for `del(...)`, exported for namespace imports as `http.delete(...)`. */
67
+ export { del as delete };
package/dist/http.js ADDED
@@ -0,0 +1,44 @@
1
+ import { RoutePattern } from '@remix-run/route-pattern';
2
+ /**
3
+ * Declare an HTTP resource namespace.
4
+ *
5
+ * @remarks The resource `path` is joined with any parent resource path. Child
6
+ * property names are API names only; they do not affect the URL unless the child
7
+ * is another resource or an action with an explicit path.
8
+ */
9
+ export function resource(path, children) {
10
+ return {
11
+ kind: 'resource',
12
+ path: RoutePattern.parse(path),
13
+ children,
14
+ };
15
+ }
16
+ export function get(pathOrSchema, schema) {
17
+ return action('GET', pathOrSchema, schema);
18
+ }
19
+ export function post(pathOrSchema, schema) {
20
+ return action('POST', pathOrSchema, schema);
21
+ }
22
+ export function put(pathOrSchema, schema) {
23
+ return action('PUT', pathOrSchema, schema);
24
+ }
25
+ export function patch(pathOrSchema, schema) {
26
+ return action('PATCH', pathOrSchema, schema);
27
+ }
28
+ export function del(pathOrSchema, schema) {
29
+ return action('DELETE', pathOrSchema, schema);
30
+ }
31
+ /** Alias for `del(...)`, exported for namespace imports as `http.delete(...)`. */
32
+ export { del as delete };
33
+ function action(method, pathOrSchema, schema) {
34
+ const path = typeof pathOrSchema === 'string' ? RoutePattern.parse(pathOrSchema) : undefined;
35
+ schema ??= typeof pathOrSchema === 'string' ? {} : pathOrSchema;
36
+ const request = ((args = {}) => ({
37
+ schema,
38
+ path: path ?? RoutePattern.parse(''),
39
+ method,
40
+ args,
41
+ $result: undefined,
42
+ }));
43
+ return { kind: 'action', path, method, schema, request };
44
+ }
package/dist/route.d.ts CHANGED
@@ -5,15 +5,16 @@ import type { RouteRequestFactory, RouteSchema, RouteSchemaMap } from './types.j
5
5
  * Create a compile-time-only marker for a route's JSON response payload type.
6
6
  *
7
7
  * @remarks `$type<T>()` does not perform runtime validation. It lets Rouzer type
8
- * server handler return values and client shorthand methods for routes whose
8
+ * server handler return values and client action functions for routes whose
9
9
  * responses are expected to be JSON.
10
10
  *
11
11
  * @example
12
12
  * ```ts
13
- * const helloRoute = route('hello/:name', {
14
- * GET: {
15
- * response: $type<{ message: string }>(),
16
- * },
13
+ * import { $type } from 'rouzer'
14
+ * import * as http from 'rouzer/http'
15
+ *
16
+ * const hello = http.get('hello/:name', {
17
+ * response: $type<{ message: string }>(),
17
18
  * })
18
19
  * ```
19
20
  */
@@ -22,15 +23,17 @@ export declare namespace $type {
22
23
  var symbol: symbol;
23
24
  }
24
25
  /**
25
- * Shared route declaration produced by `route(...)`.
26
+ * Low-level route declaration produced by `route(...)`.
26
27
  *
27
28
  * @remarks A `Route` stores the parsed URL pattern, the method schema map, and a
28
- * request factory for each declared method. Pass route maps to both
29
- * `createRouter().use(...)` and `createClient({ routes })` to share the same
30
- * contract on both sides of an HTTP boundary.
29
+ * request factory for each declared method. Use those factories with
30
+ * `client.request(...)` or `client.json(...)` when you need explicit response
31
+ * handling. For shared server/client route trees, prefer `rouzer/http` actions
32
+ * and resources; `createRouter().use(...)` and `createClient({ routes })` expect
33
+ * that HTTP route tree shape.
31
34
  */
32
35
  export type Route<P extends string = string, T extends RouteSchemaMap = RouteSchemaMap> = {
33
- /** Parsed route pattern used for URL generation and server-side matching. */
36
+ /** Parsed route pattern used for request URL generation. */
34
37
  path: RoutePattern<P>;
35
38
  /** Method schemas declared for this route. */
36
39
  methods: T;
@@ -40,10 +43,14 @@ export type Route<P extends string = string, T extends RouteSchemaMap = RouteSch
40
43
  /**
41
44
  * Declare one URL pattern and its supported HTTP method schemas.
42
45
  *
46
+ * @remarks This helper creates low-level request descriptor factories. Prefer
47
+ * `rouzer/http` action helpers for routes that will be registered with
48
+ * `createRouter().use(...)` or mirrored by `createClient({ routes })`.
49
+ *
43
50
  * @param pattern Route pattern parsed by `@remix-run/route-pattern`.
44
51
  * @param methods Method schemas that describe request validation and optional
45
52
  * response typing.
46
- * @returns A shared route declaration with request factories such as `.GET(...)`
47
- * and `.POST(...)` for the declared methods.
53
+ * @returns A route declaration with request factories such as `.GET(...)` and
54
+ * `.POST(...)` for the declared methods.
48
55
  */
49
56
  export declare function route<P extends string, T extends RouteSchemaMap>(pattern: P, methods: T): Route<P, T>;
package/dist/route.js CHANGED
@@ -4,15 +4,16 @@ import { mapEntries } from './common.js';
4
4
  * Create a compile-time-only marker for a route's JSON response payload type.
5
5
  *
6
6
  * @remarks `$type<T>()` does not perform runtime validation. It lets Rouzer type
7
- * server handler return values and client shorthand methods for routes whose
7
+ * server handler return values and client action functions for routes whose
8
8
  * responses are expected to be JSON.
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * const helloRoute = route('hello/:name', {
13
- * GET: {
14
- * response: $type<{ message: string }>(),
15
- * },
12
+ * import { $type } from 'rouzer'
13
+ * import * as http from 'rouzer/http'
14
+ *
15
+ * const hello = http.get('hello/:name', {
16
+ * response: $type<{ message: string }>(),
16
17
  * })
17
18
  * ```
18
19
  */
@@ -23,14 +24,18 @@ $type.symbol = Symbol();
23
24
  /**
24
25
  * Declare one URL pattern and its supported HTTP method schemas.
25
26
  *
27
+ * @remarks This helper creates low-level request descriptor factories. Prefer
28
+ * `rouzer/http` action helpers for routes that will be registered with
29
+ * `createRouter().use(...)` or mirrored by `createClient({ routes })`.
30
+ *
26
31
  * @param pattern Route pattern parsed by `@remix-run/route-pattern`.
27
32
  * @param methods Method schemas that describe request validation and optional
28
33
  * response typing.
29
- * @returns A shared route declaration with request factories such as `.GET(...)`
30
- * and `.POST(...)` for the declared methods.
34
+ * @returns A route declaration with request factories such as `.GET(...)` and
35
+ * `.POST(...)` for the declared methods.
31
36
  */
32
37
  export function route(pattern, methods) {
33
- const path = new RoutePattern(pattern);
38
+ const path = RoutePattern.parse(pattern);
34
39
  const createFetch = (method, schema) => (args = {}) => {
35
40
  return {
36
41
  schema,
@@ -1,6 +1,6 @@
1
1
  import type { HattipHandler } from '@hattip/core';
2
2
  import { ApplyMiddleware, chain, ExtractMiddleware, MiddlewareChain, MiddlewareTypes } from 'alien-middleware';
3
- import type { Routes } from '../types.js';
3
+ import type { HttpRouteTree } from '../http.js';
4
4
  import type { RouteRequestHandlerMap } from './types.js';
5
5
  export { chain };
6
6
  /** Configuration for `createRouter`. */
@@ -54,11 +54,15 @@ export interface Router<T extends MiddlewareTypes = any> extends HattipHandler<T
54
54
  */
55
55
  use<const TMiddleware extends ExtractMiddleware<this>>(middleware: TMiddleware | null): Router<ApplyMiddleware<this, TMiddleware>>;
56
56
  /**
57
- * Clone this router and add the given routes and handlers to the chain.
57
+ * Clone this router and add the given HTTP route tree and handlers to the
58
+ * chain.
59
+ *
60
+ * @remarks The handler object mirrors the resource tree. Resource nodes are
61
+ * nested objects, and action nodes are direct handler functions.
58
62
  *
59
63
  * @returns a new `Router` instance.
60
64
  */
61
- use<TRoutes extends Routes>(routes: TRoutes, handlers: RouteRequestHandlerMap<TRoutes, this>): Router<T>;
65
+ use<TRoutes extends HttpRouteTree>(routes: TRoutes, handlers: RouteRequestHandlerMap<TRoutes, this>): Router<T>;
62
66
  }
63
67
  /**
64
68
  * Create a Rouzer router that can be mounted by any Hattip adapter.