UNPKG

@jd-data-limited/easy-fm

Version:

easy-fm is a Node.js module that allows you to interact with a [FileMaker database stored](https://www.claris.com/filemaker/) on a [FileMaker server](https://www.claris.com/filemaker/server/). This module interacts with your server using the [FileMaker

234 lines (233 loc) 9.25 kB
/* * Copyright (c) 2023-2024. See LICENSE file for more information */ import { LayoutRecord } from '../layoutRecord.js'; import { FMError } from '../../FMError.js'; import { FindRequestSymbol } from '../../utils/query.js'; export class RecordGetOperation { layout; limit = 100; scriptData = {}; sortData = []; portals; offset = 1; requests = []; constructor(layout, options) { this.layout = layout; this.sortData = []; this.portals = options.portals; this.offset = options.offset ?? 1; // Offset refers to the starting record. offset 1 is the same as no offset. this.limit = options.limit ?? 100; if (options.requests) { for (const req of options.requests) this.addRequest(req.req, req.omit ?? false); } } get isFindRequest() { return this.requests.length !== 0; } formatQueries() { const test = this.requests.map(query => { const out = {}; for (const key of Object.keys(query.req)) { if (query.req[key]) out[key] = query.req[key]; else { out[key] = query.req[key]; } } if (query.omit) out.omit = 'true'; return out; }); return test; } generateParamsBody(offset, limit) { const params = { limit: limit.toString(), offset: offset.toString(), dateformats: 2 // Ensure dates are received in ISO8601 format }; if (this.sortData.length !== 0) params.sort = this.sortData; if (this.scriptData.after) params.script = this.scriptData.after.name; if (this.scriptData.after?.parameter) params['script.param'] = this.scriptData.after.parameter; if (this.scriptData.presort) params['script.presort'] = this.scriptData.presort.name; if (this.scriptData.presort?.parameter) params['script.presort.param'] = this.scriptData.presort.parameter; if (this.scriptData.prerequest) params['script.prerequest'] = this.scriptData.prerequest.name; if (this.scriptData.prerequest?.parameter) params['script.prerequest.param'] = this.scriptData.prerequest.parameter; if (this.requests.length !== 0) params.query = this.formatQueries(); const portals = Object.keys(this.portals); params.portal = portals; for (const portal of portals) { params[`offset.${portal.toString()}`] = this.portals[portal]?.offset; params[`limit.${portal.toString()}`] = this.portals[portal]?.limit; } return params; } generateParamsURL(offset, limit) { const params = new URLSearchParams({ _limit: limit.toString(), _offset: offset.toString(), dateformats: '2' // Ensure dates are received in ISO8601 format }); if (this.sortData.length !== 0) params.set('_sort', JSON.stringify(this.sortData)); if (this.scriptData.after) params.set('script', this.scriptData.after.name); if (this.scriptData.after?.parameter) params.set('script.param', this.scriptData.after.parameter); if (this.scriptData.presort) params.set('script.presort', this.scriptData.presort.name); if (this.scriptData.presort?.parameter) params.set('script.presort.param', this.scriptData.presort.parameter); if (this.scriptData.prerequest) params.set('script.prerequest', this.scriptData.prerequest.name); if (this.scriptData.prerequest?.parameter) params.set('script.prerequest.param', this.scriptData.prerequest.parameter); const portals = Object.keys(this.portals); for (const portal of portals) { params.set(`_offset.${portal.toString()}`, (this.portals[portal]?.limit ?? '').toString()); params.set(`_offset.${portal.toString()}`, (this.portals[portal]?.offset ?? '').toString()); } params.set('portal', JSON.stringify(portals)); return params; } /** * Configures any FileMaker scripts to be run as a part of the request * * @param {ScriptRequestData} scripts - The script request data to set. * @return {this} - The current instance of the class. */ scripts(scripts) { this.scriptData = scripts; return this; } /** * Sorts the data based on the given field name and sort order. * * @param {string} fieldName - The name of the field by which the data should be sorted. * @param {SortOrder} sortOrder - The sort order to be applied (either "asc" for ascending or "desc" for descending). * * @return {this} - Returns the current instance of the object. */ sort(fieldName, sortOrder) { this.sortData.push({ fieldName, sortOrder }); return this; } parseFindRequest(query) { const out = {}; for (const key of Object.keys(query)) { out[key] = query[key][FindRequestSymbol].map(item => { if (typeof item === 'string') return item; // Re-write date into correct format return item .moment .clone() .utcOffset(this.layout.database.host.timezoneOffsetFunc(item.moment)) .format(item.type === 'date' ? this.layout.database.host.dateFormat : item.type == 'time' ? this.layout.database.host.timeFormat : this.layout.database.host.timeStampFormat); }).join(''); } return out; } /** * Adds a new request/query to the list of queries. * * @param {FindRequest} query - The find request to be added. * @param {boolean} [omit=false] - Flag to indicate if the find request should be omitted. * @return {Object} - The current object instance. */ addRequest(query, omit = false) { this.requests.push({ req: this.parseFindRequest(query), omit }); return this; } /** * Perform a fetch operation. * * @returns {Promise} A promise that resolves with the result of the fetch operation. */ async fetch() { return await this.performFind(this.offset, this.limit); } async performFind(offset, limit) { const trace = new Error(); await this.layout.getLayoutMeta(); const isFind = this.isFindRequest; let endpoint = this.layout.endpoint + (isFind ? '/_find' : '/records'); if (!isFind) endpoint += '?' + new URLSearchParams(this.generateParamsURL(offset, limit)).toString(); const reqData = { // port: 443, method: isFind ? 'POST' : 'GET', body: isFind ? JSON.stringify(this.generateParamsBody(offset, limit)) : undefined }; try { const res = await this.layout.database._apiRequestJSON(endpoint, reqData); if (res.messages[0].code === '0' && res.response) { // console.log("RESOLVING") if (!this.layout.metadata) await this.layout.getLayoutMeta(); return res.response.data.map(item => { return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData); }); } else { throw new FMError(res.messages[0].code, res.httpStatus, res, trace); } } catch (e) { if (e instanceof FMError) { if (e.code === 401) { // No records found, so return empty set return []; } } throw e; } } [Symbol.asyncIterator]() { let nextOffset = this.offset; const startOffset = JSON.parse(JSON.stringify(this.offset)); const limit = this.limit; let exitAfterLastRecord = false; let records = []; const fetch = async () => { const theoreticalLimit = (limit - nextOffset) + startOffset; if (theoreticalLimit === 0) { exitAfterLastRecord = true; records = []; return; } records = await this.performFind(nextOffset, theoreticalLimit < 100 ? theoreticalLimit : 100); nextOffset += 100; if (records.length < 100) exitAfterLastRecord = true; }; return { next: async () => { if (records.length === 0 && !exitAfterLastRecord) { await fetch(); } if (records.length === 0 && exitAfterLastRecord) { return { done: true, value: undefined }; } else { const record = records.shift(); return { done: false, value: record }; } } }; } }