Mappersmith

repository·master·Indexed 43 days ago

https://github.com/tulios/mappersmith

A lightweight REST client for Node.js and the browser (version 2.47.1) that centralizes API configurations into a single manifest to decouple HTTP logic from application code. It features a flexible middleware system, support for dynamic path parameters, query string aliasing, and built-in gateways like Fetch.

Tokens
17.6K
Snippets
57
Records
65
Agent score
55%

What's inside Mappersmith

  1. Use match functions for mocks

    master

    Both mockClient and mockRequest support match functions to validate request properties.

    • mockClient accepts match functions for body and params.
    • mockRequest accepts match functions for body and url.

    Built-in match functions from mappersmith/test (m):

    • m.stringMatching(regexp): Accepts a regular expression.
    • m.stringContaining(string): Accepts a substring.
    • m.anything(): Accepts any value.
    • m.uuid4(): Accepts a UUID v4.

    You can also provide a custom function that returns a boolean:

    import { m } from 'mappersmith/test'
    
    // Custom function match
    mockClient(client).with({
      body: (body) => body === 'something'
    })
    import { m } from 'mappersmith/test'
    
    m.stringMatching(/something/)
    m.stringContaining('some-string')
    m.anything()
    m.uuid4()
  2. Configure resource methods in Mappersmith

    master

    Resources are defined by a name and a collection of methods. Each method can be configured with properties that define how the request is constructed.

    Key configuration properties for a method definition include:

    • path: The URL path. Supports dynamic segments like {id} (required), {group} (with defaults), or {market?} (optional).
    • method: The HTTP method (e.g., 'post', 'put').
    • host: An alternative host for this specific method.
    • headers: Default headers for the method.
    • params: Default parameters for the method.
    • binary: Set to true if the response is binary data (returns a Buffer in Node.js or a Blob in the browser).
    • bodyAttr, headersAttr, authAttr, timeoutAttr, signalAttr, hostAttr, pathAttr: Used to specify a custom parameter name if the default special parameter (like body or headers) cannot be used by your API.
    const client = forge({
      resources: {
        User: {
          all: { path: '/users' },
          byId: { path: '/users/{id}' },
          byGroup: { path: '/users/groups/{group}', params: { group: 'general' } },
          count: { path: '/{market?}/users' } 
        },
        Blog: {
          create: { method: 'post', path: '/blogs', headers: { 'X-Special-Header': 'value' } },
          bySubject: { path: '/blogs', queryParamAlias: { subjectId: 'subject_id' } },
          byDate: { path: ({date}) => `${date.getYear()}/${date.getMonth()}/${date.getDate()}` }
        }
      }
    })
  3. How middleware works in Mappersmith

    master

    Middleware allows you to customize the behavior between your client and the API. A middleware is a function that returns an object containing two optional methods: prepareRequest and response.

    The prepareRequest phase

    prepareRequest is used to modify the request before it is sent. It receives a next function which, when called, returns a Promise resolving to the Request object. You can use request.enhance() to create a new request based on the previous one.

    The response phase

    response is used to modify the response after it is received. It receives a next function which, when called, returns a Promise resolving to the Response object. You can use response.enhance() to create a new response based on the previous one.

    Passing information between middleware

    You can pass information from an earlier middleware to a later one using the request context via request.enhance({}, { key: 'value' }) and retrieving it with request.getContext().

    const MyMiddleware = () => ({
      prepareRequest(next) {
        return next().then(request => request.enhance({
          headers: { 'x-special-request': '->' }
        }))
      }
    })
    
    const MyMiddlewareTwo = () => ({
      async prepareRequest(next) {
        const request = await next()
        const { message } = request.getContext()
        console.log(message)
        return request
      }
    })
    // Order in forge: middleware: [MyMiddlewareOne, MyMiddlewareTwo]
  4. How gateways and the transport layer work

    master

    Mappersmith uses a pluggable transport layer called a gateway. By default, it includes three gateways: xhr, http, and fetch. Mappersmith automatically selects the appropriate gateway based on your environment (Node.js, service worker, or browser).

    You can implement a custom gateway by following the pattern used in the XHR implementation and assigning it to the global configs.gateway object.

    import { configs } from 'mappersmith'
    import { MyGateway } from './my-gateway'
    
    configs.gateway = MyGateway
  5. Configure middleware scope (Global, Client, or Resource)

    master

    Middleware can be applied at three different levels. When multiple levels are used, they are applied in the following order: Resource level $\rightarrow$ Client level $\rightarrow$ Global level.

    1. Resource level: Applied only to specific resources or methods within a client. Configured in the resources section of the manifest.
    2. Client level: Applied to all resources within a specific client. Configured in the root of the forge manifest.
    3. Global level: Applied to all clients created via forge. Configured via the configs.middleware object.

    Note: Global middleware can be disabled for a specific client by setting ignoreGlobalMiddleware: true in the forge configuration.

    // Resource level
    const client = forge({
      resources: {
        User: { all: { middleware: [ MyMiddleware ], path: '/users' } }
      }
    })
    
    // Client level
    const client = forge({
      middleware: [ MyMiddleware ],
      resources: { User: { all: { path: '/users' } } }
    })
    
    // Global level
    import { configs } from 'mappersmith'
    configs.middleware = [ MyMiddleware ]
  6. How to use Mappersmith

    master

    To create an API client, provide a manifest object to the forge function. The manifest defines a host (optional if the API is on the same domain) and a resources object. Each resource contains named methods that map to specific API paths.

    Note: You must configure a gateway (e.g., Fetch) via configs.gateway before using the client.

    import forge, { configs } from "mappersmith"
    import { Fetch } from "mappersmith/gateway/fetch"
    
    // Configure the gateway
    configs.gateway = Fetch;
    
    const github = forge({
      clientId: "github",
      host: "https://www.githubstatus.com",
      resources: {
        Status: {
          current: { path: "/api/v2/status.json" },
          summary: { path: "/api/v2/summary.json" },
          components: { path: "/api/v2/components.json" },
        },
      },
    });
    
    // Call a resource method
    git-hub.Status.current().then((response) => {
      console.log(`summary`, response.data());
    });
  7. Set up the Mappersmith test library

    master

    To use the Mappersmith testing utilities, you must call install() before your tests run and uninstall() after they complete. This ensures a clean environment for each test case.

    Example using Jasmine:

    import { install, uninstall } from 'mappersmith/test'
    
    describe('Feature', () => {
      beforeEach(() => install())
      afterEach(() => uninstall())
    })
    import { install, uninstall } from 'mappersmith/test'
    
    describe('Feature', () => {
      beforeEach(() => install())
      afterEach(() => uninstall())
    })
  8. Configure the Fetch gateway

    master

    Mappersmith does not provide polyfills for fetch. To use the Fetch gateway, you must first assign a native fetch implementation to configs.fetch, then set the gateway to FetchGateway.

    Usage

    1. Assign the fetch function: configs.fetch = fetchFunction.
    2. Set the gateway: configs.gateway = FetchGateway.
    3. (Optional) Configure fetch-specific options via configs.gatewayConfigs.Fetch (e.g., credentials).
    import { FetchGateway } from 'mappersmith/gateway'
    import { configs } from 'mappersmith'
    
    // 1. Assign the implementation
    configs.fetch = fetch
    
    // 2. Set the gateway
    configs.gateway = FetchGateway
    
    // 3. Extra configurations
    configs.gatewayConfigs.Fetch = {
      credentials: 'same-origin'
    }
  9. Use Mappersmith with CommonJS

    master

    If you are using CommonJS (require), use the following import patterns to access the default forge function, configs, and the FetchGateway.

    const forge = require("mappersmith").default;
    const { configs } = require("mappersmith");
    const FetchGateway = require("mappersmith/gateway/fetch").default;
  10. Override the request host

    master

    You can specify a different host for a specific method in the resource definition using the host key.

    To override the host dynamically during a method call, use the host parameter. Note: Since version 2.34.0, you must set allowResourceHostOverride: true in the forge configuration to enable this behavior.

    const client = forge({
      host: 'https://new-host.com',
      allowResourceHostOverride: true,
      resources: {
        User: {
          all: { path: '/users', host: 'https://old-host.com' }
        }
      }
    })
    
    // Dynamic override
    client.User.all({ host: 'http://very-old-api.com' })