UNPKG

8.4 kBMarkdownView Raw
1# fusion-plugin-rpc
2
3[![Build status](https://badge.buildkite.com/4c8b6bc04b61175d66d26b54b1d88d52e24fecb1b537c54551.svg?branch=master)](https://buildkite.com/uberopensource/fusionjs)
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/fusionjs/tree/master/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- [Customization](#customization)
30- [API](#api)
31 - [Registration API](#registration-api)
32 - [Dependencies](#dependencies)
33 - [Service API](#service-api)
34 - [`mock`](#mock)
35
36---
37
38### Installation
39
40```
41yarn add fusion-plugin-rpc
42```
43
44---
45
46### Usage
47
48```js
49import {createPlugin} from 'fusion-core';
50export default createPlugin({
51 deps: {RPC: RPCToken},
52 middleware: ({RPCFactory}) => (ctx, next) => {
53 RPC.from(ctx).request('getUser', 1).then(console.log);
54 }
55);
56```
57
58---
59
60### Setup
61
62```js
63// src/main.js
64import React from 'react';
65import App, {createPlugin} from 'fusion-core';
66import RPC, {
67 RPCToken,
68 RPCHandlersToken,
69 ResponseError,
70} from 'fusion-plugin-rpc';
71import UniversalEvents, {
72 UniversalEventsToken,
73} from 'fusion-plugin-universal-events';
74import {FetchToken} from 'fusion-tokens';
75import fetch from 'unfetch';
76
77// Define your rpc methods server side
78const handlers = __NODE__ && {
79 getUser: async (args, ctx) => {
80 return {some: 'data' + args};
81 },
82 test: async (args, ctx) => {
83 // Error Handling Example
84 try {
85 doThing();
86 } catch (e) {
87 const error = new ResponseError('Failed to do thing');
88 error.code = 'DOTHING';
89 error.meta = {
90 custom: 'metadata',
91 };
92 throw error;
93 }
94 },
95};
96
97export default () => {
98 const app = new App(<div />);
99
100 app.register(RPCToken, RPC);
101 app.register(UniversalEventsToken, UniversalEvents);
102 __NODE__
103 ? app.register(RPCHandlersToken, handlers)
104 : app.register(FetchToken, fetch);
105
106 return app;
107};
108```
109
110---
111
112### Customization
113
114The plugin can accept an optional config token for modifying the default behavior.
115
116#### Modify RPC Routes
117
118```js
119// src/main.js
120import React from 'react';
121import App, {createPlugin} from 'fusion-core';
122import RPC, {
123 RPCToken,
124 RPCHandlersToken,
125 ResponseError,
126 RPCHandlersConfigToken,
127} from 'fusion-plugin-rpc';
128import UniversalEvents, {
129 UniversalEventsToken,
130} from 'fusion-plugin-universal-events';
131import {FetchToken} from 'fusion-tokens';
132import fetch from 'unfetch';
133
134import handlers from './redux/handlers';
135
136export default () => {
137 const app = new App(<div />);
138
139 app.register(RPCHandlersConfigToken, {
140 // Modify RPC endpoints to be accessible at /nested/api/rpcs/<RPC_ID>
141 apiPath: 'nested/api/rpcs',
142 });
143
144 app.register(RPCToken, RPC);
145 app.register(UniversalEventsToken, UniversalEvents);
146 __NODE__
147 ? app.register(RPCHandlersToken, handlers)
148 : app.register(FetchToken, fetch);
149
150 return app;
151};
152```
153
154---
155
156### API
157
158#### Registration API
159
160##### `RPC`
161
162```js
163import RPC from 'fusion-plugin-rpc';
164```
165
166The RPC plugin. Provides the RPC [service API](#service-api).
167
168##### `RPCToken`
169
170```js
171import {RPCToken} from 'fusion-plugin-rpc-redux-react';
172```
173
174The canonical token for the RPC plugin. Typically, it should be registered with
175the [RPC](#rpc) plugin.
176
177#### Dependencies
178
179##### `UniversalEventsToken`
180
181Required. See
182[https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-universal-events#api](https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-universal-events#api)
183
184##### `RPCHandlersToken`
185
186```js
187import {RPCHandlersToken} from 'fusion-plugin-rpc-redux-react';
188```
189
190##### `RPCHandlersConfigToken`
191
192```js
193import {RPCHandlersConfigToken} from 'fusion-plugin-rpc';
194```
195
196Configures what RPC handlers exist. Required. Server-only.
197
198###### Types
199
200```flow
201type RPCHandlers = Object<string, () => any>
202```
203
204You can register a value of type `RPCHandlers` or a Plugin that provides a value
205of type `RPCHandlers`.
206
207##### `FetchToken`
208
209Required. Browser-only. See
210[https://github.com/fusionjs/fusionjs/tree/master/fusion-tokens#fetchtoken](https://github.com/fusionjs/fusionjs/tree/master/fusion-tokens#fetchtoken)
211
212##### `ReduxToken`
213
214Required. See
215[https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-react-redux](https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-react-redux)
216
217##### `ReducerToken`
218
219Required. See
220[https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-react-redux](https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-react-redux)
221
222##### `RPCHandlersConfigToken`
223
224Optional.
225
226```flow
227type RPCConfigType = {
228 apiPath?: string,
229}
230```
231
232---
233
234#### Service API
235
236```js
237const rpc: RPC = Rpc.from((ctx: Context));
238```
239
240- `ctx: Context` - Required. A
241 [Fusion.js context](https://github.com/fusionjs/fusionjs/tree/master/fusion-core#context)
242- returns `rpc: {request: (method: string, args: any) => Promise<any>}`
243
244 - `request: (method: string, args: any) => Promise<any>` - Makes an RPC call
245 via an HTTP request. If on the server, this will directly call the `method`
246 handler with `(args, ctx)`.
247
248 If on the browser, this will `POST` to `/api/${method}` (unless modified;
249 see [customization](#customization)) endpoint with JSON serialized args as the
250 request body. The server will then deserialize the args and call the rpc
251 handler. The response will be serialized and send back to the browser.
252
253 - `method: string` - Required. The RPC method name
254 - `args: any` - Optional. Arguments to pass to the server-side RPC handler.
255 Must be JSON-serializable.
256
257### mock
258
259The package also exports a mock RPC plugin which can be useful for testing. For
260example:
261
262```js
263import {mock as MockRPC, RPCToken} from 'fusion-plugin-rpc';
264
265app.register(RPCToken, mock);
266```
267
268### Error Handling
269
270Use the `ResponseError` error subclass for sending error responses. If this
271error class is not used, a generic message will be sent to the client.
272
273```js
274import {ResponseError} from 'fusion-plugin-rpc';
275
276function testHandler() {
277 try {
278 doThing();
279 } catch (e) {
280 const error = new ResponseError('Failed to do thing');
281 error.code = 'DOTHING';
282 error.meta = {
283 custom: 'metadata',
284 };
285 throw error;
286 }
287}
288```
289
290### Generating mock RPC handlers from fixtures
291
292The package also exports a getMockRpcHandlers util which can be useful for testing.
293Fixtures need to be of the following type
294
295```js
296type RpcResponse = Object | ResponseError;
297type RpcResponseMap = Array<{
298 args: Array<*>,
299 response: RpcResponse,
300}>;
301type RpcFixtureT = {[string]: RpcResponseMap | RpcResponse};
302```
303
304`getMockRpcHandlers` has the following interface:
305
306```js
307type getMockRpcHandlersT = (
308 fixtures: Array<RpcFixtureT>,
309 onMockRpc?: OnMockRpcCallbackT
310) => HandlerType;
311```
312
313For example:
314
315```js
316import {getMockRpcHandlers, ResponseError} from 'fusion-plugin-rpc';
317
318const rpcFixtures = [
319 {
320 getUser: {
321 firstName: 'John',
322 lastName: 'Doe',
323 uuid: 123,
324 },
325 },
326 {
327 updateUser: [{
328 args: [{firstName: 'Jane'}],
329 response: {
330 firstName: 'John',
331 lastName: 'Doe',
332 uuid: 123,
333 },
334 }, {
335 args: [{firstName: ''}],
336 response: new ResponseError('Username cant be empty'),
337 }]
338 },
339];
340
341const mockRpcHandlers = getMockRpcHandlers(rpcFixtures);
342
343const user = await mockRpcHandlers.getUser();
344
345try {
346 const user = await mockRpcHandlers.updateUser({firstName: ''});
347} catch (updatedUserError) {
348 // When error object is passed as response in fixtures,
349 // it will be considered as a failure scenario and will be thrown by rpc handler.
350}
351```