react-router-define-api 0.1.0 → 0.1.2
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 +33 -60
- package/dist/index.cjs +9 -5
- package/dist/index.d.cts +44 -13
- package/dist/index.d.ts +44 -13
- package/dist/index.js +9 -5
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -16,103 +16,76 @@ npm install react-router-define-api
|
|
|
16
16
|
// app/routes/api.users.ts
|
|
17
17
|
import { defineApi } from 'react-router-define-api';
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
export const { loader, action } = defineApi({
|
|
20
20
|
GET: async ({ params }) => {
|
|
21
|
-
|
|
22
|
-
return { users };
|
|
21
|
+
return { users: await db.users.findMany() };
|
|
23
22
|
},
|
|
24
23
|
POST: async ({ request }) => {
|
|
25
24
|
const body = await request.formData();
|
|
26
|
-
|
|
27
|
-
return { user };
|
|
25
|
+
return { user: await db.users.create({ name: body.get('name') }) };
|
|
28
26
|
},
|
|
29
27
|
DELETE: async ({ params }) => {
|
|
30
28
|
await db.users.delete(params.id);
|
|
31
29
|
return { deleted: true };
|
|
32
30
|
},
|
|
33
31
|
});
|
|
34
|
-
|
|
35
|
-
export const loader = api.loader;
|
|
36
|
-
export const action = api.action;
|
|
37
32
|
```
|
|
38
33
|
|
|
39
34
|
### How it works
|
|
40
35
|
|
|
41
|
-
- `GET`
|
|
42
|
-
- `POST`, `PUT`, `PATCH`, `DELETE`
|
|
43
|
-
- Undefined methods
|
|
36
|
+
- `GET` → `loader`
|
|
37
|
+
- `POST`, `PUT`, `PATCH`, `DELETE` → dispatched inside `action` by `request.method`
|
|
38
|
+
- Undefined methods → `405 Method Not Allowed`
|
|
44
39
|
|
|
45
|
-
###
|
|
40
|
+
### Handler wrapper
|
|
41
|
+
|
|
42
|
+
Wrap all handlers with a higher-order function for error handling, response transformation, logging, etc.:
|
|
46
43
|
|
|
47
44
|
```ts
|
|
48
|
-
const
|
|
49
|
-
GET: async (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
/* ... */
|
|
54
|
-
},
|
|
55
|
-
PUT: async (args) => {
|
|
56
|
-
/* ... */
|
|
57
|
-
},
|
|
58
|
-
PATCH: async (args) => {
|
|
59
|
-
/* ... */
|
|
60
|
-
},
|
|
61
|
-
DELETE: async (args) => {
|
|
62
|
-
/* ... */
|
|
45
|
+
export const { loader, action } = defineApi({
|
|
46
|
+
GET: async () => ({ name: 'John' }),
|
|
47
|
+
POST: async ({ request }) => {
|
|
48
|
+
const body = await request.formData();
|
|
49
|
+
return { name: body.get('name') };
|
|
63
50
|
},
|
|
51
|
+
}, {
|
|
52
|
+
handler: loaderActionHandler,
|
|
64
53
|
});
|
|
65
54
|
```
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
### `defineApi(handlers)`
|
|
70
|
-
|
|
71
|
-
| Parameter | Type | Description |
|
|
72
|
-
| ---------- | ------------- | ---------------------------------------- |
|
|
73
|
-
| `handlers` | `ApiHandlers` | Map of HTTP methods to handler functions |
|
|
74
|
-
|
|
75
|
-
Returns `{ loader, action }` — each is `undefined` if no relevant methods are defined.
|
|
56
|
+
Example `loaderActionHandler`:
|
|
76
57
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
58
|
+
```ts
|
|
59
|
+
const loaderActionHandler = (fn) => async (args) => {
|
|
60
|
+
try {
|
|
61
|
+
const result = await fn(args);
|
|
62
|
+
return { success: true, status: 200, data: result };
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return { success: false, status: 500, message: String(error) };
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
```
|
|
82
68
|
|
|
83
|
-
|
|
69
|
+
### Response type helpers
|
|
84
70
|
|
|
85
|
-
|
|
71
|
+
Access inferred response types for client-side fetchers or shared contracts:
|
|
86
72
|
|
|
87
73
|
```ts
|
|
88
74
|
const api = defineApi({
|
|
89
75
|
GET: async ({ params }) => ({ id: params.id, name: 'John' }),
|
|
90
|
-
POST: async ({
|
|
91
|
-
const body = await request.formData();
|
|
92
|
-
return { created: true, name: body.get('name') };
|
|
93
|
-
},
|
|
76
|
+
POST: async () => ({ created: true }),
|
|
94
77
|
});
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
// api.action is undefined when no action methods defined
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Response type helpers
|
|
79
|
+
export const { loader, action } = api;
|
|
101
80
|
|
|
102
|
-
Access inferred response types via `typeof api.*Response` — useful for typing client-side fetchers or shared contracts:
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
81
|
type GetRes = typeof api.GetResponse;
|
|
106
82
|
// → { id: string | undefined; name: string }
|
|
107
83
|
|
|
108
84
|
type PostRes = typeof api.PostResponse;
|
|
109
|
-
// → { created: boolean
|
|
110
|
-
|
|
111
|
-
// Undefined methods → never
|
|
112
|
-
type PutRes = typeof api.PutResponse; // → never
|
|
85
|
+
// → { created: boolean }
|
|
113
86
|
```
|
|
114
87
|
|
|
115
|
-
These are **type-only** — zero runtime cost.
|
|
88
|
+
These are **type-only** — zero runtime cost. Undefined methods resolve to `never`.
|
|
116
89
|
|
|
117
90
|
## License
|
|
118
91
|
|
package/dist/index.cjs
CHANGED
|
@@ -31,17 +31,21 @@ var ACTION_METHODS = [
|
|
|
31
31
|
"PATCH",
|
|
32
32
|
"DELETE"
|
|
33
33
|
];
|
|
34
|
-
function defineApi(handlers) {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
34
|
+
function defineApi(handlers, options) {
|
|
35
|
+
const { handler: wrapper } = options ?? {};
|
|
36
|
+
const wrap = (fn) => wrapper ? wrapper(fn) : fn;
|
|
37
|
+
const loader = handlers.GET ? wrap(handlers.GET) : void 0;
|
|
38
|
+
const hasActionHandlers = ACTION_METHODS.some(
|
|
39
|
+
(m) => handlers[m] != null
|
|
40
|
+
);
|
|
41
|
+
const action = hasActionHandlers ? wrap(async (args) => {
|
|
38
42
|
const method = args.request.method.toUpperCase();
|
|
39
43
|
const handler = handlers[method];
|
|
40
44
|
if (typeof handler === "function") {
|
|
41
45
|
return handler(args);
|
|
42
46
|
}
|
|
43
47
|
throw new Response("Method Not Allowed", { status: 405 });
|
|
44
|
-
} : void 0;
|
|
48
|
+
}) : void 0;
|
|
45
49
|
return { loader, action };
|
|
46
50
|
}
|
|
47
51
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.d.cts
CHANGED
|
@@ -28,22 +28,47 @@ type ActionReturn<H> = (H extends {
|
|
|
28
28
|
} ? HandlerReturn<F> : never);
|
|
29
29
|
/** Extract response type for a specific method, or never if not defined */
|
|
30
30
|
type ResponseOf<H, M extends HttpMethod> = H extends Record<M, infer F> ? HandlerReturn<F> : never;
|
|
31
|
+
/**
|
|
32
|
+
* Higher-order function that wraps each method handler.
|
|
33
|
+
* Receives the original handler and returns a new handler with transformed behavior.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const loaderActionHandler: HandlerWrapper<BaseResponse<T>> = (fn) =>
|
|
38
|
+
* async (args) => {
|
|
39
|
+
* try {
|
|
40
|
+
* const result = await fn(args);
|
|
41
|
+
* return { success: true, status: 200, data: result };
|
|
42
|
+
* } catch (error) {
|
|
43
|
+
* return { success: false, status: 500, message: String(error) };
|
|
44
|
+
* }
|
|
45
|
+
* };
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
type HandlerWrapper<W = unknown> = <TArgs, TResult>(fn: (args: TArgs) => Promise<TResult>) => (args: TArgs) => Promise<W>;
|
|
49
|
+
/** Options for defineApi */
|
|
50
|
+
interface DefineApiOptions<W = never> {
|
|
51
|
+
/** Higher-order function to wrap all method handlers (e.g. for error handling, response transformation) */
|
|
52
|
+
handler?: HandlerWrapper<W>;
|
|
53
|
+
}
|
|
54
|
+
/** Apply wrapper type: if wrapper is provided, use its return type; otherwise use the raw handler return */
|
|
55
|
+
type WithWrapper<R, W> = [W] extends [never] ? R : Awaited<W>;
|
|
31
56
|
/** Return type of defineApi — loader/action presence and return types inferred from handlers */
|
|
32
|
-
type ApiExports<H extends ApiHandlers> = {
|
|
57
|
+
type ApiExports<H extends ApiHandlers, W = never> = {
|
|
33
58
|
loader: H extends {
|
|
34
59
|
GET: infer F;
|
|
35
|
-
} ? (args: LoaderFunctionArgs) => Promise<HandlerReturn<F>> : undefined;
|
|
36
|
-
action: HasActionMethods<H> extends true ? (args: ActionFunctionArgs) => Promise<ActionReturn<H>> : undefined;
|
|
60
|
+
} ? (args: LoaderFunctionArgs) => Promise<WithWrapper<HandlerReturn<F>, W>> : undefined;
|
|
61
|
+
action: HasActionMethods<H> extends true ? (args: ActionFunctionArgs) => Promise<WithWrapper<ActionReturn<H>, W>> : undefined;
|
|
37
62
|
/** Inferred return type of the GET handler */
|
|
38
|
-
GetResponse: ResponseOf<H, 'GET'>;
|
|
63
|
+
GetResponse: WithWrapper<ResponseOf<H, 'GET'>, W>;
|
|
39
64
|
/** Inferred return type of the POST handler */
|
|
40
|
-
PostResponse: ResponseOf<H, 'POST'>;
|
|
65
|
+
PostResponse: WithWrapper<ResponseOf<H, 'POST'>, W>;
|
|
41
66
|
/** Inferred return type of the PUT handler */
|
|
42
|
-
PutResponse: ResponseOf<H, 'PUT'>;
|
|
67
|
+
PutResponse: WithWrapper<ResponseOf<H, 'PUT'>, W>;
|
|
43
68
|
/** Inferred return type of the PATCH handler */
|
|
44
|
-
PatchResponse: ResponseOf<H, 'PATCH'>;
|
|
69
|
+
PatchResponse: WithWrapper<ResponseOf<H, 'PATCH'>, W>;
|
|
45
70
|
/** Inferred return type of the DELETE handler */
|
|
46
|
-
DeleteResponse: ResponseOf<H, 'DELETE'>;
|
|
71
|
+
DeleteResponse: WithWrapper<ResponseOf<H, 'DELETE'>, W>;
|
|
47
72
|
};
|
|
48
73
|
|
|
49
74
|
/**
|
|
@@ -52,18 +77,24 @@ type ApiExports<H extends ApiHandlers> = {
|
|
|
52
77
|
*
|
|
53
78
|
* @example
|
|
54
79
|
* ```ts
|
|
55
|
-
* const
|
|
80
|
+
* export const { loader, action } = defineApi({
|
|
56
81
|
* GET: async ({ params }) => ({ user: params.id }),
|
|
57
82
|
* POST: async ({ request }) => {
|
|
58
83
|
* const body = await request.formData();
|
|
59
84
|
* return { created: true };
|
|
60
85
|
* },
|
|
61
86
|
* });
|
|
87
|
+
* ```
|
|
62
88
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
89
|
+
* @example With handler wrapper
|
|
90
|
+
* ```ts
|
|
91
|
+
* export const { loader, action } = defineApi({
|
|
92
|
+
* GET: async () => ({ data: 'ok' }),
|
|
93
|
+
* }, {
|
|
94
|
+
* handler: loaderActionHandler,
|
|
95
|
+
* });
|
|
65
96
|
* ```
|
|
66
97
|
*/
|
|
67
|
-
declare function defineApi<const H extends ApiHandlers>(handlers: H): ApiExports<H>;
|
|
98
|
+
declare function defineApi<const H extends ApiHandlers, W = never>(handlers: H, options?: DefineApiOptions<W>): ApiExports<H, W>;
|
|
68
99
|
|
|
69
|
-
export { type ApiHandlers, type HttpMethod, type MethodHandler, defineApi };
|
|
100
|
+
export { type ApiHandlers, type DefineApiOptions, type HandlerWrapper, type HttpMethod, type MethodHandler, defineApi };
|
package/dist/index.d.ts
CHANGED
|
@@ -28,22 +28,47 @@ type ActionReturn<H> = (H extends {
|
|
|
28
28
|
} ? HandlerReturn<F> : never);
|
|
29
29
|
/** Extract response type for a specific method, or never if not defined */
|
|
30
30
|
type ResponseOf<H, M extends HttpMethod> = H extends Record<M, infer F> ? HandlerReturn<F> : never;
|
|
31
|
+
/**
|
|
32
|
+
* Higher-order function that wraps each method handler.
|
|
33
|
+
* Receives the original handler and returns a new handler with transformed behavior.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const loaderActionHandler: HandlerWrapper<BaseResponse<T>> = (fn) =>
|
|
38
|
+
* async (args) => {
|
|
39
|
+
* try {
|
|
40
|
+
* const result = await fn(args);
|
|
41
|
+
* return { success: true, status: 200, data: result };
|
|
42
|
+
* } catch (error) {
|
|
43
|
+
* return { success: false, status: 500, message: String(error) };
|
|
44
|
+
* }
|
|
45
|
+
* };
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
type HandlerWrapper<W = unknown> = <TArgs, TResult>(fn: (args: TArgs) => Promise<TResult>) => (args: TArgs) => Promise<W>;
|
|
49
|
+
/** Options for defineApi */
|
|
50
|
+
interface DefineApiOptions<W = never> {
|
|
51
|
+
/** Higher-order function to wrap all method handlers (e.g. for error handling, response transformation) */
|
|
52
|
+
handler?: HandlerWrapper<W>;
|
|
53
|
+
}
|
|
54
|
+
/** Apply wrapper type: if wrapper is provided, use its return type; otherwise use the raw handler return */
|
|
55
|
+
type WithWrapper<R, W> = [W] extends [never] ? R : Awaited<W>;
|
|
31
56
|
/** Return type of defineApi — loader/action presence and return types inferred from handlers */
|
|
32
|
-
type ApiExports<H extends ApiHandlers> = {
|
|
57
|
+
type ApiExports<H extends ApiHandlers, W = never> = {
|
|
33
58
|
loader: H extends {
|
|
34
59
|
GET: infer F;
|
|
35
|
-
} ? (args: LoaderFunctionArgs) => Promise<HandlerReturn<F>> : undefined;
|
|
36
|
-
action: HasActionMethods<H> extends true ? (args: ActionFunctionArgs) => Promise<ActionReturn<H>> : undefined;
|
|
60
|
+
} ? (args: LoaderFunctionArgs) => Promise<WithWrapper<HandlerReturn<F>, W>> : undefined;
|
|
61
|
+
action: HasActionMethods<H> extends true ? (args: ActionFunctionArgs) => Promise<WithWrapper<ActionReturn<H>, W>> : undefined;
|
|
37
62
|
/** Inferred return type of the GET handler */
|
|
38
|
-
GetResponse: ResponseOf<H, 'GET'>;
|
|
63
|
+
GetResponse: WithWrapper<ResponseOf<H, 'GET'>, W>;
|
|
39
64
|
/** Inferred return type of the POST handler */
|
|
40
|
-
PostResponse: ResponseOf<H, 'POST'>;
|
|
65
|
+
PostResponse: WithWrapper<ResponseOf<H, 'POST'>, W>;
|
|
41
66
|
/** Inferred return type of the PUT handler */
|
|
42
|
-
PutResponse: ResponseOf<H, 'PUT'>;
|
|
67
|
+
PutResponse: WithWrapper<ResponseOf<H, 'PUT'>, W>;
|
|
43
68
|
/** Inferred return type of the PATCH handler */
|
|
44
|
-
PatchResponse: ResponseOf<H, 'PATCH'>;
|
|
69
|
+
PatchResponse: WithWrapper<ResponseOf<H, 'PATCH'>, W>;
|
|
45
70
|
/** Inferred return type of the DELETE handler */
|
|
46
|
-
DeleteResponse: ResponseOf<H, 'DELETE'>;
|
|
71
|
+
DeleteResponse: WithWrapper<ResponseOf<H, 'DELETE'>, W>;
|
|
47
72
|
};
|
|
48
73
|
|
|
49
74
|
/**
|
|
@@ -52,18 +77,24 @@ type ApiExports<H extends ApiHandlers> = {
|
|
|
52
77
|
*
|
|
53
78
|
* @example
|
|
54
79
|
* ```ts
|
|
55
|
-
* const
|
|
80
|
+
* export const { loader, action } = defineApi({
|
|
56
81
|
* GET: async ({ params }) => ({ user: params.id }),
|
|
57
82
|
* POST: async ({ request }) => {
|
|
58
83
|
* const body = await request.formData();
|
|
59
84
|
* return { created: true };
|
|
60
85
|
* },
|
|
61
86
|
* });
|
|
87
|
+
* ```
|
|
62
88
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
89
|
+
* @example With handler wrapper
|
|
90
|
+
* ```ts
|
|
91
|
+
* export const { loader, action } = defineApi({
|
|
92
|
+
* GET: async () => ({ data: 'ok' }),
|
|
93
|
+
* }, {
|
|
94
|
+
* handler: loaderActionHandler,
|
|
95
|
+
* });
|
|
65
96
|
* ```
|
|
66
97
|
*/
|
|
67
|
-
declare function defineApi<const H extends ApiHandlers>(handlers: H): ApiExports<H>;
|
|
98
|
+
declare function defineApi<const H extends ApiHandlers, W = never>(handlers: H, options?: DefineApiOptions<W>): ApiExports<H, W>;
|
|
68
99
|
|
|
69
|
-
export { type ApiHandlers, type HttpMethod, type MethodHandler, defineApi };
|
|
100
|
+
export { type ApiHandlers, type DefineApiOptions, type HandlerWrapper, type HttpMethod, type MethodHandler, defineApi };
|
package/dist/index.js
CHANGED
|
@@ -5,17 +5,21 @@ var ACTION_METHODS = [
|
|
|
5
5
|
"PATCH",
|
|
6
6
|
"DELETE"
|
|
7
7
|
];
|
|
8
|
-
function defineApi(handlers) {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
8
|
+
function defineApi(handlers, options) {
|
|
9
|
+
const { handler: wrapper } = options ?? {};
|
|
10
|
+
const wrap = (fn) => wrapper ? wrapper(fn) : fn;
|
|
11
|
+
const loader = handlers.GET ? wrap(handlers.GET) : void 0;
|
|
12
|
+
const hasActionHandlers = ACTION_METHODS.some(
|
|
13
|
+
(m) => handlers[m] != null
|
|
14
|
+
);
|
|
15
|
+
const action = hasActionHandlers ? wrap(async (args) => {
|
|
12
16
|
const method = args.request.method.toUpperCase();
|
|
13
17
|
const handler = handlers[method];
|
|
14
18
|
if (typeof handler === "function") {
|
|
15
19
|
return handler(args);
|
|
16
20
|
}
|
|
17
21
|
throw new Response("Method Not Allowed", { status: 405 });
|
|
18
|
-
} : void 0;
|
|
22
|
+
}) : void 0;
|
|
19
23
|
return { loader, action };
|
|
20
24
|
}
|
|
21
25
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-router-define-api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Define HTTP method handlers for React Router v7 routes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -49,5 +49,13 @@
|
|
|
49
49
|
"typescript": "^6.0.2",
|
|
50
50
|
"ultracite": "^7.4.0",
|
|
51
51
|
"vitest": "^4.1.2"
|
|
52
|
-
}
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "https://github.com/sonicname/react-router-define-api.git"
|
|
56
|
+
},
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/sonicname/react-router-define-api/issues"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/sonicname/react-router-define-api#readme"
|
|
53
61
|
}
|