Skip to main content

Command


Introduction

Command is a class that creates a template for request and all necessary information needed for it to be made. It's strength lays in it's strict and predictable data structure. Thanks to which we can dump it, save it to some storage as a json and recreate it later from it. This approach allows you to develop a full persistence flow - we can easily persist command dumps between sessions.

The command contains mass about its behavior in the queues or cache, data on the server, and information necessary to execute a valid request. In combination with typescript we get a very friendly flow, resistant to mistakes.

We can trigger the request with the send method, which add request to the queue and returns the response.


Purpose

  • Configuration of requests templates
  • Standardization of the data schema in system
  • Sending requests by using dispatchers

Initialization

The command should be initialized from the builder instance, this way we pass a shared reference to the place that manages communication in the application.

caution

We are using currying in createCommand method to achieve auto generated types for the endpoint string. This solution will be removed once https://github.com/microsoft/TypeScript/issues/10571 get resolved.

import { builder } from "./builder";

export const getUsers = builder.createCommand<UserModel[]>()({
method: "GET",
endpoint: "/users",
});

export const getUser = builder.createCommand<UserModel>()({
method: "GET",
endpoint: "/users/:userId",
});

export const postUser = builder.createCommand<UserModel, UserPostDataType>()({
method: "POST",
endpoint: "/users",
});

export const patchUser = builder.createCommand<UserModel, Partial<UserPostDataType>>()({
method: "PATCH",
endpoint: "/users/:userId",
});

export const deleteUser = builder.createCommand<null>()({
method: "DELETE",
endpoint: "/users/:userId",
});

Request building

Initialize

The process begins with command initialization. At this point, we have the option of an extensive configuration of how our request will behave, but most of it is optional. It is also possible to prepare a global configuration in the builder and avoid copying the setup between commands.

const postUser = builder.createCommand<null>()({
endpoint: "/some-endpoint",
headers: {},
auth: true,
method: "POST"
cancelable: false,
retry: 2,
retryTime: 1000,
cache: false,
cacheTime: 50000
queued: false,
deduplicate: false,
offline: false,
options: {}, // Client options
disableRequestInterceptors: false,
disableResponseInterceptors: false,
});

Request Data

You can set any data to be sent to the server by using the setData method.

// Regular data
postUser.setData({ name: "John", age: 18 })

// Form data
const data = new FormData();
...
postFile.setData(data)

Parameters

Parameters has to be defined in the endpoint of the command by stating it with :.

const getNote = builder.createCommand()({
endpoint: "/note/:noteId";
})
const getCategory = builder.createCommand()({
endpoint: "/category/:categoryId";
})
const getCategoryNote = builder.createCommand()({
endpoint: "/category/:noteId";
})

When we have properly prepared commands that expect parameters, we can add them using the setParams method. Additionally by generic typescript, these parameters will match the endpoint ones by using literal types and require them.

getNote.setParams({ noteId: 1 });
getCategory.setParams({ categoryId: 2 });
getCategoryNote.setParams({ categoryId: 2, noteId: 1 });

Query parameters

You can set query params by using the setQueryParams method. With typescript you can set it up to be accepted as strings, objects or strict interface. The encoding type for arrays and other options is possible to be setup on the builder. You can also provide your own encoding logic.

getUsers.setQueryParams({ search: "John", sort: "age" });

Trigger request

You can perform a request with the send method.

// Simple Send
getNotes.send();

// Chained Send
getUsers.setQueryParams({ search: "John", sort: "age" }).send();

// Multiple chained Send
getCategory.setParams({ categoryId: 2 }).setQueryParams({ sortNotes: "age" }).send();

For usage with React checkout our hooks docs.


Features

You can read more in the API reference and guides.

Cancellation

Queueing

Offline

Deduplication

Authentication

Data Mapping


Methods

Using methods on a command is different from the other classes in Hyper Fetch. This is to ensure isolation between different uses - not to overwrite previously prepared commands with each other and to dynamically generate keys for queues or cache.

danger

Using any method on command returns its clone! We don't return a reference!

// ❌ WRONG

const command = getUser;

command.setParams({ userId: 1 }); // Returns CLONED command with assigned params

command.send(); // Server error - no params
// ✅ Good

const command = getUser;

const commandWithParams = command.setParams({ userId: 1 }); // Returns CLONED command with assigned params

commandWithParams.send(); // Success

Keys

Each command gets its identifiers - queueKey, cacheKey, abortKey and effectKey. They are needed to determine under which key items will be cached, queued, canceled or handled by Effects. By default keys are auto-generated based on the current values of parameters, endpoint, method, but you can overwrite those values with the proper method.


Typescript

Builder has four generic types built in. This includes Response, Payload, Local Error Response, Query Params.

type Response = { name: string }; // What's returned from request
type Payload = { email: string }; // What's send with request
type LocalError = { nameMessage: string }; // Additional "local" errors like errors for particular form
type QueryParams = { sort: string; search: string }; // Query params interface

const someCommand = builder.createCommand<Response, Payload, LocalError, QueryParams>()({
endpoint: "category/:categoryId",
});

someCommand.setData(); // Require the 'Payload' type
someCommand.setParams(); // Require { categoryId: Param } type
someCommand.setQueryParams(); // Require the 'QueryParams' type

const [data, error] = someCommand.send();

data; // Has 'Response' type
error; // Has 'GlobalError' or 'LocalError' type

Parameters

Configuration options

Name Type Description
abortKey string Key which will be used to cancel requests. Autogenerated by default.
auth boolean Should the onAuth method get called on this command
cache boolean Should we save the response to cache
cacheKey string Key which will be used to cache requests. Autogenerated by default.
cacheTime number Time for which the cache is considered up-to-date
cancelable boolean Should enable cancelable mode in the Dispatcher
deduplicate boolean Should we deduplicate two requests made at the same time into one
deduplicateTime number Time of pooling for the deduplication to be active (default 10ms)
disableRequestInterceptors boolean Disable pre-request interceptors
disableResponseInterceptors boolean Disable post-request interceptors
effectKey string Key which will be used to use effects. Autogenerated by default.
endpoint GenericEndpoint Determine the endpoint for command request
headers HeadersInit Custom headers for command
method HttpMethodsType Request method GET | POST | PATCH | PUT | DELETE
offline boolean Do we want to store request made in offline mode for latter use when we go back online
options ClientOptions Additional options for your client, by default XHR options
queueKey string Key which will be used to queue requests. Autogenerated by default.
queued boolean Should the requests from this command be send one-by-one
retry number Retry count when request is failed
retryTime number Retry time delay between retries