Skip to content

Instantly share code, notes, and snippets.

@JustinChristensen
Created August 21, 2021 02:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JustinChristensen/c9febbbdcc79d377e7158d4e8a9b4692 to your computer and use it in GitHub Desktop.
Save JustinChristensen/c9febbbdcc79d377e7158d4e8a9b4692 to your computer and use it in GitHub Desktop.
export type Curry1<A, R> = {
(): Curry1<A, R>;
(a: A): R;
};
export type Curry2<A, B, R> = {
(): Curry2<A, B, R>;
(a: A): Curry1<B, R>;
(a: A, b: B): R;
};
export type Curry3<A, B, C, R> = {
(): Curry3<A, B, C, R>;
(a: A): Curry2<B, C, R>;
(a: A, b: B): Curry1<C, R>;
(a: A, b: B, c: C): R;
};
export type RestFunc = (...args: any[]) => any;
export type Curry = {
<A, R>(fn: (a: A) => R): Curry1<A, R>;
<A, B, R>(fn: (a: A, b: B) => R): Curry2<A, B, R>;
<A, B, C, R>(fn: (a: A, b: B, c: C) => R): Curry3<A, B, C, R>;
(fn: RestFunc): RestFunc;
};
export const curry: Curry = (fn: RestFunc) => (...args: any[]) => {
if (args.length >= fn.length) return fn(...args);
return (...nextArgs: any[]) => (curry(fn) as RestFunc)(...args.concat(nextArgs));
};
export type HasRequest = {
request?: Request
};
export type HasResponse = {
response?: Response
};
export const pinReq = curry((req: Request, x: HasRequest) => (x.request = req, x));
export const pinResp = curry((resp: Response, x: HasResponse) => (x.response = resp, x));
export const reject = curry(<T>(fn: (err: Error) => T, err: Error): Promise<T> => Promise.reject(fn(err)));
export type MappedFn<T, U> = ((x: T, y: U) => void);
export type MappedFns<T, U> = MappedFn<T, U>[];
export const mapApply = async <T, U>(x: T, y: U, ...fns: (MappedFn<T, U> | MappedFns<T, U>)[]) => {
for (const f of fns.flat()) await f(x, y);
return x;
};
export const requestContext = {
token: 'Bearer foo bar baz'
};
type RequestContext = typeof requestContext;
export const handleResp = curry((context: RequestContext, respFns: MappedFns<Response, RequestContext>, resp: Response) =>
mapApply(resp, context, respFns).catch(reject(pinResp(resp))));
type FetchFn = typeof fetch;
export const composeFetch = (
reqFns: MappedFns<Request, RequestContext>,
respFns: MappedFns<Response, RequestContext>,
fetch: FetchFn = globalThis.fetch
) => (urlOrReq: RequestInfo, init?: RequestInit, context = requestContext) => {
const req = new Request(urlOrReq, init);
return mapApply(req, context, reqFns).then(() =>
fetch(req).then(handleResp(context, [pinReq(req), ...respFns]))
).catch(reject(pinReq(req)));
};
export class UnexpectedContentTypeError extends Error {
constructor(contentType: string | null) {
if (contentType) super(`unexpected content type: ${contentType}`);
else super(`missing content type`);
this.name = 'UnexpectedContentTypeError';
}
}
export const setAuth = (req: Request, context: RequestContext) =>
req.headers.set('Authentication', context.token);
export const acceptJSON = (req: Request) =>
req.headers.set('Accept', 'application/json');
export const demandJSON = (resp: Response) => {
const ct = resp.headers.get('content-type');
if (!ct || !ct.includes('application/json'))
throw new UnexpectedContentTypeError(ct);
};
export const parseJSON = (resp: Response) => resp.json().then(o => (resp.json = o));
export const fetchAuthJSON = composeFetch([ acceptJSON, setAuth ], [
demandJSON,
parseJSON
]);
fetchAuthJSON('https://jsonplaceholder.typicode.com/todos')
.then(console.log, console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment