
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
Simple Remote Data (SRD) is a fully static land compliant implementation of the Remote Data type in TypeScript - built with Higer Kinded Types (HKT's) inspired by fp-ts and Elm Remote Data.
The idea for using HKT's in TypeScript is based on Lightweight higher-kinded polymorphism.
Static Land CompliantWith yarn
yarn add srd
or if you prefer npm
npm i srd
SRD supports CJS, UMD and ESM bundle outputs.
The following is a common use case in React. Fetching data async, showing it on screen and handling initial, loading, and error states.
Without SRD, we would need something like this:
import React, { useState, useEffect } from 'react'
const App = () => {
const [asked, setAsked] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [data, setData] = useState(null)
useEffect(() => {
setAsked(true)
setLoading(true)
fetch('...')
.then((data) => {
setData(data)
setError(false)
setLoading(false)
})
.catch((err) => {
setData(null)
setError(err)
setLoading(false)
})
}, [])
if (!asked) {
return <div>Empty</div>
}
if (loading) {
return <div>Loading...</div>
}
if (error) {
return <div>{error}</div>
}
if (data) {
return <div>{data}</div>
}
return <div>Something went wrong...</div>
}
That's a lot of code for something we do very often, and highly error prone if we forget to unset/set some state values.
Here's the SRD way - using only 1 state variable instead of 4, preventing any impossible states:
import React, { useState, useEffect } from 'react'
import { SRD, notAsked, loading, failure, success } from 'srd'
const App = () => {
const [rd, setRd] = useState(notAsked())
useEffect(() => {
setRd(loading())
fetch('...')
.then((data) => setRd(success(data)))
.catch((err) => setRd(failure(err)))
}, [])
return SRD.match({
notAsked: () => <div>Empty</div>,
loading: () => <div>Loading...</div>,
failure: (err) => <div>{err}</div>,
success: (data) => <div>{data}</div>,
}, rd)
}
That's it! Very easy to use, and 90% of the time that's everything you will need.
SRD works even better with Typescript! Declare your RD type once and have typescript powerfully infer it everywhere! Like magic!
import React, { useState, useEffect } from 'react'
import { SRD, RD, notAsked, loading, failure, success } from 'srd'
import { Person, getPerson } from './people'
const App = () => {
const [rd, setRd] = useState<RD<string, Person>>(notAsked())
useEffect(() => {
setRd(loading())
getPerson(123)
.then((person) => setRd(success(person)))
.catch((err) => setRd(failure(err)))
}, [])
return SRD.match({
notAsked: () => <div>Empty</div>,
loading: () => <div>Loading...</div>,
failure: (msg) => <div>{msg}</div>,
success: (person) => <div>{person}</div>,
}, rd)
}
SRD comes with many of the Static Land functions that we all know and love. Here is a breakdown of all the supported algebras and utilities:
For comparing 2 SRD's to see if they are the same type.
*Note: This only compares the data types and not the inner value. So
Success(5) != Failure(5)butSuccess(5) == Success(80).
equals :: (RD e a, RD e b) -> boolean
import { SRD, success, notAsked } from 'SRD'
SRD.equals(success(5), notAsked()) // false
Allowing the type to be mapped over by the function provided.
map :: (a -> b, RD e a) -> RD e b
import { SRD, success, loading } from 'SRD'
const double = x => x * 2
const rd1 = success(4)
const rd2 = loading()
SRD.map(double, rd1) // success(8)
SRD.map(double, rd2) // loading()
Allowing the type to be bimapped over by the functions provided. Common usecase is for when you need to map and mapFailure in one shot.
bimap :: (e -> b, a -> c, RD e a) -> RD b c
import { SRD, success, failure } from 'SRD'
const double = x => x * 2
const formatErr = err => `Something went wrong: ${err}`
const rd1 = success(4)
const rd2 = failure('404 not found')
SRD.bimap(formatErr, double, rd1) // success(8)
SRD.bimap(formatErr, double, rd2) // failure('Something went wrong: 404 not found')
Apply a function wrapped in a SRD to a value wrapped in a SRD.
ap :: (RD e (a -> b), RD e a) -> RD e b
import { SRD, success, failure } from 'SRD'
const double = x => x * 2
const formatErr = err => `Something went wrong: ${err}`
const rd1 = success(4)
const rd2 = failure('404 not found')
SRD.ap(success(double), rd1)) // success(8)
SRD.ap(success(double), rd2)) // failure('404 not found')
Always returns a success with whatever value is passed within.
of :: a -> RD e a
import { SRD } from 'SRD'
SRD.of(4) // success(4)
Provide a default value to be returned when an SRD is not a success type.
alt :: (RD e a, RD e a) -> RD e a
import { SRD, success, loading, notAsked } from 'SRD'
SRD.alt(success(4), notAsked()) // success(4)
SRD.alt(success(50), success(4)) // success(4)
SRD.alt(loading(), notAsked()) // loading()
SRD.alt(loading(), success(4)) // success(4)
Similar to map but the callback must return another SRD.
chain :: (a -> RD e b, RD e a) -> RD e b
import { SRD, success, failure, notAsked } from 'SRD'
SRD.chain(x => success(x * 2), success(4)) // success(8)
SRD.chain(x => success(x * 2), notAsked()) // notAsked()
SRD.chain(x => failure('failed'), success(4)) // failure('failed')
Provide a mapper object for each SRD type and whichever type the SRD is - that function will run.
data Matcher e a ::
{ notAsked :: () -> c
, loading :: () -> c
, failure :: e -> c
, success :: a -> c
}
match :: (Matcher e a -> c, RD e a) -> c
import { SRD, success } from 'SRD'
SRD.match({
notAsked: () => 'Empty',
loading: () => 'Loading...',
failure: e => `Err: ${e}`,
success: data => `My data is ${data}`
}, success(4)) // My data is 4
Similar to map but instead of running the callback on a success, it calls it on a failure.
mapFailure :: (e -> b, RD e a) -> RD b a
import { SRD, success, failure } from 'SRD'
SRD.mapFailure(x => `hello ${x}`, success(4)) // success(4)
SRD.mapFailure(x => `hello ${x}`, failure('bob')) // failure('hello bob')
Similar to map but takes 2 SRD's instead of one, and if both are a success, the provided callback will be called.
map2 :: (a b -> c, RD e a, RD e b) -> RD e c
import { SRD, success, failure } from 'SRD'
SRD.map2((x, y) => x + y, success(4), success(8)) // success(12)
SRD.map2((x, y) => x + y, failure('bob'), success(8)) // failure('bob')
SRD.map2((x, y) => x + y, success(8), failure('bob')) // failure('bob')
Similar to map2 but takes 3 SRD's instead of two, and if all three are a success, the provided callback will be called.
map3 :: (a b c -> d, RD e a, RD e b, RD e c) -> RD e d
import { SRD, success, failure, notAsked, loading } from 'SRD'
const add3 = (x, y, z) = x + y + z
SRD.map3(add3, success(4), success(8), success(10)) // success(22)
SRD.map3(add3, failure('bob'), success(8), notAsked()) // failure('bob')
SRD.map3(add3, success(8), loading(), failure('bob')) // loading()
Similar to alt, but unwraps the SRD from it's type and runs the callback on it. If the SRD is a success the inner value is passed to the callback and returned, any other value the default is returned.
unwrap :: (b, a -> b, RD e a) -> b
import { SRD, success, notAsked, loading } from 'SRD'
const double = x => x * 2
SRD.unwrap(6, double, success(8)) // 16
SRD.unwrap(6, double, notAsked()) // 6
SRD.unwrap(6, double, loading()) // 6
Similar to unwrap, but takes a default thunk instead of a default value.
unpack :: (() -> b, a -> b, RD e a) -> b
import { SRD, success, notAsked, loading } from 'SRD'
const double = x => x * 2
SRD.unpack(() => 6, double, success(8)) // 16
SRD.unpack(() => 6, double, notAsked()) // 6
SRD.unpack(() => 6, double, loading()) // 6
Takes a default value and an SRD. If the SRD is a success then the inner value is returned, otherwise the default value is returned.
withDefault :: (a, RD e a) -> a
import { SRD, success, notAsked, loading } from 'SRD'
SRD.withDefault(4, success(8)) // 8
SRD.withDefault(4, notAsked()) // 4
SRD.withDefault(4, loading()) // 4
FAQs
Static Land Compliant Remote Data Type in TypeScript
We found that srd demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.