UNPKG

ketting

Version:

Opinionated HATEOAS / Rest client.

138 lines (109 loc) 3.17 kB
import { BaseState } from './base-state.js'; import { parseLink } from '../http/util.js'; import { Link } from '../link.js'; import Client from '../client.js'; /** * Turns a HTTP response into a JsonApiState */ export const factory = async (client: Client, uri: string, response: Response): Promise<BaseState<JsonApiTopLevelObject>> => { const body = await response.json(); const links = parseLink(uri, response.headers.get('Link')); links.add( ...parseJsonApiLinks(uri, body), ...parseJsonApiCollection(uri, body), ); return new BaseState({ client, uri, data: body, headers: response.headers, links: links, }); }; /** * A JSON:API link can either be a string, or an object with at least a href * property. */ type JsonApiLink = string | { href: string }; /** * This type is a full 'links' object, which might appear on the top level * or on resource objects. */ type JsonApiLinksObject = { self?: JsonApiLink; profile?: JsonApiLink; [rel: string]: JsonApiLink | JsonApiLink[] | undefined; }; /** * This is a single JSON:API resource. Its type contains just the properties * we care about. */ type JsonApiResource = { type: string; id: string; links?: JsonApiLinksObject; }; /** * This type represents a valid JSON:API response. We're only interested * in the links object at the moment, so everything else is (for now) * untyped. */ type JsonApiTopLevelObject = { links?: JsonApiLinksObject; data: JsonApiResource | JsonApiResource[] | null; [s: string]: any; }; /** * This function takes a JSON:API object, and extracts the links property. */ function parseJsonApiLinks(contextUri: string, body: JsonApiTopLevelObject): Link[] { const result: Link[] = []; if (body.links === undefined) { return result; } for (const [rel, linkValue] of Object.entries(body.links)) { if (Array.isArray(linkValue)) { result.push(...linkValue.map( link => parseJsonApiLink(contextUri, rel, link))); } else { result.push(parseJsonApiLink(contextUri, rel, linkValue!)); } } return result; } /** * Find collection members in JSON:API objects. * * A JSON:API top-level object might represent a collection that has 0 or more * members. * * Members of this collection should appear as an 'item' link to the parent. */ function parseJsonApiCollection(contextUri: string, body: JsonApiTopLevelObject): Link[] { if (!Array.isArray(body.data)) { // Not a collection return []; } const result: Link[] = []; for (const member of body.data) { if ('links' in member && 'self' in member.links!) { const selfLink = parseJsonApiLink(contextUri, 'self', member.links!.self!); result.push({ context: contextUri, href: selfLink.href, rel: 'item' }); } } return result; } /** * This function takes a single link value from a JSON:API link object, and * returns a object of type Link */ function parseJsonApiLink(contextUri: string, rel: string, link: JsonApiLink): Link { return ({ context: contextUri, rel, href: typeof link === 'string' ? link : link.href, }); }