UNPKG

@hirosystems/token-metadata-api-client

Version:

Client for @hirosystems/token-metadata-api

614 lines (568 loc) 18.8 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.TokenMetadataApiClient = {})); })(this, (function (exports) { function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } // settings & const const PATH_PARAM_RE = /\{[^{}]+\}/g; /** * Returns a cheap, non-cryptographically-secure random ID * Courtesy of @imranbarbhuiya (https://github.com/imranbarbhuiya) */ function randomID() { return Math.random().toString(36).slice(2, 11); } /** * Create an openapi-fetch client. * @type {import("./index.js").default} */ function createClient$1(clientOptions) { let { baseUrl = "", Request: CustomRequest = globalThis.Request, fetch: baseFetch = globalThis.fetch, querySerializer: globalQuerySerializer, bodySerializer: globalBodySerializer, headers: baseHeaders, ...baseOptions } = { ...clientOptions }; baseUrl = removeTrailingSlash(baseUrl); const middlewares = []; /** * Per-request fetch (keeps settings created in createClient() * @param {T} url * @param {import('./index.js').FetchOptions<T>} fetchOptions */ async function coreFetch(schemaPath, fetchOptions) { const { baseUrl: localBaseUrl, fetch = baseFetch, Request = CustomRequest, headers, params = {}, parseAs = "json", querySerializer: requestQuerySerializer, bodySerializer = globalBodySerializer ?? defaultBodySerializer, body, ...init } = fetchOptions || {}; if (localBaseUrl) { baseUrl = removeTrailingSlash(localBaseUrl); } let querySerializer = typeof globalQuerySerializer === "function" ? globalQuerySerializer : createQuerySerializer(globalQuerySerializer); if (requestQuerySerializer) { querySerializer = typeof requestQuerySerializer === "function" ? requestQuerySerializer : createQuerySerializer({ ...(typeof globalQuerySerializer === "object" ? globalQuerySerializer : {}), ...requestQuerySerializer, }); } const serializedBody = body === undefined ? undefined : bodySerializer(body); const defaultHeaders = // with no body, we should not to set Content-Type serializedBody === undefined || // if serialized body is FormData; browser will correctly set Content-Type & boundary expression serializedBody instanceof FormData ? {} : { "Content-Type": "application/json", }; const requestInit = { redirect: "follow", ...baseOptions, ...init, body: serializedBody, headers: mergeHeaders(defaultHeaders, baseHeaders, headers, params.header), }; let id; let options; let request = new CustomRequest(createFinalURL(schemaPath, { baseUrl, params, querySerializer }), requestInit); /** Add custom parameters to Request object */ for (const key in init) { if (!(key in request)) { request[key] = init[key]; } } if (middlewares.length) { id = randomID(); // middleware (request) options = Object.freeze({ baseUrl, fetch, parseAs, querySerializer, bodySerializer, }); for (const m of middlewares) { if (m && typeof m === "object" && typeof m.onRequest === "function") { const result = await m.onRequest({ request, schemaPath, params, options, id, }); if (result) { if (!(result instanceof CustomRequest)) { throw new Error("onRequest: must return new Request() when modifying the request"); } request = result; } } } } // fetch! let response = await fetch(request); // middleware (response) // execute in reverse-array order (first priority gets last transform) if (middlewares.length) { for (let i = middlewares.length - 1; i >= 0; i--) { const m = middlewares[i]; if (m && typeof m === "object" && typeof m.onResponse === "function") { const result = await m.onResponse({ request, response, schemaPath, params, options, id, }); if (result) { if (!(result instanceof Response)) { throw new Error("onResponse: must return new Response() when modifying the response"); } response = result; } } } } // handle empty content // note: we return `{}` because we want user truthy checks for `.data` or `.error` to succeed if (response.status === 204 || response.headers.get("Content-Length") === "0") { return response.ok ? { data: {}, response } : { error: {}, response }; } // parse response (falling back to .text() when necessary) if (response.ok) { // if "stream", skip parsing entirely if (parseAs === "stream") { return { data: response.body, response }; } return { data: await response[parseAs](), response }; } // handle errors let error = await response.text(); try { error = JSON.parse(error); // attempt to parse as JSON } catch { // noop } return { error, response }; } return { /** Call a GET endpoint */ GET(url, init) { return coreFetch(url, { ...init, method: "GET" }); }, /** Call a PUT endpoint */ PUT(url, init) { return coreFetch(url, { ...init, method: "PUT" }); }, /** Call a POST endpoint */ POST(url, init) { return coreFetch(url, { ...init, method: "POST" }); }, /** Call a DELETE endpoint */ DELETE(url, init) { return coreFetch(url, { ...init, method: "DELETE" }); }, /** Call a OPTIONS endpoint */ OPTIONS(url, init) { return coreFetch(url, { ...init, method: "OPTIONS" }); }, /** Call a HEAD endpoint */ HEAD(url, init) { return coreFetch(url, { ...init, method: "HEAD" }); }, /** Call a PATCH endpoint */ PATCH(url, init) { return coreFetch(url, { ...init, method: "PATCH" }); }, /** Call a TRACE endpoint */ TRACE(url, init) { return coreFetch(url, { ...init, method: "TRACE" }); }, /** Register middleware */ use(...middleware) { for (const m of middleware) { if (!m) { continue; } if (typeof m !== "object" || !("onRequest" in m || "onResponse" in m)) { throw new Error("Middleware must be an object with one of `onRequest()` or `onResponse()`"); } middlewares.push(m); } }, /** Unregister middleware */ eject(...middleware) { for (const m of middleware) { const i = middlewares.indexOf(m); if (i !== -1) { middlewares.splice(i, 1); } } }, }; } class PathCallForwarder { constructor(client, url) { this.client = client; this.url = url; } GET(init) { return this.client.GET(this.url, init); } PUT(init) { return this.client.PUT(this.url, init); } POST(init) { return this.client.POST(this.url, init); } DELETE(init) { return this.client.DELETE(this.url, init); } OPTIONS(init) { return this.client.OPTIONS(this.url, init); } HEAD(init) { return this.client.HEAD(this.url, init); } PATCH(init) { return this.client.PATCH(this.url, init); } TRACE(init) { return this.client.TRACE(this.url, init); } } class PathClientProxyHandler { constructor() { this.client = null; } // Assume the property is an URL. get(coreClient, url) { const forwarder = new PathCallForwarder(coreClient, url); this.client[url] = forwarder; return forwarder; } } /** * Wrap openapi-fetch client to support a path based API. * @type {import("./index.js").wrapAsPathBasedClient} */ function wrapAsPathBasedClient(coreClient) { const handler = new PathClientProxyHandler(); const proxy = new Proxy(coreClient, handler); // Put the proxy on the prototype chain of the actual client. // This means if we do not have a memoized PathCallForwarder, // we fall back to the proxy to synthesize it. // However, the proxy itself is not on the hot-path (if we fetch the same // endpoint multiple times, only the first call will hit the proxy). function Client() {} Client.prototype = proxy; const client = new Client(); // Feed the client back to the proxy handler so it can store the generated // PathCallForwarder. handler.client = client; return client; } /** * Convenience method to an openapi-fetch path based client. * Strictly equivalent to `wrapAsPathBasedClient(createClient(...))`. * @type {import("./index.js").createPathBasedClient} */ function createPathBasedClient(clientOptions) { return wrapAsPathBasedClient(createClient$1(clientOptions)); } // utils /** * Serialize primitive param values * @type {import("./index.js").serializePrimitiveParam} */ function serializePrimitiveParam(name, value, options) { if (value === undefined || value === null) { return ""; } if (typeof value === "object") { throw new Error( "Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.", ); } return `${name}=${options?.allowReserved === true ? value : encodeURIComponent(value)}`; } /** * Serialize object param (shallow only) * @type {import("./index.js").serializeObjectParam} */ function serializeObjectParam(name, value, options) { if (!value || typeof value !== "object") { return ""; } const values = []; const joiner = { simple: ",", label: ".", matrix: ";", }[options.style] || "&"; // explode: false if (options.style !== "deepObject" && options.explode === false) { for (const k in value) { values.push(k, options.allowReserved === true ? value[k] : encodeURIComponent(value[k])); } const final = values.join(","); // note: values are always joined by comma in explode: false (but joiner can prefix) switch (options.style) { case "form": { return `${name}=${final}`; } case "label": { return `.${final}`; } case "matrix": { return `;${name}=${final}`; } default: { return final; } } } // explode: true for (const k in value) { const finalName = options.style === "deepObject" ? `${name}[${k}]` : k; values.push(serializePrimitiveParam(finalName, value[k], options)); } const final = values.join(joiner); return options.style === "label" || options.style === "matrix" ? `${joiner}${final}` : final; } /** * Serialize array param (shallow only) * @type {import("./index.js").serializeArrayParam} */ function serializeArrayParam(name, value, options) { if (!Array.isArray(value)) { return ""; } // explode: false if (options.explode === false) { const joiner = { form: ",", spaceDelimited: "%20", pipeDelimited: "|" }[options.style] || ","; // note: for arrays, joiners vary wildly based on style + explode behavior const final = (options.allowReserved === true ? value : value.map((v) => encodeURIComponent(v))).join(joiner); switch (options.style) { case "simple": { return final; } case "label": { return `.${final}`; } case "matrix": { return `;${name}=${final}`; } // case "spaceDelimited": // case "pipeDelimited": default: { return `${name}=${final}`; } } } // explode: true const joiner = { simple: ",", label: ".", matrix: ";" }[options.style] || "&"; const values = []; for (const v of value) { if (options.style === "simple" || options.style === "label") { values.push(options.allowReserved === true ? v : encodeURIComponent(v)); } else { values.push(serializePrimitiveParam(name, v, options)); } } return options.style === "label" || options.style === "matrix" ? `${joiner}${values.join(joiner)}` : values.join(joiner); } /** * Serialize query params to string * @type {import("./index.js").createQuerySerializer} */ function createQuerySerializer(options) { return function querySerializer(queryParams) { const search = []; if (queryParams && typeof queryParams === "object") { for (const name in queryParams) { const value = queryParams[name]; if (value === undefined || value === null) { continue; } if (Array.isArray(value)) { search.push( serializeArrayParam(name, value, { style: "form", explode: true, ...options?.array, allowReserved: options?.allowReserved || false, }), ); continue; } if (typeof value === "object") { search.push( serializeObjectParam(name, value, { style: "deepObject", explode: true, ...options?.object, allowReserved: options?.allowReserved || false, }), ); continue; } search.push(serializePrimitiveParam(name, value, options)); } } return search.join("&"); }; } /** * Handle different OpenAPI 3.x serialization styles * @type {import("./index.js").defaultPathSerializer} * @see https://swagger.io/docs/specification/serialization/#path */ function defaultPathSerializer(pathname, pathParams) { let nextURL = pathname; for (const match of pathname.match(PATH_PARAM_RE) ?? []) { let name = match.substring(1, match.length - 1); let explode = false; let style = "simple"; if (name.endsWith("*")) { explode = true; name = name.substring(0, name.length - 1); } if (name.startsWith(".")) { style = "label"; name = name.substring(1); } else if (name.startsWith(";")) { style = "matrix"; name = name.substring(1); } if (!pathParams || pathParams[name] === undefined || pathParams[name] === null) { continue; } const value = pathParams[name]; if (Array.isArray(value)) { nextURL = nextURL.replace(match, serializeArrayParam(name, value, { style, explode })); continue; } if (typeof value === "object") { nextURL = nextURL.replace(match, serializeObjectParam(name, value, { style, explode })); continue; } if (style === "matrix") { nextURL = nextURL.replace(match, `;${serializePrimitiveParam(name, value)}`); continue; } nextURL = nextURL.replace(match, style === "label" ? `.${encodeURIComponent(value)}` : encodeURIComponent(value)); } return nextURL; } /** * Serialize body object to string * @type {import("./index.js").defaultBodySerializer} */ function defaultBodySerializer(body) { if (body instanceof FormData) { return body; } return JSON.stringify(body); } /** * Construct URL string from baseUrl and handle path and query params * @type {import("./index.js").createFinalURL} */ function createFinalURL(pathname, options) { let finalURL = `${options.baseUrl}${pathname}`; if (options.params?.path) { finalURL = defaultPathSerializer(finalURL, options.params.path); } let search = options.querySerializer(options.params.query ?? {}); if (search.startsWith("?")) { search = search.substring(1); } if (search) { finalURL += `?${search}`; } return finalURL; } /** * Merge headers a and b, with b taking priority * @type {import("./index.js").mergeHeaders} */ function mergeHeaders(...allHeaders) { const finalHeaders = new Headers(); for (const h of allHeaders) { if (!h || typeof h !== "object") { continue; } const iterator = h instanceof Headers ? h.entries() : Object.entries(h); for (const [k, v] of iterator) { if (v === null) { finalHeaders.delete(k); } else if (Array.isArray(v)) { for (const v2 of v) { finalHeaders.append(k, v2); } } else if (v !== undefined) { finalHeaders.set(k, v); } } } return finalHeaders; } /** * Remove trailing slash from url * @type {import("./index.js").removeTrailingSlash} */ function removeTrailingSlash(url) { if (url.endsWith("/")) { return url.substring(0, url.length - 1); } return url; } function createClient(options) { return createClient$1(_extends({ baseUrl: 'https://api.mainnet.hiro.so' }, options)); } exports.createClient = createClient; exports.createFinalURL = createFinalURL; exports.createPathBasedClient = createPathBasedClient; exports.createQuerySerializer = createQuerySerializer; exports.defaultBodySerializer = defaultBodySerializer; exports.defaultPathSerializer = defaultPathSerializer; exports.mergeHeaders = mergeHeaders; exports.randomID = randomID; exports.removeTrailingSlash = removeTrailingSlash; exports.serializeArrayParam = serializeArrayParam; exports.serializeObjectParam = serializeObjectParam; exports.serializePrimitiveParam = serializePrimitiveParam; exports.wrapAsPathBasedClient = wrapAsPathBasedClient; })); //# sourceMappingURL=index.umd.js.map