8000 GitHub - lttb/convey: Typesafe client-server communication
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

lttb/convey

Repository files navigation

convey

This project is still at an early stage and is not production ready. API could be changed.

Key Features

  • 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

Examples

codesandbox example

Quick Start

Install dependencies:

npm install --save @convey/core
npm install --save-dev @convey/babel-plugin

Optional for usage with react:

npm install --save @convey/react

Babel config

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/,
			},
		],
	],
}

Server handler config

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)
}

Client config

See nextjs pages/_app.tsx for example

import {setConfig} from '@convey/core'
import {createResolverFetcher} from '@convey/core/client'

setConfig({
	fetch: createResolverFetcher(),
})

Declare and use resolvers

Server resolver

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',
	},
)

Client resolver usage

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>
	)
}

Thanks

This project was heavily inspired by work of amazing engineers at Yandex.Market:

0