rouzer 1.0.0-beta.14 → 1.0.0-beta.16
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/dist/auth.d.ts +1 -0
- package/dist/auth.js +1 -0
- package/dist/client/index.d.ts +29 -5
- package/dist/client/index.js +63 -49
- package/dist/route.d.ts +6 -3
- package/dist/route.js +6 -3
- package/dist/server/router.d.ts +3 -3
- package/dist/server/router.js +1 -1
- package/dist/types.d.ts +19 -19
- package/package.json +1 -1
- package/readme.md +29 -1
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { Route } from '../route.js';
|
|
2
|
+
import type { InferRouteResponse, Promisable, RouteArgs, RouteRequest, RouteSchema } from '../types.js';
|
|
3
|
+
export type RouzerClient<TRoutes extends Record<string, Route> = Record<string, never>> = ReturnType<typeof createClient<TRoutes>>;
|
|
4
|
+
export declare function createClient<TRoutes extends Record<string, Route> = Record<string, never>>(config: {
|
|
3
5
|
/**
|
|
4
6
|
* Base URL to use for all requests.
|
|
5
7
|
*/
|
|
@@ -8,12 +10,21 @@ export declare function createClient(config: {
|
|
|
8
10
|
* Default headers to send with every request.
|
|
9
11
|
*/
|
|
10
12
|
headers?: Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* Pass in routes to attach them as methods on the client.
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const client = createClient({ baseURL: '/api/', routes: { helloRoute } })
|
|
18
|
+
* client.helloRoute.GET({ path: { name: 'world' } })
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
routes?: TRoutes;
|
|
11
22
|
/**
|
|
12
23
|
* Custom handler for non-200 response to a `.json()` request. By default, the
|
|
13
24
|
* response is always parsed as JSON, regardless of the HTTP status code.
|
|
14
25
|
*/
|
|
15
26
|
onJsonError?: (response: Response) => Promisable<Response>;
|
|
16
|
-
}): {
|
|
27
|
+
}): { [K in keyof TRoutes]: TRoutes[K]["methods"] extends infer TMethods ? { [M in keyof TMethods]: RouteRequestFunction<Extract<TMethods[M], RouteSchema>>; } : never; } & {
|
|
17
28
|
config: {
|
|
18
29
|
/**
|
|
19
30
|
* Base URL to use for all requests.
|
|
@@ -23,14 +34,27 @@ export declare function createClient(config: {
|
|
|
23
34
|
* Default headers to send with every request.
|
|
24
35
|
*/
|
|
25
36
|
headers?: Record<string, string> | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Pass in routes to attach them as methods on the client.
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const client = createClient({ baseURL: '/api/', routes: { helloRoute } })
|
|
42
|
+
* client.helloRoute.GET({ path: { name: 'world' } })
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
routes?: TRoutes | undefined;
|
|
26
46
|
/**
|
|
27
47
|
* Custom handler for non-200 response to a `.json()` request. By default, the
|
|
28
48
|
* response is always parsed as JSON, regardless of the HTTP status code.
|
|
29
49
|
*/
|
|
30
50
|
onJsonError?: ((response: Response) => Promisable<Response>) | undefined;
|
|
31
51
|
};
|
|
32
|
-
request<T extends RouteRequest>({ path: pathBuilder, method, args: { path, query, body, headers },
|
|
52
|
+
request: <T extends RouteRequest>({ path: pathBuilder, method, args: { path, query, body, headers }, schema, }: T) => Promise<Response & {
|
|
33
53
|
json(): Promise<T["$result"]>;
|
|
34
54
|
}>;
|
|
35
|
-
json<T extends RouteRequest>(
|
|
55
|
+
json: <T extends RouteRequest>(props: T) => Promise<T["$result"]>;
|
|
36
56
|
};
|
|
57
|
+
type RouteRequestFunction<T extends RouteSchema> = (args: RouteArgs<T>) => Promise<T extends {
|
|
58
|
+
response: any;
|
|
59
|
+
} ? InferRouteResponse<T> : Response>;
|
|
60
|
+
export {};
|
package/dist/client/index.js
CHANGED
|
@@ -1,55 +1,69 @@
|
|
|
1
|
-
import { shake } from '../common.js';
|
|
1
|
+
import { mapValues, shake } from '../common.js';
|
|
2
2
|
export function createClient(config) {
|
|
3
3
|
const baseURL = config.baseURL.replace(/\/$/, '');
|
|
4
|
+
async function request({ path: pathBuilder, method, args: { path, query, body, headers }, schema, }) {
|
|
5
|
+
if (schema.path) {
|
|
6
|
+
path = schema.path.parse(path);
|
|
7
|
+
}
|
|
8
|
+
let url;
|
|
9
|
+
const href = pathBuilder.href(path);
|
|
10
|
+
if (href[0] === '/') {
|
|
11
|
+
url = new URL(baseURL);
|
|
12
|
+
url.pathname += pathBuilder.href(path);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
url = new URL(href);
|
|
16
|
+
}
|
|
17
|
+
if (schema.query) {
|
|
18
|
+
query = schema.query.parse(query ?? {});
|
|
19
|
+
url.search = new URLSearchParams(query).toString();
|
|
20
|
+
}
|
|
21
|
+
else if (query) {
|
|
22
|
+
throw new Error('Unexpected query parameters');
|
|
23
|
+
}
|
|
24
|
+
if (schema.body) {
|
|
25
|
+
body = schema.body.parse(body !== undefined ? body : {});
|
|
26
|
+
}
|
|
27
|
+
else if (body !== undefined) {
|
|
28
|
+
throw new Error('Unexpected body');
|
|
29
|
+
}
|
|
30
|
+
if (config.headers || headers) {
|
|
31
|
+
headers = {
|
|
32
|
+
...config.headers,
|
|
33
|
+
...(headers && shake(headers)),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (schema.headers) {
|
|
37
|
+
headers = schema.headers.parse(headers);
|
|
38
|
+
}
|
|
39
|
+
return fetch(url, {
|
|
40
|
+
method,
|
|
41
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
42
|
+
headers: headers,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function json(props) {
|
|
46
|
+
const response = await request(props);
|
|
47
|
+
if (!response.ok && config.onJsonError) {
|
|
48
|
+
return config.onJsonError(response);
|
|
49
|
+
}
|
|
50
|
+
return response.json();
|
|
51
|
+
}
|
|
4
52
|
return {
|
|
53
|
+
...(config.routes
|
|
54
|
+
? mapValues(config.routes, route => connectRoute(route, request, json))
|
|
55
|
+
: null),
|
|
5
56
|
config,
|
|
6
|
-
request
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
url = new URL(href);
|
|
18
|
-
}
|
|
19
|
-
if (route.query) {
|
|
20
|
-
query = route.query.parse(query ?? {});
|
|
21
|
-
url.search = new URLSearchParams(query).toString();
|
|
22
|
-
}
|
|
23
|
-
else if (query) {
|
|
24
|
-
throw new Error('Unexpected query parameters');
|
|
25
|
-
}
|
|
26
|
-
if (route.body) {
|
|
27
|
-
body = route.body.parse(body !== undefined ? body : {});
|
|
28
|
-
}
|
|
29
|
-
else if (body !== undefined) {
|
|
30
|
-
throw new Error('Unexpected body');
|
|
31
|
-
}
|
|
32
|
-
if (config.headers || headers) {
|
|
33
|
-
headers = {
|
|
34
|
-
...config.headers,
|
|
35
|
-
...(headers && shake(headers)),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
if (route.headers) {
|
|
39
|
-
headers = route.headers.parse(headers);
|
|
40
|
-
}
|
|
41
|
-
return fetch(url, {
|
|
42
|
-
method,
|
|
43
|
-
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
44
|
-
headers: headers,
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
async json(request) {
|
|
48
|
-
const response = await this.request(request);
|
|
49
|
-
if (!response.ok && config.onJsonError) {
|
|
50
|
-
return config.onJsonError(response);
|
|
51
|
-
}
|
|
52
|
-
return response.json();
|
|
53
|
-
},
|
|
57
|
+
request,
|
|
58
|
+
json,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function connectRoute(route, request, json) {
|
|
62
|
+
return {
|
|
63
|
+
...route,
|
|
64
|
+
...mapValues(route.methods, (schema, key) => {
|
|
65
|
+
const fetch = schema.response ? json : request;
|
|
66
|
+
return (args) => fetch(route[key](args));
|
|
67
|
+
}),
|
|
54
68
|
};
|
|
55
69
|
}
|
package/dist/route.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { RoutePattern } from '@remix-run/route-pattern';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RouteFunction, RouteSchema, RouteSchemaMap, Unchecked } from './types.js';
|
|
3
3
|
export declare function $type<T>(): Unchecked<T>;
|
|
4
|
-
export
|
|
4
|
+
export type Route<P extends string = string, T extends RouteSchemaMap = RouteSchemaMap> = {
|
|
5
5
|
path: RoutePattern<P>;
|
|
6
6
|
methods: T;
|
|
7
|
-
} & {
|
|
7
|
+
} & {
|
|
8
|
+
[K in keyof T]: RouteFunction<Extract<T[K], RouteSchema>>;
|
|
9
|
+
};
|
|
10
|
+
export declare function route<P extends string, T extends RouteSchemaMap>(pattern: P, methods: T): Route<P, T>;
|
package/dist/route.js
CHANGED
|
@@ -5,14 +5,17 @@ export function $type() {
|
|
|
5
5
|
}
|
|
6
6
|
export function route(pattern, methods) {
|
|
7
7
|
const path = new RoutePattern(pattern);
|
|
8
|
-
const createFetch = (method,
|
|
8
|
+
const createFetch = (method, schema) => (args) => {
|
|
9
9
|
return {
|
|
10
|
-
|
|
10
|
+
schema,
|
|
11
11
|
path,
|
|
12
12
|
method,
|
|
13
13
|
args,
|
|
14
14
|
$result: undefined,
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
|
-
return Object.assign({ path, methods }, mapEntries(methods, (method,
|
|
17
|
+
return Object.assign({ path, methods }, mapEntries(methods, (method, schema) => [
|
|
18
|
+
method,
|
|
19
|
+
createFetch(method, schema),
|
|
20
|
+
]));
|
|
18
21
|
}
|
package/dist/server/router.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Params } from '@remix-run/route-pattern';
|
|
2
2
|
import { chain, MiddlewareChain, type HattipContext, type MiddlewareContext } from 'alien-middleware';
|
|
3
3
|
import * as z from 'zod/mini';
|
|
4
|
-
import type { InferRouteResponse,
|
|
4
|
+
import type { InferRouteResponse, MutationRouteSchema, Promisable, QueryRouteSchema, Routes } from '../types.js';
|
|
5
5
|
export { chain };
|
|
6
6
|
type EmptyMiddlewareChain<TPlatform = unknown> = MiddlewareChain<{
|
|
7
7
|
initial: {
|
|
@@ -82,13 +82,13 @@ interface CreateRouterConfig<TRoutes extends Routes, TMiddleware extends Middlew
|
|
|
82
82
|
routes: TRoutes;
|
|
83
83
|
middlewares?: TMiddleware;
|
|
84
84
|
}
|
|
85
|
-
export declare function createRouter<TRoutes extends Routes, TMiddleware extends MiddlewareChain = EmptyMiddlewareChain>(config: CreateRouterConfig<TRoutes, TMiddleware>): (handlers: { [K in keyof TRoutes]: { [M in keyof TRoutes[K]["methods"]]: TRoutes[K]["methods"][M] extends infer T ? T extends TRoutes[K]["methods"][M] ? T extends
|
|
85
|
+
export declare function createRouter<TRoutes extends Routes, TMiddleware extends MiddlewareChain = EmptyMiddlewareChain>(config: CreateRouterConfig<TRoutes, TMiddleware>): (handlers: { [K in keyof TRoutes]: { [M in keyof TRoutes[K]["methods"]]: TRoutes[K]["methods"][M] extends infer T ? T extends TRoutes[K]["methods"][M] ? T extends QueryRouteSchema ? (context: MiddlewareContext<TMiddleware> & {
|
|
86
86
|
path: T extends {
|
|
87
87
|
path: any;
|
|
88
88
|
} ? z.infer<T["path"]> : Params<TRoutes[K]["path"]["source"]>;
|
|
89
89
|
query: z.infer<T["query"]>;
|
|
90
90
|
headers: z.infer<T["headers"]>;
|
|
91
|
-
}) => Promisable<Response | InferRouteResponse<T>> : T extends
|
|
91
|
+
}) => Promisable<Response | InferRouteResponse<T>> : T extends MutationRouteSchema ? (context: MiddlewareContext<TMiddleware> & {
|
|
92
92
|
path: T extends {
|
|
93
93
|
path: any;
|
|
94
94
|
} ? z.infer<T["path"]> : Params<TRoutes[K]["path"]["source"]>;
|
package/dist/server/router.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RoutePattern } from '@remix-run/route-pattern';
|
|
2
2
|
import { chain, } from 'alien-middleware';
|
|
3
|
-
import { mapValues } from '../common.js';
|
|
4
3
|
import * as z from 'zod/mini';
|
|
4
|
+
import { mapValues } from '../common.js';
|
|
5
5
|
export { chain };
|
|
6
6
|
export function createRouter(config) {
|
|
7
7
|
const keys = Object.keys(config.routes);
|
package/dist/types.d.ts
CHANGED
|
@@ -4,31 +4,33 @@ export type Promisable<T> = T | Promise<T>;
|
|
|
4
4
|
export type Unchecked<T> = {
|
|
5
5
|
__unchecked__: T;
|
|
6
6
|
};
|
|
7
|
-
export type
|
|
7
|
+
export type QueryRouteSchema = {
|
|
8
8
|
path?: z.ZodMiniObject<any>;
|
|
9
9
|
query?: z.ZodMiniObject<any>;
|
|
10
10
|
body?: never;
|
|
11
11
|
headers?: z.ZodMiniObject<any>;
|
|
12
|
-
response
|
|
12
|
+
response?: Unchecked<any>;
|
|
13
13
|
};
|
|
14
|
-
export type
|
|
14
|
+
export type MutationRouteSchema = {
|
|
15
15
|
path?: z.ZodMiniObject<any>;
|
|
16
16
|
query?: never;
|
|
17
|
-
body
|
|
17
|
+
body?: z.ZodMiniType<any, any>;
|
|
18
18
|
headers?: z.ZodMiniObject<any>;
|
|
19
19
|
response?: Unchecked<any>;
|
|
20
20
|
};
|
|
21
|
-
export type
|
|
22
|
-
GET?:
|
|
23
|
-
POST?:
|
|
24
|
-
PUT?:
|
|
25
|
-
PATCH?:
|
|
26
|
-
DELETE?:
|
|
21
|
+
export type RouteSchemaMap = {
|
|
22
|
+
GET?: QueryRouteSchema;
|
|
23
|
+
POST?: MutationRouteSchema;
|
|
24
|
+
PUT?: MutationRouteSchema;
|
|
25
|
+
PATCH?: MutationRouteSchema;
|
|
26
|
+
DELETE?: MutationRouteSchema;
|
|
27
27
|
};
|
|
28
|
+
export type Method = string & keyof RouteSchemaMap;
|
|
29
|
+
export type RouteSchema = QueryRouteSchema | MutationRouteSchema;
|
|
28
30
|
export type Routes = {
|
|
29
31
|
[key: string]: {
|
|
30
32
|
path: RoutePattern;
|
|
31
|
-
methods:
|
|
33
|
+
methods: RouteSchemaMap;
|
|
32
34
|
};
|
|
33
35
|
};
|
|
34
36
|
declare class Any {
|
|
@@ -41,23 +43,21 @@ type PathArgs<T> = T extends {
|
|
|
41
43
|
} : {
|
|
42
44
|
path: TParams;
|
|
43
45
|
} : unknown : unknown;
|
|
44
|
-
type QueryArgs<T> = T extends
|
|
46
|
+
type QueryArgs<T> = T extends QueryRouteSchema & {
|
|
45
47
|
query: infer TQuery;
|
|
46
48
|
} ? {} extends z.infer<TQuery> ? {
|
|
47
49
|
query?: z.infer<TQuery>;
|
|
48
50
|
} : {
|
|
49
51
|
query: z.infer<TQuery>;
|
|
50
52
|
} : unknown;
|
|
51
|
-
type MutationArgs<T> = T extends
|
|
53
|
+
type MutationArgs<T> = T extends MutationRouteSchema & {
|
|
52
54
|
body: infer TBody;
|
|
53
55
|
} ? {} extends z.infer<TBody> ? {
|
|
54
56
|
body?: z.infer<TBody>;
|
|
55
57
|
} : {
|
|
56
58
|
body: z.infer<TBody>;
|
|
57
59
|
} : unknown;
|
|
58
|
-
export type RouteArgs<T extends
|
|
59
|
-
T
|
|
60
|
-
] extends [Any] ? {
|
|
60
|
+
export type RouteArgs<T extends RouteSchema = any> = ([T] extends [Any] ? {
|
|
61
61
|
query?: any;
|
|
62
62
|
body?: any;
|
|
63
63
|
path?: any;
|
|
@@ -65,7 +65,7 @@ export type RouteArgs<T extends QueryMethod | MutationMethod = any> = ([
|
|
|
65
65
|
headers?: Record<string, string | undefined>;
|
|
66
66
|
};
|
|
67
67
|
export type RouteRequest<TResult = any> = {
|
|
68
|
-
|
|
68
|
+
schema: RouteSchema;
|
|
69
69
|
path: RoutePattern;
|
|
70
70
|
method: string;
|
|
71
71
|
args: RouteArgs;
|
|
@@ -74,10 +74,10 @@ export type RouteRequest<TResult = any> = {
|
|
|
74
74
|
export type RouteResponse<TResult = any> = Response & {
|
|
75
75
|
json(): Promise<TResult>;
|
|
76
76
|
};
|
|
77
|
-
export type InferRouteResponse<T extends
|
|
77
|
+
export type InferRouteResponse<T extends RouteSchema> = T extends {
|
|
78
78
|
response: Unchecked<infer TResponse>;
|
|
79
79
|
} ? TResponse : void;
|
|
80
|
-
export type RouteFunction<T extends
|
|
80
|
+
export type RouteFunction<T extends RouteSchema> = {
|
|
81
81
|
(args: RouteArgs<T>): RouteRequest<InferRouteResponse<T>>;
|
|
82
82
|
$args: RouteArgs<T>;
|
|
83
83
|
$response: InferRouteResponse<T>;
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -85,7 +85,11 @@ export const handler = createRouter({
|
|
|
85
85
|
middlewares,
|
|
86
86
|
basePath: 'api/',
|
|
87
87
|
cors: {
|
|
88
|
-
allowOrigins: [
|
|
88
|
+
allowOrigins: [
|
|
89
|
+
'example.net',
|
|
90
|
+
'https://*.example.com',
|
|
91
|
+
'*://localhost:3000',
|
|
92
|
+
],
|
|
89
93
|
},
|
|
90
94
|
debug: process.env.NODE_ENV === 'development',
|
|
91
95
|
})({
|
|
@@ -125,6 +129,30 @@ const response = await client.request(
|
|
|
125
129
|
const { message } = await response.json()
|
|
126
130
|
```
|
|
127
131
|
|
|
132
|
+
Optionally pass your routes map to `createClient` to get per-route methods on the client:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import * as routes from './routes'
|
|
136
|
+
|
|
137
|
+
const client = createClient({
|
|
138
|
+
baseURL: '/api/',
|
|
139
|
+
routes,
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Routes that define a `response` type will call `client.json()` under the hood and return the parsed value; routes without one return the raw `Response`:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
// helloRoute has a response schema, so you get the parsed payload
|
|
147
|
+
const { message } = await client.helloRoute.GET({
|
|
148
|
+
path: { name: 'world' },
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// imagine pingRoute has no response schema; you get a Response object
|
|
152
|
+
const pingResponse = await client.pingRoute.GET({})
|
|
153
|
+
const pingText = await pingResponse.text()
|
|
154
|
+
```
|
|
155
|
+
|
|
128
156
|
## Add an endpoint
|
|
129
157
|
|
|
130
158
|
1. Declare it in `routes.ts` with `route(...)` and `zod/mini` schemas.
|