UNPKG

ketting

Version:

Opiniated HATEAOS / Rest client.

189 lines 7.06 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.factory = exports.HalState = void 0; const base_state_1 = require("./base-state"); const util_1 = require("../http/util"); const link_1 = require("../link"); const uri_1 = require("../util/uri"); /** * Represents a resource state in the HAL format */ class HalState extends base_state_1.BaseState { serializeBody() { return JSON.stringify(Object.assign({ _links: this.serializeLinks() }, this.data)); } serializeLinks() { const links = { self: { href: this.uri }, }; for (const link of this.links.getAll()) { const { rel, context } = link, attributes = __rest(link, ["rel", "context"]); if (rel === 'self') { // skip continue; } if (links[rel] === undefined) { // First link of its kind links[rel] = attributes; } else if (Array.isArray(links[rel])) { // Add link to link array. links[rel].push(attributes); } else { // 1 link with this rel existed, so we will transform it to an array. links[rel] = [links[rel], attributes]; } } return links; } clone() { return new HalState(this.uri, this.data, new Headers(this.headers), new link_1.Links(this.uri, this.links), [], this.actionInfo); } } exports.HalState = HalState; /** * Turns a HTTP response into a HalState */ const factory = async (uri, response) => { var _a; const body = await response.json(); const links = util_1.parseLink(uri, response.headers.get('Link')); // The HAL factory is also respondible for plain JSON, which might be an // array. if (Array.isArray(body)) { return new HalState(uri, body, response.headers, links, []); } links.add(...parseHalLinks(uri, body)); // Remove _links and _embedded from body const { _embedded, _links, _templates } = body, newBody = __rest(body, ["_embedded", "_links", "_templates"]); const halForm = (_a = body._templates) === null || _a === void 0 ? void 0 : _a.default; return new HalState(uri, newBody, response.headers, links, parseHalEmbedded(uri, body, response.headers), halForm ? [parseHalForm(uri, halForm)] : []); }; exports.factory = factory; /** * Parse the Hal _links object and populate the 'links' property. */ function parseHalLinks(context, body) { var _a, _b; if (body._links === undefined) { return []; } const result = []; /** * We're capturing all rel-link pairs so we don't duplicate them if they * re-appear in _embedded. * * Links that are embedded _should_ appear in both lists, but not everyone * does this. */ const foundLinks = new Set(); for (const [relType, links] of Object.entries(body._links)) { const linkList = Array.isArray(links) ? links : [links]; for (const link of linkList) { foundLinks.add(relType + ';' + link.href); } result.push(...parseHalLink(context, relType, linkList)); } if (body._embedded) { // eslint-disable-next-line prefer-const for (let [rel, innerBodies] of Object.entries(body._embedded)) { if (!Array.isArray(innerBodies)) { innerBodies = [innerBodies]; } for (const innerBody of innerBodies) { const href = (_b = (_a = innerBody === null || innerBody === void 0 ? void 0 : innerBody._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href; if (!href) { continue; } if (foundLinks.has(rel + ';' + href)) { continue; } result.push({ rel: rel, href: href, context: context, }); } } } return result; } /** * Parses a single HAL link from a _links object */ function parseHalLink(context, rel, links) { const result = []; for (const link of links) { result.push(Object.assign({ rel, context }, link)); } return result; } /** * Parse the HAL _embedded object. Right now we're just grabbing the * information from _embedded and turn it into links. */ function parseHalEmbedded(context, body, headers) { if (body._embedded === undefined) { return []; } const result = []; for (const embedded of Object.values(body._embedded)) { let embeddedList; if (!Array.isArray(embedded)) { embeddedList = [embedded]; } else { embeddedList = embedded; } for (const embeddedItem of embeddedList) { if (embeddedItem._links === undefined || embeddedItem._links.self === undefined || Array.isArray(embeddedItem._links.self)) { // Skip any embedded without a self link. continue; } // Remove _links and _embedded from body const { _embedded, _links } = embeddedItem, newBody = __rest(embeddedItem, ["_embedded", "_links"]); result.push(new HalState(uri_1.resolve(context, embeddedItem._links.self.href), newBody, new Headers({ 'Content-Type': headers.get('Content-Type'), }), new link_1.Links(context, parseHalLinks(context, embeddedItem)), // Parsing nested embedded items. Note that we assume that the base url is relative to // the outermost parent, not relative to the embedded item. HAL is not clear on this. parseHalEmbedded(context, embeddedItem, headers))); } } return result; } function parseHalForm(context, templ) { return { uri: context, name: 'default', title: templ.title, method: templ.method, contentType: templ.contentType || 'application/json', fields: templ.properties ? templ.properties.map(prop => parseHalField(prop)) : [], }; } function parseHalField(halField) { return { name: halField.name, type: 'text', required: halField.required || false, readOnly: halField.readOnly || false, value: halField.value, pattern: halField.regex ? new RegExp(halField.regex) : undefined, label: halField.prompt, }; } //# sourceMappingURL=hal.js.map