react-router-define-api 0.1.1 → 0.1.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/README.md CHANGED
@@ -18,8 +18,7 @@ import { defineApi } from 'react-router-define-api';
18
18
 
19
19
  export const { loader, action } = defineApi({
20
20
  GET: async ({ params }) => {
21
- const users = await db.users.findMany();
22
- return { users };
21
+ return { users: await db.users.findMany() };
23
22
  },
24
23
  POST: async ({ request }) => {
25
24
  const body = await request.formData();
@@ -38,6 +37,35 @@ export const { loader, action } = defineApi({
38
37
  - `POST`, `PUT`, `PATCH`, `DELETE` → dispatched inside `action` by `request.method`
39
38
  - Undefined methods → `405 Method Not Allowed`
40
39
 
40
+ ### Handler wrapper
41
+
42
+ Wrap all handlers with a higher-order function for error handling, response transformation, logging, etc.:
43
+
44
+ ```ts
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') };
50
+ },
51
+ }, {
52
+ handler: loaderActionHandler,
53
+ });
54
+ ```
55
+
56
+ Example `loaderActionHandler`:
57
+
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
+ ```
68
+
41
69
  ### Response type helpers
42
70
 
43
71
  Access inferred response types for client-side fetchers or shared contracts:
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 loader = handlers.GET ?? void 0;
36
- const hasActionHandlers = ACTION_METHODS.some((m) => handlers[m] != null);
37
- const action = hasActionHandlers ? async (args) => {
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<unknown>> =
38
+ * (fn) => 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> = (fn: (args: LoaderFunctionArgs | ActionFunctionArgs) => unknown) => (args: LoaderFunctionArgs | ActionFunctionArgs) => 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 api = defineApi({
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
- * export const loader = api.loader;
64
- * export const action = api.action;
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<unknown>> =
38
+ * (fn) => 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> = (fn: (args: LoaderFunctionArgs | ActionFunctionArgs) => unknown) => (args: LoaderFunctionArgs | ActionFunctionArgs) => 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 api = defineApi({
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
- * export const loader = api.loader;
64
- * export const action = api.action;
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 loader = handlers.GET ?? void 0;
10
- const hasActionHandlers = ACTION_METHODS.some((m) => handlers[m] != null);
11
- const action = hasActionHandlers ? async (args) => {
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.1",
3
+ "version": "0.1.3",
4
4
  "description": "Define HTTP method handlers for React Router v7 routes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",