GithubHelp home page GithubHelp logo

fusionjs / fusion-plugin-rpc Goto Github PK

View Code? Open in Web Editor NEW
2.0 6.0 17.0 903 KB

Migrated to https://github.com/fusionjs/fusionjs

License: MIT License

JavaScript 98.21% Shell 1.45% Dockerfile 0.33%
fusion fusionjs

fusion-plugin-rpc's Introduction

fusion-plugin-rpc

Build status

Fetch data on the server and client with an RPC style interface.

RPC is a natural way of expressing that a server-side function should be run in response to a client-side function call. Unlike RESTful architectures, RPC-based architectures are not required to conform to statelessness constraints and are free to return session-scoped data. Additionally, the semantics of RPC calls are not constrained by the availability of suitably-descriptive HTTP methods and RPC calls can express complex state change requests more naturally as verbs (e.g. returnProduct(id)) rather than object-orientation (e.g. PATCH /api/orders/:id).

If you're using React/Redux, you should use fusion-plugin-rpc-redux-react instead of this package.


Table of contents


Installation

yarn add fusion-plugin-rpc

Usage

import {createPlugin} from 'fusion-core';
export default createPlugin({
  deps: {RPC: RPCToken},
  middleware: ({RPCFactory}) => (ctx, next) => {
    RPC.from(ctx).request('getUser', 1).then(console.log);
  }
);

Setup

// src/main.js
import React from 'react';
import App, {createPlugin} from 'fusion-core';
import RPC, {
  RPCToken,
  RPCHandlersToken,
  ResponseError,
} from 'fusion-plugin-rpc';
import UniversalEvents, {
  UniversalEventsToken,
} from 'fusion-plugin-universal-events';
import {FetchToken} from 'fusion-tokens';
import fetch from 'unfetch';

// Define your rpc methods server side
const handlers = __NODE__ && {
  getUser: async (args, ctx) => {
    return {some: 'data' + args};
  },
  test: async (args, ctx) => {
    // Error Handling Example
    try {
      doThing();
    } catch (e) {
      const error = new ResponseError('Failed to do thing');
      error.code = 'DOTHING';
      error.meta = {
        custom: 'metadata',
      };
      throw error;
    }
  },
};

export default () => {
  const app = new App(<div />);

  app.register(RPCToken, RPC);
  app.register(UniversalEventsToken, UniversalEvents);
  __NODE__
    ? app.register(RPCHandlersToken, handlers)
    : app.register(FetchToken, fetch);

  return app;
};

API

Registration API

RPC
import RPC from 'fusion-plugin-rpc';

The RPC plugin. Provides the RPC service API.

RPCToken
import {RPCToken} from 'fusion-plugin-rpc-redux-react';

The canonical token for the RPC plugin. Typically, it should be registered with the RPC plugin.

Dependencies

UniversalEventsToken

Required. See https://github.com/fusionjs/fusion-plugin-universal-events#api

RPCHandlersToken
import {RPCHandlersToken} from 'fusion-plugin-rpc-redux-react';

Configures what RPC handlers exist. Required. Server-only.

Types
type RPCHandlers = Object<string, () => any>

You can register a value of type RPCHandlers or a Plugin that provides a value of type RPCHandlers.

FetchToken

Required. Browser-only. See https://github.com/fusionjs/fusion-tokens#fetchtoken

ReduxToken

Required. See https://github.com/fusionjs/fusion-plugin-react-redux

ReducerToken

Required. See https://github.com/fusionjs/fusion-plugin-react-redux


Service API

const rpc: RPC = Rpc.from((ctx: Context));
  • ctx: Context - Required. A Fusion.js context

  • returns rpc: {request: (method: string, args: any) => Promise<any>}

    • request: (method: string, args: any) => Promise<any> - Makes an RPC call via an HTTP request. If on the server, this will directly call the method handler with (args, ctx).

      If on the browser, this will POST to /api/${method} endpoint with JSON serialized args as the request body. The server will then deserialize the args and call the rpc handler. The response will be serialized and send back to the browser.

      • method: string - Required. The RPC method name
      • args: any - Optional. Arguments to pass to the server-side RPC handler. Must be JSON-serializable.

mock

The package also exports a mock RPC plugin which can be useful for testing. For example:

import {mock as MockRPC, RPCToken} from 'fusion-plugin-rpc';

app.register(RPCToken, mock);

Error Handling

Use the ResponseError error subclass for sending error responses. If this error class is not used, a generic message will be sent to the client.

import {ResponseError} from 'fusion-plugin-rpc';

function testHandler() {
  try {
    doThing();
  } catch (e) {
    const error = new ResponseError('Failed to do thing');
    error.code = 'DOTHING';
    error.meta = {
      custom: 'metadata',
    };
    throw error;
  }
}

Generating mock RPC handlers from fixtures

The package also exports a getMockRpcHandlers util which can be useful for testing. Fixtures need to be of the following type

type RpcResponse = Object | ResponseError;
type RpcResponseMap = Array<{
  args: Array<*>,
  response: RpcResponse,
}>;
type RpcFixtureT = {[string]: RpcResponseMap | RpcResponse};

getMockRpcHandlers has the following interface:

type getMockRpcHandlersT = (
  fixtures: Array<RpcFixtureT>,
  onMockRpc?: OnMockRpcCallbackT
) => HandlerType;

For example:

import {getMockRpcHandlers, ResponseError} from 'fusion-plugin-rpc';

const rpcFixtures = [
  {
    getUser: {
      firstName: 'John',
      lastName: 'Doe',
      uuid: 123,
    },
  },
  {
    updateUser: [{
      args: [{firstName: 'Jane'}],
      response: {
        firstName: 'John',
        lastName: 'Doe',
        uuid: 123,
      },
    }, {
      args: [{firstName: ''}],
      response: new ResponseError('Username cant be empty'),
    }]
  },
];

const mockRpcHandlers = getMockRpcHandlers(rpcFixtures);

const user = await mockRpcHandlers.getUser();

try {
  const user = await mockRpcHandlers.updateUser({firstName: ''});
} catch (updatedUserError) {
  // When error object is passed as response in fixtures,
  // it will be considered as a failure scenario and will be thrown by rpc handler.
}

fusion-plugin-rpc's People

Contributors

albertywu avatar alexmsmithca avatar chrisdothtml avatar derekjuber avatar flyingsky avatar ganemone avatar jpteasdale avatar kevingrandon avatar lhorie avatar mlmorg avatar nadiia avatar renovate-bot avatar renovate[bot] avatar rtsao avatar sagiavinash avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

fusion-plugin-rpc's Issues

Update API to use .request rather than copying methods

We currently copy methods from {handlers} onto the RPC service. In the browser, we send down a serialized version of those handlers. While this has a nice API, it has a few problems.

  1. Unnecessary additions to html size
  2. Bad developer experience when using hot-reloading. If you add a new handler on the server, you need to refresh to page in order to use it.

A .request API is simpler and solves these problems.

Add Flow types to source files

Problem/Rationale

Source code currently lacks Flow types. As a widely used plugin, it could benefit from static checking.

Solution/Change/Deliverable

Implement Flow types

Add Token dependencies to readme

Problem/Rationale

Documentation regarding Fusion API is out of date given recent changes to leverage new Dependency Injection architecture.

Solution/Change/Deliverable

Update documentation

Update check for ctx in constructor

We need to update the core plugin api to pass null or undefined to the Service constructor when a user calls .of(). Once that is done, we can update our check to check for truthiness rather than ctx.headers.

Add support for FormData

Rationale: Currently, RPC always assume JSON data. Supporting FormData would enable the ability to do file uploads

Ensure plugin provides `{from(ctx)}`

Plugins developed by the FusionJS team should provide a consistent interface for obtaining a request-scoped service instance, as described here: fusionjs/fusion-core#72 (comment)

Rationale:

A consistent interface reduces the cognitive load on framework users since it becomes one less thing that requires looking up documentation

Deliverable:

Change the provided interface from (ctx) => T to {from: (ctx) => T}

Thoughts on always sending 200 status code?

HTTP status codes don't translate nicely to the web rpc pattern. We currently send a 400 status code on RPC failure, however this doesn't really make any sense. From the RPC framework perspective, the request succeeded. We could potentially send some error status code for serialization / deserialization errors, but apart from that I think we should simply default to a 200 status code, and allow the user to override it via the ctx argument passed to the rpc handler.

Thoughts?

Investigate ways to improve rpc error handling

The current process for failing an RPC is to return a Promise that rejects. This works fairly well, however it falls a bit short when you want to serialize data with the RPC failure.

It is generally good practice to only instances of an Error object to Promise.reject(), and similarly to only throw Error objects (see https://eslint.org/docs/rules/prefer-promise-reject-errors and https://eslint.org/docs/rules/no-throw-literal). However, lets say you want to signal an RPC failure, and you want to serialize some JSON down to the client. We currently only send the error.message data to the client, meaning you are very restricted in how you handle errors.

Node has somewhat recently standardized on using Error.code to signal the type of error (see https://nodejs.org/api/errors.html#errors_error_code). A good first step would be supporting this property on the rejected error.

If error.code is not enough (which I suspect it won't be), we could support serializing arbitrary JSON on the error.meta key.

Thoughts?

Update API for Error Handling

The current API of rpc handlers has users return a Promise that resolves to a response for the rpc request. If the promise rejects, the plugin will use the properties on the error (other than the stack trace) as the response. This causes issues however when a handler allows an error to bubble from a library. The bubbled error will be used for the response, and can contain information the user may not want in the response, such as leaked information from the request.

To resolve this, we should enforce that endpoints have explicit error handling that describes exactly the data they want to respond with. We can do this by ignoring the data in rejection errors unless they come from a special error subclass ResponseError which will be provided by the rpc plugin.

Example RPC Handler

import {ResponseError} from 'fusion-plugin-rpc';

function testHandler(args, ctx) {
   try {
    doThing();
  } catch (e) {
    throw new ResponseError('Failed to do thing', { custom: 'metadata' });
  }
}
// Responds with
{
  status: 'failure'
  data: {
    message: 'Failed to do thing',
    meta: {
      custom: 'metadata',
    },
  }
}

If a normal error is thrown, the plugin will log a warning and respond with an unknown error message.

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: Preset name not found within published preset config (monorepo:angularmaterial). Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

Update error message to have more info

Unhelpful error message.

: new Error(
'UnknownError - Use ResponseError for more detailed error messages'
);

Type of issue

Feature request

Description

At the moment, the aforementioned error message is unclear. Adding a little more info won't hurt.
The first thought that came to my mind when I saw the message was that I would need to extract ResponseError from the original thrown error message.

Current behavior

message: "UnknownError - Use ResponseError for more detailed error messages"

Expected behavior

message: "UnknownError - Use ResponseError from fusion-plugin-rpc (or fusion-plugin-rpc-redux-react if you are using React) package for more detailed error messages"

Steps to reproduce

  1. Just throw an error in your rpc call and you'll see the error message

Your environment

  • fusion-plugin-rpc version: 2.3.0

  • Node.js version (node --version): 8.11.3

  • npm version (npm --version): 5.6.0

  • Operating System: macOS 10.14.3

Import Flow declaration for 'fetch'

Problem/Rationale

Codebase currently has a copy/pasta type definition for fetch. Other plugins also have this same copy/pasta.

Solution/Change/Deliverable

We can either:

  • Centralize the type definition in a common FusionJS package.
  • Find common Flow definition for fetch and import the libdef.

Update fusion-plugin-rpc handling of routePrefix to not prepend to fetch calls

Type of issue

Bug

Description

fusion-plugin-rpc should integrate better with routePrefix. The standard way of handling routePrefix is to provide a fetch implementation that automatically appends the prefix.

Current behavior

fusion-plugin-rpc prepends the routePrefix to its calls to fetch, which results in a double route prefix url.

Expected behavior

fusion-plugin-rpc should call fetch without prepending the route prefix.

Steps to reproduce

  1. run a fusion application with the ROUTE_PREFIX environment variable set
  2. add client side rpc data fetching

Make createPlugin asynchronous

Problem/Rationale

Instantiation of a plugin may not always be best served by being synchronous.

Solution/Change/Deliverable

Allow createPlugin and it's associated methods to be asynchronous.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.