@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
JavaScript
/*
* 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;
}
}