@creatorem/next-trpc 1.0.1 → 1.0.5

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,2 +1,215 @@
1
1
  # next-trpc
2
- A simple typed rpc interface to easily type api endpoints in a app router nextjs application
2
+
3
+ A simple typed rpc interface to easily type api endpoints in an app router nextjs application.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install @creatorem/next-trpc
9
+ ```
10
+
11
+ <br/>
12
+
13
+ ## Setup
14
+
15
+ ### Create a router file
16
+
17
+ Contains your api endpoints.
18
+
19
+ `trpc/router.ts`
20
+
21
+ ```ts
22
+ import { router, endpoint } from "@creatorem/next-trpc";
23
+ import z from "zod";
24
+
25
+ export const appRouter = router({
26
+ getUser: endpoint.action(async ({ db }) => {
27
+ return await myAsyncFunction();
28
+ }),
29
+ greeting: endpoint
30
+ .input(
31
+ // you can add zod typechecking to your entry params
32
+ z.object({
33
+ name: z.string(),
34
+ age: z.coerce.number(),
35
+ })
36
+ )
37
+ .action(({ name, age }) => {
38
+ return `Hi my name is ${name}, and I am ${age} years old.`;
39
+ }),
40
+ });
41
+
42
+ export type AppRouter = typeof appRouter;
43
+ ```
44
+
45
+ ### Connect your router to an api endpoint.
46
+
47
+ `app/api/trpc/[trpc]/route.ts`
48
+
49
+ ```ts
50
+ import { createTrpcAPI } from "@creatorem/next-trpc/server";
51
+ import { appRouter } from "~/trpc/router";
52
+
53
+ const handler = createTrpcAPI({
54
+ router: appRouter,
55
+ });
56
+
57
+ export { handler as GET, handler as POST };
58
+ ```
59
+
60
+ ### Start fetching with a type safe client!
61
+
62
+ `trpc/client.ts`
63
+
64
+ ```ts
65
+ import { envs } from "~/envs";
66
+ import { createTrpcClient } from "@creatorem/next-trpc/client";
67
+ import { type AppRouter } from "./router";
68
+
69
+ const url = envs().NEXT_PUBLIC_YOUR_APP_URL + "/api/trpc";
70
+
71
+ export const trpc = createTrpcClient<AppRouter>({
72
+ url,
73
+ headers: async () => {
74
+ // add custom headers like Authorization to make it works with auth logic
75
+ return {
76
+ /* Authorization: `Bearer ${jwt!}` */
77
+ };
78
+ },
79
+ });
80
+ ```
81
+
82
+ Done !
83
+
84
+ ## Usage
85
+
86
+ Now you can use the `trpc` client and server side.
87
+
88
+ `app/layout.tsx`
89
+
90
+ ```tsx
91
+ import React from "react";
92
+ import { redirect } from "next/navigation";
93
+ import { trpc } from "~/trpc/client";
94
+
95
+ export default async function Layout(
96
+ props: React.PropsWithChildren
97
+ ): Promise<React.JSX.Element> {
98
+ const user = await trpc.getUser.fetch();
99
+
100
+ if (!user) {
101
+ return redirect(/* path to login page */);
102
+ }
103
+
104
+ return <>{props.children}</>;
105
+ }
106
+ ```
107
+
108
+ ### Integrated useQuery hook usage
109
+
110
+ We offer a client side only function to create client object that pre-implement the `useQuery` hook from `@tanstack/react-query` package.
111
+
112
+ You need to have `@tanstack/react-query` installed.
113
+
114
+ ```sh
115
+ npm install @tanstack/react-query
116
+ ```
117
+
118
+ Then you can create the following file :
119
+
120
+ `trpc/query-client.ts`
121
+
122
+ ```ts
123
+ import "client-only";
124
+
125
+ import { envs } from "~/envs";
126
+ import { createTrpcQueryClient } from "@creatorem/next-trpc/query-client";
127
+ import { type AppRouter } from "./router";
128
+
129
+ const url = envs().NEXT_PUBLIC_YOUR_APP_URL + "/api/trpc";
130
+
131
+ export const clientTrpc = createTrpcQueryClient<AppRouter>({
132
+ url,
133
+ headers: async () => {
134
+ // add custom headers like Authorization to make it works with auth logic
135
+ return {
136
+ /* Authorization: `Bearer ${jwt!}` */
137
+ };
138
+ },
139
+ });
140
+ ```
141
+
142
+ Now you can do :
143
+
144
+ ```tsx
145
+ "use client";
146
+
147
+ import React from "react";
148
+ import { clientTrpc } from "~/trpc/query-client";
149
+
150
+ export const MyClientComponent: React.FC<React.PropsWithChildren> = (props) => {
151
+ const { data: user } = clientTrpc.getUser.useQuery();
152
+ /* ... */
153
+
154
+ return <>{props.children}</>;
155
+ };
156
+ ```
157
+
158
+ > [!WARNING]
159
+ > Do not forget to wrap your app with `<QueryClientProvider>`. See [installation instructions](https://tanstack.com/query/latest/docs/framework/react/installation) for more details.
160
+
161
+ ## Use a router context
162
+
163
+ You can use a context object to pass data to all your endpoint callbacks.
164
+
165
+ `trpc/router.ts`
166
+
167
+ ```ts {4-7,9}
168
+ import { CtxRouter, endpoint } from "@creatorem/next-trpc";
169
+ import z from "zod";
170
+
171
+ export const createContext = async () => {
172
+ /* your own context logic here ... */
173
+ return { db /* let's say db is a database client. */ };
174
+ };
175
+
176
+ const ctx = new CtxRouter<Awaited<ReturnType<typeof createContext>>>();
177
+
178
+ export const appRouter = ctx.router({
179
+ getUser: ctx.endpoint.action(async ({ db }) => {
180
+ return await db.user.get();
181
+ }),
182
+ greeting: ctx.endpoint
183
+ .input(
184
+ // you can add zod typechecking to your entry params
185
+ z.object({
186
+ name: z.string(),
187
+ age: z.coerce.number(),
188
+ })
189
+ )
190
+ .action(({ name, age }) => {
191
+ return `Hi my name is ${name}, and I am ${age} years old.`;
192
+ }),
193
+ });
194
+
195
+ export type AppRouter = typeof appRouter;
196
+ ```
197
+
198
+ > [!NOTE]
199
+ > Param types are serialized during the http request to the api. You need to use `z.coerce` for non-string types.
200
+
201
+ Next pass your context to the nextjs api endpoint.
202
+
203
+ `app/api/trpc/[trpc]/route.ts`
204
+
205
+ ```ts {6}
206
+ import { createTrpcAPI } from "@creatorem/next-trpc/server";
207
+ import { appRouter, createContext } from "~/trpc/router";
208
+
209
+ const handler = createTrpcAPI({
210
+ router: appRouter,
211
+ ctx: createContext,
212
+ });
213
+
214
+ export { handler as GET, handler as POST };
215
+ ```
package/dist/core.d.ts CHANGED
@@ -1,21 +1,27 @@
1
- import { type Schema } from "zod";
1
+ import z, { type Schema } from "zod";
2
2
  import type { NextRequest } from "next/server";
3
3
  export type Endpoint<Output, Input extends Schema | undefined = undefined, Ctx = {}> = {
4
4
  input?: Input;
5
5
  action: Input extends undefined ? (context: {
6
6
  request: NextRequest;
7
- } & Ctx) => Output : (input: Input extends Schema ? import("zod").infer<Input> : Input, context: {
7
+ } & Ctx) => Output : (input: Input extends Schema ? z.infer<Input> : Input, context: {
8
8
  request: NextRequest;
9
9
  } & Ctx) => Output;
10
10
  };
11
11
  export declare const endpoint: {
12
- input: <Input extends Schema, Ctx>(schema: Input) => {
13
- action: <Output>(actionFn: Endpoint<Output, Input, Ctx>["action"]) => Endpoint<Output, Input, Ctx>;
12
+ input: <Input extends Schema>(schema: Input) => {
13
+ action: <Output, Ctx = {}>(actionFn: Endpoint<Output, Input, Ctx>["action"]) => Endpoint<Output, Input, Ctx>;
14
14
  };
15
- action: <Output, Ctx>(actionFn: Endpoint<Output, undefined, Ctx>["action"]) => Endpoint<Output, undefined, Ctx>;
15
+ action: <Output, Ctx = {}>(actionFn: Endpoint<Output, undefined, Ctx>["action"]) => Endpoint<Output, undefined, Ctx>;
16
16
  };
17
17
  export type Router<Ctx> = Record<string, Endpoint<any, any, Ctx>>;
18
18
  export declare const router: <Ctx, R extends Router<Ctx>>(r: R) => R;
19
19
  export declare class CtxRouter<Ctx> {
20
+ endpoint: {
21
+ input: <Input extends Schema>(schema: Input) => {
22
+ action: <Output>(actionFn: Endpoint<Output, Input, Ctx>["action"]) => Endpoint<Output, Input, Ctx>;
23
+ };
24
+ action: <Output>(actionFn: Endpoint<Output, undefined, Ctx>["action"]) => Endpoint<Output, undefined, Ctx>;
25
+ };
20
26
  router<R extends Router<Ctx>>(r: R): R;
21
27
  }
package/dist/core.js CHANGED
@@ -1,4 +1,7 @@
1
- export const endpoint = {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CtxRouter = exports.router = exports.endpoint = void 0;
4
+ exports.endpoint = {
2
5
  input: (schema) => ({
3
6
  action: (actionFn) => ({
4
7
  input: schema,
@@ -9,9 +12,24 @@ export const endpoint = {
9
12
  action: actionFn,
10
13
  }),
11
14
  };
12
- export const router = (r) => r;
13
- export class CtxRouter {
15
+ const router = (r) => r;
16
+ exports.router = router;
17
+ class CtxRouter {
18
+ constructor() {
19
+ this.endpoint = {
20
+ input: (schema) => ({
21
+ action: (actionFn) => ({
22
+ input: schema,
23
+ action: actionFn,
24
+ }),
25
+ }),
26
+ action: (actionFn) => ({
27
+ action: actionFn,
28
+ }),
29
+ };
30
+ }
14
31
  router(r) {
15
32
  return r;
16
33
  }
17
34
  }
35
+ exports.CtxRouter = CtxRouter;
@@ -1,6 +1,6 @@
1
1
  import { type NextRequest, NextResponse } from "next/server";
2
2
  import { type router as routerFn, Router } from "./core";
3
- export declare const createRpcAPI: <Ctx>({ router, ctx, }: {
3
+ export declare const createTrpcAPI: <Ctx>({ router, ctx, }: {
4
4
  router: ReturnType<typeof routerFn<any, Router<any>>>;
5
5
  ctx?: (request: NextRequest) => Promise<Ctx>;
6
6
  }) => (request: NextRequest, context: {
@@ -1,6 +1,9 @@
1
+ "use strict";
1
2
  "server-only";
2
- import { NextResponse } from "next/server";
3
- import { camelize } from "./utils";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createTrpcAPI = void 0;
5
+ const server_1 = require("next/server");
6
+ const utils_1 = require("./utils");
4
7
  const parseInput = (request, endpoint) => {
5
8
  if (!endpoint.input)
6
9
  return undefined;
@@ -13,24 +16,24 @@ const parseInput = (request, endpoint) => {
13
16
  // Validate input with endpoint schema
14
17
  return endpoint.input.parse(paramsObj);
15
18
  };
16
- export const createRpcAPI = ({ router, ctx, }) => {
19
+ const createTrpcAPI = ({ router, ctx, }) => {
17
20
  return async function handler(request, context) {
18
21
  const params = (await context.params);
19
- if (!("rpc" in params)) {
20
- return NextResponse.json({
22
+ if (!("trpc" in params)) {
23
+ return server_1.NextResponse.json({
21
24
  data: null,
22
- error: "You must call createAPI in a [rpc]/route.ts file.",
25
+ error: "You must call createAPI in a [trpc]/route.ts file.",
23
26
  }, { status: 400 });
24
27
  }
25
- if (!params.rpc) {
26
- return NextResponse.json({
28
+ if (!params.trpc) {
29
+ return server_1.NextResponse.json({
27
30
  data: null,
28
- error: "You must pass a params in your [rpc]/you-must-put-a-param-here call",
31
+ error: "You must pass a params in your [trpc]/you-must-put-a-param-here call",
29
32
  }, { status: 400 });
30
33
  }
31
- const endpointAttribute = camelize(params.rpc);
34
+ const endpointAttribute = (0, utils_1.camelize)(params.trpc);
32
35
  if (!(endpointAttribute in router) || !router[endpointAttribute]) {
33
- return NextResponse.json({
36
+ return server_1.NextResponse.json({
34
37
  data: null,
35
38
  error: `No ${endpointAttribute} endpoints found in the router object.`,
36
39
  }, { status: 400 });
@@ -38,18 +41,19 @@ export const createRpcAPI = ({ router, ctx, }) => {
38
41
  const endpoint = router[endpointAttribute];
39
42
  try {
40
43
  const input = parseInput(request, endpoint);
41
- const context = ctx ? await ctx(request) : {};
44
+ const context = (ctx ? (await ctx(request)) : {});
42
45
  const result = endpoint.input
43
46
  ? await endpoint.action(input, Object.assign(Object.assign({}, context), { request }))
44
47
  : await endpoint.action(Object.assign(Object.assign({}, context), { request }));
45
- return NextResponse.json({ data: result }, { status: 200 });
48
+ return server_1.NextResponse.json({ data: result }, { status: 200 });
46
49
  }
47
50
  catch (error) {
48
51
  if (error instanceof Error && "issues" in error) {
49
52
  // Zod validation error
50
- return NextResponse.json({ data: null, error: "Invalid request data", details: error.message }, { status: 400 });
53
+ return server_1.NextResponse.json({ data: null, error: "Invalid request data", details: error.message }, { status: 400 });
51
54
  }
52
- return NextResponse.json({ data: null, error: "Internal Server Error" }, { status: 500 });
55
+ return server_1.NextResponse.json({ data: null, error: "Internal Server Error" }, { status: 500 });
53
56
  }
54
57
  };
55
58
  };
59
+ exports.createTrpcAPI = createTrpcAPI;
@@ -0,0 +1,19 @@
1
+ import { Router, type Endpoint, type router } from "./core";
2
+ import z from "zod";
3
+ export type EndpointClient<Input, Output> = Input extends undefined ? {
4
+ fetch: () => Promise<Output>;
5
+ } : {
6
+ fetch: (input: Input extends import("zod").Schema ? z.infer<Input> : Input) => Promise<Output>;
7
+ };
8
+ type TrpcClient<R extends Router<any>> = {
9
+ [K in keyof R]: R[K] extends Endpoint<infer Output, infer Input, any> ? EndpointClient<Input, Output> : never;
10
+ };
11
+ export declare const getTrpcFetch: ({ endpointSlug, url, headers, }: {
12
+ endpointSlug: string;
13
+ } & createTrpcClientOptions) => (input?: any) => Promise<any>;
14
+ export interface createTrpcClientOptions {
15
+ url: string;
16
+ headers?: HeadersInit | (() => Promise<HeadersInit>);
17
+ }
18
+ export declare const createTrpcClient: <R extends ReturnType<typeof router<any, Router<any>>>>(opts: createTrpcClientOptions) => TrpcClient<R>;
19
+ export {};
@@ -1,6 +1,9 @@
1
- import { kebabize } from "./utils";
2
- export const getRpcFetch = ({ endpointSlug, url, headers, }) => async (input) => {
3
- const endpointName = kebabize(endpointSlug);
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTrpcClient = exports.getTrpcFetch = void 0;
4
+ const utils_1 = require("./utils");
5
+ const getTrpcFetch = ({ endpointSlug, url, headers, }) => async (input) => {
6
+ const endpointName = (0, utils_1.kebabize)(endpointSlug);
4
7
  // Build URL with search params if input exists
5
8
  let requestUrl = `${url}/${endpointName}`;
6
9
  if (input) {
@@ -10,7 +13,7 @@ export const getRpcFetch = ({ endpointSlug, url, headers, }) => async (input) =>
10
13
  }
11
14
  requestUrl += `?${searchParams.toString()}`;
12
15
  }
13
- const headerObject = typeof headers === "function" ? await headers() : headers;
16
+ const headerObject = typeof headers === "function" ? await headers() : headers || {};
14
17
  const response = await fetch(requestUrl, {
15
18
  method: "GET",
16
19
  headers: Object.assign({ "Content-Type": "application/json" }, headerObject),
@@ -22,16 +25,17 @@ export const getRpcFetch = ({ endpointSlug, url, headers, }) => async (input) =>
22
25
  const result = await response.json();
23
26
  return result.data;
24
27
  };
25
- // export const createRpcClient = <R extends ReturnType<typeof router>>({
26
- export const createRpcClient = (opts) => {
28
+ exports.getTrpcFetch = getTrpcFetch;
29
+ const createTrpcClient = (opts) => {
27
30
  return new Proxy({}, {
28
31
  get(target, prop) {
29
32
  if (typeof prop === "string") {
30
33
  return {
31
- fetch: getRpcFetch(Object.assign({ endpointSlug: prop }, opts)),
34
+ fetch: (0, exports.getTrpcFetch)(Object.assign({ endpointSlug: prop }, opts)),
32
35
  };
33
36
  }
34
37
  return undefined;
35
38
  },
36
39
  });
37
40
  };
41
+ exports.createTrpcClient = createTrpcClient;
@@ -0,0 +1,10 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { type EndpointClient, createTrpcClientOptions } from "./create-trpc-client";
3
+ import type { Router, Endpoint, router } from "./core";
4
+ type TrpcClientWithQuery<R extends Router<any>> = {
5
+ [K in keyof R]: R[K] extends Endpoint<infer Output, infer Input, any> ? EndpointClient<Input, Output> & {
6
+ useQuery: (queryOptions?: Omit<Parameters<typeof useQuery>[0], "queryKey" | "queryFn">) => ReturnType<typeof useQuery<Promise<Output>, Error, Promise<Output>, string[]>>;
7
+ } : never;
8
+ };
9
+ export declare const createTrpcQueryClient: <R extends ReturnType<typeof router<any, Router<any>>>>(opts: createTrpcClientOptions) => TrpcClientWithQuery<R>;
10
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createTrpcQueryClient = void 0;
5
+ const react_query_1 = require("@tanstack/react-query");
6
+ const create_trpc_client_1 = require("./create-trpc-client");
7
+ const createTrpcQueryClient = (opts) => {
8
+ return new Proxy({}, {
9
+ get(target, prop) {
10
+ if (typeof prop === "string") {
11
+ return {
12
+ fetch: (0, create_trpc_client_1.getTrpcFetch)(Object.assign({ endpointSlug: prop }, opts)),
13
+ useQuery: (queryOptions) => {
14
+ const endpointName = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
15
+ return (0, react_query_1.useQuery)(Object.assign(Object.assign({}, queryOptions), { queryKey: [endpointName], queryFn: (0, create_trpc_client_1.getTrpcFetch)(Object.assign({ endpointSlug: prop }, opts)) }));
16
+ },
17
+ };
18
+ }
19
+ return undefined;
20
+ },
21
+ });
22
+ };
23
+ exports.createTrpcQueryClient = createTrpcQueryClient;