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: