UNPKG

@proofgeist/fmdapi

Version:
230 lines 8.04 kB
import { FileMakerError } from "../index.js"; export class BaseFetchAdapter { server; db; refreshToken; baseUrl; constructor(options) { this.server = options.server; this.db = options.db; this.refreshToken = options.refreshToken ?? false; this.baseUrl = new URL(`${this.server}/fmi/data/vLatest/databases/${this.db}`); if (this.db === "") throw new Error("Database name is required"); } // eslint-disable-next-line @typescript-eslint/no-unused-vars getToken = async (args) => { // method must be implemented in subclass throw new Error("getToken method not implemented by Fetch Adapter"); }; request = async (params) => { const { query, body, method = "GET", retry = false, fetchOptions = {}, } = params; const url = new URL(`${this.baseUrl}${params.url}`); if (query) { const { _sort, ...rest } = query; const searchParams = new URLSearchParams(rest); if (query.portalRanges && typeof query.portalRanges === "object") { for (const [portalName, value] of Object.entries(query.portalRanges)) { if (value) { value.offset && value.offset > 0 && searchParams.set(`_offset.${portalName}`, value.offset.toString()); value.limit && searchParams.set(`_limit.${portalName}`, value.limit.toString()); } } } if (_sort) { searchParams.set("_sort", JSON.stringify(_sort)); } searchParams.delete("portalRanges"); url.search = searchParams.toString(); } if (body && "portalRanges" in body) { for (const [portalName, value] of Object.entries(body.portalRanges)) { if (value) { value.offset && value.offset > 0 && url.searchParams.set(`_offset.${portalName}`, value.offset.toString()); value.limit && url.searchParams.set(`_limit.${portalName}`, value.limit.toString()); } } delete body.portalRanges; } const controller = new AbortController(); let timeout = null; if (params.timeout) timeout = setTimeout(() => controller.abort(), params.timeout); const token = await this.getToken({ refresh: retry }); const headers = new Headers(fetchOptions?.headers); headers.set("Authorization", `Bearer ${token}`); // Only set Content-Type for JSON bodies if (!(body instanceof FormData)) { headers.set("Content-Type", "application/json"); } const res = await fetch(url.toString(), { ...fetchOptions, method, body: body instanceof FormData ? body : body ? JSON.stringify(body) : undefined, headers, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore signal: controller.signal, }); if (timeout) clearTimeout(timeout); let respData; try { respData = await res.json(); } catch { respData = {}; } if (!res.ok) { if (respData?.messages?.[0].code === "952" && !retry && this.refreshToken) { // token expired, get new token and retry once return this.request({ ...params, retry: true }); } else { throw new FileMakerError(respData?.messages?.[0].code ?? "500", `Filemaker Data API failed with (${res.status}): ${JSON.stringify(respData, null, 2)}`); } } return respData.response; }; list = async (opts) => { const { data, layout } = opts; const resp = await this.request({ url: `/layouts/${layout}/records`, query: data, fetchOptions: opts.fetch, timeout: opts.timeout, }); return resp; }; get = async (opts) => { const { data, layout } = opts; const resp = await this.request({ url: `/layouts/${layout}/records/${data.recordId}`, fetchOptions: opts.fetch, timeout: opts.timeout, }); return resp; }; find = async (opts) => { const { data, layout } = opts; const resp = await this.request({ url: `/layouts/${layout}/_find`, body: data, method: "POST", fetchOptions: opts.fetch, timeout: opts.timeout, }); return resp; }; create = async (opts) => { const { data, layout } = opts; const resp = await this.request({ url: `/layouts/${layout}/records`, body: data, method: "POST", fetchOptions: opts.fetch, timeout: opts.timeout, }); return resp; }; update = async (opts) => { const { data: { recordId, ...data }, layout, } = opts; const resp = await this.request({ url: `/layouts/${layout}/records/${recordId}`, body: data, method: "PATCH", fetchOptions: opts.fetch, timeout: opts.timeout, }); return resp; }; delete = async (opts) => { const { data, layout } = opts; const resp = await this.request({ url: `/layouts/${layout}/records/${data.recordId}`, method: "DELETE", fetchOptions: opts.fetch, timeout: opts.timeout, }); return resp; }; layoutMetadata = async (opts) => { return (await this.request({ url: `/layouts/${opts.layout}`, fetchOptions: opts.fetch, timeout: opts.timeout, })); }; /** * Execute a script within the database */ executeScript = async (opts) => { const { script, scriptParam, layout } = opts; const resp = await this.request({ url: `/layouts/${layout}/script/${script}`, query: scriptParam ? { "script.param": scriptParam } : undefined, fetchOptions: opts.fetch, timeout: opts.timeout, }); return resp; }; /** * Returns a list of available layouts on the database. */ layouts = async (opts) => { return (await this.request({ url: "/layouts", fetchOptions: opts?.fetch, timeout: opts?.timeout, })); }; /** * Returns a list of available scripts on the database. */ scripts = async (opts) => { return (await this.request({ url: "/scripts", fetchOptions: opts?.fetch, timeout: opts?.timeout, })); }; containerUpload = async (opts) => { let url = `/layouts/${opts.layout}/records/${opts.data.recordId}/containers/${opts.data.containerFieldName}`; if (opts.data.repetition) url += `/${opts.data.repetition}`; const formData = new FormData(); formData.append("upload", opts.data.file); await this.request({ url, method: "POST", body: formData, timeout: opts.timeout, fetchOptions: opts.fetch, }); }; /** * Set global fields for the current session */ globals = async (opts) => { return (await this.request({ url: "/globals", method: "PATCH", body: { globalFields: opts.globalFields }, fetchOptions: opts?.fetch, timeout: opts?.timeout, })); }; } //# sourceMappingURL=fetch-base.js.map