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

269 lines (268 loc) 12.3 kB
/* * Copyright (c) 2023-2024. See LICENSE file for more information */ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); }; import { RecordTypes } from '../types.js'; import { ApiFieldDisplayTypes, ApiFieldResultTypes, ApiFieldTypes } from '../models/apiResults.js'; import { FMError } from '../FMError.js'; /** * A class representing a field in a record. * * @template T - The type of the field value. */ let Field = (() => { let _instanceExtraInitializers = []; let _private_streamAsync_decorators; let _private_streamAsync_descriptor; let _arrayBuffer_decorators; return class Field { static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; _private_streamAsync_decorators = [containerDownloadFunction]; _arrayBuffer_decorators = [containerDownloadFunction]; __esDecorate(this, _private_streamAsync_descriptor = { value: __setFunctionName(async function () { const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true }); if (!req.ok || !req.body) { throw new Error(`HTTP Error: ${req.status} (${req.statusText})`); } return { data: req.body, mime: req.headers.get('Content-Type') ?? '' }; }, "#streamAsync") }, _private_streamAsync_decorators, { kind: "method", name: "#streamAsync", static: false, private: true, access: { has: obj => #streamAsync in obj, get: obj => obj.#streamAsync }, metadata: _metadata }, null, _instanceExtraInitializers); __esDecorate(this, null, _arrayBuffer_decorators, { kind: "method", name: "arrayBuffer", static: false, private: false, access: { has: obj => "arrayBuffer" in obj, get: obj => obj.arrayBuffer }, metadata: _metadata }, null, _instanceExtraInitializers); if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); } parent = __runInitializers(this, _instanceExtraInitializers); id; _value; #originalContents; static firstContainerDownload = null; constructor(record, id, originalContents) { this.parent = record; this.id = id; this._value = originalContents; this.#originalContents = originalContents; } get edited() { return this.#originalContents !== this._value; } /** * Resets the 'edited' state of this field, without changing its value */ updateOriginalContents() { this.#originalContents = this._value; } /** * Sets the value of the field. * * @param {T | null} content - The new content value to be set. Can be either the type T or null. * @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'. */ set(content) { if (this.metadata.result === 'container') throw new Error('Cannot set container value using set(). Use upload() instead.'); else this._value = content; } get metadata() { if (!this.parent.layout.metadata) { // Default to a regular text field return { name: this.id.toString(), type: ApiFieldTypes.NORMAL, displayType: ApiFieldDisplayTypes.EDIT_TEXT, result: ApiFieldResultTypes.TEXT, global: false, autoEnter: true, fourDigitYear: false, maxRepeat: 1, maxCharacters: 0, notEmpty: false, numeric: false, timeOfDay: false, repetitions: 1, valueList: '' }; } if (this.parent.type === RecordTypes.PORTAL && this.parent.portal) { return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || 'portal not attached'].find(i => i.name === this.id) ?? { name: this.id.toString(), type: ApiFieldTypes.NORMAL, displayType: ApiFieldDisplayTypes.EDIT_TEXT, result: ApiFieldResultTypes.TEXT, global: false, autoEnter: true, fourDigitYear: false, maxRepeat: 1, maxCharacters: 0, notEmpty: false, numeric: false, timeOfDay: false, repetitions: 1, valueList: '' }; } else { return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? { name: this.id.toString(), type: ApiFieldTypes.NORMAL, displayType: ApiFieldDisplayTypes.EDIT_TEXT, result: ApiFieldResultTypes.TEXT, global: false, autoEnter: true, fourDigitYear: false, maxRepeat: 1, maxCharacters: 0, notEmpty: false, numeric: false, timeOfDay: false, repetitions: 1, valueList: '' }; } } get value() { // if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value" return this._value; } set value(value) { this.set(value); } get string() { if (typeof this._value === 'string') { return this._value; } throw new Error('Field value is not a string'); } /** * Uploads a file to the container field. * * @param {Buffer} file - The file content as a buffer. * * @throws {Error} - Cannot upload a file to the field if it's not a container field. * @throws {Error} - Upload failed with HTTP error. * * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded. */ async upload(file) { if (this.metadata.result !== 'container') { throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)'); } const form = new FormData(); form.append('upload', file); const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, { method: 'POST', headers: { // Authorization: 'Bearer ' + this.parent.layout.database.token, // 'Content-Type': 'multipart/form-data' }, body: form }); if (!res.ok) { throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`); } const data = await res.json(); if (data.messages[0].code === '0') return; else { throw new FMError(data.messages[0].code, res.status, res); } } get #streamAsync() { return _private_streamAsync_descriptor.value; } async stream() { return await this.#streamAsync(); } async arrayBuffer() { const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true }); if (!req.ok) throw new Error(`HTTP Error: ${req.status} (${req.statusText})`); return { data: await req.arrayBuffer(), mime: req.headers.get('Content-Type') ?? '' }; } }; })(); export { Field }; function containerDownloadFunction(originalMethod, context) { async function replacementMethod(...args) { return await ContainerDownloadExecutor.processTask(async () => await originalMethod.call(this, ...args)); } return replacementMethod; } class ContainerDownloadExecutorClass { hasDownloadedFirstContainer = 0; waitingFuncs = []; async processTask(task) { const isFirstTask = this.hasDownloadedFirstContainer === 0; if (isFirstTask) { this.hasDownloadedFirstContainer = 1; let res; try { res = await task(); } catch (e) { this.hasDownloadedFirstContainer = 0; const nextTask = this.waitingFuncs.splice(0, 1)[0]; if (nextTask) { this.processTask(nextTask.func) .then(res => nextTask.done(res)) .catch(e => nextTask.err(e)); } throw e; } this.hasDownloadedFirstContainer = 2; for (const task of this.waitingFuncs) { task.func() .then((res) => task.done(res)) .catch((e) => task.err(e)); } return res; } else if (this.hasDownloadedFirstContainer === 1) { return await new Promise((resolve, reject) => { this.waitingFuncs.push({ func: task, done: resolve, err: reject }); }); } else return await task(); } } const ContainerDownloadExecutor = new ContainerDownloadExecutorClass();