Compare commits

..

253 Commits

Author SHA1 Message Date
wobsoriano
7c8664d37d chore: release v0.4.4 2022-12-18 23:43:20 -08:00
Robert Soriano
be20e63b79 Merge pull request #54 from wobsoriano/with-client
Bringing back tRPC Client
2022-12-18 23:38:54 -08:00
wobsoriano
5b1cdb6846 update installation 2022-12-18 23:33:47 -08:00
wobsoriano
80573fb32e move links outside of client 2022-12-18 23:31:55 -08:00
wobsoriano
f01b8d5d8d docs: improve docs 2022-12-18 23:25:46 -08:00
wobsoriano
09e0a6c479 feat: option to select headers to pass to useRequestHeaders in http links 2022-12-18 22:35:36 -08:00
wobsoriano
a5cb0c754e move links to another file 2022-12-18 22:22:15 -08:00
wobsoriano
ff88817168 feat: add convenience link wrappers 2022-12-18 22:17:37 -08:00
wobsoriano
f1ca99521e remove unused imports 2022-12-18 21:53:20 -08:00
wobsoriano
143092a16a add engines property 2022-12-18 21:51:03 -08:00
wobsoriano
27f7ed5fb4 eslint fix 2022-12-18 21:50:10 -08:00
wobsoriano
cf38d0a13b chore: update deps 2022-12-18 21:49:45 -08:00
wobsoriano
c14a154723 update example 2022-12-18 21:42:56 -08:00
wobsoriano
0f00e561a1 docs: add simple and recommended usage pages 2022-12-18 21:42:12 -08:00
wobsoriano
0f3c0fff39 docs: bump @nuxt-themes/docus to 1.1.10 2022-12-18 20:13:54 -08:00
wobsoriano
483b6e8076 feat: remove useMutation 2022-12-18 19:43:51 -08:00
wobsoriano
491c04739a cleanup 2022-12-18 16:14:59 -08:00
wobsoriano
b1ddfc146b cleanup 2022-12-18 15:36:11 -08:00
wobsoriano
ee85f3ccd1 feat: add custom Nuxt client 2022-12-18 15:35:56 -08:00
Robert Soriano
d8d4c92ae8 Merge pull request #53 from cawa-93/patch-1
docs: Add missed import in code sample
2022-12-14 23:21:48 -08:00
Alex Kozack
00da42d77e docs: Add missed import in code sample 2022-12-13 18:07:15 +02:00
Robert Soriano
3db1100c87 Merge pull request #51 from cawa-93/patch-1
docs: Replace regular `fetch` with a `$fetch` from nuxt
2022-12-12 20:42:12 -08:00
Robert Soriano
fdde3f1db4 Merge pull request #52 from cawa-93/next
docs: Infer `Context` type
2022-12-12 19:39:41 -08:00
Alex Kozack
4ff57dfb97 docs: Infer Context type 2022-12-12 15:56:53 +02:00
Alex Kozack
aba32c7541 docs: Replace regular fetch with a $fetch from nuxt
This feature was implementet in https://github.com/wobsoriano/trpc-nuxt/pull/28 and describe better way to work with ssr
2022-12-12 15:17:28 +02:00
Robert Soriano
01c2bd5701 Merge pull request #47 from nkhdo/patch-2
Update 1.installation.md
2022-11-28 10:51:57 -08:00
Hoang Do
fe2e45be70 Update 1.installation.md
`@trpc/server` and `@trpc/client` are now pointing to v10, while the `next` tag is deprecated
2022-11-28 17:01:59 +07:00
wobsoriano
2c2df9e2bd chore: release v0.4.3 2022-11-24 13:12:18 -08:00
wobsoriano
5cf1acfa7e feat(deps): update minimum required @trpc/server and @trpc/nuxt version to 10.0.0 2022-11-24 13:12:14 -08:00
wobsoriano
f67f7fc5f4 update docs 2022-11-24 13:10:10 -08:00
wobsoriano
e11edc59eb update docs 2022-11-24 13:08:28 -08:00
wobsoriano
e522a59a4c update docs 2022-11-24 13:07:25 -08:00
wobsoriano
1d7be4642d update docs 2022-11-24 13:07:13 -08:00
wobsoriano
3552017e4f update docs 2022-11-24 13:05:32 -08:00
wobsoriano
de690a7914 update docs 2022-11-24 13:00:40 -08:00
wobsoriano
d2666650de update docs 2022-11-24 12:57:29 -08:00
wobsoriano
e3d35c6b04 update docs 2022-11-24 12:56:59 -08:00
wobsoriano
b569afde50 update docs 2022-11-24 12:53:17 -08:00
wobsoriano
d4f3942fff update docs 2022-11-24 12:52:17 -08:00
wobsoriano
68003e9c3e update docs 2022-11-24 12:51:28 -08:00
Robert Soriano
b27938f108 Merge pull request #44 from benfavre/patch-1
Update package.json
2022-11-21 20:15:33 -08:00
Webdesign29
9e443ac559 Update package.json
Fix calling --watch for tsup when using "dev" command
2022-11-19 22:11:12 +01:00
wobsoriano
8ba002407a update lockfile 2022-11-17 02:44:00 -08:00
wobsoriano
f061b0531f chore: release v0.4.2 2022-11-17 02:42:20 -08:00
wobsoriano
f08b724df7 feat: get req,res values from event.node 2022-11-17 02:42:01 -08:00
wobsoriano
4085d5fe26 fix: request types 2022-11-17 02:41:01 -08:00
wobsoriano
13713343ce fix: request types 2022-11-17 02:39:45 -08:00
wobsoriano
d6f11bb301 feat(deps): bump h3 to 1.0.1 2022-11-17 02:38:27 -08:00
wobsoriano
c5c1f1cdba feat(deps): bump h3, ohash and ufo to 1.0.0 2022-11-17 02:38:17 -08:00
wobsoriano
0681dd554d Merge branch 'next' of https://github.com/wobsoriano/trpc-nuxt into next 2022-11-16 08:48:14 -08:00
wobsoriano
e7a76694af update docs 2022-11-16 08:48:09 -08:00
wobsoriano
da75c18ebe update local nuxt dep to stable 2022-11-16 08:42:45 -08:00
wobsoriano
6289575900 bump local trpc deps to rc.8 2022-11-16 08:40:35 -08:00
Robert Soriano
ac212672e4 Update 2.server-side-calls.md 2022-11-14 12:26:48 -08:00
Robert Soriano
b8e2a83e26 Merge pull request #42 from nkhdo/patch-1
Update 2.server-side-calls.md
2022-11-14 10:43:06 -08:00
Hoang Do
fd98b0d414 Update 2.server-side-calls.md
Fix wrong variable import
2022-11-15 00:46:06 +07:00
wobsoriano
20ab235856 docs: add auth tips 2022-11-13 11:18:57 -08:00
wobsoriano
7205c356f8 docs: update aborting procs 2022-11-13 11:08:28 -08:00
wobsoriano
e14827e0c7 docs: add aborting procs 2022-11-13 11:05:47 -08:00
wobsoriano
eea6406d5a docs: update example 2022-11-13 10:58:27 -08:00
wobsoriano
2d3409a25b docs: update example 2022-11-13 10:58:08 -08:00
wobsoriano
7a71a6e128 docs: add mutation example 2022-11-13 10:55:23 -08:00
wobsoriano
d8e89297cd docs: typo fix 2022-11-13 10:41:35 -08:00
wobsoriano
6768205318 docs: update composables 2022-11-13 10:41:22 -08:00
wobsoriano
c20a092a31 docs: add tips section 2022-11-13 10:37:24 -08:00
wobsoriano
64df8fb7ad infer error in playground 2022-11-13 09:43:23 -08:00
wobsoriano
d5567e5826 remove character 2022-11-12 21:16:19 -08:00
wobsoriano
6b898e836c update readme 2022-11-12 21:15:19 -08:00
wobsoriano
fd38c4f983 chore: release v0.4.1 2022-11-12 20:16:38 -08:00
wobsoriano
48d4c6342f update playground 2022-11-12 20:16:29 -08:00
wobsoriano
297c3d52f4 docs: update usage 2022-11-12 20:15:52 -08:00
wobsoriano
0f9653d1a4 remove /module 2022-11-12 20:12:36 -08:00
wobsoriano
85998101c7 feat: remove nuxt 3 wrappers for client 2022-11-12 20:10:50 -08:00
wobsoriano
d6cb770154 update installation instructions 2022-11-12 18:41:44 -08:00
wobsoriano
c222008524 update installation instructions 2022-11-12 18:41:16 -08:00
wobsoriano
9cd21eb300 update readme docs url 2022-11-12 18:40:15 -08:00
wobsoriano
b4e83dbb0b add back preset 2022-11-12 18:38:25 -08:00
wobsoriano
0fa63c86ba test node preset 2022-11-12 18:31:35 -08:00
wobsoriano
7371dc4c10 update docs 2022-11-12 18:28:26 -08:00
wobsoriano
77ab5f0dc6 reinstall deps 2022-11-12 18:20:07 -08:00
wobsoriano
ba15d15d67 remove patched @vueuse/head 2022-11-12 18:17:10 -08:00
wobsoriano
7999039088 update github config 2022-11-12 18:13:49 -08:00
wobsoriano
dc15500074 dont prerender index 2022-11-12 18:13:00 -08:00
wobsoriano
99a83562aa add tmp docs link 2022-11-12 18:09:34 -08:00
wobsoriano
e04344d508 docs: remove package manager 2022-11-12 18:08:22 -08:00
wobsoriano
0f9d26ab60 chore: release v0.4.0 2022-11-12 18:01:55 -08:00
wobsoriano
e6b4386bd8 remove tag 2022-11-12 18:01:10 -08:00
wobsoriano
813b9ff7d7 move eslint config to package.json 2022-11-12 17:55:32 -08:00
wobsoriano
5dce1f4681 update readme 2022-11-12 17:54:17 -08:00
wobsoriano
470ba96be2 transpile github module 2022-11-12 16:32:18 -08:00
wobsoriano
4532add2a7 docs: patch vueuse/head 2022-11-12 16:26:43 -08:00
wobsoriano
876ed1d8d2 bump local trpc deps to rc.7 2022-11-08 09:30:58 -08:00
wobsoriano
611e22d205 delete merge conflicts 2022-11-08 09:27:22 -08:00
wobsoriano
c1857f1079 eslint fix 2022-11-08 09:25:21 -08:00
wobsoriano
796fa43a83 fix lockfile 2022-11-08 09:23:14 -08:00
wobsoriano
e3ce0bcaae merge conflicts 2022-11-08 09:22:30 -08:00
wobsoriano
e916f17d60 update docs 2022-11-08 09:06:08 -08:00
wobsoriano
0fdb9e0e17 update colors 2022-11-08 09:05:36 -08:00
wobsoriano
5bd5756d26 update og image 2022-11-08 09:00:41 -08:00
wobsoriano
67f95756ab fix docs 2022-11-08 08:59:32 -08:00
wobsoriano
1fc4d9d0d0 Revert "feat: add file routing option"
This reverts commit 750783e860.
2022-11-05 21:24:24 -07:00
wobsoriano
c11a5cfa8a remove examples folder 2022-11-05 20:13:48 -07:00
wobsoriano
92c9f6390c disable file routing by default 2022-11-05 20:06:02 -07:00
wobsoriano
3a6c5b659b update function names 2022-11-05 19:50:39 -07:00
wobsoriano
750783e860 feat: add file routing option 2022-11-05 19:22:19 -07:00
wobsoriano
d9c8f877d3 eslint fixes 2022-11-05 12:57:45 -07:00
wobsoriano
e359702c41 eslint fixes 2022-11-05 11:56:14 -07:00
wobsoriano
e9081d00ad replace eslint config 2022-11-05 11:49:46 -07:00
wobsoriano
83f98e34fa more eslint fixes 2022-11-04 08:04:58 -07:00
wobsoriano
512a0ae7b1 more eslint fixes 2022-11-04 08:04:46 -07:00
wobsoriano
c89f8747ea bump local deps 2022-11-04 08:04:28 -07:00
wobsoriano
3e8a2de6e5 more eslint fixes 2022-11-04 08:02:52 -07:00
wobsoriano
5d98873cc3 eslint fix 2022-11-04 08:01:24 -07:00
wobsoriano
a8e5538b05 release v0.3.3 2022-11-04 07:58:20 -07:00
wobsoriano
24ecb41429 eslint fix 2022-11-04 07:58:16 -07:00
wobsoriano
e8bf427d2b fix types 2022-11-04 07:57:38 -07:00
wobsoriano
347d49482f reinstall deps 2022-11-04 07:55:39 -07:00
wobsoriano
2f7a1276ad feat(deps): bump @nuxt/kit to 3.0.0-rc.13 2022-11-04 07:53:42 -07:00
wobsoriano
f6db0c78cd add .vercel to .gitignore 2022-11-04 07:51:44 -07:00
wobsoriano
d6012eb5c8 feat: import vue functions from #imports 2022-11-04 07:49:37 -07:00
wobsoriano
c1ab3ecf77 update scripts 2022-11-04 07:44:09 -07:00
wobsoriano
0e8f6cf9f6 update playground 2022-11-04 07:41:27 -07:00
wobsoriano
0f5c09c7e0 update docs 2022-11-04 07:40:49 -07:00
wobsoriano
5080e9a4a7 bump local trpc deps to 10.0.0-rc.4 2022-11-04 07:39:50 -07:00
wobsoriano
7490dbd090 chore: bump to 0.4.0-beta.15 2022-11-04 07:37:49 -07:00
wobsoriano
1dc0ca7808 feat: remove custom async data error handler 2022-11-04 07:37:08 -07:00
wobsoriano
ffb692a168 feat(deps): bump nuxt to 3.0.0-rc.13 2022-11-04 07:34:47 -07:00
wobsoriano
d3203f0536 remove docs 2022-11-02 09:43:35 -07:00
wobsoriano
83e2b2810d release v0.3.2 2022-11-02 09:42:08 -07:00
wobsoriano
2b4fff9faa feat(deps): bump h3 to 0.8.6 2022-11-02 09:41:39 -07:00
wobsoriano
752fe721a2 feat(deps): lock @trpc/server and @trpc/client to version 9 2022-11-02 09:40:48 -07:00
wobsoriano
808cac4756 eslint fixes 2022-11-02 09:38:14 -07:00
wobsoriano
0c44f73c9f log info about plugin installation 2022-11-02 09:34:16 -07:00
wobsoriano
182875a781 feat: add option to specify if plugin should be installed 2022-11-02 09:30:40 -07:00
wobsoriano
0298f836b8 more eslint fixes 2022-11-02 09:21:15 -07:00
wobsoriano
ea50430945 eslint fixes 2022-11-02 09:18:17 -07:00
wobsoriano
0e240663ca remove local pnpm deps 2022-11-02 09:18:00 -07:00
wobsoriano
bb5e02383e remove unused moduile 2022-11-02 08:59:55 -07:00
wobsoriano
c01dddea02 remove local pnpm 2022-11-02 08:56:58 -07:00
wobsoriano
f92aec5b91 feat(deps): bump h3 to 0.8.6 2022-11-02 08:55:52 -07:00
wobsoriano
b6f92d954c update local trpc dependencies to 10.0.0-rc.3 2022-11-02 08:54:58 -07:00
wobsoriano
9108dcbb1d feat: never cache mutation 2022-10-31 22:39:40 -07:00
Robert Soriano
07f415640f bump to 0.4.0-beta.14 2022-10-31 10:09:07 -07:00
Robert Soriano
d5d211d2a8 feat: add subscription types 2022-10-31 10:06:41 -07:00
Robert Soriano
3bfd3585b3 remove unused types 2022-10-31 09:58:51 -07:00
Robert Soriano
8ba8d79114 remove unused generic type 2022-10-31 09:55:00 -07:00
Robert Soriano
b7f72eac0a feat: respect user added abort signal 2022-10-31 09:53:00 -07:00
Robert Soriano
4c8a6d7b35 feat: add abortOnUnmount option 2022-10-31 09:50:15 -07:00
Robert Soriano
76619ac541 bump to 0.4.0-beta.13 2022-10-31 01:01:44 -07:00
Robert Soriano
d5384cef02 add .vercel to ignored 2022-10-31 00:50:10 -07:00
Robert Soriano
fb3a0a834e add vercel static output to ignored 2022-10-31 00:43:08 -07:00
Robert Soriano
b58d662edc update docs 2022-10-31 00:37:45 -07:00
Robert Soriano
5d560867bb update http handler params 2022-10-30 22:24:49 -07:00
Robert Soriano
ac7fbf3998 update trpc param error handler 2022-10-30 22:21:08 -07:00
Robert Soriano
45fbab4c5f update docs 2022-10-30 21:40:20 -07:00
Robert Soriano
05ad1f96f6 export server types 2022-10-30 21:31:57 -07:00
Robert Soriano
41e6117113 add zod error formatting for playground 2022-10-30 21:30:07 -07:00
Robert Soriano
81c7608fb0 add batching and other opts 2022-10-30 21:25:37 -07:00
Robert Soriano
a614bc588e allow passing of other client options 2022-10-30 21:13:54 -07:00
Robert Soriano
3b0609e395 allow passing of other client options 2022-10-30 21:09:42 -07:00
Robert Soriano
0819c21b94 eslint fix 2022-10-30 18:19:32 -07:00
Robert Soriano
deea70b743 check trpc path via h3 context params 2022-10-30 18:17:14 -07:00
Robert Soriano
0ee322c8a2 run dev playground with concurrently 2022-10-30 17:35:23 -07:00
Robert Soriano
fc225ced0a bump to beta.12 2022-10-30 17:06:43 -07:00
Robert Soriano
b1d00c1f88 update compat 2022-10-30 17:06:35 -07:00
Robert Soriano
e0a6bb3576 bump to beta.11 2022-10-30 17:03:59 -07:00
Robert Soriano
fcd6cb1546 fix module compat 2022-10-30 17:03:49 -07:00
Robert Soriano
471ac30040 bump to beta.10 2022-10-30 17:00:27 -07:00
Robert Soriano
a7013db712 add module helper 2022-10-30 17:00:19 -07:00
Robert Soriano
bdcff360f0 bump to beta.9 2022-10-30 14:24:50 -07:00
Robert Soriano
1490790edc import composables from nuxt/app 2022-10-30 14:24:39 -07:00
Robert Soriano
4c5e6812b1 bump to beta.8 2022-10-30 13:36:28 -07:00
Robert Soriano
23d2954aae import composables from #imports 2022-10-30 13:35:58 -07:00
Robert Soriano
3c39746fa2 bump to beta.7 2022-10-30 13:31:04 -07:00
Robert Soriano
5fb7a9d444 import composables from nuxt/app 2022-10-30 13:30:57 -07:00
Robert Soriano
a2275567e8 bump to beta.6 2022-10-30 13:25:11 -07:00
Robert Soriano
c040db4308 update esm extensions to mjs 2022-10-30 13:24:39 -07:00
Robert Soriano
72755315a5 bump to beta.5 2022-10-30 13:09:58 -07:00
Robert Soriano
3d49810641 switch package type to module 2022-10-30 13:09:45 -07:00
Robert Soriano
ebb6637238 chore: release v0.4.0-beta.4 2022-10-30 13:02:11 -07:00
Robert Soriano
0c83ade918 update release script 2022-10-30 13:02:01 -07:00
Robert Soriano
97d9b3ddf8 import useAsyncData and useState from #app 2022-10-30 12:57:57 -07:00
Robert Soriano
eaa61f3700 bump local pnpm to 7.14.1 2022-10-30 12:35:30 -07:00
Robert Soriano
a5a9f430d5 chore: release v0.4.0-beta.3 2022-10-30 12:34:40 -07:00
Robert Soriano
94f59c396a update local deps 2022-10-30 12:30:03 -07:00
Robert Soriano
fe4c10ae25 remove outDir 2022-10-30 12:29:01 -07:00
Robert Soriano
7a97127353 update directory structure 2022-10-30 12:25:28 -07:00
Robert Soriano
0c4a43ec19 update playground 2022-10-30 12:19:57 -07:00
Robert Soriano
da6d738406 update docs 2022-10-30 12:00:21 -07:00
Robert Soriano
70c0b35c48 complete usage docs 2022-10-30 11:18:57 -07:00
Robert Soriano
da52b350ba add initial docs 2022-10-30 11:00:09 -07:00
Robert Soriano
b45c613ce7 immediately execute mutations 2022-10-30 01:52:20 -07:00
Robert Soriano
025ce7e028 chore: release v0.4.0-beta.2 2022-10-29 23:10:16 -07:00
Robert Soriano
4e637a4ec3 add root tsconfig 2022-10-29 23:09:44 -07:00
Robert Soriano
2b5daf54fa chore: release v0.4.0-beta.1 2022-10-29 22:52:42 -07:00
Robert Soriano
217c3fd8e9 chore: release v0.4.0-beta.0 2022-10-29 22:51:51 -07:00
Robert Soriano
14617d864d fix build 2022-10-29 22:48:13 -07:00
Robert Soriano
3522cc9327 move to pnpm workspace 2022-10-29 22:45:57 -07:00
Robert Soriano
09500bc868 update readme 2022-10-29 22:39:43 -07:00
Robert Soriano
6ddfbf9629 update playground 2022-10-29 22:38:10 -07:00
Robert Soriano
97dfefe6cd add superjson to playground 2022-10-29 22:33:59 -07:00
Robert Soriano
b61dbd1786 update playground 2022-10-29 22:29:21 -07:00
Robert Soriano
4bb2cdc2d2 save trpc server error to client 2022-10-29 22:16:06 -07:00
Robert Soriano
29909328c4 update playground 2022-10-29 21:58:00 -07:00
Robert Soriano
da44e5429f update devDeps 2022-10-29 21:51:27 -07:00
Robert Soriano
ae920a37fd make trpc-nuxt flexible by exporting server and client functions instead of nuxt modules 2022-10-29 21:50:27 -07:00
Robert Soriano
bad2de134b remove queryKey for mutation 2022-10-29 19:38:29 -07:00
Robert Soriano
14da8bbda6 update example 2022-10-29 19:31:42 -07:00
Robert Soriano
e69dacf07a import useAsyncData from #app 2022-10-29 19:26:32 -07:00
Robert Soriano
f8de361eaf add inspiration comment 2022-10-29 19:24:43 -07:00
Robert Soriano
0a5735125f move types to another file 2022-10-29 19:23:54 -07:00
Robert Soriano
a04ed72c45 update playground 2022-10-29 19:20:47 -07:00
Robert Soriano
d435c7c9c5 update export paths 2022-10-29 19:18:15 -07:00
Robert Soriano
a3ce2c9a92 remove unused module imports 2022-10-29 19:15:59 -07:00
Robert Soriano
b0e7cc1854 rename api to server 2022-10-29 19:12:44 -07:00
Robert Soriano
9d07dab57d add custom fetch function for node 18 2022-10-29 19:12:01 -07:00
Robert Soriano
7257842438 rewrite client 2022-10-29 19:02:14 -07:00
Robert Soriano
b72b0449c1 update client types 2022-10-28 15:50:54 -07:00
Robert Soriano
d47fbc4537 add exported router type to plugin 2022-10-28 15:40:31 -07:00
Robert Soriano
c0b570ca09 api support for trpc v10 2022-10-28 15:39:31 -07:00
Robert Soriano
95f5478cdc update server routes in playground 2022-10-28 15:27:50 -07:00
Robert Soriano
000ac03295 update client to use v10 client 2022-10-28 15:13:29 -07:00
Robert Soriano
91421c1849 move @trpc/client and @trpc/server to peerDependencies 2022-10-28 15:06:13 -07:00
Robert Soriano
bdeada0e6e Merge pull request #32 from littlejon85/9-server-error-discard
fix: don't discard error in client
2022-10-28 14:53:09 -07:00
Robert Soriano
fd9b86fb18 release v0.3.1 2022-10-21 12:27:16 -07:00
Robert Soriano
5fab0e54b0 update local deps 2022-10-21 12:24:18 -07:00
Robert Soriano
7d2f464699 Merge pull request #34 from mahdiboomeri/chore
chore: bump nuxt to RC 12
2022-10-21 08:10:53 -07:00
Mahdi Boomeri
fdb9c2b474 refactor: playground nuxt.config 2022-10-21 17:00:22 +03:30
Mahdi Boomeri
4ed504e1e9 fix: h3 CompatibilityEvent 2022-10-21 16:56:09 +03:30
Mahdi Boomeri
92747711c6 chore: bump other deps 2022-10-21 16:53:53 +03:30
Mahdi Boomeri
3646e8cdfe chore: nump nuxt 2022-10-21 16:43:22 +03:30
Jan Dlouhý
88214185a8 fix: use useAsyncData error in client 2022-10-15 22:36:28 +02:00
Jan Dlouhý
ad03ae7b65 fix: don't discard error in client 2022-10-11 14:35:21 +02:00
Robert Soriano
09e8ab536a feat: replace useBody with readBody 2022-10-01 09:15:36 -07:00
Robert Soriano
f7dadd482d Update README.md 2022-09-29 10:20:05 -07:00
Robert Soriano
7b69cff1ed Merge pull request #30 from cawa-93/patch-1
docs: Fix `baseURL` in code sample
2022-09-27 10:18:15 -07:00
Alex Kozack
f2d7763fa4 docs: Fix baseURL in code sample 2022-09-27 10:41:38 +03:00
Robert Soriano
20f5a08cbd update readme 2022-09-24 08:26:36 -07:00
Robert Soriano
75980c0953 release v0.3.0 2022-09-24 08:25:33 -07:00
Robert Soriano
a6f61d404f feat(deps): bump h3 to 0.7.21 2022-09-24 08:24:57 -07:00
Robert Soriano
e53d1be61e feat(deps): bump @nuxt/kit to 3.0.0-rc-11 2022-09-24 08:23:29 -07:00
Robert Soriano
d152ebc7be chore: remove volta config 2022-09-24 08:20:24 -07:00
Robert Soriano
f6daa2b7c3 Merge pull request #28 from robinWongM/feature/direct-api-call-during-ssr
feat: use globalThis.$fetch to call API handler directly on server-side
2022-09-24 08:19:16 -07:00
Robin Wong
7962c7f61e fix: return original FetchResponse when ohmyfetch throws error 2022-09-18 09:46:59 +00:00
Robin Wong
453845fb14 fix: eslint errors 2022-09-18 08:44:43 +00:00
Robin Wong
ee3ee485ba feat: use globalThis.$fetch to call API handler directly on server-side 2022-09-16 16:53:15 +00:00
Robert Soriano
cdf29bee67 release v0.2.8 2022-09-08 09:36:32 -07:00
Robert Soriano
d3d35404b1 update release script 2022-09-08 09:36:24 -07:00
Robert Soriano
e57c919157 Merge pull request #26 from elfpie/feature/update-nuxt
Bump nuxt to RC9
2022-09-08 08:11:24 -07:00
Felipe
d0fffbbc64 feat(deps): bump nuxt to 3.0.0-rc.9 2022-09-07 17:59:23 -03:00
Felipe
433d5ada46 feat(deps): bump @trpc/client to 9.27.2 2022-09-07 17:59:23 -03:00
Felipe
5d180460af feat(deps): bump @trpc/server to 9.27.2 2022-09-07 17:59:23 -03:00
Felipe
8991148d39 feat(deps): bump @nuxt/kit to 3.0.0-rc.9 2022-09-07 17:59:15 -03:00
59 changed files with 5166 additions and 3903 deletions

2
.gitignore vendored
View File

@@ -49,3 +49,5 @@ coverage
Network Trash Folder
Temporary Items
.apdisk
.vercel

152
README.md
View File

@@ -1,12 +1,10 @@
# tRPC-Nuxt
[![Version](https://img.shields.io/npm/v/trpc-nuxt?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/trpc-nuxt)
End-to-end typesafe APIs with [tRPC.io](https://trpc.io/) in Nuxt applications.
<p align="center">
<figure>
<img src="https://i.imgur.com/AjmNUxj.gif" alt="Demo" />
<img src="https://i.imgur.com/3AZlBZH.gif" alt="Demo" />
<figcaption>
<p align="center">
The client above is <strong>not</strong> importing any code from the server, only its type declarations.
@@ -15,153 +13,9 @@ End-to-end typesafe APIs with [tRPC.io](https://trpc.io/) in Nuxt applications.
</figure>
</p>
**⚠️ Currently works on Node v16 until [this issue](https://github.com/trpc/trpc/issues/2420) gets fixed.**
Docs: https://trpc-nuxt.vercel.app
## Install
```bash
npm i trpc-nuxt
```
```ts
// nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
modules: ['trpc-nuxt'],
trpc: {
baseURL: 'http://localhost:3000', // defaults to http://localhost:3000
endpoint: '/trpc', // defaults to /trpc
},
typescript: {
strict: true // required to make input/output types work
}
})
```
## Usage
Expose your tRPC [routes](https://trpc.io/docs/router) under `~/server/trpc/index.ts`:
```ts
// ~/server/trpc/index.ts
import type { inferAsyncReturnType } from '@trpc/server'
import * as trpc from '@trpc/server'
import { z } from 'zod' // yup/superstruct/zod/myzod/custom
export const router = trpc.router()
// queries and mutations...
.query('getUsers', {
async resolve(req) {
// use your ORM of choice
return await UserModel.all()
},
})
.mutation('createUser', {
// validate input with Zod
input: z.object({ name: z.string().min(5) }),
async resolve(req) {
// use your ORM of choice
return await UserModel.create({
data: req.input,
})
},
})
```
Use the client like so:
```ts
const client = useClient() // auto-imported
const users = await client.query('getUsers')
const newUser = await client.mutation('createUser', {
name: 'wagmi'
})
```
## useAsyncQuery
A thin wrapper around [`useAsyncData`](https://v3.nuxtjs.org/api/composables/use-async-data/) and `client.query()`.
The first argument is a `[path, input]`-tuple - if the `input` is optional, you can omit the, `input`-part.
You'll notice that you get autocompletion on the `path` and automatic typesafety on the `input`.
```ts
const {
data,
pending,
error,
refresh
} = await useAsyncQuery(['getUser', { id: 69 }], {
// pass useAsyncData options here
lazy: false
})
```
## useClientHeaders
A composable that lets you add additional properties to pass to the tRPC Client. It uses `useState` from [nuxt 3](https://v3.nuxtjs.org/api/composables/use-state).
```ts
const headers = useClientHeaders()
const { data: token } = await useAsyncQuery(['auth.login', { username, password }])
headers.value.Authorization = `Bearer ${token}`
// All client calls will now include the Authorization header.
```
## Options
trpc-nuxt accepts the following options exposed under `~/server/trpc/index.ts`:
```ts
import * as trpc from '@trpc/server'
import type { inferAsyncReturnType } from '@trpc/server'
import type { CompatibilityEvent } from 'h3'
import type { OnErrorPayload } from 'trpc-nuxt/api'
export const router = trpc.router<inferAsyncReturnType<typeof createContext>>()
// Optional
// https://trpc.io/docs/context
export const createContext = (event: CompatibilityEvent) => {
// ...
return {
/** context data */
}
}
// Optional
// https://trpc.io/docs/caching#using-responsemeta--to-cache-responses
export const responseMeta = () => {
// ...
return {
// { headers: ... }
}
}
// Optional
// https://trpc.io/docs/error-handling#handling-errors
export const onError = (payload: OnErrorPayload<typeof router>) => {
// Do whatever here like send to bug reporting and stuff
}
```
## Recipes
- [Validation](/recipes/validation.md)
- [Authorization](/recipes/authorization.md)
- [Merging Routers](/recipes/merging-routers.md)
- [Error Handling](/recipes/error-handling.md)
- [Error Formatting](/recipes/error-formatting.md)
- [Inference Helpers](/recipes/inference-helpers.md)
Learn more about tRPC.io [here](https://trpc.io/docs).
For version 3 of this module (tRPC v9, auto-imports, auto handlers), [go here](https://github.com/wobsoriano/trpc-nuxt/tree/v3).
## Recommended IDE Setup

1
api.d.ts vendored
View File

@@ -1 +0,0 @@
export * from './dist/runtime/api'

1
client.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from './dist/client/index'

1
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vercel

1
docs/README.md Normal file
View File

@@ -0,0 +1 @@
docs

25
docs/app.config.ts Normal file
View File

@@ -0,0 +1,25 @@
export default defineAppConfig({
docus: {
title: 'tRPC Nuxt',
image: 'https://og-image.vercel.app/tRPC-Nuxt',
alt: 'tRPC-Nuxt cover',
url: 'https://trpc-nuxt.vercel.app',
debug: false,
socials: {
github: 'wobsoriano/trpc-nuxt'
},
aside: {
level: 1
},
footer: {
credits: true,
icons: [
{
label: 'NuxtJS',
href: 'https://nuxtjs.org',
component: 'IconNuxt'
}
]
}
}
})

View File

@@ -0,0 +1,46 @@
---
title: Installation
description: tRPC-Nuxt provides first class integration with tRPC.
---
# Installation
::code-group
```bash [pnpm]
pnpm add @trpc/server @trpc/client trpc-nuxt zod
```
```bash [npm]
npm install @trpc/server @trpc/client trpc-nuxt zod
```
```bash [yarn]
yarn add @trpc/server @trpc/client trpc-nuxt zod
```
::
```ts [nuxt.config.ts]
export default defineNuxtConfig({
build: {
transpile: ['trpc-nuxt/client']
}
})
```
#### Why @trpc/server?
For implementing tRPC endpoints and routers.
#### Why @trpc/client?
For making typesafe API calls from your client.
#### Why zod?
Most examples use [Zod](https://github.com/colinhacks/zod) for input validation and tRPC.io highly recommends it, though it isn't required.
## Next Steps
Now that you've installed the required dependencies, you are ready to start building your application.

View File

@@ -0,0 +1,118 @@
---
title: Simple
description: tRPC-Nuxt provides first class integration with tRPC.
---
# Simple Usage
## 1. Create a tRPC router
Initialize your tRPC backend using the `initTRPC` function and create your first router.
::code-group
```ts [server/trpc/trpc.ts]
/**
* This is your entry point to setup the root configuration for tRPC on the server.
* - `initTRPC` should only be used once per app.
* - We export only the functionality that we use so we can enforce which base procedures should be used
*
* Learn how to create protected base procedures and other things below:
* @see https://trpc.io/docs/v10/router
* @see https://trpc.io/docs/v10/procedures
*/
import { initTRPC } from '@trpc/server'
const t = initTRPC.create()
/**
* Unprotected procedure
**/
export const publicProcedure = t.procedure;
export const router = t.router;
export const middleware = t.middleware;
```
```ts [server/api/trpc/[trpc].ts]
/**
* This is the API-handler of your app that contains all your API routes.
* On a bigger app, you will probably want to split this file up into multiple files.
*/
import { createNuxtApiHandler } from 'trpc-nuxt'
import { publicProcedure, router } from '~/server/trpc/trpc'
import { z } from 'zod'
export const appRouter = router({
hello: publicProcedure
// This is the input schema of your procedure
.input(
z.object({
text: z.string().nullish(),
}),
)
.query(({ input }) => {
// This is what you're returning to your client
return {
greeting: `hello ${input?.text ?? 'world'}`,
}
}),
})
// export only the type definition of the API
// None of the actual implementation is exposed to the client
export type AppRouter = typeof appRouter;
// export API handler
export default createNuxtApiHandler({
router: appRouter,
createContext: () => ({}),
})
```
::
## 2. Create tRPC client plugin
Create a strongly-typed plugin using your API's type signature.
```ts [plugins/client.ts]
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client'
import type { AppRouter } from '~/server/trpc/routers'
export default defineNuxtPlugin(() => {
/**
* createTRPCNuxtClient adds a `useQuery` composable
* built on top of `useAsyncData`.
*/
const client = createTRPCNuxtClient<AppRouter>({
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
})
return {
provide: {
client,
},
}
})
```
## 3. Make an API request
```vue [pages/index.vue]
<script setup lang="ts">
const { $client } = useNuxtApp()
const hello = await $client.hello.useQuery({ text: 'client' })
</script>
<template>
<div>
<p>{{ hello.data?.greeting }}</p>
</div>
</template>
```

View File

@@ -0,0 +1,153 @@
---
title: Recommended
description: tRPC-Nuxt provides first class integration with tRPC.
---
# Recommended Usage
Recommended but not enforced file structure.
```graphql
.
server
api
trpc
[trpc].ts # <-- tRPC HTTP handler
[..]
trpc
routers
index.ts # <-- main app router
todo.ts # <-- sub routers
[..]
context.ts # <-- create app context
trpc.ts # <-- procedure helpers
plugins
client.ts # <-- tRPC client plugin
[..]
```
## 1. Create a tRPC router
Initialize your tRPC backend using the `initTRPC` function and create your first router.
::code-group
```ts [server/trpc/trpc.ts]
/**
* This is your entry point to setup the root configuration for tRPC on the server.
* - `initTRPC` should only be used once per app.
* - We export only the functionality that we use so we can enforce which base procedures should be used
*
* Learn how to create protected base procedures and other things below:
* @see https://trpc.io/docs/v10/router
* @see https://trpc.io/docs/v10/procedures
*/
import { initTRPC } from '@trpc/server'
import { Context } from '~/server/trpc/context'
const t = initTRPC.context<Context>().create()
/**
* Unprotected procedure
**/
export const publicProcedure = t.procedure;
export const router = t.router;
export const middleware = t.middleware;
```
```ts [server/trpc/routers/index.ts]
import { z } from 'zod'
import { publicProcedure, router } from '../trpc'
export const appRouter = router({
hello: publicProcedure
.input(
z.object({
text: z.string().nullish(),
}),
)
.query(({ input }) => {
return {
greeting: `hello ${input?.text ?? 'world'}`,
}
}),
})
// export type definition of API
export type AppRouter = typeof appRouter
```
```ts [server/api/trpc/[trpc].ts]
import { createNuxtApiHandler } from 'trpc-nuxt'
import { appRouter } from '~/server/trpc/routers'
import { createContext } from '~/server/trpc/context'
// export API handler
export default createNuxtApiHandler({
router: appRouter,
createContext,
})
```
```ts [server/trpc/context.ts]
import { inferAsyncReturnType } from '@trpc/server'
/**
* Creates context for an incoming request
* @link https://trpc.io/docs/context
*/
export const createContext = () => {}
export type Context = inferAsyncReturnType<typeof createContext>;
```
::
::alert{type=info}
If you need to split your router into several subrouters, you can implement them in the `server/trpc/routers` directory and import and [merge them](https://trpc.io/docs/v10/merging-routers) to a single root `appRouter`.
::
## 2. Create tRPC client plugin
Create a strongly-typed plugin using your API's type signature.
```ts [plugins/client.ts]
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client'
import type { AppRouter } from '~/server/trpc/routers'
export default defineNuxtPlugin(() => {
/**
* createTRPCNuxtClient adds a `useQuery` composable
* built on top of `useAsyncData`.
*/
const client = createTRPCNuxtClient<AppRouter>({
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
})
return {
provide: {
client,
},
}
})
```
## 3. Make an API request
```vue [pages/index.vue]
<script setup lang="ts">
const { $client } = useNuxtApp()
const hello = await $client.hello.useQuery({ text: 'client' })
</script>
<template>
<div>
<p>{{ hello.data?.greeting }}</p>
</div>
</template>
```

View File

@@ -0,0 +1,53 @@
---
title: Client
description: tRPC-Nuxt provides first class integration with tRPC.
---
# Nuxt client
The magic of tRPC is making strongly typed API calls without relying on code generation. With full-stack TypeScript projects, you can directly import types from the server into the client! This is a vital part of how tRPC works.
## Initialize a tRPC client
Create a typesafe client via a Nuxt [plugin](https://nuxt.com/docs/guide/directory-structure/plugins) with the `createTRPCNuxtClient` method from `trpc-nuxt/client`, and add a `links` array with a [terminating link](https://trpc.io/docs/links#the-terminating-link). If you want to learn more about tRPC links, check out the docs [here](https://trpc.io/docs/links):
::alert{type="info"}
`createTRPCNuxtClient` extends [createTRPCProxyClient](https://trpc.io/docs/vanilla#initialize-a-trpc-client) and adds a `useQuery` method built on top of [useAsyncData](https://nuxt.com/docs/api/composables/use-async-data).
::
```ts [plugins/client.ts]
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client'
import type { AppRouter } from '~/server/trpc/routers'
export default defineNuxtPlugin(() => {
const client = createTRPCNuxtClient<AppRouter>({
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
})
return {
provide: {
client,
},
}
})
```
As you can see, we passed `AppRouter` as a type argument of `createTRPCNuxtClient`. This returns a strongly typed `client` instance, a proxy that mirrors the structure of your `AppRouter` on the client:
```vue [pages/index.vue]
<script setup lang="ts">
const { $client } = useNuxtApp()
const getUser = await $client.getUser.useQuery('id_bilbo');
// => { data: { id: 'id_bilbo', name: 'Bilbo' }, pending: false, error: false };
const bilbo = await $client.getUser.query('id_bilbo');
// => { id: 'id_bilbo', name: 'Bilbo' };
const frodo = await $client.createUser.mutate({ name: 'Frodo' });
// => { id: 'id_frodo', name: 'Frodo' };
</script>
```

View File

@@ -0,0 +1,58 @@
---
title: HTTP Link
description: httpLink is a terminating link that sends a tRPC operation to a tRPC procedure over HTTP.
---
# HTTP Link
`httpLink` is a [terminating link](https://trpc.io/docs/links#the-terminating-link) that sends a tRPC operation to a tRPC procedure over HTTP.
`httpLink` supports both POST and GET requests.
::alert{type="info"}
`httpLink` imported from `trpc-nuxt/client` is a convenience wrapper around the original `httpLink` that replaces regular `fetch` with a [`$fetch`](https://nuxt.com/docs/api/utils/dollarfetch) from Nuxt. It also sets the default headers using [`useRequestHeaders`](https://nuxt.com/docs/api/composables/use-request-headers#userequestheaders).
::
## Usage
You can import and add the `httpLink` to the `links` array as such:
```ts
import { createTRPCNuxtClient, httpLink } from 'trpc-nuxt/client'
import type { AppRouter } from '~/server/trpc/routers'
const client = createTRPCNuxtClient<AppRouter>({
links: [
httpLink({
url: '/api/trpc',
}),
],
})
```
## `httpLink` Options
The `httpLink` function takes an options object that has the `HTTPLinkOptions` shape.
```ts
export interface HTTPLinkOptions {
url: string;
/**
* Select headers to pass to `useRequestHeaders`.
*/
pickHeaders?: string[];
/**
* Add ponyfill for fetch.
*/
fetch?: typeof fetch;
/**
* Add ponyfill for AbortController
*/
AbortController?: typeof AbortController | null;
/**
* Headers to be set on outgoing requests or a callback that of said headers
* @link http://trpc.io/docs/v10/header
*/
headers?: HTTPHeaders | (() => HTTPHeaders | Promise<HTTPHeaders>);
}
```

View File

@@ -0,0 +1,88 @@
---
title: HTTP Batch Link
description: httpBatchLink is a terminating link that batches an array of individual tRPC operations into a single HTTP request that's sent to a single tRPC procedure.
---
# HTTP Batch Link
`httpBatchLink` is a [terminating link](https://trpc.io/docs/links#the-terminating-link) that batches an array of individual tRPC operations into a single HTTP request that's sent to a single tRPC procedure.
::alert{type="info"}
`httpBatchLink` imported from `trpc-nuxt/client` is a convenience wrapper around the original `httpBatchLink` that replaces regular `fetch` with a [`$fetch`](https://nuxt.com/docs/api/utils/dollarfetch) from Nuxt. It also sets the default headers using [`useRequestHeaders`](https://nuxt.com/docs/api/composables/use-request-headers#userequestheaders).
::
## Usage
You can import and add the `httpBatchLink` to the `links` array as such:
```ts
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client'
import type { AppRouter } from '~/server/trpc/routers'
const client = createTRPCNuxtClient<AppRouter>({
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
})
```
After that, you can make use of batching by setting all your procedures in a `Promise.all`. The code below will produce exactly one HTTP request and on the server exactly `one` database query:
```ts
const somePosts = await Promise.all([
$client.post.byId.query(1);
$client.post.byId.query(2);
$client.post.byId.query(3);
])
```
## `httpBatchLink` Options
The `httpBatchLink` function takes an options object that has the `HTTPBatchLinkOptions` shape.
```ts
export interface HttpBatchLinkOptions extends HTTPLinkOptions {
maxURLLength?: number;
}
export interface HTTPLinkOptions {
url: string;
/**
* Select headers to pass to `useRequestHeaders`.
*/
pickHeaders?: string[];
/**
* Add ponyfill for fetch.
*/
fetch?: typeof fetch;
/**
* Add ponyfill for AbortController
*/
AbortController?: typeof AbortController | null;
/**
* Headers to be set on outgoing requests or a callback that of said headers
* @link http://trpc.io/docs/v10/header
*/
headers?: HTTPHeaders | (() => HTTPHeaders | Promise<HTTPHeaders>);
}
```
## Setting a maximum URL length
When sending batch requests, sometimes the URL can become too large causing HTTP errors like [413 Payload Too Large](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413), [414 URI Too Long](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414), and [404 Not Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404). The `maxURLLength` option will limit the number of requests that can be sent together in a batch.
```ts
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client';
import type { AppRouter } from '~/server/trpc/routers'
const client = createTRPCNuxtClient<AppRouter>({
links: [
httpBatchLink({
url: '/api/trpc',
maxURLLength: 2083, // a suitable size
}),
],
});
```

View File

@@ -0,0 +1,44 @@
---
title: Composables
---
# Composables
It is often useful to wrap functionality of your `@trpc/client` api within other functions. For this purpose, it's necessary to be able to infer input types and output types generated by your `@trpc/server` router.
::alert{type="info"}
[createTRPCNuxtClient](/get-started/client/create) adds a `useQuery` method built on top of [useAsyncData](https://nuxt.com/docs/api/composables/use-async-data/).
::
## Inference Helpers
`@trpc/server` exports the following helper types to assist with inferring these types from the `AppRouter` exported by your `@trpc/server` router:
- `inferRouterInputs<TRouter>`
- `inferRouterOutputs<TRouter>`
Let's assume we have this example query wrapped within Nuxt's [useAsyncData](https://v3.nuxtjs.org/api/composables/use-async-data/):
```ts
const { data, error } = await useAsyncData(() => $client.todo.getTodos.query())
```
We can wrap this in a composable and also set the client error types:
```ts [composables/useGetTodos.ts]
import { TRPCClientError } from '@trpc/client'
import type { inferRouterOutputs } from '@trpc/server'
import type { AppRouter } from '@/server/trpc/routers'
type RouterOutput = inferRouterOutputs<AppRouter>
type GetTodosOutput = RouterOutput['todo']['getTodos']
type ErrorOutput = TRPCClientError<AppRouter>
export default function useGetTodos() {
const { $client } = useNuxtApp()
return useAsyncData<GetTodosOutput, ErrorOutput>(() => $client.todo.getTodos.query())
}
```
Now, we have a fully-typed composable.

View File

@@ -0,0 +1,44 @@
---
title: Headers
---
# Headers
We can use the built-in [useRequestHeaders](https://v3.nuxtjs.org/api/composables/use-request-headers/) to set outgoing request headers:
::alert{type="info"}
[createTRPCNuxtClient](/get-started/client/create) has this feature by default.
::
```ts [plugins/client.ts]
export default defineNuxtPlugin(() => {
const headers = useRequestHeaders()
const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
// headers need to be a function so it gets called dynamically
// every HTTP request
headers() {
// You can add more custom headers here
return headers
}
}),
],
})
return {
provide: {
client,
},
}
})
```
```ts [server/trpc/context.ts]
export function createContext (event: H3Event) {
console.log('cookies', parseCookies(event))
return {}
}
```

View File

@@ -0,0 +1,103 @@
---
title: Authorization
---
# Authorization
The `createContext` function is called for each incoming request so here you can add contextual information about the calling user from the request object.
::alert{type="warning"}
Before you can access request headers in any context or middleware, you need to set the outgoing request headers. See [here](/get-started/tips/headers).
::
## Create context from request headers
```ts [server/trpc/context.ts]
import { inferAsyncReturnType } from '@trpc/server'
import { decodeAndVerifyJwtToken } from './somewhere/in/your/app/utils'
export async function createContext(event: H3Event) {
// Create your context based on the request object
// Will be available as `ctx` in all your resolvers
// This is just an example of something you might want to do in your ctx fn
const authorization = getRequestHeader(event, 'authorization')
async function getUserFromHeader() {
if (authorization) {
const user = await decodeAndVerifyJwtToken(authorization.split(' ')[1])
return user
}
return null
}
const user = await getUserFromHeader()
return {
user,
}
}
type Context = inferAsyncReturnType<typeof createContext>
```
## Option 1: Authorize using resolver
```ts
import { TRPCError, initTRPC } from '@trpc/server'
import type { Context } from '../context'
export const t = initTRPC.context<Context>().create()
const appRouter = t.router({
// open for anyone
hello: t.procedure
.input(z.string().nullish())
.query(({ input, ctx }) => `hello ${input ?? ctx.user?.name ?? 'world'}`),
// checked in resolver
secret: t.procedure.query(({ ctx }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return {
secret: 'sauce',
}
}),
})
```
## Option 2: Authorize using middleware
```ts
import { TRPCError, initTRPC } from '@trpc/server'
export const t = initTRPC.context<Context>().create()
const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.user?.isAdmin) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return next({
ctx: {
user: ctx.user,
},
})
})
// you can reuse this for any procedure
export const protectedProcedure = t.procedure.use(isAuthed)
t.router({
// this is accessible for everyone
hello: t.procedure
.input(z.string().nullish())
.query(({ input, ctx }) => `hello ${input ?? ctx.user?.name ?? 'world'}`),
admin: t.router({
// this is accessible only to admins
secret: protectedProcedure.query(({ ctx }) => {
return {
secret: 'sauce',
}
}),
}),
})
```
This page is entirely based on [authorization docs](https://trpc.io/docs/v10/authorization) of tRPC with a minimal change made to work with Nuxt.

View File

@@ -0,0 +1,84 @@
---
title: Server Side Calls
---
# Server Side Calls
You may need to call your procedure(s) directly from the server, `createCaller()` function returns you an instance of `RouterCaller` able to execute queries and mutations.
## Input query example
We create the router with a input query and then we call the asynchronous `greeting` procedure to get the result.
::code-group
```ts [server/trpc/trpc.ts]
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
const t = initTRPC.create()
export const router = t.router({
// Create procedure at path 'greeting'
greeting: t.procedure
.input(z.object({ name: z.string() }))
.query(({ input }) => `Hello ${input.name}`),
})
```
```ts [server/api/greeting.ts]
import { router } from '@/server/trpc/trpc'
export default eventHandler(async (event) => {
const { name } = getQuery(event)
const caller = router.createCaller({})
const greeting = await caller.greeting({ name })
return {
greeting
}
})
```
::
## Mutation example
We create the router with a mutation and then we call the asynchronous `post` procedure to get the result.
::code-group
```ts [server/trpc/trpc.ts]
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
const posts = ['One', 'Two', 'Three']
const t = initTRPC.create()
export const router = t.router({
post: t.router({
add: t.procedure.input(z.string()).mutation(({ input }) => {
posts.push(input)
return posts
}),
}),
})
```
```ts [server/api/post.ts]
import { router } from '@/server/trpc/trpc'
export default eventHandler(async (event) => {
const body = await getBody(event)
const caller = router.createCaller({})
const post = await caller.post.add(body.post)
return {
post
}
})
```
::

View File

@@ -0,0 +1,24 @@
---
title: Aborting Procedures
---
# Aborting Procedures
tRPC adheres to the industry standard when it comes to aborting procedures. All you have to do is pass an `AbortSignal` to the query-options and then call its parent `AbortController`'s `abort` method.
```ts [composables/useGetTodo.ts]
export default function useGetTodo(id: number) {
const { $client } = useNuxtApp()
const ac = new AbortController()
onScopeDispose(() => {
ac.abort()
})
return useAsyncData(() => {
return $client.todo.getTodo.query(id, {
signal: ac.signal
})
})
}
```

View File

@@ -0,0 +1,4 @@
---
navigation: false
redirect: /get-started/installation
---

View File

@@ -0,0 +1,6 @@
---
title: Basic
description: tRPC-Nuxt provides first class integration with tRPC.
---
# Basic Example

View File

@@ -0,0 +1,6 @@
---
title: Multiple Routers
description: tRPC-Nuxt provides first class integration with tRPC.
---
# Multi Routers

31
docs/content/index.md Normal file
View File

@@ -0,0 +1,31 @@
---
title: "tRPC Nuxt"
description: "End-to-end typesafe APIs in Nuxt applications."
navigation: false
layout: page
---
::block-hero
---
cta:
- Get Started
- /get-started/installation
secondary:
- Star on GitHub ->
- https://github.com/wobsoriano/trpc-nuxt
snippet: npm install trpc-nuxt
---
#title
tRPC [Nuxt]{.text-primary-500}
#description
End-to-end typesafe APIs in Nuxt applications.
#extra
::list
- Automatic typesafety
- Snappy DX
- Autocompletion on the client, for inputs, outputs and errors
::
::

22
docs/nuxt.config.ts Normal file
View File

@@ -0,0 +1,22 @@
export default defineNuxtConfig({
app: {
pageTransition: false,
layoutTransition: false
},
modules: ['@nuxtlabs/github-module'],
extends: process.env.DOCUS_THEME_PATH || '@nuxt-themes/docus',
github: {
owner: 'wobsoriano',
repo: 'trpc-nuxt',
branch: 'next'
},
colorMode: {
preference: 'dark'
},
build: {
transpile: [/content-edge/, /github-module/]
},
nitro: {
preset: 'vercel'
}
})

17
docs/package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "docs",
"description": "Docs for TRPC-Nuxt",
"scripts": {
"dev": "nuxi dev",
"build": "nuxi build",
"generate": "nuxi build",
"preview": "nuxi preview"
},
"dependencies": {
"nuxt": "^3.0.0"
},
"devDependencies": {
"@nuxt-themes/docus": "^1.1.10",
"@nuxtlabs/github-module": "^1.5.4"
}
}

18
docs/tokens.config.ts Normal file
View File

@@ -0,0 +1,18 @@
import { defineTheme } from 'pinceau'
export default defineTheme({
colors: {
primary: {
50: '#BFEDFC',
100: '#B0E9FB',
200: '#93DEFA',
300: '#76D4F9',
400: '#58C8F7',
500: '#3BBBF6',
600: '#0BA6F3',
700: '#0981C2',
800: '#075E91',
900: '#043D61'
}
}
})

3
docs/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}

View File

@@ -1,66 +1,83 @@
{
"name": "trpc-nuxt",
"description": "End-to-end typesafe APIs in Nuxt applications.",
"type": "module",
"version": "0.2.7",
"packageManager": "pnpm@7.5.0",
"packageManager": "pnpm@7.18.2",
"version": "0.4.4",
"license": "MIT",
"main": "./dist/module.cjs",
"types": "./dist/types.d.ts",
"sideEffects": false,
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/module.mjs",
"require": "./dist/module.cjs"
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
},
"./api": {
"import": "./dist/runtime/api.mjs",
"types": "./dist/runtime/api.d.ts"
"./client": {
"types": "./dist/client/index.d.ts",
"require": "./dist/client/index.cjs",
"import": "./dist/client/index.mjs"
}
},
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist",
"*.d.ts"
"client.d.ts"
],
"scripts": {
"prepublishOnly": "nr build",
"build": "nuxt-module-build",
"play": "nr build && nuxi dev playground",
"build:playground": "nuxi build playground",
"dev": "concurrently \"pnpm build -- --watch\" \"pnpm --filter playground dev\"",
"dev:prepare": "pnpm build && nuxt prepare playground",
"prepublishOnly": "pnpm build",
"build": "tsup",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"release": "bumpp --commit --push --tag && pnpm publish",
"prepare": "nuxi prepare playground"
"release": "bumpp && npm publish"
},
"peerDependencies": {
"@trpc/client": "^10.0.0",
"@trpc/server": "^10.0.0"
},
"dependencies": {
"@nuxt/kit": "3.0.0-rc.8",
"@trpc/client": "^9.27.1",
"@trpc/server": "^9.27.1",
"dedent": "^0.7.0",
"defu": "^6.0.0",
"h3": "^0.7.10",
"ohash": "^0.1.5",
"pathe": "^0.3.0",
"ufo": "^0.8.5"
"h3": "^1.0.2",
"ofetch": "^1.0.0",
"ohash": "^1.0.0",
"ufo": "^1.0.0"
},
"devDependencies": {
"@antfu/eslint-config": "^0.23.1",
"@antfu/ni": "^0.16.2",
"@nuxt/module-builder": "^0.1.7",
"@types/dedent": "^0.7.0",
"bumpp": "^7.2.0",
"eslint": "^8.14.0",
"nuxt": "3.0.0-rc.8",
"pnpm": "^7.5.0",
"trpc-nuxt": "workspace:*",
"zod": "^3.16.0"
"@nuxt/eslint-config": "^0.1.1",
"@trpc/client": "^10.5.0",
"@trpc/server": "^10.5.0",
"bumpp": "^8.2.1",
"concurrently": "^7.5.0",
"eslint": "^8.25.0",
"tsup": "6.4.0",
"typescript": "^4.7.4"
},
"eslintConfig": {
"extends": "@antfu",
"extends": [
"@nuxt/eslint-config"
],
"rules": {
"no-console": "warn"
"@typescript-eslint/no-unused-vars": [
"off"
],
"vue/multi-word-component-names": "off",
"vue/no-multiple-template-root": "off"
}
},
"volta": {
"node": "16.14.2"
"eslintIgnore": [
"*.json",
"node_modules",
"*.md",
"dist",
".output"
],
"pnpm": {
"overrides": {
"nuxt": "3.0.0"
}
},
"engines": {
"node": "^16.13.0 || ^18.12.0"
}
}

View File

@@ -1,13 +1,6 @@
import { defineNuxtConfig } from 'nuxt'
import Module from '../src/module'
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
modules: [Module],
runtimeConfig: {
baseURL: 'http://localhost:3000',
},
typescript: {
strict: true,
},
build: {
transpile: ['trpc-nuxt/client']
}
})

View File

@@ -1,4 +1,21 @@
{
"name": "playground",
"private": true
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@trpc/client": "^10.5.0",
"@trpc/server": "^10.5.0",
"superjson": "^1.11.0",
"trpc-nuxt": "workspace:*",
"zod": "^3.19.1"
},
"devDependencies": {
"nuxt": "^3.0.0"
}
}

View File

@@ -1,19 +0,0 @@
<script setup lang="ts">
const counter = useCookie('counter')
counter.value = counter.value || Math.round(Math.random() * 1000)
</script>
<template>
<div>
<h1> Counter: {{ counter || '-' }}</h1>
<button @click="counter = null">
reset
</button>
<button @click="counter--">
-
</button>
<button @click="counter++">
+
</button>
</div>
</template>

View File

@@ -1,55 +1,54 @@
<script setup lang="ts">
const client = useClient()
const headers = useClientHeaders()
const { data: todos, pending, error, refresh } = await useAsyncQuery(['getTodos'])
const addHeader = () => {
headers.value.authorization = 'Bearer abcdefghijklmnop'
console.log(headers.value)
}
const { $client } = useNuxtApp()
const addTodo = async () => {
const title = Math.random().toString(36).slice(2, 7)
try {
const result = await client.mutation('addTodo', {
const x = await $client.todo.addTodo.mutate({
id: Date.now(),
userId: 69,
title,
completed: false,
completed: false
})
console.log('Todo: ', result)
}
catch (e) {
console.log(x)
} catch (e) {
console.log(e)
}
}
const { data: todos, pending, error, refresh } = await $client.todo.getTodos.useQuery()
</script>
<template>
<div v-if="pending">
Loading...
</div>
<div v-else-if="error?.data?.code">
Error: {{ error.data.code }}
</div>
<div v-else-if="todos">
<ul>
<li v-for="t in todos.slice(0, 10)" :key="t.id">
<NuxtLink :class="{ completed: t.completed }" :to="`/todo/${t.id}`">
Title: {{ t.title }}
</NuxtLink>
</li>
</ul>
<button @click="addTodo">
Add Todo
</button>
<button @click="() => refresh()">
Refresh
</button>
<button @click="addHeader">
Add header
</button>
<div>
<div v-if="pending">
Loading...
</div>
<div v-else-if="error?.data?.code">
Error: {{ error.data.code }}
</div>
<div v-else>
<ul>
<li
v-for="t in todos?.slice(0, 10)"
:key="t.id"
>
<NuxtLink
:class="{ completed: t.completed }"
:to="`/todo/${t.id}`"
>
Title: {{ t.title }}
</NuxtLink>
</li>
</ul>
<button @click="addTodo">
Add Todo
</button>
<button @click="() => refresh()">
Refresh
</button>
</div>
</div>
</template>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
const route = useRoute()
const { data: todo, pending, error } = await useAsyncQuery(['getTodo', Number(route.params.id)])
const { $client } = useNuxtApp()
const { data: todo, pending, error } = await useAsyncData(() => $client.todo.getTodo.query(Number(route.params.id)))
</script>
<template>
@@ -11,8 +12,8 @@ const { data: todo, pending, error } = await useAsyncQuery(['getTodo', Number(ro
{{ error.data.code }}
</div>
<div v-else>
ID: {{ todo.id }} <br>
Title: {{ todo.title }} <br>
Completed: {{ todo.completed }}
ID: {{ todo?.id }} <br>
Title: {{ todo?.title }} <br>
Completed: {{ todo?.completed }}
</div>
</template>

View File

@@ -0,0 +1,25 @@
import { loggerLink } from '@trpc/client'
import superjson from 'superjson'
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client'
import type { AppRouter } from '~~/server/trpc/routers'
export default defineNuxtPlugin(() => {
const client = createTRPCNuxtClient<AppRouter>({
transformer: superjson,
links: [
// adds pretty logs to your console in development and logs errors in production
loggerLink({
enabled: opts =>
process.env.NODE_ENV === 'development' ||
(opts.direction === 'down' && opts.result instanceof Error)
}),
httpBatchLink()
]
})
return {
provide: {
client
}
}
})

View File

@@ -0,0 +1,23 @@
import { createNuxtApiHandler } from 'trpc-nuxt'
import { appRouter } from '@/server/trpc/routers'
import { createContext } from '@/server/trpc/context'
export default createNuxtApiHandler({
router: appRouter,
/**
* @link https://trpc.io/docs/context
*/
createContext,
onError ({ error }) {
if (error.code === 'INTERNAL_SERVER_ERROR') {
// send to bug reporting
console.error('Something went wrong', error)
}
}
/**
* @link https://trpc.io/docs/caching#api-response-caching
*/
// responseMeta() {
// // ...
// },
})

View File

@@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { inferAsyncReturnType } from '@trpc/server'
import type { H3Event } from 'h3'
export type Context = inferAsyncReturnType<typeof createContext>
/**
* Creates context for an incoming request
* @link https://trpc.io/docs/context
*/
export function createContext (
event: H3Event
) {
// for API-response caching see https://trpc.io/docs/caching
console.log('cookies', parseCookies(event))
return {}
}

View File

@@ -1,53 +0,0 @@
import * as trpc from '@trpc/server'
import type { inferAsyncReturnType } from '@trpc/server'
import { z } from 'zod'
import type { CompatibilityEvent } from 'h3'
const baseURL = 'https://jsonplaceholder.typicode.com'
const TodoShape = z.object({
userId: z.number(),
id: z.number(),
title: z.string(),
completed: z.boolean(),
})
export type Todo = z.infer<typeof TodoShape>
export const router = trpc.router<Context>()
.query('getTodos', {
async resolve() {
return await $fetch<Todo[]>(`${baseURL}/todos`)
},
})
.query('getTodo', {
input: z.number(),
async resolve(req) {
return await $fetch<Todo>(`${baseURL}/todos/${req.input}`)
},
})
.mutation('addTodo', {
input: TodoShape,
async resolve(req) {
console.log(req.input)
return await $fetch<Todo>(`${baseURL}/todos`, {
method: 'POST',
body: req.input,
})
},
})
export async function createContext(event: CompatibilityEvent) {
// Create your context based on the request object
// Will be available as `ctx` in all your resolvers
// This is just an example of something you'd might want to do in your ctx fn
// const x = useCookies(event)
console.log(event.req.headers)
return {
}
}
type Context = inferAsyncReturnType<typeof createContext>

View File

@@ -0,0 +1,10 @@
import { router } from '../trpc'
import { todoRouter } from './todo'
import { userRouter } from './user'
export const appRouter = router({
todo: todoRouter,
user: userRouter
})
export type AppRouter = typeof appRouter

View File

@@ -0,0 +1,33 @@
import { z } from 'zod'
import { publicProcedure, router } from '../trpc'
const baseURL = 'https://jsonplaceholder.typicode.com'
const TodoShape = z.object({
userId: z.number(),
id: z.number(),
title: z.string(),
completed: z.boolean()
})
export type Todo = z.infer<typeof TodoShape>
export const todoRouter = router({
getTodos: publicProcedure
.query(() => {
return $fetch<Todo[]>(`${baseURL}/todos`)
}),
getTodo: publicProcedure
.input(z.number())
.query((req) => {
return $fetch<Todo>(`${baseURL}/todos/${req.input}`)
}),
addTodo: publicProcedure
.input(TodoShape)
.mutation((req) => {
return $fetch<Todo>(`${baseURL}/todos`, {
method: 'POST',
body: req.input
})
})
})

View File

@@ -0,0 +1,33 @@
import { z } from 'zod'
import { publicProcedure, router } from '../trpc'
const baseURL = 'https://jsonplaceholder.typicode.com'
const UserShape = z.object({
id: z.number(),
name: z.string(),
username: z.string(),
email: z.string()
})
export type User = z.infer<typeof UserShape>
export const userRouter = router({
getUsers: publicProcedure
.query(() => {
return $fetch<User[]>(`${baseURL}/users`)
}),
getUser: publicProcedure
.input(z.number())
.query((req) => {
return $fetch<User>(`${baseURL}/users/${req.input}`)
}),
addUser: publicProcedure
.input(UserShape)
.mutation((req) => {
return $fetch<User>(`${baseURL}/users`, {
method: 'POST',
body: req.input
})
})
})

View File

@@ -0,0 +1,43 @@
import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
import { ZodError } from 'zod'
import type { Context } from './context'
const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter ({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST' &&
error.cause instanceof ZodError
? error.cause!.flatten()
: null
}
}
}
})
/**
* Create a router
* @see https://trpc.io/docs/v10/router
*/
export const router = t.router
/**
* Create an unprotected procedure
* @see https://trpc.io/docs/v10/procedures
**/
export const publicProcedure = t.procedure
/**
* @see https://trpc.io/docs/v10/middlewares
*/
export const middleware = t.middleware
/**
* @see https://trpc.io/docs/v10/merging-routers
*/
export const mergeRouters = t.mergeRouters

4
playground/tsconfig.json Normal file
View File

@@ -0,0 +1,4 @@
{
// https://v3.nuxtjs.org/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

6507
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,3 @@
packages:
- playground
- docs

View File

@@ -1,102 +0,0 @@
## Authorization
The `createContext`-function is called for each incoming request so here you can add contextual information about the calling user from the request object.
### Create context from request headers
```ts
// ~/server/trpc/index.ts
import type { inferAsyncReturnType } from '@trpc/server'
import type { CompatibilityEvent } from 'h3'
import { decodeAndVerifyJwtToken } from '~/somewhere/in/your/app/utils'
// The app's context - is generated for each incoming request
export async function createContext({ req }: CompatibilityEvent) {
// Create your context based on the request object
// Will be available as `ctx` in all your resolvers
// This is just an example of something you'd might want to do in your ctx fn
async function getUserFromHeader() {
if (req.headers.authorization) {
const user = await decodeAndVerifyJwtToken(req.headers.authorization.split(' ')[1])
return user
}
return null
}
const user = await getUserFromHeader()
return {
user,
}
}
type Context = inferAsyncReturnType<typeof createContext>
// [..] Define API handler and app router
```
### Option 1: Authorize using resolver
```ts
import { TRPCError } from '@trpc/server'
export const router = trpc
.router<Context>()
// open for anyone
.query('hello', {
input: z.string().nullish(),
resolve: ({ input, ctx }) => {
return `hello ${input ?? ctx.user?.name ?? 'world'}`
},
})
// checked in resolver
.query('secret', {
resolve: ({ ctx }) => {
if (!ctx.user)
throw new TRPCError({ code: 'UNAUTHORIZED' })
return {
secret: 'sauce',
}
},
})
```
### Option 2: Authorize using middleware
```ts
import * as trpc from '@trpc/server'
import { TRPCError } from '@trpc/server'
// Merging routers: https://trpc.io/docs/merging-routers
export const router = trpc
.router<Context>()
// this is accessible for everyone
.query('hello', {
input: z.string().nullish(),
resolve: ({ input, ctx }) => {
return `hello ${input ?? ctx.user?.name ?? 'world'}`
},
})
.merge(
'admin.',
trpc.router<Context>()
// this protects all procedures defined next in this router
.middleware(async ({ ctx, next }) => {
if (!ctx.user?.isAdmin)
throw new TRPCError({ code: 'UNAUTHORIZED' })
return next()
})
.query('secret', {
resolve: ({ ctx }) => {
return {
secret: 'sauce',
}
},
}),
)
```
Learn more about authorization [here](https://trpc.io/docs/authorization).

View File

@@ -1,41 +0,0 @@
## Error Formatting
The error formatting in your router will be inferred all the way to your client (& Vue components).
### Adding custom formatting
```ts
// ~/server/trpc/index.ts
import * as trpc from '@trpc/server'
export const router = trpc.router<Context>()
.formatError(({ shape, error }) => {
return {
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST'
&& error.cause instanceof ZodError
? error.cause.flatten()
: null,
}
}
})
```
### Usage in Vue
```html
<script setup lang="ts">
const { error } = await useAsyncQuery(['getUser', { id: 69 }])
</script>
<template>
<pre v-if="error?.data?.zodError">
{{ JSON.stringify(error.data.zodError, null, 2) }}
</pre>
</template>
```
Learn more about error formatting [here](https://trpc.io/docs/error-formatting).

View File

@@ -1,15 +0,0 @@
## Handling errors
All errors that occur in a procedure go through the `onError` method before being sent to the client. Here you can handle or change errors.
```ts
// ~/server/trpc/index.ts
import * as trpc from '@trpc/server'
export function onError({ error, type, path, input, ctx, req }) {
console.error('Error:', error)
if (error.code === 'INTERNAL_SERVER_ERROR') {
// send to bug reporting
}
}
```

View File

@@ -1,80 +0,0 @@
## Inference Helpers
`@trpc/server` exports the following helper types to assist with inferring these types from the `router` exported in `~/server/trpc/index.ts`:
- `inferProcedureOutput<TProcedure>`
- `inferProcedureInput<TProcedure>`
- `inferSubscriptionOutput<TRouter, TPath>`
```ts
// ~/utils/trpc.ts
import type { router } from '~/server/trpc/index.ts'
type AppRouter = typeof router
/**
* Enum containing all api query paths
*/
export type TQuery = keyof AppRouter['_def']['queries']
/**
* Enum containing all api mutation paths
*/
export type TMutation = keyof AppRouter['_def']['mutations']
/**
* Enum containing all api subscription paths
*/
export type TSubscription = keyof AppRouter['_def']['subscriptions']
/**
* This is a helper method to infer the output of a query resolver
* @example type HelloOutput = InferQueryOutput<'hello'>
*/
export type InferQueryOutput<TRouteKey extends TQuery> = inferProcedureOutput<
AppRouter['_def']['queries'][TRouteKey]
>
/**
* This is a helper method to infer the input of a query resolver
* @example type HelloInput = InferQueryInput<'hello'>
*/
export type InferQueryInput<TRouteKey extends TQuery> = inferProcedureInput<
AppRouter['_def']['queries'][TRouteKey]
>
/**
* This is a helper method to infer the output of a mutation resolver
* @example type HelloOutput = InferMutationOutput<'hello'>
*/
export type InferMutationOutput<TRouteKey extends TMutation> =
inferProcedureOutput<AppRouter['_def']['mutations'][TRouteKey]>
/**
* This is a helper method to infer the input of a mutation resolver
* @example type HelloInput = InferMutationInput<'hello'>
*/
export type InferMutationInput<TRouteKey extends TMutation> =
inferProcedureInput<AppRouter['_def']['mutations'][TRouteKey]>
/**
* This is a helper method to infer the output of a subscription resolver
* @example type HelloOutput = InferSubscriptionOutput<'hello'>
*/
export type InferSubscriptionOutput<TRouteKey extends TSubscription> =
inferProcedureOutput<AppRouter['_def']['subscriptions'][TRouteKey]>
/**
* This is a helper method to infer the asynchronous output of a subscription resolver
* @example type HelloAsyncOutput = InferAsyncSubscriptionOutput<'hello'>
*/
export type InferAsyncSubscriptionOutput<TRouteKey extends TSubscription> =
inferSubscriptionOutput<AppRouter, TRouteKey>
/**
* This is a helper method to infer the input of a subscription resolver
* @example type HelloInput = InferSubscriptionInput<'hello'>
*/
export type InferSubscriptionInput<TRouteKey extends TSubscription> =
inferProcedureInput<AppRouter['_def']['subscriptions'][TRouteKey]>
```

View File

@@ -1,46 +0,0 @@
# Merging Routers
Writing all API-code in your code in the same file is not a great idea. It's easy to merge routers with other routers.
Define your routes:
```ts
// ~/server/trpc/routes/posts.ts
export const posts = trpc.router()
.query('list', {
resolve() {
// ..
return []
}
})
```
```ts
// ~/server/trpc/routes/users.ts
export const users = trpc.router()
.query('list', {
resolve() {
// ..
return []
}
})
```
```ts
// ~/server/trpc/index.ts
import { users } from './routes/users'
import { posts } from './routes/posts'
export const router = trpc.router()
.merge('user.', users) // prefix user procedures with "user."
.merge('post.', posts) // prefix post procedures with "post."
```
and use it like this:
```html
<script setup lang="ts">
const { data: users } = await useAsyncQuery(['user.list'])
const { data: posts } = await useAsyncQuery(['post.list'])
</script>
```

View File

@@ -1,49 +0,0 @@
## Validation
tRPC works out-of-the-box with yup/superstruct/zod/myzod/custom validators.
### Input Validation
```ts
// ~/server/trpc/index.ts
import { z } from 'zod'
export const router = trpc
.router()
.mutation('createUser', {
// validate input with Zod
input: z.object({
name: z.string().min(5)
}),
async resolve(req) {
// use your ORM of choice
return await UserModel.create({
data: req.input,
})
},
})
```
### Output Validation
```ts
// ~/server/trpc/index.ts
import { z } from 'zod'
export const router = trpc
.router()
.query('hello', {
// validate output with Zod
output: z.object({
greeting: z.string()
}),
// expects return type of { greeting: string }
resolve() {
return {
greeting: 'hello!',
}
},
})
```
Learn more about input validation [here](https://trpc.io/docs/router#input-validation).

71
src/client/index.ts Normal file
View File

@@ -0,0 +1,71 @@
import { type CreateTRPCClientOptions, type inferRouterProxyClient, createTRPCProxyClient } from '@trpc/client'
import { type AnyRouter } from '@trpc/server'
import { createFlatProxy, createRecursiveProxy } from '@trpc/server/shared'
import { hash } from 'ohash'
import { type DecoratedProcedureRecord } from './types'
// @ts-expect-error: Nuxt auto-imports
import { getCurrentInstance, onScopeDispose, useAsyncData } from '#imports'
/**
* Calculates the key used for `useAsyncData` call
*/
export function getQueryKey (
path: string,
input: unknown
): string {
return input === undefined ? path : `${path}-${hash(input || '')}`
}
export function createNuxtProxyDecoration<TRouter extends AnyRouter> (name: string, client: inferRouterProxyClient<TRouter>) {
return createRecursiveProxy((opts) => {
const args = opts.args
const pathCopy = [name, ...opts.path]
// The last arg is for instance `.useMutation` or `.useQuery()`
const lastArg = pathCopy.pop()!
// The `path` ends up being something like `post.byId`
const path = pathCopy.join('.')
const [input, otherOptions] = args
if (lastArg === 'useQuery') {
const { trpc, ...asyncDataOptions } = otherOptions || {} as any
let controller: AbortController
if (trpc?.abortOnUnmount) {
if (getCurrentInstance()) {
onScopeDispose(() => {
controller?.abort?.()
})
}
controller = typeof AbortController !== 'undefined' ? new AbortController() : {} as AbortController
}
const queryKey = getQueryKey(path, input)
return useAsyncData(queryKey, () => (client as any)[path].query(input, {
signal: controller?.signal,
...trpc
}), asyncDataOptions)
}
return (client as any)[path][lastArg](input)
})
}
export function createTRPCNuxtClient<TRouter extends AnyRouter> (opts: CreateTRPCClientOptions<TRouter>) {
const client = createTRPCProxyClient<TRouter>(opts)
const decoratedClient = createFlatProxy((key) => {
return createNuxtProxyDecoration(key, client)
}) as DecoratedProcedureRecord<TRouter['_def']['record'], TRouter>
return decoratedClient
}
export {
httpBatchLink,
httpLink
} from './links'

78
src/client/links.ts Normal file
View File

@@ -0,0 +1,78 @@
import { httpLink as _httpLink, httpBatchLink as _httpBatchLink } from '@trpc/client'
import { type AnyRouter } from '@trpc/server'
import { FetchError } from 'ofetch'
// @ts-expect-error: Nuxt auto-imports
import { useRequestHeaders } from '#imports'
import { type HTTPLinkOptions as _HTTPLinkOptions } from '@trpc/client/dist/links/internals/httpUtils'
function customFetch(input: RequestInfo | URL, init?: RequestInit) {
return globalThis.$fetch.raw(input.toString(), init)
.catch((e) => {
if (e instanceof FetchError && e.response) { return e.response }
throw e
})
.then(response => ({
...response,
json: () => Promise.resolve(response._data)
}))
}
export interface HTTPLinkOptions extends _HTTPLinkOptions {
/**
* Select headers to pass to `useRequestHeaders`.
*/
pickHeaders?: string[]
}
/**
* This is a convenience wrapper around the original httpLink
* that replaces regular `fetch` with a `$fetch` from Nuxt. It
* also sets the default headers based on `useRequestHeaders` values.
*
* During server-side rendering, calling $fetch to fetch your internal API routes
* will directly call the relevant function (emulating the request),
* saving an additional API call.
*
* @see https://nuxt.com/docs/api/utils/dollarfetch
*/
export function httpLink<TRouter extends AnyRouter>(opts?: HTTPLinkOptions) {
const headers = useRequestHeaders(opts?.pickHeaders)
return _httpLink<TRouter>({
url: '/api/trpc',
headers () {
return headers
},
fetch: customFetch,
...opts,
})
}
export interface HttpBatchLinkOptions extends HTTPLinkOptions {
maxURLLength?: number;
}
/**
* This is a convenience wrapper around the original httpBatchLink
* that replaces regular `fetch` with a `$fetch` from Nuxt. It
* also sets the default headers based on `useRequestHeaders` values.
*
* During server-side rendering, calling $fetch to fetch your internal API routes
* will directly call the relevant function (emulating the request),
* saving an additional API call.
*
* @see https://nuxt.com/docs/api/utils/dollarfetch
*/
export function httpBatchLink<TRouter extends AnyRouter>(opts?: HttpBatchLinkOptions) {
const headers = useRequestHeaders(opts?.pickHeaders)
return _httpBatchLink<TRouter>({
url: '/api/trpc',
headers () {
return headers
},
fetch: customFetch,
...opts,
})
}

80
src/client/types.ts Normal file
View File

@@ -0,0 +1,80 @@
import type { TRPCClientErrorLike, TRPCRequestOptions as _TRPCRequestOptions } from '@trpc/client'
import { type TRPCSubscriptionObserver } from '@trpc/client/dist/internals/TRPCClient'
import type {
AnyMutationProcedure,
AnyProcedure,
AnyQueryProcedure,
AnyRouter,
ProcedureRouterRecord,
inferProcedureInput,
inferProcedureOutput,
ProcedureArgs,
AnySubscriptionProcedure
} from '@trpc/server'
import { type inferObservableValue, type Unsubscribable } from '@trpc/server/observable'
import { inferTransformedProcedureOutput } from '@trpc/server/shared'
import type {
AsyncData,
AsyncDataOptions,
KeyOfRes,
PickFrom,
_Transform
} from 'nuxt/dist/app/composables/asyncData'
interface TRPCRequestOptions extends _TRPCRequestOptions {
abortOnUnmount?: boolean
}
type Resolver<TProcedure extends AnyProcedure> = (
...args: ProcedureArgs<TProcedure['_def']>
) => Promise<inferTransformedProcedureOutput<TProcedure>>;
type SubscriptionResolver<
TProcedure extends AnyProcedure,
TRouter extends AnyRouter,
> = (
...args: [
input: ProcedureArgs<TProcedure['_def']>[0],
opts: ProcedureArgs<TProcedure['_def']>[1] &
Partial<
TRPCSubscriptionObserver<
inferObservableValue<inferProcedureOutput<TProcedure>>,
TRPCClientErrorLike<TRouter>
>
>,
]
) => Unsubscribable
type DecorateProcedure<
TProcedure extends AnyProcedure,
TRouter extends AnyRouter,
> = TProcedure extends AnyQueryProcedure
? {
useQuery: <
TData = inferTransformedProcedureOutput<TProcedure>,
Transform extends _Transform<TData> = _Transform<TData, TData>,
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>,
>(
input: inferProcedureInput<TProcedure>,
opts?: AsyncDataOptions<TData, Transform, PickKeys> & { trpc?: TRPCRequestOptions },
) => AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, TRPCClientErrorLike<TProcedure>>,
query: Resolver<TProcedure>
} : TProcedure extends AnyMutationProcedure ? {
mutate: Resolver<TProcedure>
} : TProcedure extends AnySubscriptionProcedure ? {
subscribe: SubscriptionResolver<TProcedure, TRouter>
} : never
/**
* @internal
*/
export type DecoratedProcedureRecord<
TProcedures extends ProcedureRouterRecord,
TRouter extends AnyRouter,
> = {
[TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter
? DecoratedProcedureRecord<TProcedures[TKey]['_def']['record'], TRouter>
: TProcedures[TKey] extends AnyProcedure
? DecorateProcedure<TProcedures[TKey], TRouter>
: never;
}

126
src/index.ts Normal file
View File

@@ -0,0 +1,126 @@
import type { ResponseMeta } from '@trpc/server/http'
import { resolveHTTPResponse } from '@trpc/server/http'
import type {
AnyRouter,
ProcedureType,
inferRouterContext,
inferRouterError
} from '@trpc/server'
import {
TRPCError
} from '@trpc/server'
import { createURL } from 'ufo'
import type { H3Event } from 'h3'
import { createError, defineEventHandler, isMethod, readBody } from 'h3'
import type { TRPCResponse } from '@trpc/server/rpc'
type MaybePromise<T> = T | Promise<T>
export type CreateContextFn<TRouter extends AnyRouter> = (event: H3Event) => MaybePromise<inferRouterContext<TRouter>>
export interface ResponseMetaFnPayload<TRouter extends AnyRouter> {
data: TRPCResponse<unknown, inferRouterError<TRouter>>[]
ctx?: inferRouterContext<TRouter>
paths?: string[]
type: ProcedureType | 'unknown'
errors: TRPCError[]
}
export type ResponseMetaFn<TRouter extends AnyRouter> = (opts: ResponseMetaFnPayload<TRouter>) => ResponseMeta
export interface OnErrorPayload<TRouter extends AnyRouter> {
error: TRPCError
type: ProcedureType | 'unknown'
path: string | undefined
req: H3Event['node']['req']
input: unknown
ctx: undefined | inferRouterContext<TRouter>
}
export type OnErrorFn<TRouter extends AnyRouter> = (opts: OnErrorPayload<TRouter>) => void
export interface ResolveHTTPRequestOptions<TRouter extends AnyRouter> {
router: TRouter
createContext?: CreateContextFn<TRouter>
responseMeta?: ResponseMetaFn<TRouter>
onError?: OnErrorFn<TRouter>
batching?: {
enabled: boolean
}
}
function getPath (event: H3Event): string | null {
if (typeof event.context.params.trpc === 'string') { return event.context.params.trpc }
if (Array.isArray(event.context.params.trpc)) { return event.context.params.trpc.join('/') }
return null
}
export function createNuxtApiHandler<TRouter extends AnyRouter> ({
router,
createContext,
responseMeta,
onError,
batching
}: ResolveHTTPRequestOptions<TRouter>) {
return defineEventHandler(async (event) => {
const {
req,
res
} = event.node
const $url = createURL(req.url!)
const path = getPath(event)
if (path === null) {
const error = router.getErrorShape({
error: new TRPCError({
message:
'Param "trpc" not found - is the file named `[trpc]`.ts or `[...trpc].ts`?',
code: 'INTERNAL_SERVER_ERROR'
}),
type: 'unknown',
ctx: undefined,
path: undefined,
input: undefined
})
throw createError({
statusCode: 500,
statusMessage: JSON.stringify(error)
})
}
const httpResponse = await resolveHTTPResponse({
batching,
router,
req: {
method: req.method!,
headers: req.headers,
body: isMethod(event, 'GET') ? null : await readBody(event),
query: $url.searchParams
},
path,
createContext: async () => await createContext?.(event),
responseMeta,
onError: (o) => {
onError?.({
...o,
req
})
}
})
const { status, headers, body } = httpResponse
res.statusCode = status
headers && Object.keys(headers).forEach((key) => {
res.setHeader(key, headers[key]!)
})
return body
})
}

View File

@@ -1,66 +0,0 @@
import { fileURLToPath } from 'url'
import { join, resolve } from 'pathe'
import { defu } from 'defu'
import dedent from 'dedent'
import { addAutoImport, addPlugin, addServerHandler, addTemplate, defineNuxtModule } from '@nuxt/kit'
export interface ModuleOptions {
baseURL: string
endpoint: string
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'trpc-nuxt',
configKey: 'trpc',
},
defaults: {
baseURL: 'http://localhost:3000',
endpoint: '/trpc',
},
async setup(options, nuxt) {
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
nuxt.options.build.transpile.push(runtimeDir, '#build/trpc-handler')
const handlerPath = join(nuxt.options.buildDir, 'trpc-handler.ts')
const trpcOptionsPath = join(nuxt.options.srcDir, 'server/trpc')
// Final resolved configuration
const finalConfig = nuxt.options.runtimeConfig.public.trpc = defu(nuxt.options.runtimeConfig.public.trpc, {
baseURL: options.baseURL,
endpoint: options.endpoint,
})
addAutoImport([
{ name: 'useClient', from: join(runtimeDir, 'client') },
{ name: 'useAsyncQuery', from: join(runtimeDir, 'client') },
{ name: 'useClientHeaders', from: join(runtimeDir, 'client') },
{ name: 'getQueryKey', from: join(runtimeDir, 'client') },
])
addServerHandler({
route: `${finalConfig.endpoint}/*`,
handler: handlerPath,
})
addPlugin(resolve(runtimeDir, 'plugin'))
addTemplate({
filename: 'trpc-handler.ts',
write: true,
getContents() {
return dedent`
import { createTRPCHandler } from 'trpc-nuxt/api'
import * as functions from '${trpcOptionsPath}'
export default createTRPCHandler({
...functions,
endpoint: '${finalConfig.endpoint}'
})
`
},
})
},
})

View File

@@ -1,90 +0,0 @@
import { resolveHTTPResponse } from '@trpc/server'
import type {
AnyRouter,
ProcedureType,
ResponseMeta,
TRPCError,
inferRouterContext,
inferRouterError,
} from '@trpc/server'
import { createURL } from 'ufo'
import type { CompatibilityEvent } from 'h3'
import { defineEventHandler, isMethod, useBody } from 'h3'
import type { TRPCResponse } from '@trpc/server/dist/declarations/src/rpc'
type MaybePromise<T> = T | Promise<T>
export type CreateContextFn<TRouter extends AnyRouter> = (event: CompatibilityEvent) => MaybePromise<inferRouterContext<TRouter>>
export interface ResponseMetaFnPayload<TRouter extends AnyRouter> {
data: TRPCResponse<unknown, inferRouterError<TRouter>>[]
ctx?: inferRouterContext<TRouter>
paths?: string[]
type: ProcedureType | 'unknown'
errors: TRPCError[]
}
export type ResponseMetaFn<TRouter extends AnyRouter> = (opts: ResponseMetaFnPayload<TRouter>) => ResponseMeta
export interface OnErrorPayload<TRouter extends AnyRouter> {
error: TRPCError
type: ProcedureType | 'unknown'
path: string | undefined
req: CompatibilityEvent['req']
input: unknown
ctx: undefined | inferRouterContext<TRouter>
}
export type OnErrorFn<TRouter extends AnyRouter> = (opts: OnErrorPayload<TRouter>) => void
export function createTRPCHandler<Router extends AnyRouter>({
router,
createContext,
responseMeta,
onError,
endpoint,
}: {
router: Router
createContext?: CreateContextFn<Router>
responseMeta?: ResponseMetaFn<Router>
onError?: OnErrorFn<Router>
endpoint: string
}) {
return defineEventHandler(async (event) => {
const {
req,
res,
} = event
const $url = createURL(req.url!)
const httpResponse = await resolveHTTPResponse({
router,
req: {
method: req.method!,
headers: req.headers,
body: isMethod(event, 'GET') ? null : await useBody(event),
query: $url.searchParams,
},
path: $url.pathname.substring(endpoint.length + 1),
createContext: async () => createContext?.(event),
responseMeta,
onError: (o) => {
onError?.({
...o,
req,
})
},
})
const { status, headers, body } = httpResponse
res.statusCode = status
headers && Object.keys(headers).forEach((key) => {
res.setHeader(key, headers[key]!)
})
return body
})
}

View File

@@ -1,82 +0,0 @@
import type {
AsyncData,
AsyncDataOptions,
KeyOfRes,
PickFrom,
_Transform,
} from 'nuxt/dist/app/composables/asyncData'
import type { ProcedureRecord, inferHandlerInput, inferProcedureInput, inferProcedureOutput } from '@trpc/server'
import type { TRPCClient, TRPCClientErrorLike } from '@trpc/client'
import { objectHash } from 'ohash'
import type { Ref } from 'vue'
import { useAsyncData, useNuxtApp, useState } from '#app'
import type { router } from '~/server/trpc'
type MaybeRef<T> = T | Ref<T>
type AppRouter = typeof router
export type inferProcedures<
TObj extends ProcedureRecord<any, any, any, any, any, any>,
> = {
[TPath in keyof TObj]: {
input: inferProcedureInput<TObj[TPath]>
output: inferProcedureOutput<TObj[TPath]>
};
}
export type TQueries = AppRouter['_def']['queries']
export type TError = TRPCClientErrorLike<AppRouter>
export type TQueryValues = inferProcedures<AppRouter['_def']['queries']>
/**
* Calculates the key used for `useAsyncData` call
* @param pathAndInput
*/
export function getQueryKey<
TPath extends keyof TQueryValues & string,
>(pathAndInput: [path: TPath, ...args: inferHandlerInput<TQueries[TPath]>]) {
return `${pathAndInput[0]}-${objectHash(pathAndInput[1] ? JSON.stringify(pathAndInput[1]) : '')}`
}
export async function useAsyncQuery<
TPath extends keyof TQueryValues & string,
TOutput extends TQueryValues[TPath]['output'] = TQueryValues[TPath]['output'],
Transform extends _Transform<TOutput> = _Transform<TOutput, TOutput>,
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>,
>(
pathAndInput: [path: TPath, ...args: inferHandlerInput<TQueries[TPath]>],
options: AsyncDataOptions<TOutput, Transform, PickKeys> = {},
): Promise<AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, TError>> {
const { $client } = useNuxtApp()
const key = getQueryKey(pathAndInput)
const serverError = useState<TError | null>(`error-${key}`, () => null)
const { error, data, ...rest } = await useAsyncData(
key,
() => $client.query(...pathAndInput),
// @ts-expect-error: Internal
options,
)
if (process.server && error.value && !serverError.value)
serverError.value = error.value as any
if (data.value)
serverError.value = null
return {
...rest,
data,
error: serverError,
} as any
}
export function useClient(): TRPCClient<AppRouter> {
const { $client } = useNuxtApp()
return $client
}
export function useClientHeaders(initialValue: MaybeRef<Record<string, any>> = {}): Ref<Record<string, any>> {
return useState('trpc-nuxt-header', () => initialValue)
}

View File

@@ -1,30 +0,0 @@
import * as trpc from '@trpc/client'
import { unref } from 'vue'
import { useClientHeaders } from './client'
import { defineNuxtPlugin, useRequestHeaders, useRuntimeConfig } from '#app'
import type { router } from '~/server/trpc'
declare type AppRouter = typeof router
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig().public.trpc
const headers = useRequestHeaders()
const otherHeaders = useClientHeaders()
const client = trpc.createTRPCClient<AppRouter>({
url: `${config.baseURL}${config.endpoint}`,
headers: () => {
return {
...unref(otherHeaders),
...headers,
}
},
})
nuxtApp.provide('client', client)
})
declare module '#app' {
interface NuxtApp {
$client: trpc.TRPCClient<AppRouter>
}
}

View File

@@ -1,3 +1,16 @@
{
"extends": "./playground/.nuxt/tsconfig.json"
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"skipLibCheck": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"allowJs": true,
"noEmit": true,
"resolveJsonModule": true,
"skipDefaultLibCheck": true
}
}

15
tsup.config.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts', 'src/client/index.ts'],
format: ['cjs', 'esm'],
splitting: false,
clean: true,
external: ['#app', '#imports', /@trpc\/client/, /@trpc\/server/],
dts: true,
outExtension ({ format }) {
return {
js: format === 'esm' ? '.mjs' : `.${format}`
}
}
})