This project is still at an early stage and is not production ready. API could be changed.
- Seameless code usage between client and server – call server functions just as normal functions (and vice-versa).
- Out of the box streaming support (with Server Sent Events by default)
- Framework agnostic
- Strong Typescript support
- Advanced resolver caching options: from HTTP-level to session storage
- Performant React component subscriptions, and automatic cache invalidation rerendering
Install dependencies:
npm install --save @convey/core
npm install --save-dev @convey/babel-plugin
Optional for usage with react
:
npm install --save @convey/react
See nextjs babel.config.js for example
Add @convey/babel-plugin
to your babel config:
// babel.config.js
module.exports = {
plugins: [
[
'@convey',
{
/**
* Determine "remote" resolvers
*
* "server" resolvers will be processed as remote for the "client" code, and vice versa
*/
remote: process.env.TARGET === 'client' ? /resolvers\/server/ : /resolvers\/client/,
},
],
],
}
See nextjs pages/api/resolver/[id].ts for example
import {createResolverHandler} from '@convey/core/server'
import * as resolvers from '@app/resolvers/server'
const handleResolver = createResolverHandler(resolvers)
export default async function handle(req, res) {
await handleResolver(req, res)
}
See nextjs pages/_app.tsx for example
import {setConfig} from '@convey/core'
import {createResolverFetcher} from '@convey/core/client'
setConfig({
fetch: createResolverFetcher(),
})
resolvers/server/index.tsx
import {exec} from 'child_process'
import {promisify} from 'util'
import {createResolver, createResolverStream} from '@convey/core'
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
/**
* This code will be executed on the server side
*/
export const getServerDate = createResolver(async () => promisify(exec)('date').then((x) => x.stdout.toString()))
export const getServerHello = createResolver((name: string) => `Hello, ${name}`)
/**
* It is also possible to declare the stream via generator function.
* By default, the data will be streamed by SSE (Server Sent Events)
*/
export const getServerHelloStream = createResolverStream(async function* (name: string) {
while (true) {
/**
* Resolvers could be called as normal functions on server side too
*/
yield await getServerHello(`${name}-${await getServerDate()}`)
await wait(1000)
}
})
After processing, on the client-side the actual code will be like:
import {createResolver, createResolverStream} from '@convey/core'
/**
* This code will be executed on the server side
*/
export const getServerDate = createResolver(
{},
{
id: '3296945930:getServerDate',
},
)
export const getServerHello = createResolver(
{},
{
id: '3296945930:getServerHello',
},
)
/**
* It is also possible to declare the stream via generator function.
* By default, the data will be streamed by SSE (Server Sent Events)
*/
export const getServerHelloStream = createResolverStream(
{},
{
id: '3296945930:getServerHelloStream',
},
)
Direct usage:
import {getServerHello, getServerHelloStream} from '@app/resolvers/server'
console.log(await getServerHello('world')) // `Hello, world`
for await (let hello of getServerHelloStream('world')) {
console.log(hello) // `Hello, world-1637759100546` every second
}
Usage with React:
import {useResolver} from '@convey/react'
import {getServerHello, getServerHelloStream} from '@app/resolvers/server'
export const HelloComponent = () => {
/**
* Component will be automatically rerendered on data invalidation
*/
const [hello] = useResolver(getServerHello('world'))
/**
* If resolver is a stream, then component will be rerendered
* on each new chunk of data
*/
const [helloStream] = useResolver(getServerHelloStream('world'))
return (
<div>
<p>Single hello: {hello}</p>
<p>Stream hello: {helloStream}</p>
</div>
)
}
This project was heavily inspired by work of amazing engineers at Yandex.Market: