trpc
trpc copied to clipboard
[RFC] Next major overview
So. We're making a new major.
Goals
- More ergonomic API for creating procedures and building out your backend
- CMD+Click from your frontend and jump straight into the backend procedure. This will work with
react-queryas well! - Better scaling than the current structure - the TypeScript server starts choking a bit when you get close to 100 procedures in your backend
- Fully backward compatible for the 99% use-case with a migration path which you can do incrementally.
Todos
Important for contributors: All of the work here is related to the
nextbranch.
Tracked in https://github.com/orgs/trpc/projects/1
- [x] New Router API
- [x] Tracked in trpc/v10-playground - the current winning proposal is https://github.com/trpc/v10-playground/pull/33
- [x] Error formatting in new API -
formatError()-equivalent. - [x]
meta-equivalent - [x] Maybe we need a
createRootRouter()that additionally accepts{ transformer: X, formatError: Y} - [x] Implement new API in tRPC
- [x] Make old routers compatible (
.legacy()at the end to transform them to the new version)
- [x] New client API with backward compatibility
- Maybe old can be
client.query('postById', input, options)whilst new stuff can beclient.queries.postById({ input, ...options})
- Maybe old can be
- [x] New react-query API with backward compat
- [ ] Make actual issues for all of these
- [x] Add all old projects in a folder (copy/paste
examples/*toexamples/.testso we can track breaking changes easily and make sure we remain backward compatible for a long time - [ ] Rewrite
TRPCClientto be functional instead of a class but reuse the same interface - [ ] Write a migration guide
- [ ] New router API
- [ ] New links
- [x] Move transformers back to
runtimeto make it backward compatible - [ ] Explore: Add
stripInternal-flag - [x] Simplify HTTP-responses
- [x] New links architecture (#1366)
- [ ] Refactor observables implementation to have
unknownerrors or make it guaranteed to beTRPCClientErrorof some sort. - [ ] Make transformers dynamically opt-in similar to how a batch request is indicated by
?batch=1maybe a transformed request could be?transform=1ortransform=TRANSFORMER_NAME? - [ ] Write a blog post about new major and API.
- [ ] "Marketing"? Hype video, new website, etc
- [ ] Decide on structure for react-query integration
- [ ] Add old website on
www/-docs asv9.trpc.io- [ ] Link it in Docusaurus
- [ ] Make sure we have deployment for a "v9"-branch
Stretch
- [ ] Add DTO for clients without transformers - #1694
- [x] #1597
- [x] New Subscriptions API (nice to have) - #1596
- [ ]
trpc.output.snapshot()https://github.com/trpc/v10-playground/issues/34
Old notes
Definite
- [x] Remove official support for node
12.x- [x] Remove E2E-test for 12.x
- [x] Change
tsconfigbuild target
- [ ] Add official support for node
16.x - [ ] Add
stripInternal-flag - [x] Remove all
@deprecatedstuff- [x] Remove
LegacyRouterintroduced in #915 - [ ] [...]
- [x] Remove
- [x] New links architecture (#1366)
- [ ] Move official support for
react-query3to4 - [ ] Simplify HTTP-responses
- [ ] From
{ id:null, result: {type: 'data', data: x } }->{ result: { data: x } } - [ ] maybe remove
id: nullfrom HTTP responses?
- [ ] From
- [ ] Migration guides for all changed parts
- [ ] New Links
Maybe ❓
Feedback please!
- [x] Optimize so router inference only needs to be done once? #1218
- [ ] ~Update so procedures needs to return
{ data: x }instead of justxso we could add metadata and potentially discriminated union of{ data: x } | { error: y }~ - [x] Decide:
- Pipe-based rather than object resolvers?
{ input: z.string(), resolve({input}) {}}-> `t.pipe(t.input(z.string), ({input}) => - Chained resolvers
- Pipe-based rather than object resolvers?
- [x] Plan how to deal with degrading typescript performance when approaching ~100 procedures (there are no runtime issues, but the DX slows down)
- [ ] Turn
client.queryresult into[ output, { meta } ] - [ ] New
useQuery()API that expects suspense as default - See this gist: https://gist.github.com/KATT/aa1ad532d6e57520b942f657569e1505 - [ ] Refactor
@trpc/serverto easier work with CloudFlare-workers etc (#1375) - [ ] Collaborate with Blitz.js? https://github.com/blitz-js/blitz/discussions/3083#discussioncomment-1841427
- [ ] https://jsonapi.org/format/
Transformers
- [x] Turn transformers into a link on the client (#1366)
- [ ] Revert above and put transformers back to runtime 😬
- [ ] ~Turn transformers into a middleware on the server~
- [ ] Make transformers dynamically opt-in similar to how a batch request is indicated by
?batch=1maybe a transformed request could be?transform=1ortransform=TRANSFORMER_NAME?
Misc
- [ ] Rename
@trpc/react/ssg->@trpc/react/ssr&createSSGHelpers->createServerSideHelpers(or something, as it is often used ingetServerSideProps()as well asgetStaticProps())
Open questions
- Should we continue to follow OpenRPC?
- Any changes that could make tRPC easier to support stuff like Svelte/Vue?
- Any changes that could make tRPC stricter support stuff like AWS Lambdas? (Already is doable to do Lambdas but through an Express->Lambda converter) #882
- Ideas?
@mmkal would be awesome if you could share how we could make an options bag on that Router<X>
I got about 2/3 of the way through a PR to do it, but got distracted. I'm also not sure that it will help all that much, but I'll try to pick it up again some time in the next week.
I picked the typearg-options-bag up again, but got loads of type instantiation is excessively deep and possibly infinite errors, as well as all sorts of other type test failures, so I don't think it's worth it.
Regarding Simplify HTTP-responses:
- This would means moving away from the JSON-RPC spec, are we ok with this?
- Would this also mean removing
id: 1from the HTTP-request? - I see you've listed
https://jsonapi.org/format/as a maybe? I've been using the Slack Web API (https://api.slack.com/web#slack-web-api__basics) recently which has a very similar RPC-type structure. Could be worth taking a look and drawing inspiration.
continued...
- Moving towards a Slack-style API might make public-facing TRPC APIs easier to use and drive greater adoption (cc. https://github.com/trpc/trpc/issues/755)
is there any example use trpc in vue 3?
Hey. Just came across this upcoming update. I wasn't aware that this update was in the works, and had started work on a similar project. https://github.com/rakeshpai/birdsong It isn't production-ready yet. It is heavily inspired by tRPC, but these are the differences from tRPC 9:
- Using actual tokens instead of strings to name methods (tRPC 10 takes care of this.)
- Cmd+click to jump to definition between client and server. (Again, 10 takes care of this.)
- Handling all the built-in JS types, eg. Dates, Maps, Sets, etc., and not just the primitives that JSON supports, without needing an external library. (tRPC requires an external library for this, but that might be ok for most people.)
- Easily extending the supported types by writing simple serialiser / deserialiser functions for the new custom types - useful for custom types that don't have internal state, like for eg.
Email. (I haven't exposed this currently even in my package, but the design has been done to allow for this.) - Being agnostic to the environment (node / deno / CF workers, etc) that the server is going to run in. This has been done by converting node's request and response objects to W3C
RequestandResponseobjects at the boundaries, and using the W3C objects everywhere in the code rather than the node built-ins. - Automatically detecting if a method's network call should be a
GETor aPOSTrather than explicitly having to specify if the method is aqueryor amutation- this has been done by seeing if the method name starts with some known prefixes likegetorlist. So,getUserswould be aGETrequest, andupdateUserwould be aPOSTjust because of how they're named. I'm on the fence about whether this is a good idea. It does simplify the API somewhat, but introduces a little bit of magic and/or forces a convention. - Keeping the client-side bundle vanishingly small - it's currently under 5kb before minification and gzip, and IIRC it's under 3kb after minification and gzip. (Aren't proxies magical!) This does forgo many runtime checks, but that should be ok since the TS types are very strict.
- A functional composition based API, rather than a chaining+router API, for writing the server code. You could put this down to personal preference - a chaining API might be what most people prefer anyway.
- Simplified imports - there's just one package to be
npm installed, and the server and client code are both part of the same package. Client-side bundling + tree-shaking eliminates server code, so there's no leakage of server code into the client bundle. - Built-in but opt-in pretty logging on the client-side similar to redux-logger to help with debugging.
I had even more ambitious plans with my package, but some of those have fallen by the wayside for now:
- Having the same API for communication between the browser main thread and web workers to enable functionality similar to Comlink. The API has been designed to minimise HTTP-specifics to enable this in the future, but I haven't done anything to actually build this out. The end result is that in large client-heavy projects, there's only one RPC API that teams need to learn, regardless of which thread/network boundary they're crossing.
- Ditto for communication between the main thread and service workers. This is particularly interesting since the semantics are still of making HTTP calls from the main thread, except you run the 'server' inside a service worker. This could potentially open up the doors to building new kinds of apps that run primarily in the service worker, but are painful to do today because of how the sw APIs are. Again, I haven't really explored this yet.
I'm very happy to know that the first two points above, which are the biggest ergonomic differences from tRPC 9, are now going to be handled in 10. With that in mind, I'm happy to abandon my project. However, I thought I'd leave the rest of the list here to see if there's interest in targeting them with upcoming updates to tRPC.
You can see usage examples of my package in the tests. Apologies for not documenting this better.
Edit: Terminology (/s/module/package)
Hey @rakeshpai! Birdsong looks great! Here's a response to your points:
I don't see this changing for us. superjson handles all of this as you noted and allows you to have custom types with serializer/deserializer functions.
Being agnostic to the environment (node / deno / CF workers, etc) that the server is going to run in
tRPC v10 can run anywhere and is agnostic to the environment. We cannot use the Fetch API Request and Response yet because they aren't available in Node so we just use a common format for request/response shapes. Also tRPC v10 has a Fetch adapter to convert these shapes to Request/Response objects.
Automatically detecting if a method's network call should be a GET or a POST rather than explicitly having to specify if the method is a query or a mutation - this has been done by seeing if the method name starts with some known prefixes like get or list
We can consider this, but I don't think it's a great idea. I'm not a fan of implicit behavior on this level. In a way, having to specify keywords in front of the procedure names is still explicit. As a side note this was also brought up in https://github.com/trpc/trpc/discussions/2270#discussioncomment-3198442.
Keeping the client-side bundle vanishingly small
We'd love to have your help here! This is something on our radar to look at before release. That being said, the client is kept fairly minimal.
Simplified imports - there's just one package to be npm installed
Yeah, this is also something we discussed. It's not a huge problem right now (compared to the other things we're working on), but we're open to ideas here.
Built-in but opt-in pretty logging on the client-side
Awesome! tRPC has this too with the loggerLink.
Having the same API for communication between the browser main thread and web workers to enable functionality similar to Comlink. The API has been designed to minimise HTTP-specifics to enable this in the future, but I haven't done anything to actually build this out.
You can likely achieve this with a custom link on the client. tRPC has also also been designed to minimize HTTP-specifics. For example, see the WebSocket link and trpc-chrome.
It seems like you've thought about this a lot and we'd love to have you involved in the decision-making for v10. We'd also love to have you as a contributor to tRPC! If you're interested, please join the Discord and we can talk more.