solidjs / solid-start

SolidStart, the Solid app framework
https://start.solidjs.com
MIT License
5.17k stars 375 forks source link

Feature request: type safety for RPCs #30

Closed orenelbaum closed 2 years ago

orenelbaum commented 2 years ago

I think that having type safe RPCs to maintain full type safety between the server and the client would be nice. From the latest example I could find it doesn't seem like this feature exists currently. We can probably look at tRPC and Blitz for inspiration.

KATT commented 2 years ago

Let me know if I can assist, I'm the creator of tRPC 🙃

nksaraf commented 2 years ago

I would love to have this as well and maybe work on it. I had implemented something like with NextJS because I didn't want to use all of Blitz just their RPC stuff, and it worked with both web and native.. pretty cool. And I didn't even have trpc back then. Had to write the RPC layer too.

But yeah the way I imagine an API for this is something analogous to server component (okay, hear me out lol)..

/users/user.api.ts


export function getUserByID(params: {}, ctx: Context) {
   ...
}

/users/User.tsx

import { createQuery } from 'solid-start';
import { getUserByID } from './user.api.ts';

function User(props) {
    const [data] = createQuery(() => props.id, getUserByID);

    return <>...</>
}

The implementation would be to compile away any imports (not types) for *.api.ts from non *.api.ts files. API files can import other API files normally. But the compilation would basically make it a fetch call,

eg.

import { createQuery } from 'solid-start';
import { fetchTrpcRequest } from 'solid-start/trpc';
const getUserByID = (...params) => {
    // this will need to know how to make the request to a trpc server that will eventually call 
    // our api function for getUserByID defined in `/users/user.api.ts`
    return fetchTrpcRequest('/users/user.api.ts', 'getUserByID' , ...params)
}

function User(props) {
    const [data] = createQuery(() => props.id, getUserByID);

    return <>...</>
}

The benefits are:

Also, these ideas are straight from Blitz, nothing original here, except they were doing things by folder so everything in a single /queries or /mutations folder would become 'API' files that you can import and would get compiled away. I didn't like the additional directories it required you to make. I wanted more colocation.

I am sure @KATT must have seen these ideas before.. I would love to know what downsides, if any, you guys discovered with this/

KATT commented 2 years ago

@theobr did a twitch stream the other week where he added trpc to a solidjs project, was pretty seamless

https://github.com/TheoBr/solid-trpc

t3dotgg commented 2 years ago

@nksaraf You might dig Telefunc https://github.com/vikejs/telefunc

nksaraf commented 2 years ago

@TheoBr Yeah I remember seeing this.. and yeah similar idea I want to bring here..

And also, I saw the stream and yeah its really the nice the API usage with tRPC.. My attempt with this is to see if we can build a layer on top of the TRPC protocol and map the server side based on file system (*.api.ts) files and their imports. I will probably use the trpc client and server in the implementation.

trusktr commented 2 years ago

I think @davedbase would love to be CC'd into these sorts of conversations.

We've been chatting about RPC primitives for solid-primitives, for example. They would allow RPC communication across boundaries such as iframes, workers, windows, http endpoints, websockets, or any other sorts of usually-async boundaries that may require serializing/deserializing data. Received data would of course map to reactive signals.

We can make primitives there, with use cases for the solid-start server in mind here.

ryansolid commented 2 years ago

Biggest thing is what ever mechanism we use will likely be compiler driven. We will support other libraries but we want the main integration to be very seamless between client server. I think the scope here is less Solid specific and more Vite Plugin.

nksaraf commented 2 years ago

Yeah the transport layer definetely needs to be abstracted.. I think we can support stuff like workers and websockets using a compiler approach similar to the inline server functions I proposed (on stream). Since those are mostly delegating work designed to run in your app (stuff like API's, workers).. they fit nicely in the model that should just be functions u write in javascript and wrap them to mark that they are designed to run in a different environment, something like C++ #IF blocks..

Communicating with another window or iframe might not be the best scope for the compiled approach since its kind of two apps communicating working in their scopes.. but something like comlink shows that getting simple function call APIs for even that communication is a pretty big DX win.

I also think having it as a framework agnostic vite plugin is a good idea.. Will help with seeing it plays in other frameworks and will help iron out the API

nksaraf commented 2 years ago

We have added both RPC's via our server function and special helpers like createServerData and createServerAction for reads and writes. We also have API routes for traditional REST API endpoints. We hope this serves our server-side use cases well