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

217 lines (216 loc) 9.25 kB
/* * Copyright (c) 2023-2024. See LICENSE file for more information */ import { RecordBase } from './recordBase.js'; import { PortalRecord } from './portalRecord.js'; import { Portal } from './portal.js'; import { FMError } from '../FMError.js'; import moment from 'moment'; export class LayoutRecord extends RecordBase { portals = {}; portalsToInclude; constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null, portalsToInclude = []) { super(layout, parseInt(recordId), parseInt(modId), fieldData); this.portalsToInclude = portalsToInclude; if (portalData) { this.processPortalData(portalData); } } get portalsArray() { return Object.values(this.portals); } /** * Asynchronously commits the changes made to the current record. * @param {extraBodyOptions} extraBody - The options for the extra body elements. * @returns {Promise<this>} - A promise that resolves with the modified object. * @throws {FMError} - If an error occurs during the commit process. */ async commit(extraBody = {}) { const data = this.toObject(); delete data.recordId; delete data.modId; if (extraBody.scripts?.after) { data.script = extraBody.scripts.after.name; if (extraBody.scripts.after.parameter) data['script.param'] = extraBody.scripts.after.parameter; } if (extraBody.scripts?.prerequest) { data['script.prerequest'] = extraBody.scripts.prerequest.name; if (extraBody.scripts.prerequest.parameter) data['script.prerequest.param'] = extraBody.scripts.prerequest.parameter; } if (extraBody.scripts?.presort) { data['script.presort'] = extraBody.scripts.presort.name; if (extraBody.scripts.presort.parameter) data['script.presort.param'] = extraBody.scripts.presort.parameter; } if (extraBody.options) data.options = extraBody.options; if (extraBody.deleteRelatedRecords) data.fieldData.deleteRelated = extraBody.deleteRelatedRecords.map(i => `${i.table}.${i.recordId}`); if (this.recordId === -1) { // This is a new LayoutRecord const res = await this.layout.database._apiRequestJSON(`${this.layout.endpoint}/records`, { method: 'POST', body: JSON.stringify(data) }); if (!res.response) { throw new FMError(res.messages[0].code, res.httpStatus, res); } else if (typeof res.response.scriptError !== 'undefined' && res.response.scriptError !== '0') { throw new FMError(res.response.scriptError, res.httpStatus, res); } else if (res.messages[0].code === '0') { this.recordId = parseInt(res.response.recordId); this.modId = parseInt(res.response.modId); return this; } else { throw new FMError(res.messages[0].code, res.httpStatus, res); } } // for (let item of Object.keys(data)) extraBody[item] = data[item] const res = await this.layout.database._apiRequestJSON(this.endpoint, { method: 'PATCH', body: JSON.stringify(data) }); if (!res.response) { throw new FMError(res.messages[0].code, res.httpStatus, res); } else if (typeof res.response.scriptError !== 'undefined' && res.response.scriptError !== '0') { throw new FMError(res.response.scriptError, res.httpStatus, res); } else if (res.messages[0].code === '0') { this.modId = +res.response.modId; this._onSave(); return this; } else { throw new FMError(res.messages[0].code, res.httpStatus, res); } } processPortalData(portalData) { for (const portalName of Object.keys(portalData)) { const _portal = new Portal(this, portalName); _portal.records = portalData[portalName].map(item => { const fieldData = item; delete fieldData.recordId; delete fieldData.modId; return new PortalRecord(this, _portal, parseInt(item.recordId), parseInt(item.modId), fieldData); }); // @ts-expect-error - This code is actually correct, but throws a typescript error this.portals[portalName] = _portal; } } /** * Re-fetches the current record from the database server. * Throws an error if commit() has not been called. * * @return {Promise<this>} A Promise that resolves to this RecordBase instance if the record is successfully retrieved. * @throws {Error} If commit() has not been called. * @throws {FMError} If the retrieval fails. */ async get() { if (this.recordId === -1) { throw new Error('Cannot get this RecordBase until a commit() is done.'); } if (!this.layout.metadata) await this.layout.getLayoutMeta(); const res = await this.layout.database._apiRequestJSON(this.endpoint, { method: 'GET' }); if (res.response && res.messages[0].code === '0') { // console.log(res, res.response.data) this.modId = +res.response.data[0].modId; this.processFieldData(res.response.data[0].fieldData); this.portalData = []; if (res.response.data[0].portalData) this.processPortalData(res.response.data[0].portalData); return this; } else { throw new FMError(res.messages[0].code, res.httpStatus, res); } } async duplicate() { const trace = new Error(); const res = await this.layout.database._apiRequestJSON(this.endpoint, { method: 'POST' }); if (!res.response) { throw new FMError(res.messages[0].code, res.httpStatus, res, trace); } else if (typeof res.response.scriptError !== 'undefined' && res.response.scriptError !== '0') { throw new FMError(res.response.scriptError, res.httpStatus, res, trace); } else if (res.messages[0].code === '0') { const data = this.toObject((a) => true, (a) => true, (a) => false, (a) => false); const _res = new LayoutRecord(this.layout, res.response.recordId, res.response.modId, data.fieldData, data.portalData); this.emit('duplicated'); return _res; } else { throw new FMError(res.messages[0].code, res.httpStatus, res, trace); } } async delete() { const res = await this.layout.database._apiRequestJSON(this.endpoint, { method: 'DELETE' }); if (typeof res.response?.scriptError !== 'undefined' && res.response?.scriptError !== '0') { throw new FMError(res.response.scriptError, res.httpStatus, res); } else if (res.messages[0].code === '0') { this.emit('deleted'); } else { throw new FMError(res.messages[0].code, res.httpStatus, res); } } fieldsToObject(filter = (a) => a.edited) { const fieldsProcessed = {}; let field; for (field of this.fieldsArray.filter(field => filter(field))) { let value = field.value; if (value instanceof Date) { let _value = moment(value); _value = _value .utcOffset(this.layout.database.host.timezoneOffsetFunc(_value)); switch (field.metadata.result) { case 'time': value = _value.format(this.layout.database.host.timeFormat); break; case 'date': value = _value.format(this.layout.database.host.dateFormat); break; default: value = _value.format(this.layout.database.host.timeStampFormat); } } fieldsProcessed[field.id] = value; } const obj = { recordId: this.recordId.toString(), modId: this.modId.toString(), fieldData: fieldsProcessed }; return obj; } toObject(filter = (a) => a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a.edited) { const obj = { ...this.fieldsToObject(filter), portalData: {} }; // Check if there's been any edited portal information const portals = this.portalsArray.filter(a => portalFilter(a)); if (portals) { obj.portalData = {}; for (const portal of portals) { obj.portalData[portal.name] = portal.records.filter(a => portalRowFilter(a)).map(record => { return record.toObject(portalFieldFilter); }); } } return obj; } }