UNPKG

6.94 kBMarkdownView Raw
1# fusion-plugin-rpc
2
3[![Build status](https://badge.buildkite.com/5165e82185b13861275cd0a69f29c2a13bc66dfb9461ee4af5.svg?branch=master)](https://buildkite.com/uberopensource/fusion-plugin-rpc)
4
5Fetch data on the server and client with an
6[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) style interface.
7
8RPC is a natural way of expressing that a server-side function should be run in
9response to a client-side function call. Unlike
10[RESTful architectures](https://en.wikipedia.org/wiki/Representational_state_transfer),
11RPC-based architectures are not required to conform to statelessness constraints
12and are free to return session-scoped data. Additionally, the semantics of RPC
13calls are not constrained by the availability of suitably-descriptive HTTP
14methods and RPC calls can express complex state change requests more naturally
15as verbs (e.g. `returnProduct(id)`) rather than object-orientation (e.g.
16`PATCH /api/orders/:id`).
17
18If you're using React/Redux, you should use
19[`fusion-plugin-rpc-redux-react`](https://github.com/fusionjs/fusion-plugin-rpc-redux-react)
20instead of this package.
21
22---
23
24### Table of contents
25
26- [Installation](#installation)
27- [Usage](#usage)
28- [Setup](#setup)
29- [API](#api)
30 - [Registration API](#registration-api)
31 - [Dependencies](#dependencies)
32 - [Service API](#service-api)
33 - [`mock`](#mock)
34
35---
36
37### Installation
38
39```
40yarn add fusion-plugin-rpc
41```
42
43---
44
45### Usage
46
47```js
48import {createPlugin} from 'fusion-core';
49export default createPlugin({
50 deps: {RPC: RPCToken},
51 middleware: ({RPCFactory}) => (ctx, next) => {
52 RPC.from(ctx).request('getUser', 1).then(console.log);
53 }
54);
55```
56
57---
58
59### Setup
60
61```js
62// src/main.js
63import React from 'react';
64import App, {createPlugin} from 'fusion-core';
65import RPC, {
66 RPCToken,
67 RPCHandlersToken,
68 ResponseError,
69} from 'fusion-plugin-rpc';
70import UniversalEvents, {
71 UniversalEventsToken,
72} from 'fusion-plugin-universal-events';
73import {FetchToken} from 'fusion-tokens';
74import fetch from 'unfetch';
75
76// Define your rpc methods server side
77const handlers = __NODE__ && {
78 getUser: async (args, ctx) => {
79 return {some: 'data' + args};
80 },
81 test: async (args, ctx) => {
82 // Error Handling Example
83 try {
84 doThing();
85 } catch (e) {
86 const error = new ResponseError('Failed to do thing');
87 error.code = 'DOTHING';
88 error.meta = {
89 custom: 'metadata',
90 };
91 throw error;
92 }
93 },
94};
95
96export default () => {
97 const app = new App(<div />);
98
99 app.register(RPCToken, RPC);
100 app.register(UniversalEventsToken, UniversalEvents);
101 __NODE__
102 ? app.register(RPCHandlersToken, handlers)
103 : app.register(FetchToken, fetch);
104
105 return app;
106};
107```
108
109---
110
111### API
112
113#### Registration API
114
115##### `RPC`
116
117```js
118import RPC from 'fusion-plugin-rpc';
119```
120
121The RPC plugin. Provides the RPC [service API](#service-api).
122
123##### `RPCToken`
124
125```js
126import {RPCToken} from 'fusion-plugin-rpc-redux-react';
127```
128
129The canonical token for the RPC plugin. Typically, it should be registered with
130the [RPC](#rpc) plugin.
131
132#### Dependencies
133
134##### `UniversalEventsToken`
135
136Required. See
137[https://github.com/fusionjs/fusion-plugin-universal-events#api](https://github.com/fusionjs/fusion-plugin-universal-events#api)
138
139##### `RPCHandlersToken`
140
141```js
142import {RPCHandlersToken} from 'fusion-plugin-rpc-redux-react';
143```
144
145Configures what RPC handlers exist. Required. Server-only.
146
147###### Types
148
149```flow
150type RPCHandlers = Object<string, () => any>
151```
152
153You can register a value of type `RPCHandlers` or a Plugin that provides a value
154of type `RPCHandlers`.
155
156##### `FetchToken`
157
158Required. Browser-only. See
159[https://github.com/fusionjs/fusion-tokens#fetchtoken](https://github.com/fusionjs/fusion-tokens#fetchtoken)
160
161##### `ReduxToken`
162
163Required. See
164[https://github.com/fusionjs/fusion-plugin-react-redux](https://github.com/fusionjs/fusion-plugin-react-redux)
165
166##### `ReducerToken`
167
168Required. See
169[https://github.com/fusionjs/fusion-plugin-react-redux](https://github.com/fusionjs/fusion-plugin-react-redux)
170
171---
172
173#### Service API
174
175```js
176const rpc: RPC = Rpc.from((ctx: Context));
177```
178
179- `ctx: Context` - Required. A
180 [Fusion.js context](https://github.com/fusionjs/fusion-core#context)
181- returns `rpc: {request: (method: string, args: any) => Promise<any>}`
182
183 - `request: (method: string, args: any) => Promise<any>` - Makes an RPC call
184 via an HTTP request. If on the server, this will directly call the `method`
185 handler with `(args, ctx)`.
186
187 If on the browser, this will `POST` to `/api/${method}` endpoint with JSON
188 serialized args as the request body. The server will then deserialize the
189 args and call the rpc handler. The response will be serialized and send back
190 to the browser.
191
192 - `method: string` - Required. The RPC method name
193 - `args: any` - Optional. Arguments to pass to the server-side RPC handler.
194 Must be JSON-serializable.
195
196### mock
197
198The package also exports a mock RPC plugin which can be useful for testing. For
199example:
200
201```js
202import {mock as MockRPC, RPCToken} from 'fusion-plugin-rpc';
203
204app.register(RPCToken, mock);
205```
206
207### Error Handling
208
209Use the `ResponseError` error subclass for sending error responses. If this
210error class is not used, a generic message will be sent to the client.
211
212```js
213import {ResponseError} from 'fusion-plugin-rpc';
214
215function testHandler() {
216 try {
217 doThing();
218 } catch (e) {
219 const error = new ResponseError('Failed to do thing');
220 error.code = 'DOTHING';
221 error.meta = {
222 custom: 'metadata',
223 };
224 throw error;
225 }
226}
227```
228
229### Generating mock RPC handlers from fixtures
230
231The package also exports a getMockRpcHandlers util which can be useful for testing.
232Fixtures need to be of the following type
233
234```js
235type RpcResponse = Object | ResponseError;
236type RpcResponseMap = Array<{
237 args: Array<*>,
238 response: RpcResponse,
239}>;
240type RpcFixtureT = {[string]: RpcResponseMap | RpcResponse};
241```
242
243`getMockRpcHandlers` has the following interface:
244
245```js
246type getMockRpcHandlersT = (
247 fixtures: Array<RpcFixtureT>,
248 onMockRpc?: OnMockRpcCallbackT
249) => HandlerType;
250```
251
252For example:
253
254```js
255import {getMockRpcHandlers, ResponseError} from 'fusion-plugin-rpc';
256
257const rpcFixtures = [
258 {
259 getUser: {
260 firstName: 'John',
261 lastName: 'Doe',
262 uuid: 123,
263 },
264 },
265 {
266 updateUser: [{
267 args: [{firstName: 'Jane'}],
268 response: {
269 firstName: 'John',
270 lastName: 'Doe',
271 uuid: 123,
272 },
273 }, {
274 args: [{firstName: ''}],
275 response: new ResponseError('Username cant be empty'),
276 }]
277 },
278];
279
280const mockRpcHandlers = getMockRpcHandlers(rpcFixtures);
281
282const user = await mockRpcHandlers.getUser();
283
284try {
285 const user = await mockRpcHandlers.updateUser({firstName: ''});
286} catch (updatedUserError) {
287 // When error object is passed as response in fixtures,
288 // it will be considered as a failure scenario and will be thrown by rpc handler.
289}
290```