UNPKG

@foxy.io/sdk

Version:

Universal SDK for a full server-side and a limited in-browser access to Foxy hAPI.

236 lines (235 loc) 11.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Node = void 0; const v8n_js_1 = require("../v8n.js"); const consola_1 = __importDefault(require("consola")); const cross_fetch_1 = require("cross-fetch"); const ResolutionError_js_1 = require("./ResolutionError.js"); const Response_js_1 = require("./Response.js"); /** * Serializes object zoom definition using hAPI format. * * @param prefix Curie prefix. * @param zoom Zoom definition as object. * @returns Serialized zoom parameter value. */ function stringifyZoom(prefix, zoom) { const scope = prefix === '' ? '' : prefix + ':'; if (typeof zoom === 'string') return scope + zoom; if (Array.isArray(zoom)) return zoom.map(v => stringifyZoom(prefix, v)).join(); return Object.entries(zoom) .map(([key, value]) => stringifyZoom(scope + key, value)) .join(); } /** * Serializes object order definition using hAPI format. * * @param order Order definition as object. * @returns Serialized order parameter value. */ function stringifyOrder(order) { if (typeof order === 'string') return order; if (Array.isArray(order)) { return order.map(item => stringifyOrder(item)).join(); } return Object.entries(order) .map(([key, value]) => `${key} ${value}`) .join(); } /** * Base class representing a resource node that can be fetched, * created, updated or deleted. You shouldn't need to create instances * of this class unless you're building a custom API client with our SDK. */ class Node { constructor(init) { Node.v8n.classConstructor.check(init); this._path = init.path; this._fetch = init.fetch; this._cache = init.cache; this._console = init.console; } /** * Resolves the URL of this node and sends a GET request * using provided parameters. * * @param query Query parameters such as zoom, fields etc. * @returns Instance of {@link APIResponse} representing this resource. */ get(query) { return __awaiter(this, void 0, void 0, function* () { Node.v8n.get.check(query); const url = yield this._resolve(); const { filters, fields, offset, limit, order, zoom } = query !== null && query !== void 0 ? query : {}; if (filters !== undefined) { filters.forEach((filter) => { const [key, value = ''] = filter.split('='); if (key) url.searchParams.append(key, value); }); } if (fields !== undefined) url.searchParams.set('fields', fields.join(',')); if (offset !== undefined) url.searchParams.set('offset', String(offset)); if (limit !== undefined) url.searchParams.set('limit', String(limit)); if (order !== undefined) url.searchParams.set('order', stringifyOrder(order)); if (zoom !== undefined) url.searchParams.set('zoom', stringifyZoom('', zoom)); const response = yield this._fetch(new cross_fetch_1.Request(url.toString())); const config = { cache: this._cache, console: this._console, fetch: this._fetch }; return new Response_js_1.Response(Object.assign(Object.assign(Object.assign({}, config), response), { body: yield response.text() })); }); } /** * Resolves the URL of this node and sends a PUT request * with provided properties, replacing the existing resource. * * @param body Complete resource object. * @returns Instance of {@link APIResponse} representing this resource. */ put(body) { return __awaiter(this, void 0, void 0, function* () { const url = yield this._resolve(); const request = new cross_fetch_1.Request(url.toString(), { body: JSON.stringify(body), method: 'PUT' }); const response = yield this._fetch(request); const config = { cache: this._cache, console: this._console, fetch: this._fetch }; return new Response_js_1.Response(Object.assign(Object.assign(Object.assign({}, config), response), { body: yield response.text() })); }); } /** * Resolves the URL of this node and sends a POST request * with provided properties, creating a resource or triggering an action. * * @param body Complete resource object. * @returns Instance of {@link APIResponse} representing this resource. */ post(body) { return __awaiter(this, void 0, void 0, function* () { const url = yield this._resolve(); const request = new cross_fetch_1.Request(url.toString(), { body: JSON.stringify(body), method: 'POST' }); const response = yield this._fetch(request); const config = { cache: this._cache, console: this._console, fetch: this._fetch }; return new Response_js_1.Response(Object.assign(Object.assign(Object.assign({}, config), response), { body: yield response.text() })); }); } /** * Resolves the URL of this node and sends a PATCH request * with provided properties, updating this resource. * * @param body Partial resource object. * @returns Instance of {@link APIResponse} representing this resource. */ patch(body) { return __awaiter(this, void 0, void 0, function* () { const url = yield this._resolve(); const request = new cross_fetch_1.Request(url.toString(), { body: JSON.stringify(body), method: 'PATCH' }); const response = yield this._fetch(request); const config = { cache: this._cache, console: this._console, fetch: this._fetch }; return new Response_js_1.Response(Object.assign(Object.assign(Object.assign({}, config), response), { body: yield response.text() })); }); } /** * Resolves the URL of this node and sends a DELETE request, * removing this resource. * * @returns Instance of {@link APIResponse} representing this resource. */ delete() { return __awaiter(this, void 0, void 0, function* () { const url = yield this._resolve(); const request = new cross_fetch_1.Request(url.toString(), { method: 'DELETE' }); const response = yield this._fetch(request); const config = { cache: this._cache, console: this._console, fetch: this._fetch }; return new Response_js_1.Response(Object.assign(Object.assign(Object.assign({}, config), response), { body: yield response.text() })); }); } /** * Resource path builder. Calling this method instructs our * SDK to find the provided curie in this resource's links and * navigate to its location on request. * * @param curie Curie to follow. * @returns Instance of {@link APINode} representing the resource at curie location. */ follow(curie) { Node.v8n.follow.check(curie); const config = { cache: this._cache, console: this._console, fetch: this._fetch }; const path = this._path.concat(curie); return new Node(Object.assign(Object.assign({}, config), { path })); } /** * Resolves resource URL from a curie chain. The first element in the path * must be a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL). * * @returns Resolved URL. * @throws Throws {@link APIResolutionError} when once of the resources can't be reached. */ _resolve() { return __awaiter(this, void 0, void 0, function* () { if (this._path.length === 1) return new URL(this._path[0].toString()); const [baseURL, curie] = this._path; const key = `${baseURL.toString()} > ${curie}`; const config = { cache: this._cache, console: this._console, fetch: this._fetch }; this._console.trace(`Trying to resolve ${key}...`); const cachedURL = this._cache.getItem(key); if (cachedURL) { this._console.success(`Resolved ${key} to ${cachedURL.toString()} using cache.`); const reducedPath = [new URL(cachedURL), ...this._path.slice(2)]; return new Node(Object.assign(Object.assign({}, config), { path: reducedPath }))._resolve(); } const response = yield this._fetch(baseURL.toString()); if (response.ok) { const json = yield response.clone().json(); if (json._links[curie]) { const url = new URL(json._links[curie].href); const reducedPath = [url, ...this._path.slice(2)]; this._cache.setItem(key, url.toString()); this._console.trace(`Cached ${url.toString()} for ${key}.`); this._console.success(`Resolved ${key} to ${url.toString()} online.`); return new Node(Object.assign(Object.assign({}, config), { path: reducedPath }))._resolve(); } } this._console.error(`Failed to resolve ${key}.`); throw new ResolutionError_js_1.ResolutionError(response); }); } } exports.Node = Node; Node.v8n = { classConstructor: v8n_js_1.v8n().schema({ cache: v8n_js_1.storageV8N, console: v8n_js_1.v8n().instanceOf(consola_1.default.constructor), fetch: v8n_js_1.v8n().typeOf('function'), path: v8n_js_1.v8n().curieChain(), }), follow: v8n_js_1.v8n().string(), get: v8n_js_1.v8n().optional(v8n_js_1.v8n().schema({ fields: v8n_js_1.v8n().optional(v8n_js_1.v8n().array().every.string()), filters: v8n_js_1.v8n().optional(v8n_js_1.v8n().array().every.string()), limit: v8n_js_1.v8n().optional(v8n_js_1.v8n().number()), offset: v8n_js_1.v8n().optional(v8n_js_1.v8n().number()), order: v8n_js_1.v8n().optional(v8n_js_1.v8n().passesAnyOf(v8n_js_1.v8n().string(), v8n_js_1.v8n().object(), v8n_js_1.v8n().array())), zoom: v8n_js_1.v8n().optional(v8n_js_1.v8n().passesAnyOf(v8n_js_1.v8n().string(), v8n_js_1.v8n().object(), v8n_js_1.v8n().array())), })), }; Node.ResolutionError = ResolutionError_js_1.ResolutionError; Node.Response = Response_js_1.Response;