UNPKG

@proofgeist/fmdapi

Version:
272 lines 10.6 kB
import { z } from "zod"; import { ZGetResponse } from "./client-types.js"; import { FileMakerError } from "./index.js"; function asNumber(input) { return typeof input === "string" ? parseInt(input) : input; } function DataApi(options) { const zodTypes = options.zodValidators; const { create, delete: _adapterDelete, find, get, list, update, layoutMetadata, containerUpload, ...otherMethods } = options.adapter; async function _list(args) { const { layout = options.layout, fetch, timeout, ...params } = args ?? {}; if (layout === undefined) throw new Error("Layout is required"); // rename and refactor limit, offset, and sort keys for this request if ("limit" in params && params.limit !== undefined) delete Object.assign(params, { _limit: params.limit })["limit"]; if ("offset" in params && params.offset !== undefined) { if (params.offset <= 1) delete params.offset; else delete Object.assign(params, { _offset: params.offset })["offset"]; } if ("sort" in params && params.sort !== undefined) delete Object.assign(params, { _sort: Array.isArray(params.sort) ? params.sort : [params.sort], })["sort"]; const result = await list({ layout, data: params, fetch, timeout, }); if (result.dataInfo.foundCount > result.dataInfo.returnedCount) { // more records found than returned if (args?.limit === undefined && args?.offset === undefined) { // and the user didn't specify a limit or offset, so we should warn them console.warn(`🚨 @proofgeist/fmdapi: Loaded only ${result.dataInfo.returnedCount} of the ${result.dataInfo.foundCount} records from your "${layout}" layout. Use the "listAll" method to automatically paginate through all records, or specify a "limit" and "offset" to handle pagination yourself.`); } } if (zodTypes) ZGetResponse(zodTypes).parse(result); return result; } async function listAll(args) { let runningData = []; const limit = args?.limit ?? 100; let offset = args?.offset ?? 1; // eslint-disable-next-line no-constant-condition while (true) { const data = (await _list({ ...args, offset, // eslint-disable-next-line @typescript-eslint/no-explicit-any })); runningData = [...runningData, ...data.data]; if (runningData.length >= data.dataInfo.foundCount) break; offset = offset + limit; } return runningData; } /** * Create a new record in a given layout */ async function _create(args) { const { layout = options.layout, fetch, timeout, ...params } = args ?? {}; if (layout === undefined) throw new Error("Layout is required"); return await create({ layout, data: params, fetch, timeout }); } /** * Get a single record by Internal RecordId */ async function _get(args) { args.recordId = asNumber(args.recordId); const { recordId, layout = options.layout, fetch, timeout, ...params } = args; if (layout === undefined) throw new Error("Layout is required"); const data = await get({ layout, data: { ...params, recordId }, fetch, timeout, }); if (zodTypes) return ZGetResponse(zodTypes).parse(data); return data; } /** * Update a single record by internal RecordId */ async function _update(args) { args.recordId = asNumber(args.recordId); const { recordId, layout = options.layout, fetch, timeout, ...params } = args; if (layout === undefined) throw new Error("Layout is required"); return await update({ layout, data: { ...params, recordId }, fetch, timeout, }); } /** * Delete a single record by internal RecordId */ async function deleteRecord(args) { args.recordId = asNumber(args.recordId); const { recordId, layout = options.layout, fetch, timeout, ...params } = args; if (layout === undefined) throw new Error("Layout is required"); return _adapterDelete({ layout, data: { ...params, recordId }, fetch, timeout, }); } /** * Find records in a given layout */ async function _find(args) { const { query: queryInput, layout = options.layout, ignoreEmptyResult = false, timeout, fetch, ...params } = args; const query = !Array.isArray(queryInput) ? [queryInput] : queryInput; if (layout === undefined) throw new Error("Layout is required"); // rename and refactor limit, offset, and sort keys for this request if ("offset" in params && params.offset !== undefined) { if (params.offset <= 1) delete params.offset; } if ("dateformats" in params && params.dateformats !== undefined) { // reassign dateformats to match FileMaker's expected values // @ts-expect-error FM wants a string, so this is fine params.dateformats = (params.dateformats === "US" ? 0 : params.dateformats === "file_locale" ? 1 : params.dateformats === "ISO8601" ? 2 : 0).toString(); } const data = (await find({ data: { ...params, query }, layout, fetch, timeout, }).catch((e) => { if (ignoreEmptyResult && e instanceof FileMakerError && e.code === "401") return { data: [], dataInfo: { foundCount: 0, returnedCount: 0 } }; throw e; })); if (data.dataInfo.foundCount > data.dataInfo.returnedCount) { // more records found than returned if (args?.limit === undefined && args?.offset === undefined) { console.warn(`🚨 @proofgeistfmdapi: Loaded only ${data.dataInfo.returnedCount} of the ${data.dataInfo.foundCount} records from your "${layout}" layout. Use the "findAll" method to automatically paginate through all records, or specify a "limit" and "offset" to handle pagination yourself.`); } } if (zodTypes) { if (data.data.length !== 0 || !ignoreEmptyResult) { // Parse if we have data or if ignoreEmptyResult is false ZGetResponse(zodTypes).parse(data); } } return data; } /** * Helper method for `find`. Will only return the first result or throw error if there is more than 1 result. */ async function findOne(args) { const res = await _find(args); if (res.data.length !== 1) throw new Error(`${res.data.length} records found; expecting exactly 1`); if (zodTypes) ZGetResponse(zodTypes).parse(res); if (!res.data[0]) throw new Error("No data found"); return { ...res, data: res.data[0] }; } /** * Helper method for `find`. Will only return the first result instead of an array. */ async function findFirst(args) { const res = await _find(args); if (zodTypes) ZGetResponse(zodTypes).parse(res); if (!res.data[0]) throw new Error("No data found"); return { ...res, data: res.data[0] }; } /** * Helper method for `find`. Will return the first result or null if no results are found. */ async function maybeFindFirst(args) { const res = await _find({ ...args, ignoreEmptyResult: true }); if (zodTypes) ZGetResponse(zodTypes).parse(res); if (!res.data[0]) return null; return { ...res, data: res.data[0] }; } /** * Helper method for `find` to page through all found results. * ⚠️ WARNING: Use with caution as this can be a slow operation with large datasets */ async function findAll(args) { let runningData = []; const limit = args.limit ?? 100; let offset = args.offset ?? 1; // eslint-disable-next-line no-constant-condition while (true) { const data = await _find({ ...args, offset, ignoreEmptyResult: true, }); runningData = [...runningData, ...data.data]; if (runningData.length === 0 || runningData.length >= data.dataInfo.foundCount) break; offset = offset + limit; } return runningData; } async function _layoutMetadata(args) { const { layout = options.layout, ...restArgs } = args ?? {}; // Explicitly define the type for params based on FetchOptions const params = restArgs; if (layout === undefined) throw new Error("Layout is required"); return await layoutMetadata({ layout, fetch: params.fetch, // Now should correctly resolve to undefined if not present timeout: params.timeout, // Now should correctly resolve to undefined if not present }); } async function _containerUpload(args) { const { layout = options.layout, ...params } = args; if (layout === undefined) throw new Error("Layout is required"); return await containerUpload({ layout, data: { ...params, containerFieldName: params.containerFieldName, repetition: params.containerFieldRepetition, }, fetch: params.fetch, timeout: params.timeout, }); } return { ...otherMethods, layout: options.layout, list: _list, listAll, create: _create, get: _get, update: _update, delete: deleteRecord, find: _find, findOne, findFirst, maybeFindFirst, findAll, layoutMetadata: _layoutMetadata, containerUpload: _containerUpload, }; } export default DataApi; export { DataApi }; //# sourceMappingURL=client.js.map