You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on May 21, 2024. It is now read-only.
In addition to #55, it also will be usefull to come up with interfaces for streams.
Server-to-Client:
Basically, there will be two major types of data streaming:
binary data - just bunch of bytes splitted and sent in chunks
json data - some structured data
Server-side streams API :
classBaseStreamResponseextendsWritable{}classBinaryStreamResponseextendsBaseStreamResponse{}classJsonStreamResponseextendsBaseStreamResponse{}// example of binary data streamdeclareProcedure(()=>{conststream=newBinaryStreamResponse()fs.createReadableStream('/path/to/static/file').pipe(stream)returnstream})// example of json data streamdeclareProcedure(()=>{asyncfunctiongetDataFromDb(){forawait(constrowsofcursor){rows.length ? stream.write(rows) : stream.end()}}conststream=newJsonStreamResponse()getDataFromDb()returnstream})
Then client-side stream API could be something like this, where client.rpc return instance of Readable (which can be used as async iterable):
conststream=awaitclient.rpc(`v1/binary-stream`,someInput)forawait(constarrayBufferofstream){// do something with buffer}
Concerns and important things that need to take care of:
Clean API for stream abortion, both client and server side
Backpressure
Consistency across different transports and environments (e.g browsers will require polyfils if use node's streams that will noticeable increase to bundle size)
Client-to-Server:
When it comes to uploading data to server, there's no point of having json streams, since any input from client should be validated, so only there will be only binary data streaming. In the end it should be easy and convenient, something like:
// server-side `create-user` proceduredeclareProcedure({handle: async(ctx,data)=>{const{
name,// string
occupation,// string
profilePhoto // Readable somehow magically appears here, but at this point of time, it only awaits to }=data// do some things, e.g validations, db calls, etc...// this signals to finally start uploading stuff and pipe it to the storage, // since all the validations or other async operations are done, // and it's actually ready to do process the dataawaitprofilePhoto.pipe(storage.saveTo(savePath))}})
Underlying implementations need to solve following problems:
Inject a stream as a "part" of an actual RPC payload, which will be very convenient and straightforward for both operations: send data (client-side) and consume it (server-side)
Allow to perform async operations without building internal buffer - not starting to push data to the server, before it actually is ready to consume it - which means that server need to signals back to the client that it's ready. This particular requirement could be quite a challenge for some transports, like HTTP.
Possible solution to problem №2 with HTTP transport is to use multiple connection for RPC with included streams:
Client makes a request to Server to initialize Stream. Server replies with headers which contains Stream ID, but not closes it (aka long-polling) and awaits for further signals.
Client makes RPC. Server calls procedure handler. Then handler has to options:
Accept stream -> signals to the awaiting connection to close with OK status in body.
Reject stream -> signals to the awaiting connection to close with NON-OK status in body.
On first long-polling response Client closes the stream if rejected, or starts uploading data if accepted
But, this approach comes with one limitation. All requests must be done to the same API worker, which could require proper load balancing configuration.
UPD:
After some thought, the best option for client-side streams so far seem to be just Streams API. It is the most portable solution for now, supported pretty much everywhere: Web, Node, Bun and Deno.
If a client is being used in Node.js or Bun env, then it can easily be transfromed to Node's Readable with Readable.fromWeb().
Also, asyncIterable is not widely supported in browsers yet, so it might be usefull to provider some toAsyncIterable() method.
UPD 2:
There's also quite common usecase, when for some RPC server no only "responds" with stream, but also with some payload. Therefore:
// server-side// example of binary data streamdeclareProcedure(()=>{constpayload={some: 'thing'}conststream=createBinaryStreamResponse(payload)fs.createReadableStream('/path/to/file').pipe(stream)returnstream})// example of json data streamdeclareProcedure(()=>{asyncfunctiongetDataFromDb(){forawait(constrowsofcursor){rows.length ? stream.write(rows) : stream.end()// .write typing expect { data: string }[]}}constpayload={some: 'thing'}conststream=createJsonStreamResponse(payload).with<{data: string}[]>()// helper for static typinggetDataFromDb()returnstream})
In addition to #55, it also will be usefull to come up with interfaces for streams.
Server-to-Client:
Basically, there will be two major types of data streaming:
binary
data - just bunch of bytes splitted and sent in chunksjson
data - some structured dataServer-side streams API :
Then client-side stream API could be something like this, where
client.rpc
return instance of Readable (which can be used as async iterable):Concerns and important things that need to take care of:
Client-to-Server:
When it comes to uploading data to server, there's no point of having json streams, since any input from client should be validated, so only there will be only binary data streaming. In the end it should be easy and convenient, something like:
And on the server side:
Underlying implementations need to solve following problems:
Possible solution to problem №2 with HTTP transport is to use multiple connection for RPC with included streams:
But, this approach comes with one limitation. All requests must be done to the same API worker, which could require proper load balancing configuration.
UPD:
After some thought, the best option for client-side streams so far seem to be just Streams API. It is the most portable solution for now, supported pretty much everywhere: Web, Node, Bun and Deno.
If a client is being used in Node.js or Bun env, then it can easily be transfromed to Node's
Readable
withReadable.fromWeb()
.Also, asyncIterable is not widely supported in browsers yet, so it might be usefull to provider some
toAsyncIterable()
method.UPD 2:
There's also quite common usecase, when for some RPC server no only "responds" with stream, but also with some payload. Therefore:
Then client-side API will be the following:
The text was updated successfully, but these errors were encountered: