Last active
December 5, 2020 18:12
-
-
Save mctep/672846e55ae4ff8c84de27589484b442 to your computer and use it in GitHub Desktop.
PoC Typescript RPC Client & Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const backend = { | |
echo: async (message: string) => message, | |
}; | |
export type Backend = typeof backend; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import express from 'express'; | |
import { backend } from './01-backend'; | |
import { createService } from './04-create-service'; | |
async function main() { | |
const port = 4000; | |
const app = express(); | |
const backendService = createService(backend); | |
app.post('/api', express.text(), async (req, res) => { | |
try { | |
res.send(await backendService(req.body)); | |
} catch { | |
res.end(); | |
} | |
}); | |
app.listen(port, () => { | |
console.log(`🚀 Server ready at: ${String(port)} port`); | |
}); | |
} | |
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fetch from 'node-fetch'; | |
import { Backend } from './01-backend'; | |
import { createClient } from './05-create-client'; | |
const client = createClient<Backend>(async (body) => { | |
const res = await fetch('http://localhost:4000/api', { | |
method: 'POST', | |
body, | |
}); | |
return rest.text(); | |
}); | |
async function main() { | |
console.log(await client.echo('Hello world')); | |
} | |
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Resolver, IRequest, Json } from './06-protocol'; | |
export function createService<T extends Resolver<T>>(resolver: T) { | |
return async (body: string): Promise<string> => { | |
const req: IRequest = JSON.parse(body); | |
try { | |
const result = await ((resolver as any)[req.method] as ( | |
...args: Json[] | |
) => Promise<Json>).apply(resolver, req.args); | |
return JSON.stringify({ type: 'success', result }); | |
} catch (result) { | |
return JSON.stringify({ type: 'error', result }); | |
} | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IRequest, IResponse, Json, Resolver } from './06-protocol'; | |
type IRequester = (body: string) => Promise<string>; | |
export function createClient<T extends Resolver<T>>(requester: IRequester): T { | |
return new Proxy({} as T, { | |
get(_, method) { | |
if (typeof method !== 'string') { | |
throw new Error(`${String(method)} property does not found`); | |
} | |
return async (...args: Json[]) => { | |
const req: IRequest = { method, args }; | |
const res: IResponse = JSON.parse(await requester(JSON.stringify(req))); | |
if (res.type === 'error') { | |
throw res.result; | |
} | |
return res.result; | |
}; | |
}, | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type Json = | |
| null | |
| boolean | |
| number | |
| string | |
| Json[] | |
| { [prop: string]: Json }; | |
export type JsonCompatible<T> = { | |
[P in keyof T]: T[P] extends Json | |
? T[P] | |
: Pick<T, P> extends Required<Pick<T, P>> | |
? never | |
: T[P] extends (() => any) | undefined | |
? never | |
: JsonCompatible<T[P]>; | |
}; | |
export type JsonCompatibleResolver<T> = T extends ( | |
...args: Array<infer A> | |
) => Promise<infer R> | |
? A extends JsonCompatible<A> | |
? R extends JsonCompatible<R> | |
? T | |
: never | |
: never | |
: never; | |
export type Resolver<T> = { [K in keyof T]: JsonCompatibleResolver<T[K]> }; | |
export type IRequest = { method: string; args: Json[] }; | |
export type IResponse = | |
| { type: 'error'; result: Json } | |
| { type: 'success'; result: Json }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment