UNPKG

@oada/client

Version:

A lightweight client tool to interact with an OADA-compliant server

186 lines 5.53 kB
/** * @license * Copyright 2021 Open Ag Data Alliance * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @packageDocumentation * Some useful functions */ import { JsonPointer } from "json-ptr"; import objectAssignDeep from "object-assign-deep"; import { TimeoutError as PTimeoutError } from "p-timeout"; // Typescript sucks at figuring out Array.isArray on its own function isArray(value) { return Array.isArray(value); } export function toArray(itemOrArray) { return isArray(itemOrArray) ? itemOrArray : [itemOrArray]; } export function toStringPath(path) { return `/${path.join("/")}`; } export function toArrayPath(path) { const arrayPath = path.split("/"); if (arrayPath.length > 0 && arrayPath[0] === "") { arrayPath.shift(); } if (arrayPath.length > 0 && arrayPath.at(-1) === "") { arrayPath.pop(); } return arrayPath; } export function getObjectAtPath(tree, path) { let result = tree; for (const key of path) { if (key in result) { result = result[key]; } else if ("*" in result) { result = result["*"]; } else { throw new Error(`Specified path /${path.join("/")} does not exist in the tree.`); } } return result; } export function toTreePath(tree, path) { const treePath = []; let current = tree; for (const key of path) { if (key in current) { treePath.push(key); current = current[key]; } else if ("*" in current) { treePath.push("*"); current = current["*"]; } else { throw new Error(`Specified path /${path.join("/")} does not exist in the tree.`); } } return treePath; } export function isResource(tree, path) { const object = getObjectAtPath(tree, path); return "_id" in object; } export function createNestedObject(object, nestPath) { const reversedArray = nestPath.slice().reverse(); let result = object; for (const key of reversedArray) { result = { [key]: result }; } return result; } /** * Use an Error class for timed out requests */ export class TimeoutError extends PTimeoutError { get code() { return "REQUEST_TIMEDOUT"; } name = "TimeoutError"; constructor(request) { super("Request timed out"); Object.assign(this, request); } } /** * Ensure we throw real `Error`s */ export async function fixError(error) { // Try to normalize the various ways of sending the error codes const code = `${error.code ?? error.status ?? error.statusCode}`; error.code ??= code; error.status ??= Number(code); error.statusCode ??= Number(code); if (error instanceof Error) { return error; } // TODO: Clean up this mess let body = {}; try { // @ts-expect-error try to get error body? // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call body = (await error.json?.()) ?? error.data; } catch { } const message = error.message ?? body?.message ?? (error.statusText ? `${error.status} ${error.statusText}` : `${error.status}`); const ret = new Error(message, { cause: error }); ret.code = code; for (const key in error) { // @ts-expect-error HACK: just do it ret[key] = error[key]; } return ret; } export const changeSym = Symbol("change"); /** * Tell TS we should never reach here (i.e., this should never be called) * @internal */ export function assertNever(value, message) { throw new Error(message ?? `Bad value: ${value}`); } /** * Replace `null` values in delete changes with `undefined` * @internal */ export function translateDelete(body) { if (body === null) { return undefined; } if (typeof body !== "object") { return body; } if (Array.isArray(body)) { return body.map((item) => translateDelete(item)); } return Object.fromEntries(Object.entries(body).map(([key, value]) => [ key, translateDelete(value), ])); } /** * Construct object representing the change tree * @internal */ export function buildChangeObject(rootChange, ...children) { const changeBody = { [changeSym]: [rootChange], ...(rootChange.type === "delete" ? translateDelete(rootChange.body) : rootChange.body), }; for (const change of children) { const ptr = JsonPointer.create(change.path); const old = ptr.get(changeBody); const changes = old?.[changeSym] ?? []; const body = change.type === "delete" ? translateDelete(change.body) : change.body; const merged = objectAssignDeep(old ?? {}, body); merged[changeSym] = [...changes, change]; ptr.set(changeBody, merged, true); } return changeBody; } //# sourceMappingURL=utils.js.map