rouzer 1.0.0-beta.1 → 1.0.0-beta.3
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/client/index.d.ts +35 -0
- package/dist/client/index.js +46 -0
- package/dist/common.d.ts +47 -0
- package/dist/common.js +51 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/route.d.ts +8 -0
- package/dist/route.js +18 -0
- package/dist/server/router.d.ts +54 -0
- package/dist/server/router.js +115 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.js +1 -0
- package/package.json +16 -6
- package/.vscode/settings.json +0 -5
- package/src/client/index.ts +0 -70
- package/src/common.ts +0 -99
- package/src/index.ts +0 -4
- package/src/route.ts +0 -44
- package/src/server/router.ts +0 -248
- package/src/types.ts +0 -89
- package/tsconfig.json +0 -10
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare function createClient(config: {
|
|
2
|
+
/**
|
|
3
|
+
* Base URL to use for all requests.
|
|
4
|
+
*/
|
|
5
|
+
baseURL: string;
|
|
6
|
+
/**
|
|
7
|
+
* Default headers to send with every request.
|
|
8
|
+
*/
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Custom handler for non-200 response to a `.json()` request. By default, the
|
|
12
|
+
* response is always parsed as JSON, regardless of the HTTP status code.
|
|
13
|
+
*/
|
|
14
|
+
onJsonError?: (response: Response) => Response;
|
|
15
|
+
}): {
|
|
16
|
+
config: {
|
|
17
|
+
/**
|
|
18
|
+
* Base URL to use for all requests.
|
|
19
|
+
*/
|
|
20
|
+
baseURL: string;
|
|
21
|
+
/**
|
|
22
|
+
* Default headers to send with every request.
|
|
23
|
+
*/
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
/**
|
|
26
|
+
* Custom handler for non-200 response to a `.json()` request. By default, the
|
|
27
|
+
* response is always parsed as JSON, regardless of the HTTP status code.
|
|
28
|
+
*/
|
|
29
|
+
onJsonError?: (response: Response) => Response;
|
|
30
|
+
};
|
|
31
|
+
request<T extends RouteRequest>({ pathPattern, method, args: { path, query, body, headers }, route, }: T): Promise<Response & {
|
|
32
|
+
json(): Promise<T["$result"]>;
|
|
33
|
+
}>;
|
|
34
|
+
json<T extends RouteRequest>(request: T): Promise<T["$result"]>;
|
|
35
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { shake } from '../common';
|
|
2
|
+
export function createClient(config) {
|
|
3
|
+
return {
|
|
4
|
+
config,
|
|
5
|
+
request({ pathPattern, method, args: { path, query, body, headers }, route, }) {
|
|
6
|
+
if (route.path) {
|
|
7
|
+
path = route.path.parse(path);
|
|
8
|
+
}
|
|
9
|
+
const url = new URL(pathPattern.href(path), config.baseURL);
|
|
10
|
+
if (route.query) {
|
|
11
|
+
query = route.query.parse(query ?? {});
|
|
12
|
+
url.search = new URLSearchParams(query).toString();
|
|
13
|
+
}
|
|
14
|
+
else if (query) {
|
|
15
|
+
throw new Error('Unexpected query parameters');
|
|
16
|
+
}
|
|
17
|
+
if (route.body) {
|
|
18
|
+
body = route.body.parse(body !== undefined ? body : {});
|
|
19
|
+
}
|
|
20
|
+
else if (body !== undefined) {
|
|
21
|
+
throw new Error('Unexpected body');
|
|
22
|
+
}
|
|
23
|
+
if (config.headers || headers) {
|
|
24
|
+
headers = {
|
|
25
|
+
...config.headers,
|
|
26
|
+
...(headers && shake(headers)),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (route.headers) {
|
|
30
|
+
headers = route.headers.parse(headers);
|
|
31
|
+
}
|
|
32
|
+
return fetch(url, {
|
|
33
|
+
method,
|
|
34
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
35
|
+
headers,
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
async json(request) {
|
|
39
|
+
const response = await this.request(request);
|
|
40
|
+
if (!response.ok && config.onJsonError) {
|
|
41
|
+
return config.onJsonError(response);
|
|
42
|
+
}
|
|
43
|
+
return response.json();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
package/dist/common.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map over all the keys to create a new object.
|
|
3
|
+
*
|
|
4
|
+
* @see https://radashi.js.org/reference/object/mapEntries
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* const a = { a: 1, b: 2, c: 3 }
|
|
8
|
+
* mapEntries(a, (key, value) => [value, key])
|
|
9
|
+
* // => { 1: 'a', 2: 'b', 3: 'c' }
|
|
10
|
+
* ```
|
|
11
|
+
* @version 12.1.0
|
|
12
|
+
*/
|
|
13
|
+
export declare function mapEntries<TKey extends string | number | symbol, TValue, TNewKey extends string | number | symbol, TNewValue>(obj: Record<TKey, TValue>, toEntry: (key: TKey, value: TValue) => [TNewKey, TNewValue]): Record<TNewKey, TNewValue>;
|
|
14
|
+
/**
|
|
15
|
+
* Removes (shakes out) undefined entries from an object. Optional
|
|
16
|
+
* second argument shakes out values by custom evaluation.
|
|
17
|
+
*
|
|
18
|
+
* Note that non-enumerable keys are never shaken out.
|
|
19
|
+
*
|
|
20
|
+
* @see https://radashi.js.org/reference/object/shake
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const a = { a: 1, b: undefined, c: 3 }
|
|
24
|
+
* shake(a)
|
|
25
|
+
* // => { a: 1, c: 3 }
|
|
26
|
+
* ```
|
|
27
|
+
* @version 12.1.0
|
|
28
|
+
*/
|
|
29
|
+
export declare function shake<T extends object>(obj: T): {
|
|
30
|
+
[K in keyof T]: Exclude<T[K], undefined>;
|
|
31
|
+
};
|
|
32
|
+
export declare function shake<T extends object>(obj: T, filter: ((value: unknown) => boolean) | undefined): T;
|
|
33
|
+
/**
|
|
34
|
+
* Map over all the keys to create a new object.
|
|
35
|
+
*
|
|
36
|
+
* @see https://radashi.js.org/reference/object/mapValues
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const a = { a: 1, b: 2, c: 3 }
|
|
40
|
+
* mapValues(a, (value, key) => value * 2)
|
|
41
|
+
* // => { a: 2, b: 4, c: 6 }
|
|
42
|
+
* ```
|
|
43
|
+
* @version 12.1.0
|
|
44
|
+
*/
|
|
45
|
+
export declare function mapValues<T extends object, U>(obj: T, mapFunc: (value: Required<T>[keyof T], key: keyof T) => U): {
|
|
46
|
+
[K in keyof T]: U;
|
|
47
|
+
};
|
package/dist/common.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map over all the keys to create a new object.
|
|
3
|
+
*
|
|
4
|
+
* @see https://radashi.js.org/reference/object/mapEntries
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* const a = { a: 1, b: 2, c: 3 }
|
|
8
|
+
* mapEntries(a, (key, value) => [value, key])
|
|
9
|
+
* // => { 1: 'a', 2: 'b', 3: 'c' }
|
|
10
|
+
* ```
|
|
11
|
+
* @version 12.1.0
|
|
12
|
+
*/
|
|
13
|
+
export function mapEntries(obj, toEntry) {
|
|
14
|
+
if (!obj) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
18
|
+
const [newKey, newValue] = toEntry(key, value);
|
|
19
|
+
acc[newKey] = newValue;
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
22
|
+
}
|
|
23
|
+
export function shake(obj, filter = value => value === undefined) {
|
|
24
|
+
if (!obj) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
28
|
+
if (!filter(obj[key])) {
|
|
29
|
+
acc[key] = obj[key];
|
|
30
|
+
}
|
|
31
|
+
return acc;
|
|
32
|
+
}, {});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Map over all the keys to create a new object.
|
|
36
|
+
*
|
|
37
|
+
* @see https://radashi.js.org/reference/object/mapValues
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const a = { a: 1, b: 2, c: 3 }
|
|
41
|
+
* mapValues(a, (value, key) => value * 2)
|
|
42
|
+
* // => { a: 2, b: 4, c: 6 }
|
|
43
|
+
* ```
|
|
44
|
+
* @version 12.1.0
|
|
45
|
+
*/
|
|
46
|
+
export function mapValues(obj, mapFunc) {
|
|
47
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
48
|
+
acc[key] = mapFunc(obj[key], key);
|
|
49
|
+
return acc;
|
|
50
|
+
}, {});
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/route.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RoutePattern } from '@remix-run/route-pattern';
|
|
2
|
+
import type { Routes } from './types';
|
|
3
|
+
export declare function $type<T>(): Unchecked<T>;
|
|
4
|
+
export declare function route<P extends string, T extends Routes>(path: P, routes: T): {
|
|
5
|
+
path: P;
|
|
6
|
+
pathPattern: RoutePattern<string>;
|
|
7
|
+
routes: T;
|
|
8
|
+
} & { [K in keyof T]: RouteFunction<any>; };
|
package/dist/route.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RoutePattern } from '@remix-run/route-pattern';
|
|
2
|
+
import { mapEntries } from './common';
|
|
3
|
+
export function $type() {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
export function route(path, routes) {
|
|
7
|
+
const pathPattern = new RoutePattern(path);
|
|
8
|
+
const createFetch = (method, route) => (args) => {
|
|
9
|
+
return {
|
|
10
|
+
route,
|
|
11
|
+
pathPattern,
|
|
12
|
+
method,
|
|
13
|
+
args,
|
|
14
|
+
$result: undefined,
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
return Object.assign({ path, pathPattern, routes }, mapEntries(routes, (method, route) => [method, createFetch(method, route)]));
|
|
18
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AdapterRequestContext } from '@hattip/core';
|
|
2
|
+
import { type Params } from '@remix-run/route-pattern';
|
|
3
|
+
import { chain, MiddlewareChain, type MiddlewareContext } from 'alien-middleware';
|
|
4
|
+
import * as z from 'zod/mini';
|
|
5
|
+
import type { Routes } from '../types';
|
|
6
|
+
export { chain };
|
|
7
|
+
type EmptyMiddlewareChain<TPlatform = unknown> = MiddlewareChain<{
|
|
8
|
+
initial: {
|
|
9
|
+
env: {};
|
|
10
|
+
properties: {};
|
|
11
|
+
};
|
|
12
|
+
current: {
|
|
13
|
+
env: {};
|
|
14
|
+
properties: {};
|
|
15
|
+
};
|
|
16
|
+
platform: TPlatform;
|
|
17
|
+
}>;
|
|
18
|
+
export type RouterConfig<TRoutes extends Record<string, {
|
|
19
|
+
path: string;
|
|
20
|
+
routes: Routes;
|
|
21
|
+
}> = any, TMiddleware extends MiddlewareChain = any> = {
|
|
22
|
+
routes: TRoutes;
|
|
23
|
+
middlewares?: TMiddleware;
|
|
24
|
+
debug?: boolean;
|
|
25
|
+
};
|
|
26
|
+
export declare function createRouter<TRoutes extends Record<string, {
|
|
27
|
+
path: string;
|
|
28
|
+
routes: Routes;
|
|
29
|
+
}>, TMiddleware extends MiddlewareChain = EmptyMiddlewareChain>(config: {
|
|
30
|
+
routes: TRoutes;
|
|
31
|
+
middlewares?: TMiddleware;
|
|
32
|
+
debug?: boolean;
|
|
33
|
+
}): (handlers: { [K in keyof TRoutes]: { [M in keyof TRoutes[K]["routes"]]: TRoutes[K]["routes"][M] extends infer T ? T extends TRoutes[K]["routes"][M] ? T extends QueryRoute ? (context: MiddlewareContext<TMiddleware> & {
|
|
34
|
+
query: z.infer<T["query"]>;
|
|
35
|
+
params: Params<TRoutes[K]["path"]>;
|
|
36
|
+
headers: z.infer<T["headers"]>;
|
|
37
|
+
}) => Promisable<TResult | Response> : T extends MutationRoute ? (context: MiddlewareContext<TMiddleware> & {
|
|
38
|
+
body: z.infer<T["body"]>;
|
|
39
|
+
params: Params<TRoutes[K]["path"]>;
|
|
40
|
+
headers: z.infer<T["headers"]>;
|
|
41
|
+
}) => Promisable<TResult | Response> : never : never : never; }; }) => import("alien-middleware").ApplyMiddleware<TMiddleware, (context: AdapterRequestContext<TMiddleware extends MiddlewareChain<infer T extends {
|
|
42
|
+
initial: {
|
|
43
|
+
env: object;
|
|
44
|
+
properties: object;
|
|
45
|
+
};
|
|
46
|
+
current: {
|
|
47
|
+
env: object;
|
|
48
|
+
properties: object;
|
|
49
|
+
};
|
|
50
|
+
platform: unknown;
|
|
51
|
+
}> ? T["platform"] : never> & {
|
|
52
|
+
url?: URL;
|
|
53
|
+
params?: {};
|
|
54
|
+
}) => Promise<Response>>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { RoutePattern } from '@remix-run/route-pattern';
|
|
2
|
+
import { chain, } from 'alien-middleware';
|
|
3
|
+
import { mapValues } from '../common';
|
|
4
|
+
import * as z from 'zod/mini';
|
|
5
|
+
export { chain };
|
|
6
|
+
export function createRouter(config) {
|
|
7
|
+
const keys = Object.keys(config.routes);
|
|
8
|
+
const middlewares = config.middlewares ?? chain();
|
|
9
|
+
const patterns = mapValues(config.routes, ({ path }) => new RoutePattern(path));
|
|
10
|
+
return (handlers) => middlewares.use(async function (context) {
|
|
11
|
+
const request = context.request;
|
|
12
|
+
const method = request.method.toUpperCase();
|
|
13
|
+
const url = (context.url ??= new URL(request.url));
|
|
14
|
+
for (let i = 0; i < keys.length; i++) {
|
|
15
|
+
const pattern = patterns[keys[i]];
|
|
16
|
+
const match = pattern.match(url);
|
|
17
|
+
if (!match) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const route = config.routes[keys[i]].routes[method];
|
|
21
|
+
if (!route) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (route.headers) {
|
|
25
|
+
const error = parseHeaders(context, enableStringParsing(route.headers));
|
|
26
|
+
if (error) {
|
|
27
|
+
return httpClientError(error, 'Invalid request headers', config);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (route.query) {
|
|
31
|
+
const error = parseQueryString(context, enableStringParsing(route.query));
|
|
32
|
+
if (error) {
|
|
33
|
+
return httpClientError(error, 'Invalid query string', config);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (route.body) {
|
|
37
|
+
const error = await parseRequestBody(context, route.body);
|
|
38
|
+
if (error) {
|
|
39
|
+
return httpClientError(error, 'Invalid request body', config);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const handler = handlers[keys[i]][method];
|
|
43
|
+
if (!handler) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
context.params = match.params;
|
|
47
|
+
const result = await handler(context);
|
|
48
|
+
if (result instanceof Response) {
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
return Response.json(result);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function httpClientError(error, message, config) {
|
|
56
|
+
return Response.json({
|
|
57
|
+
...error,
|
|
58
|
+
message: config.debug ? `${message}: ${error.message}` : message,
|
|
59
|
+
}, { status: 400 });
|
|
60
|
+
}
|
|
61
|
+
function parseHeaders(context, schema) {
|
|
62
|
+
const headers = Object.fromEntries(context.request.headers);
|
|
63
|
+
const result = schema.safeParse(headers);
|
|
64
|
+
if (!result.success) {
|
|
65
|
+
return result.error;
|
|
66
|
+
}
|
|
67
|
+
context.headers = result.data;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function parseQueryString(context, schema) {
|
|
71
|
+
const result = schema.safeParse(Object.fromEntries(context.url.searchParams));
|
|
72
|
+
if (!result.success) {
|
|
73
|
+
return result.error;
|
|
74
|
+
}
|
|
75
|
+
context.query = result.data;
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
async function parseRequestBody(context, schema) {
|
|
79
|
+
const result = await context.request.json().then(body => schema.safeParse(body), error => ({ success: false, error }));
|
|
80
|
+
if (!result.success) {
|
|
81
|
+
return result.error;
|
|
82
|
+
}
|
|
83
|
+
context.body = result.data;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const seen = new WeakMap();
|
|
87
|
+
/**
|
|
88
|
+
* Traverse object and array schemas, finding schemas that expect a number or
|
|
89
|
+
* boolean, and replace those schemas with a new schema that parses the input
|
|
90
|
+
* value as a number or boolean.
|
|
91
|
+
*/
|
|
92
|
+
function enableStringParsing(schema) {
|
|
93
|
+
if (schema.type === 'number') {
|
|
94
|
+
return z.pipe(z.transform(Number), schema);
|
|
95
|
+
}
|
|
96
|
+
if (schema.type === 'boolean') {
|
|
97
|
+
return z.pipe(z.transform(toBooleanStrict), schema);
|
|
98
|
+
}
|
|
99
|
+
if (schema.type === 'object') {
|
|
100
|
+
const cached = seen.get(schema);
|
|
101
|
+
if (cached) {
|
|
102
|
+
return cached;
|
|
103
|
+
}
|
|
104
|
+
const modified = z.object(mapValues(schema.def.shape, enableStringParsing));
|
|
105
|
+
seen.set(schema, modified);
|
|
106
|
+
return modified;
|
|
107
|
+
}
|
|
108
|
+
if (schema.type === 'array') {
|
|
109
|
+
return z.array(enableStringParsing(schema.def.element));
|
|
110
|
+
}
|
|
111
|
+
return schema;
|
|
112
|
+
}
|
|
113
|
+
function toBooleanStrict(value) {
|
|
114
|
+
return value === 'true' || (value === 'false' ? false : value);
|
|
115
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Params, RoutePattern } from '@remix-run/route-pattern';
|
|
2
|
+
import * as z from 'zod/mini';
|
|
3
|
+
export type Promisable<T> = T | Promise<T>;
|
|
4
|
+
export type Unchecked<T> = {
|
|
5
|
+
__unchecked__: T;
|
|
6
|
+
};
|
|
7
|
+
export type QueryRoute = {
|
|
8
|
+
path?: z.ZodMiniObject<any>;
|
|
9
|
+
query?: z.ZodMiniObject<any>;
|
|
10
|
+
body?: never;
|
|
11
|
+
headers?: z.ZodMiniObject<any>;
|
|
12
|
+
response: Unchecked<any>;
|
|
13
|
+
};
|
|
14
|
+
export type MutationRoute = {
|
|
15
|
+
path?: z.ZodMiniObject<any>;
|
|
16
|
+
query?: never;
|
|
17
|
+
body: z.ZodMiniType<any, any>;
|
|
18
|
+
headers?: z.ZodMiniObject<any>;
|
|
19
|
+
response?: Unchecked<any>;
|
|
20
|
+
};
|
|
21
|
+
export type Routes = {
|
|
22
|
+
GET?: QueryRoute;
|
|
23
|
+
POST?: MutationRoute;
|
|
24
|
+
PUT?: MutationRoute;
|
|
25
|
+
PATCH?: MutationRoute;
|
|
26
|
+
DELETE?: MutationRoute;
|
|
27
|
+
};
|
|
28
|
+
declare class Any {
|
|
29
|
+
private isAny;
|
|
30
|
+
}
|
|
31
|
+
type PathArgs<T> = T extends {
|
|
32
|
+
path: infer TPath extends string;
|
|
33
|
+
} ? Params<TPath> extends infer TParams ? {} extends TParams ? {
|
|
34
|
+
path?: TParams;
|
|
35
|
+
} : {
|
|
36
|
+
path: TParams;
|
|
37
|
+
} : unknown : unknown;
|
|
38
|
+
type QueryArgs<T> = T extends QueryRoute & {
|
|
39
|
+
query: infer TQuery;
|
|
40
|
+
} ? {} extends z.infer<TQuery> ? {
|
|
41
|
+
query?: z.infer<TQuery>;
|
|
42
|
+
} : {
|
|
43
|
+
query: z.infer<TQuery>;
|
|
44
|
+
} : unknown;
|
|
45
|
+
type MutationArgs<T> = T extends MutationRoute & {
|
|
46
|
+
body: infer TBody;
|
|
47
|
+
} ? {} extends z.infer<TBody> ? {
|
|
48
|
+
body?: z.infer<TBody>;
|
|
49
|
+
} : {
|
|
50
|
+
body: z.infer<TBody>;
|
|
51
|
+
} : unknown;
|
|
52
|
+
export type RouteArgs<T extends QueryRoute | MutationRoute = any> = ([
|
|
53
|
+
T
|
|
54
|
+
] extends [Any] ? {
|
|
55
|
+
query?: any;
|
|
56
|
+
body?: any;
|
|
57
|
+
path?: any;
|
|
58
|
+
} : QueryArgs<T> & MutationArgs<T> & PathArgs<T>) & Omit<RequestInit, 'method' | 'body' | 'headers'> & {
|
|
59
|
+
headers?: Record<string, string | undefined>;
|
|
60
|
+
};
|
|
61
|
+
export type RouteRequest<TResult = any> = {
|
|
62
|
+
route: QueryRoute | MutationRoute;
|
|
63
|
+
pathPattern: RoutePattern;
|
|
64
|
+
method: string;
|
|
65
|
+
args: RouteArgs;
|
|
66
|
+
$result: TResult;
|
|
67
|
+
};
|
|
68
|
+
export type RouteResponse<TResult = any> = Response & {
|
|
69
|
+
json(): Promise<TResult>;
|
|
70
|
+
};
|
|
71
|
+
export type InferRouteResponse<T extends QueryRoute | MutationRoute> = T extends {
|
|
72
|
+
response: Unchecked<infer TResponse>;
|
|
73
|
+
} ? TResponse : void;
|
|
74
|
+
export type RouteFunction<T extends QueryRoute | MutationRoute> = {
|
|
75
|
+
(args: RouteArgs<T>): RouteRequest<InferRouteResponse<T>>;
|
|
76
|
+
$args: RouteArgs<T>;
|
|
77
|
+
$response: InferRouteResponse<T>;
|
|
78
|
+
};
|
|
79
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rouzer",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"exports": {
|
|
5
6
|
".": {
|
|
6
7
|
"types": "./dist/index.d.ts",
|
|
7
8
|
"import": "./dist/index.js"
|
|
8
9
|
}
|
|
9
10
|
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsgo -b tsconfig.json"
|
|
12
|
-
},
|
|
13
11
|
"peerDependencies": {
|
|
14
12
|
"zod": ">=4"
|
|
15
13
|
},
|
|
@@ -24,5 +22,17 @@
|
|
|
24
22
|
"@remix-run/route-pattern": "^0.15.3",
|
|
25
23
|
"alien-middleware": "^0.10.2"
|
|
26
24
|
},
|
|
27
|
-
"prettier": "@alloc/prettier-config"
|
|
28
|
-
|
|
25
|
+
"prettier": "@alloc/prettier-config",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/alloc/rouzer.git"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"!*.tsbuildinfo"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsgo -b tsconfig.json"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/.vscode/settings.json
DELETED
package/src/client/index.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { shake } from '../common'
|
|
2
|
-
import type { RouteRequest } from '../types'
|
|
3
|
-
|
|
4
|
-
export function createClient(config: {
|
|
5
|
-
/**
|
|
6
|
-
* Base URL to use for all requests.
|
|
7
|
-
*/
|
|
8
|
-
baseURL: string
|
|
9
|
-
/**
|
|
10
|
-
* Default headers to send with every request.
|
|
11
|
-
*/
|
|
12
|
-
headers?: Record<string, string>
|
|
13
|
-
/**
|
|
14
|
-
* Custom handler for non-200 response to a `.json()` request. By default, the
|
|
15
|
-
* response is always parsed as JSON, regardless of the HTTP status code.
|
|
16
|
-
*/
|
|
17
|
-
onJsonError?: (response: Response) => Response
|
|
18
|
-
}) {
|
|
19
|
-
return {
|
|
20
|
-
config,
|
|
21
|
-
request<T extends RouteRequest>({
|
|
22
|
-
pathPattern,
|
|
23
|
-
method,
|
|
24
|
-
args: { path, query, body, headers },
|
|
25
|
-
route,
|
|
26
|
-
}: T) {
|
|
27
|
-
if (route.path) {
|
|
28
|
-
path = route.path.parse(path)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const url = new URL(pathPattern.href(path), config.baseURL)
|
|
32
|
-
|
|
33
|
-
if (route.query) {
|
|
34
|
-
query = route.query.parse(query ?? {})
|
|
35
|
-
url.search = new URLSearchParams(query).toString()
|
|
36
|
-
} else if (query) {
|
|
37
|
-
throw new Error('Unexpected query parameters')
|
|
38
|
-
}
|
|
39
|
-
if (route.body) {
|
|
40
|
-
body = route.body.parse(body !== undefined ? body : {})
|
|
41
|
-
} else if (body !== undefined) {
|
|
42
|
-
throw new Error('Unexpected body')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (config.headers || headers) {
|
|
46
|
-
headers = {
|
|
47
|
-
...config.headers,
|
|
48
|
-
...(headers && shake(headers)),
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (route.headers) {
|
|
53
|
-
headers = route.headers.parse(headers) as any
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return fetch(url, {
|
|
57
|
-
method,
|
|
58
|
-
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
59
|
-
headers,
|
|
60
|
-
}) as Promise<Response & { json(): Promise<T['$result']> }>
|
|
61
|
-
},
|
|
62
|
-
async json<T extends RouteRequest>(request: T): Promise<T['$result']> {
|
|
63
|
-
const response = await this.request(request)
|
|
64
|
-
if (!response.ok && config.onJsonError) {
|
|
65
|
-
return config.onJsonError(response)
|
|
66
|
-
}
|
|
67
|
-
return response.json()
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
}
|
package/src/common.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Map over all the keys to create a new object.
|
|
3
|
-
*
|
|
4
|
-
* @see https://radashi.js.org/reference/object/mapEntries
|
|
5
|
-
* @example
|
|
6
|
-
* ```ts
|
|
7
|
-
* const a = { a: 1, b: 2, c: 3 }
|
|
8
|
-
* mapEntries(a, (key, value) => [value, key])
|
|
9
|
-
* // => { 1: 'a', 2: 'b', 3: 'c' }
|
|
10
|
-
* ```
|
|
11
|
-
* @version 12.1.0
|
|
12
|
-
*/
|
|
13
|
-
export function mapEntries<
|
|
14
|
-
TKey extends string | number | symbol,
|
|
15
|
-
TValue,
|
|
16
|
-
TNewKey extends string | number | symbol,
|
|
17
|
-
TNewValue,
|
|
18
|
-
>(
|
|
19
|
-
obj: Record<TKey, TValue>,
|
|
20
|
-
toEntry: (key: TKey, value: TValue) => [TNewKey, TNewValue]
|
|
21
|
-
): Record<TNewKey, TNewValue> {
|
|
22
|
-
if (!obj) {
|
|
23
|
-
return {} as Record<TNewKey, TNewValue>
|
|
24
|
-
}
|
|
25
|
-
return Object.entries(obj).reduce(
|
|
26
|
-
(acc, [key, value]) => {
|
|
27
|
-
const [newKey, newValue] = toEntry(key as TKey, value as TValue)
|
|
28
|
-
acc[newKey] = newValue
|
|
29
|
-
return acc
|
|
30
|
-
},
|
|
31
|
-
{} as Record<TNewKey, TNewValue>
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Removes (shakes out) undefined entries from an object. Optional
|
|
37
|
-
* second argument shakes out values by custom evaluation.
|
|
38
|
-
*
|
|
39
|
-
* Note that non-enumerable keys are never shaken out.
|
|
40
|
-
*
|
|
41
|
-
* @see https://radashi.js.org/reference/object/shake
|
|
42
|
-
* @example
|
|
43
|
-
* ```ts
|
|
44
|
-
* const a = { a: 1, b: undefined, c: 3 }
|
|
45
|
-
* shake(a)
|
|
46
|
-
* // => { a: 1, c: 3 }
|
|
47
|
-
* ```
|
|
48
|
-
* @version 12.1.0
|
|
49
|
-
*/
|
|
50
|
-
export function shake<T extends object>(
|
|
51
|
-
obj: T
|
|
52
|
-
): {
|
|
53
|
-
[K in keyof T]: Exclude<T[K], undefined>
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function shake<T extends object>(
|
|
57
|
-
obj: T,
|
|
58
|
-
filter: ((value: unknown) => boolean) | undefined
|
|
59
|
-
): T
|
|
60
|
-
|
|
61
|
-
export function shake<T extends object>(
|
|
62
|
-
obj: T,
|
|
63
|
-
filter: (value: unknown) => boolean = value => value === undefined
|
|
64
|
-
): T {
|
|
65
|
-
if (!obj) {
|
|
66
|
-
return {} as T
|
|
67
|
-
}
|
|
68
|
-
return (Object.keys(obj) as (keyof T)[]).reduce((acc, key) => {
|
|
69
|
-
if (!filter(obj[key])) {
|
|
70
|
-
acc[key] = obj[key]
|
|
71
|
-
}
|
|
72
|
-
return acc
|
|
73
|
-
}, {} as T)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Map over all the keys to create a new object.
|
|
78
|
-
*
|
|
79
|
-
* @see https://radashi.js.org/reference/object/mapValues
|
|
80
|
-
* @example
|
|
81
|
-
* ```ts
|
|
82
|
-
* const a = { a: 1, b: 2, c: 3 }
|
|
83
|
-
* mapValues(a, (value, key) => value * 2)
|
|
84
|
-
* // => { a: 2, b: 4, c: 6 }
|
|
85
|
-
* ```
|
|
86
|
-
* @version 12.1.0
|
|
87
|
-
*/
|
|
88
|
-
export function mapValues<T extends object, U>(
|
|
89
|
-
obj: T,
|
|
90
|
-
mapFunc: (value: Required<T>[keyof T], key: keyof T) => U
|
|
91
|
-
): { [K in keyof T]: U } {
|
|
92
|
-
return (Object.keys(obj) as (keyof T)[]).reduce(
|
|
93
|
-
(acc, key) => {
|
|
94
|
-
acc[key] = mapFunc(obj[key], key)
|
|
95
|
-
return acc
|
|
96
|
-
},
|
|
97
|
-
{} as { [K in keyof T]: U }
|
|
98
|
-
)
|
|
99
|
-
}
|
package/src/index.ts
DELETED
package/src/route.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { RoutePattern } from '@remix-run/route-pattern'
|
|
2
|
-
import { mapEntries } from './common'
|
|
3
|
-
import type {
|
|
4
|
-
MutationRoute,
|
|
5
|
-
QueryRoute,
|
|
6
|
-
RouteArgs,
|
|
7
|
-
RouteFunction,
|
|
8
|
-
RouteRequest,
|
|
9
|
-
Routes,
|
|
10
|
-
Unchecked,
|
|
11
|
-
} from './types'
|
|
12
|
-
|
|
13
|
-
export function $type<T>() {
|
|
14
|
-
return null as unknown as Unchecked<T>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function route<P extends string, T extends Routes>(path: P, routes: T) {
|
|
18
|
-
const pathPattern = new RoutePattern(path)
|
|
19
|
-
const createFetch =
|
|
20
|
-
(method: string, route: QueryRoute | MutationRoute) =>
|
|
21
|
-
(args: RouteArgs): RouteRequest => {
|
|
22
|
-
return {
|
|
23
|
-
route,
|
|
24
|
-
pathPattern,
|
|
25
|
-
method,
|
|
26
|
-
args,
|
|
27
|
-
$result: undefined!,
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return Object.assign(
|
|
32
|
-
{ path, pathPattern, routes },
|
|
33
|
-
mapEntries(
|
|
34
|
-
routes as Record<string, QueryRoute | MutationRoute>,
|
|
35
|
-
(method, route) => [method, createFetch(method, route)]
|
|
36
|
-
)
|
|
37
|
-
) as unknown as {
|
|
38
|
-
path: P
|
|
39
|
-
pathPattern: RoutePattern
|
|
40
|
-
routes: T
|
|
41
|
-
} & {
|
|
42
|
-
[K in keyof T]: RouteFunction<Extract<T[K], QueryRoute | MutationRoute>>
|
|
43
|
-
}
|
|
44
|
-
}
|
package/src/server/router.ts
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import type { AdapterRequestContext } from '@hattip/core'
|
|
2
|
-
import { RoutePattern, type Params } from '@remix-run/route-pattern'
|
|
3
|
-
import {
|
|
4
|
-
chain,
|
|
5
|
-
MiddlewareChain,
|
|
6
|
-
type MiddlewareContext,
|
|
7
|
-
} from 'alien-middleware'
|
|
8
|
-
import { mapValues } from '../common'
|
|
9
|
-
import * as z from 'zod/mini'
|
|
10
|
-
import type {
|
|
11
|
-
InferRouteResponse,
|
|
12
|
-
MutationRoute,
|
|
13
|
-
Promisable,
|
|
14
|
-
QueryRoute,
|
|
15
|
-
Routes,
|
|
16
|
-
} from '../types'
|
|
17
|
-
|
|
18
|
-
export { chain }
|
|
19
|
-
|
|
20
|
-
type EmptyMiddlewareChain<TPlatform = unknown> = MiddlewareChain<{
|
|
21
|
-
initial: {
|
|
22
|
-
env: {}
|
|
23
|
-
properties: {}
|
|
24
|
-
}
|
|
25
|
-
current: {
|
|
26
|
-
env: {}
|
|
27
|
-
properties: {}
|
|
28
|
-
}
|
|
29
|
-
platform: TPlatform
|
|
30
|
-
}>
|
|
31
|
-
|
|
32
|
-
export type RouterConfig<
|
|
33
|
-
TRoutes extends Record<string, { path: string; routes: Routes }> = any,
|
|
34
|
-
TMiddleware extends MiddlewareChain = any,
|
|
35
|
-
> = {
|
|
36
|
-
routes: TRoutes
|
|
37
|
-
middlewares?: TMiddleware
|
|
38
|
-
debug?: boolean
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function createRouter<
|
|
42
|
-
TRoutes extends Record<string, { path: string; routes: Routes }>,
|
|
43
|
-
TMiddleware extends MiddlewareChain = EmptyMiddlewareChain,
|
|
44
|
-
>(config: { routes: TRoutes; middlewares?: TMiddleware; debug?: boolean }) {
|
|
45
|
-
const keys = Object.keys(config.routes)
|
|
46
|
-
const middlewares = config.middlewares ?? (chain() as TMiddleware)
|
|
47
|
-
const patterns = mapValues(
|
|
48
|
-
config.routes,
|
|
49
|
-
({ path }) => new RoutePattern(path)
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
type RequestContext = MiddlewareContext<TMiddleware>
|
|
53
|
-
|
|
54
|
-
type RequestHandler<TArgs extends object, TResult> = (
|
|
55
|
-
context: RequestContext & TArgs
|
|
56
|
-
) => Promisable<TResult | Response>
|
|
57
|
-
|
|
58
|
-
type InferRequestHandler<T, P extends string> = T extends QueryRoute
|
|
59
|
-
? RequestHandler<
|
|
60
|
-
{
|
|
61
|
-
query: z.infer<T['query']>
|
|
62
|
-
params: Params<P>
|
|
63
|
-
headers: z.infer<T['headers']>
|
|
64
|
-
},
|
|
65
|
-
InferRouteResponse<T>
|
|
66
|
-
>
|
|
67
|
-
: T extends MutationRoute
|
|
68
|
-
? RequestHandler<
|
|
69
|
-
{
|
|
70
|
-
body: z.infer<T['body']>
|
|
71
|
-
params: Params<P>
|
|
72
|
-
headers: z.infer<T['headers']>
|
|
73
|
-
},
|
|
74
|
-
InferRouteResponse<T>
|
|
75
|
-
>
|
|
76
|
-
: never
|
|
77
|
-
|
|
78
|
-
type RequestHandlers = {
|
|
79
|
-
[K in keyof TRoutes]: {
|
|
80
|
-
[M in keyof TRoutes[K]['routes']]: InferRequestHandler<
|
|
81
|
-
TRoutes[K]['routes'][M],
|
|
82
|
-
TRoutes[K]['path']
|
|
83
|
-
>
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
type TPlatform =
|
|
88
|
-
TMiddleware extends MiddlewareChain<infer T> ? T['platform'] : never
|
|
89
|
-
|
|
90
|
-
return (handlers: RequestHandlers) =>
|
|
91
|
-
middlewares.use(async function (
|
|
92
|
-
context: AdapterRequestContext<TPlatform> & {
|
|
93
|
-
url?: URL
|
|
94
|
-
params?: {}
|
|
95
|
-
}
|
|
96
|
-
) {
|
|
97
|
-
const request = context.request as Request
|
|
98
|
-
const method = request.method.toUpperCase() as keyof Routes
|
|
99
|
-
const url: URL = (context.url ??= new URL(request.url))
|
|
100
|
-
|
|
101
|
-
for (let i = 0; i < keys.length; i++) {
|
|
102
|
-
const pattern = patterns[keys[i]]
|
|
103
|
-
|
|
104
|
-
const match = pattern.match(url)
|
|
105
|
-
if (!match) {
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const route = config.routes[keys[i]].routes[method]
|
|
110
|
-
if (!route) {
|
|
111
|
-
continue
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (route.headers) {
|
|
115
|
-
const error = parseHeaders(
|
|
116
|
-
context,
|
|
117
|
-
enableStringParsing(route.headers)
|
|
118
|
-
)
|
|
119
|
-
if (error) {
|
|
120
|
-
return httpClientError(error, 'Invalid request headers', config)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (route.query) {
|
|
125
|
-
const error = parseQueryString(
|
|
126
|
-
context,
|
|
127
|
-
enableStringParsing(route.query)
|
|
128
|
-
)
|
|
129
|
-
if (error) {
|
|
130
|
-
return httpClientError(error, 'Invalid query string', config)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (route.body) {
|
|
135
|
-
const error = await parseRequestBody(context, route.body)
|
|
136
|
-
if (error) {
|
|
137
|
-
return httpClientError(error, 'Invalid request body', config)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const handler = handlers[keys[i]][method]
|
|
142
|
-
if (!handler) {
|
|
143
|
-
continue
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
context.params = match.params
|
|
147
|
-
|
|
148
|
-
const result = await handler(context as any)
|
|
149
|
-
if (result instanceof Response) {
|
|
150
|
-
return result
|
|
151
|
-
}
|
|
152
|
-
return Response.json(result)
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function httpClientError(
|
|
158
|
-
error: any,
|
|
159
|
-
message: string,
|
|
160
|
-
config: { debug?: boolean }
|
|
161
|
-
) {
|
|
162
|
-
return Response.json(
|
|
163
|
-
{
|
|
164
|
-
...error,
|
|
165
|
-
message: config.debug ? `${message}: ${error.message}` : message,
|
|
166
|
-
},
|
|
167
|
-
{ status: 400 }
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function parseHeaders(
|
|
172
|
-
context: AdapterRequestContext & { headers?: {} },
|
|
173
|
-
schema: z.ZodMiniType<any, any>
|
|
174
|
-
) {
|
|
175
|
-
const headers = Object.fromEntries(context.request.headers as any)
|
|
176
|
-
const result = schema.safeParse(headers)
|
|
177
|
-
if (!result.success) {
|
|
178
|
-
return result.error
|
|
179
|
-
}
|
|
180
|
-
context.headers = result.data
|
|
181
|
-
return null
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function parseQueryString(
|
|
185
|
-
context: AdapterRequestContext & { url?: URL; query?: {} },
|
|
186
|
-
schema: z.ZodMiniType<any, any>
|
|
187
|
-
) {
|
|
188
|
-
const result = schema.safeParse(
|
|
189
|
-
Object.fromEntries(context.url!.searchParams as any)
|
|
190
|
-
)
|
|
191
|
-
if (!result.success) {
|
|
192
|
-
return result.error
|
|
193
|
-
}
|
|
194
|
-
context.query = result.data
|
|
195
|
-
return null
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async function parseRequestBody(
|
|
199
|
-
context: AdapterRequestContext & { body?: {} },
|
|
200
|
-
schema: z.ZodMiniType<any, any>
|
|
201
|
-
) {
|
|
202
|
-
const result = await context.request.json().then(
|
|
203
|
-
body => schema.safeParse(body),
|
|
204
|
-
error => ({ success: false, error }) as const
|
|
205
|
-
)
|
|
206
|
-
if (!result.success) {
|
|
207
|
-
return result.error
|
|
208
|
-
}
|
|
209
|
-
context.body = result.data
|
|
210
|
-
return null
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const seen = new WeakMap<z.ZodMiniType<any, any>, z.ZodMiniType<any, any>>()
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Traverse object and array schemas, finding schemas that expect a number or
|
|
217
|
-
* boolean, and replace those schemas with a new schema that parses the input
|
|
218
|
-
* value as a number or boolean.
|
|
219
|
-
*/
|
|
220
|
-
function enableStringParsing(schema: z.ZodMiniType<any, any>): typeof schema {
|
|
221
|
-
if (schema.type === 'number') {
|
|
222
|
-
return z.pipe(z.transform(Number), schema)
|
|
223
|
-
}
|
|
224
|
-
if (schema.type === 'boolean') {
|
|
225
|
-
return z.pipe(z.transform(toBooleanStrict), schema)
|
|
226
|
-
}
|
|
227
|
-
if (schema.type === 'object') {
|
|
228
|
-
const cached = seen.get(schema)
|
|
229
|
-
if (cached) {
|
|
230
|
-
return cached
|
|
231
|
-
}
|
|
232
|
-
const modified = z.object(
|
|
233
|
-
mapValues((schema as z.ZodMiniObject<any>).def.shape, enableStringParsing)
|
|
234
|
-
)
|
|
235
|
-
seen.set(schema, modified)
|
|
236
|
-
return modified
|
|
237
|
-
}
|
|
238
|
-
if (schema.type === 'array') {
|
|
239
|
-
return z.array(
|
|
240
|
-
enableStringParsing((schema as z.ZodMiniArray<any>).def.element)
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
return schema
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function toBooleanStrict(value: string) {
|
|
247
|
-
return value === 'true' || (value === 'false' ? false : value)
|
|
248
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { Params, RoutePattern } from '@remix-run/route-pattern'
|
|
2
|
-
import * as z from 'zod/mini'
|
|
3
|
-
|
|
4
|
-
export type Promisable<T> = T | Promise<T>
|
|
5
|
-
|
|
6
|
-
export type Unchecked<T> = { __unchecked__: T }
|
|
7
|
-
|
|
8
|
-
export type QueryRoute = {
|
|
9
|
-
path?: z.ZodMiniObject<any>
|
|
10
|
-
query?: z.ZodMiniObject<any>
|
|
11
|
-
body?: never
|
|
12
|
-
headers?: z.ZodMiniObject<any>
|
|
13
|
-
response: Unchecked<any>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type MutationRoute = {
|
|
17
|
-
path?: z.ZodMiniObject<any>
|
|
18
|
-
query?: never
|
|
19
|
-
body: z.ZodMiniType<any, any>
|
|
20
|
-
headers?: z.ZodMiniObject<any>
|
|
21
|
-
response?: Unchecked<any>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type Routes = {
|
|
25
|
-
GET?: QueryRoute
|
|
26
|
-
POST?: MutationRoute
|
|
27
|
-
PUT?: MutationRoute
|
|
28
|
-
PATCH?: MutationRoute
|
|
29
|
-
DELETE?: MutationRoute
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
declare class Any {
|
|
33
|
-
private isAny: true
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type PathArgs<T> = T extends { path: infer TPath extends string }
|
|
37
|
-
? Params<TPath> extends infer TParams
|
|
38
|
-
? {} extends TParams
|
|
39
|
-
? { path?: TParams }
|
|
40
|
-
: { path: TParams }
|
|
41
|
-
: unknown
|
|
42
|
-
: unknown
|
|
43
|
-
|
|
44
|
-
type QueryArgs<T> = T extends QueryRoute & { query: infer TQuery }
|
|
45
|
-
? {} extends z.infer<TQuery>
|
|
46
|
-
? { query?: z.infer<TQuery> }
|
|
47
|
-
: { query: z.infer<TQuery> }
|
|
48
|
-
: unknown
|
|
49
|
-
|
|
50
|
-
type MutationArgs<T> = T extends MutationRoute & { body: infer TBody }
|
|
51
|
-
? {} extends z.infer<TBody>
|
|
52
|
-
? { body?: z.infer<TBody> }
|
|
53
|
-
: { body: z.infer<TBody> }
|
|
54
|
-
: unknown
|
|
55
|
-
|
|
56
|
-
export type RouteArgs<T extends QueryRoute | MutationRoute = any> = ([
|
|
57
|
-
T,
|
|
58
|
-
] extends [Any]
|
|
59
|
-
? { query?: any; body?: any; path?: any }
|
|
60
|
-
: QueryArgs<T> & MutationArgs<T> & PathArgs<T>) &
|
|
61
|
-
Omit<RequestInit, 'method' | 'body' | 'headers'> & {
|
|
62
|
-
headers?: Record<string, string | undefined>
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type RouteRequest<TResult = any> = {
|
|
66
|
-
route: QueryRoute | MutationRoute
|
|
67
|
-
pathPattern: RoutePattern
|
|
68
|
-
method: string
|
|
69
|
-
args: RouteArgs
|
|
70
|
-
$result: TResult
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export type RouteResponse<TResult = any> = Response & {
|
|
74
|
-
json(): Promise<TResult>
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export type InferRouteResponse<T extends QueryRoute | MutationRoute> =
|
|
78
|
-
T extends {
|
|
79
|
-
response: Unchecked<infer TResponse>
|
|
80
|
-
}
|
|
81
|
-
? TResponse
|
|
82
|
-
: void
|
|
83
|
-
|
|
84
|
-
export type RouteFunction<T extends QueryRoute | MutationRoute> = {
|
|
85
|
-
(args: RouteArgs<T>): RouteRequest<InferRouteResponse<T>>
|
|
86
|
-
|
|
87
|
-
$args: RouteArgs<T>
|
|
88
|
-
$response: InferRouteResponse<T>
|
|
89
|
-
}
|