@jd-data-limited/easy-fm 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/src/bin/generateTypes.js +121 -215
- package/src/connection/database.d.ts +7 -7
- package/src/connection/database.js +34 -50
- package/src/connection/database.ts +43 -60
- package/src/connection/databaseBase.d.ts +4 -5
- package/src/connection/databaseBase.ts +3 -4
- package/src/records/field.d.ts +8 -16
- package/src/records/field.js +225 -124
- package/src/records/field.ts +86 -39
- package/src/records/layoutRecord.js +0 -1
- package/src/records/layoutRecord.ts +0 -1
- package/src/records/recordBase.d.ts +1 -1
- package/src/types.d.ts +2 -6
- package/src/types.js +0 -5
- package/src/types.ts +0 -5
|
@@ -7,13 +7,14 @@ import {generateAuthorizationHeaders} from './generateAuthorizationHeaders.js'
|
|
|
7
7
|
import {FMError} from '../FMError.js'
|
|
8
8
|
import {type LayoutInterface} from '../layouts/layoutInterface.js'
|
|
9
9
|
import {Layout} from '../layouts/layout.js'
|
|
10
|
-
import HTTP_REDIRECT from 'follow-redirects'
|
|
11
10
|
import {type databaseOptionsBase, type databaseOptionsWithExternalSources, type Script} from '../types.js'
|
|
12
11
|
import {type HostBase} from './HostBase.js'
|
|
13
12
|
import {type DatabaseBase} from './databaseBase.js'
|
|
14
13
|
import {type ApiLayout, type ApiResults} from '../models/apiResults.js'
|
|
15
14
|
import {type DatabaseStructure} from '../databaseStructure.js'
|
|
16
|
-
import {type
|
|
15
|
+
import fetch, {type HeadersInit, type RequestInfo, type RequestInit, type Response} from 'node-fetch'
|
|
16
|
+
// @ts-expect-error
|
|
17
|
+
import fetchWithCookies, {CookieJar} from 'node-fetch-cookies'
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Represents a database connection.
|
|
@@ -23,7 +24,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
23
24
|
private _token: string = ''
|
|
24
25
|
readonly host: HostBase
|
|
25
26
|
private readonly connection_details: databaseOptionsWithExternalSources
|
|
26
|
-
private cookies
|
|
27
|
+
private cookies = new CookieJar()
|
|
27
28
|
readonly name: string
|
|
28
29
|
readonly debug: boolean
|
|
29
30
|
|
|
@@ -66,7 +67,6 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
66
67
|
}
|
|
67
68
|
})
|
|
68
69
|
await _fetch.json()
|
|
69
|
-
// console.log(data)
|
|
70
70
|
this._token = ''
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -79,18 +79,17 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
79
79
|
* @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
|
|
80
80
|
*/
|
|
81
81
|
async login (forceLogin = false) {
|
|
82
|
-
|
|
82
|
+
if (this.token !== '' && !forceLogin) return
|
|
83
|
+
|
|
83
84
|
// Reset cookies
|
|
84
|
-
this.cookies =
|
|
85
|
+
this.cookies = new CookieJar()
|
|
85
86
|
|
|
86
87
|
await this.host.getMetadata()
|
|
87
|
-
if (this.token !== '' && !forceLogin) throw new Error('Already logged in. Run logout() first')
|
|
88
88
|
|
|
89
89
|
if (this.connection_details.credentials.method === 'token') {
|
|
90
90
|
this._token = (this.connection_details.credentials).token
|
|
91
91
|
return this.token
|
|
92
92
|
} else {
|
|
93
|
-
console.time()
|
|
94
93
|
const url = new URL(`${this.endpoint}/sessions`)
|
|
95
94
|
url.hostname = this.host.hostname
|
|
96
95
|
const res = await fetch(url, {
|
|
@@ -103,8 +102,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
103
102
|
})
|
|
104
103
|
})
|
|
105
104
|
})
|
|
106
|
-
|
|
107
|
-
const _res = (await res.json())
|
|
105
|
+
const _res = (await res.json()) as any
|
|
108
106
|
if (res.status === 200) {
|
|
109
107
|
this._token = res.headers.get('x-fm-data-access-token') ?? ''
|
|
110
108
|
return this._token
|
|
@@ -131,30 +129,53 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
131
129
|
return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
|
|
132
130
|
}
|
|
133
131
|
|
|
134
|
-
async _apiRequestRaw (url:
|
|
132
|
+
async _apiRequestRaw (url: URL | RequestInfo, options: RequestInit & {
|
|
133
|
+
headers?: Record<string, string>
|
|
134
|
+
useCookieJar?: boolean
|
|
135
|
+
} = {}, autoRelogin = true): Promise<Response> {
|
|
135
136
|
if (this.debug) console.log(`EASYFM DEBUG: ${JSON.stringify(options)} ${url instanceof Request ? url.url : url}`)
|
|
136
137
|
if (this.token === '') await this.login(true)
|
|
137
138
|
|
|
138
139
|
if (!options.headers) options.headers = {}
|
|
139
140
|
options.headers.authorization = 'Bearer ' + this._token
|
|
141
|
+
// options.redirect = "manual"
|
|
142
|
+
// options.headers.cookies = this._generateCookieHeader()
|
|
143
|
+
// console.log(options.headers.cookies)
|
|
140
144
|
// options.rejectUnauthorized = this.host.verify
|
|
141
145
|
|
|
142
|
-
const _fetch =
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
const _fetch: Response =
|
|
147
|
+
options.useCookieJar
|
|
148
|
+
? await fetchWithCookies(this.cookies, url, options)
|
|
149
|
+
: await fetch(url, options)
|
|
150
|
+
// if (_fetch.headers.get('set-cookie')) {
|
|
151
|
+
// console.log(_fetch.headers.get('set-cookie'))
|
|
152
|
+
// for (const cookie of (_fetch.headers.get("Set-Cookie") ?? "").split("; ")) {
|
|
153
|
+
// const cookieSplit = cookie.split('=')
|
|
154
|
+
// console.log("COOKIE SPLIT:", cookieSplit)
|
|
155
|
+
// if (cookieSplit[1]) this.cookies[cookieSplit[0]] = cookieSplit[1]
|
|
156
|
+
// }
|
|
157
|
+
// }
|
|
158
|
+
// console.log(this.cookies, _fetch.status)
|
|
159
|
+
// // console.log(this, this.cookies, url)
|
|
160
|
+
// console.trace()
|
|
150
161
|
if (_fetch.status === 401 && autoRelogin) {
|
|
162
|
+
console.trace()
|
|
151
163
|
await this.login(true)
|
|
152
164
|
return await this._apiRequestRaw(url, options, false)
|
|
165
|
+
} else return _fetch
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private _generateCookieHeader () {
|
|
169
|
+
const out: string[] = []
|
|
170
|
+
for (const key of Object.keys(this.cookies)) {
|
|
171
|
+
out.push(`${key}=${this.cookies[key]}`)
|
|
153
172
|
}
|
|
154
|
-
return
|
|
173
|
+
return out.join('; ')
|
|
155
174
|
}
|
|
156
175
|
|
|
157
|
-
async _apiRequestJSON<T = any>(url:
|
|
176
|
+
async _apiRequestJSON<T = any>(url: URL | RequestInfo, options: RequestInit & {
|
|
177
|
+
headers?: Record<string, string>
|
|
178
|
+
} = {}): Promise<ApiResults<T>> {
|
|
158
179
|
if (!options.headers) options.headers = {}
|
|
159
180
|
options.headers['content-type'] = options.headers['content-type'] ? options.headers['content-type'] : 'application/json'
|
|
160
181
|
const _fetch = await this._apiRequestRaw(url, options)
|
|
@@ -200,44 +221,6 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
200
221
|
}
|
|
201
222
|
|
|
202
223
|
script (name: string, parameter = ''): Script {
|
|
203
|
-
return ({
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async streamURL (url: string): Promise<IncomingMessage> {
|
|
207
|
-
return await new Promise((resolve, reject) => {
|
|
208
|
-
if (!url || typeof url !== 'string') {
|
|
209
|
-
throw new Error('URL is empty, or has invalid value')
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const headers: Record<string, string> = {}
|
|
213
|
-
if (Object.keys(this.cookies).length !== 0) {
|
|
214
|
-
headers.Cookie = Object.keys(this.cookies)
|
|
215
|
-
.map(key => {
|
|
216
|
-
return key + '=' + this.cookies[key]
|
|
217
|
-
})
|
|
218
|
-
.join('; ')
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const checkForCookies = (res: HTTP_REDIRECT.ResponseDetails) => {
|
|
222
|
-
// Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
|
|
223
|
-
if (res.headers['set-cookie']) {
|
|
224
|
-
for (const cookie of res.headers['set-cookie']) {
|
|
225
|
-
const cookieSplit = cookie.split('=')
|
|
226
|
-
this.cookies[cookieSplit[0]] = cookieSplit[1]
|
|
227
|
-
}
|
|
228
|
-
res.headers['set-cookie'] = undefined
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Automatically switch between the http and https modules, based on which is needed
|
|
233
|
-
(url.startsWith('https') ? HTTP_REDIRECT.https : HTTP_REDIRECT.http).get(url, {
|
|
234
|
-
headers,
|
|
235
|
-
beforeRedirect: (options: any, res: HTTP_REDIRECT.ResponseDetails) => { checkForCookies(res) }
|
|
236
|
-
}, (res) => {
|
|
237
|
-
checkForCookies(res)
|
|
238
|
-
|
|
239
|
-
resolve(res)
|
|
240
|
-
})
|
|
241
|
-
})
|
|
224
|
+
return ({name, parameter} satisfies Script)
|
|
242
225
|
}
|
|
243
226
|
}
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
1
|
import { type HostBase } from './HostBase.js';
|
|
3
2
|
import { type ApiResults } from '../models/apiResults.js';
|
|
4
|
-
import { type
|
|
3
|
+
import { type RequestInfo, type RequestInit, type Response } from 'node-fetch';
|
|
5
4
|
export interface DatabaseBase {
|
|
6
5
|
host: HostBase;
|
|
7
6
|
readonly name: string;
|
|
8
7
|
endpoint: string;
|
|
9
8
|
token: string;
|
|
10
|
-
_apiRequestJSON: <T = unknown>(url:
|
|
9
|
+
_apiRequestJSON: <T = unknown>(url: URL | RequestInfo, options?: RequestInit & {
|
|
11
10
|
headers?: Record<string, string>;
|
|
12
11
|
} | undefined, autoRelogin?: boolean) => Promise<ApiResults<T>>;
|
|
13
|
-
_apiRequestRaw: (url:
|
|
12
|
+
_apiRequestRaw: (url: URL | RequestInfo, options?: RequestInit & {
|
|
14
13
|
headers?: Record<string, string>;
|
|
14
|
+
useCookieJar?: boolean;
|
|
15
15
|
} | undefined) => Promise<Response>;
|
|
16
|
-
streamURL: (url: string) => Promise<IncomingMessage>;
|
|
17
16
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import {type HostBase} from './HostBase.js'
|
|
6
6
|
import {type ApiResults} from '../models/apiResults.js'
|
|
7
|
-
import {type
|
|
7
|
+
import {type RequestInfo, type RequestInit, type Response} from 'node-fetch'
|
|
8
8
|
|
|
9
9
|
export interface DatabaseBase {
|
|
10
10
|
host: HostBase
|
|
@@ -14,7 +14,6 @@ export interface DatabaseBase {
|
|
|
14
14
|
|
|
15
15
|
// layouts: DatabaseStructure["layouts"]
|
|
16
16
|
|
|
17
|
-
_apiRequestJSON: <T = unknown>(url:
|
|
18
|
-
_apiRequestRaw: (url:
|
|
19
|
-
streamURL: (url: string) => Promise<IncomingMessage>
|
|
17
|
+
_apiRequestJSON: <T = unknown>(url: URL | RequestInfo, options?: RequestInit & { headers?: Record<string, string> } | undefined, autoRelogin?: boolean) => Promise<ApiResults<T>>
|
|
18
|
+
_apiRequestRaw: (url: URL | RequestInfo, options?: RequestInit & { headers?: Record<string, string>, useCookieJar?: boolean } | undefined) => Promise<Response>
|
|
20
19
|
}
|
package/src/records/field.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
/// <reference types="node"
|
|
2
|
-
|
|
3
|
-
import type * as http from 'http';
|
|
4
|
-
import { type ContainerBufferResult, DownloadModes, RecordTypes } from '../types.js';
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { RecordTypes } from '../types.js';
|
|
5
3
|
import { type ApiFieldMetadata } from '../models/apiResults.js';
|
|
6
4
|
import { type LayoutBase } from '../layouts/layoutBase.js';
|
|
7
5
|
import { type Moment } from 'moment';
|
|
@@ -21,10 +19,12 @@ export interface Parentable {
|
|
|
21
19
|
* @template T - The type of the field value.
|
|
22
20
|
*/
|
|
23
21
|
export declare class Field<T extends FieldValue> {
|
|
22
|
+
#private;
|
|
24
23
|
parent: Parentable;
|
|
25
24
|
id: string;
|
|
26
25
|
protected _value: T | null;
|
|
27
26
|
edited: boolean;
|
|
27
|
+
static firstContainerDownload: Promise<any> | null;
|
|
28
28
|
constructor(record: Parentable, id: string, contents: T);
|
|
29
29
|
/**
|
|
30
30
|
* Sets the value of the field.
|
|
@@ -40,22 +40,14 @@ export declare class Field<T extends FieldValue> {
|
|
|
40
40
|
/**
|
|
41
41
|
* Uploads a file to the container field.
|
|
42
42
|
*
|
|
43
|
-
* @param {Buffer}
|
|
44
|
-
* @param {string} filename - The filename of the file.
|
|
45
|
-
* @param {string} mime - The MIME type of the file.
|
|
43
|
+
* @param {Buffer} file - The file content as a buffer.
|
|
46
44
|
*
|
|
47
45
|
* @throws {Error} - Cannot upload a file to the field if it's not a container field.
|
|
48
46
|
* @throws {Error} - Upload failed with HTTP error.
|
|
49
47
|
*
|
|
50
48
|
* @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
|
|
51
49
|
*/
|
|
52
|
-
upload(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*
|
|
56
|
-
* @param {DownloadModes} mode - The mode to use for downloading the resource.
|
|
57
|
-
* @return {Promise<http.IncomingMessage>} - A promise that resolves with the incoming message representing the downloaded resource.
|
|
58
|
-
*/
|
|
59
|
-
download(mode: DownloadModes.Stream): Promise<http.IncomingMessage>;
|
|
60
|
-
download(mode: DownloadModes.Buffer): Promise<ContainerBufferResult>;
|
|
50
|
+
upload(file: File): Promise<void>;
|
|
51
|
+
stream(): Promise<NodeJS.ReadableStream>;
|
|
52
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
61
53
|
}
|
package/src/records/field.js
CHANGED
|
@@ -1,60 +1,100 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
5
|
+
var useValue = arguments.length > 2;
|
|
6
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
7
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
8
|
+
}
|
|
9
|
+
return useValue ? value : void 0;
|
|
10
|
+
};
|
|
11
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
12
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
13
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
14
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
15
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
16
|
+
var _, done = false;
|
|
17
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
18
|
+
var context = {};
|
|
19
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
20
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
21
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
22
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
23
|
+
if (kind === "accessor") {
|
|
24
|
+
if (result === void 0) continue;
|
|
25
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
26
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
27
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
28
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
29
|
+
}
|
|
30
|
+
else if (_ = accept(result)) {
|
|
31
|
+
if (kind === "field") initializers.unshift(_);
|
|
32
|
+
else descriptor[key] = _;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
36
|
+
done = true;
|
|
37
|
+
};
|
|
38
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
39
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
40
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
41
|
+
};
|
|
42
|
+
import { RecordTypes } from '../types.js';
|
|
5
43
|
import { ApiFieldDisplayTypes, ApiFieldResultTypes, ApiFieldTypes } from '../models/apiResults.js';
|
|
44
|
+
import { FMError } from '../FMError.js';
|
|
6
45
|
/**
|
|
7
46
|
* A class representing a field in a record.
|
|
8
47
|
*
|
|
9
48
|
* @template T - The type of the field value.
|
|
10
49
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
42
|
-
result: ApiFieldResultTypes.TEXT,
|
|
43
|
-
global: false,
|
|
44
|
-
autoEnter: true,
|
|
45
|
-
fourDigitYear: false,
|
|
46
|
-
maxRepeat: 1,
|
|
47
|
-
maxCharacters: 0,
|
|
48
|
-
notEmpty: false,
|
|
49
|
-
numeric: false,
|
|
50
|
-
timeOfDay: false,
|
|
51
|
-
repetitions: 1,
|
|
52
|
-
valueList: ''
|
|
53
|
-
};
|
|
50
|
+
let Field = (() => {
|
|
51
|
+
let _instanceExtraInitializers = [];
|
|
52
|
+
let _private_streamAsync_decorators;
|
|
53
|
+
let _private_streamAsync_descriptor;
|
|
54
|
+
let _arrayBuffer_decorators;
|
|
55
|
+
return class Field {
|
|
56
|
+
static {
|
|
57
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
58
|
+
_private_streamAsync_decorators = [containerDownloadFunction];
|
|
59
|
+
_arrayBuffer_decorators = [containerDownloadFunction];
|
|
60
|
+
__esDecorate(this, _private_streamAsync_descriptor = { value: __setFunctionName(async function () {
|
|
61
|
+
const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true });
|
|
62
|
+
if (!req.ok || !req.body) {
|
|
63
|
+
throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
|
|
64
|
+
}
|
|
65
|
+
return req.body;
|
|
66
|
+
}, "#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);
|
|
67
|
+
__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);
|
|
68
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
69
|
+
}
|
|
70
|
+
parent = __runInitializers(this, _instanceExtraInitializers);
|
|
71
|
+
id;
|
|
72
|
+
_value;
|
|
73
|
+
edited;
|
|
74
|
+
static firstContainerDownload = null;
|
|
75
|
+
constructor(record, id, contents) {
|
|
76
|
+
this.parent = record;
|
|
77
|
+
this.id = id;
|
|
78
|
+
this._value = contents;
|
|
79
|
+
this.edited = false;
|
|
54
80
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Sets the value of the field.
|
|
83
|
+
*
|
|
84
|
+
* @param {T | null} content - The new content value to be set. Can be either the type T or null.
|
|
85
|
+
* @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
|
|
86
|
+
*/
|
|
87
|
+
set(content) {
|
|
88
|
+
if (this.metadata.result === 'container')
|
|
89
|
+
throw new Error('Cannot set container value using set(). Use upload() instead.');
|
|
90
|
+
else
|
|
91
|
+
this._value = content;
|
|
92
|
+
this.edited = true;
|
|
93
|
+
}
|
|
94
|
+
get metadata() {
|
|
95
|
+
if (!this.parent.layout.metadata) {
|
|
96
|
+
// Default to a regular text field
|
|
97
|
+
return {
|
|
58
98
|
name: this.id.toString(),
|
|
59
99
|
type: ApiFieldTypes.NORMAL,
|
|
60
100
|
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
@@ -70,90 +110,151 @@ export class Field {
|
|
|
70
110
|
repetitions: 1,
|
|
71
111
|
valueList: ''
|
|
72
112
|
};
|
|
113
|
+
}
|
|
114
|
+
if (this.parent.type === RecordTypes.PORTAL && this.parent.portal) {
|
|
115
|
+
return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || 'portal not attached'].find(i => i.name === this.id) ??
|
|
116
|
+
{
|
|
117
|
+
name: this.id.toString(),
|
|
118
|
+
type: ApiFieldTypes.NORMAL,
|
|
119
|
+
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
120
|
+
result: ApiFieldResultTypes.TEXT,
|
|
121
|
+
global: false,
|
|
122
|
+
autoEnter: true,
|
|
123
|
+
fourDigitYear: false,
|
|
124
|
+
maxRepeat: 1,
|
|
125
|
+
maxCharacters: 0,
|
|
126
|
+
notEmpty: false,
|
|
127
|
+
numeric: false,
|
|
128
|
+
timeOfDay: false,
|
|
129
|
+
repetitions: 1,
|
|
130
|
+
valueList: ''
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
|
|
135
|
+
name: this.id.toString(),
|
|
136
|
+
type: ApiFieldTypes.NORMAL,
|
|
137
|
+
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
138
|
+
result: ApiFieldResultTypes.TEXT,
|
|
139
|
+
global: false,
|
|
140
|
+
autoEnter: true,
|
|
141
|
+
fourDigitYear: false,
|
|
142
|
+
maxRepeat: 1,
|
|
143
|
+
maxCharacters: 0,
|
|
144
|
+
notEmpty: false,
|
|
145
|
+
numeric: false,
|
|
146
|
+
timeOfDay: false,
|
|
147
|
+
repetitions: 1,
|
|
148
|
+
valueList: ''
|
|
149
|
+
};
|
|
150
|
+
}
|
|
73
151
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
name: this.id.toString(),
|
|
77
|
-
type: ApiFieldTypes.NORMAL,
|
|
78
|
-
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
79
|
-
result: ApiFieldResultTypes.TEXT,
|
|
80
|
-
global: false,
|
|
81
|
-
autoEnter: true,
|
|
82
|
-
fourDigitYear: false,
|
|
83
|
-
maxRepeat: 1,
|
|
84
|
-
maxCharacters: 0,
|
|
85
|
-
notEmpty: false,
|
|
86
|
-
numeric: false,
|
|
87
|
-
timeOfDay: false,
|
|
88
|
-
repetitions: 1,
|
|
89
|
-
valueList: ''
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
get value() {
|
|
94
|
-
// if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
|
|
95
|
-
return this._value;
|
|
96
|
-
}
|
|
97
|
-
set value(value) {
|
|
98
|
-
this._value = value;
|
|
99
|
-
}
|
|
100
|
-
get string() {
|
|
101
|
-
if (typeof this._value === 'string') {
|
|
152
|
+
get value() {
|
|
153
|
+
// if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
|
|
102
154
|
return this._value;
|
|
103
155
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Uploads a file to the container field.
|
|
108
|
-
*
|
|
109
|
-
* @param {Buffer} buffer - The file content as a buffer.
|
|
110
|
-
* @param {string} filename - The filename of the file.
|
|
111
|
-
* @param {string} mime - The MIME type of the file.
|
|
112
|
-
*
|
|
113
|
-
* @throws {Error} - Cannot upload a file to the field if it's not a container field.
|
|
114
|
-
* @throws {Error} - Upload failed with HTTP error.
|
|
115
|
-
*
|
|
116
|
-
* @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
|
|
117
|
-
*/
|
|
118
|
-
async upload(buffer, filename, mime) {
|
|
119
|
-
if (this.metadata.result !== 'container') {
|
|
120
|
-
throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)');
|
|
156
|
+
set value(value) {
|
|
157
|
+
this._value = value;
|
|
121
158
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
Authorization: 'Bearer ' + this.parent.layout.database.token,
|
|
128
|
-
'Content-Type': 'multipart/form-data'
|
|
129
|
-
},
|
|
130
|
-
body: form
|
|
131
|
-
});
|
|
132
|
-
if (!res.ok)
|
|
133
|
-
throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`);
|
|
134
|
-
}
|
|
135
|
-
async download(mode = DownloadModes.Stream) {
|
|
136
|
-
if (this.metadata.result !== ApiFieldResultTypes.CONTAINER)
|
|
137
|
-
throw new Error('Cannot perform download() on a non-container field');
|
|
138
|
-
const stream = await this.parent.layout.database.streamURL(this.string);
|
|
139
|
-
if (mode === DownloadModes.Stream) {
|
|
140
|
-
return stream;
|
|
159
|
+
get string() {
|
|
160
|
+
if (typeof this._value === 'string') {
|
|
161
|
+
return this._value;
|
|
162
|
+
}
|
|
163
|
+
throw new Error('Field value is not a string');
|
|
141
164
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Uploads a file to the container field.
|
|
167
|
+
*
|
|
168
|
+
* @param {Buffer} file - The file content as a buffer.
|
|
169
|
+
*
|
|
170
|
+
* @throws {Error} - Cannot upload a file to the field if it's not a container field.
|
|
171
|
+
* @throws {Error} - Upload failed with HTTP error.
|
|
172
|
+
*
|
|
173
|
+
* @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
|
|
174
|
+
*/
|
|
175
|
+
async upload(file) {
|
|
176
|
+
if (this.metadata.result !== 'container') {
|
|
177
|
+
throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)');
|
|
178
|
+
}
|
|
179
|
+
const form = new FormData();
|
|
180
|
+
form.append('upload', file);
|
|
181
|
+
const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
headers: {
|
|
184
|
+
// Authorization: 'Bearer ' + this.parent.layout.database.token,
|
|
185
|
+
// 'Content-Type': 'multipart/form-data'
|
|
186
|
+
},
|
|
187
|
+
body: form
|
|
146
188
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
189
|
+
if (!res.ok) {
|
|
190
|
+
throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`);
|
|
191
|
+
}
|
|
192
|
+
const data = await res.json();
|
|
193
|
+
if (data.messages[0].code === '0')
|
|
194
|
+
return;
|
|
195
|
+
else {
|
|
196
|
+
throw new FMError(data.messages[0].code, res.status, res);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
get #streamAsync() { return _private_streamAsync_descriptor.value; }
|
|
200
|
+
async stream() {
|
|
201
|
+
return await this.#streamAsync();
|
|
202
|
+
}
|
|
203
|
+
async arrayBuffer() {
|
|
204
|
+
const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true });
|
|
205
|
+
if (!req.ok)
|
|
206
|
+
throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
|
|
207
|
+
return await req.arrayBuffer();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
})();
|
|
211
|
+
export { Field };
|
|
212
|
+
function containerDownloadFunction(originalMethod, context) {
|
|
213
|
+
async function replacementMethod(...args) {
|
|
214
|
+
return await ContainerDownloadExecutor.processTask(async () => await originalMethod.call(this, ...args));
|
|
215
|
+
}
|
|
216
|
+
return replacementMethod;
|
|
217
|
+
}
|
|
218
|
+
class ContainerDownloadExecutorClass {
|
|
219
|
+
hasDownloadedFirstContainer = 0;
|
|
220
|
+
waitingFuncs = [];
|
|
221
|
+
async processTask(task) {
|
|
222
|
+
const isFirstTask = this.hasDownloadedFirstContainer === 0;
|
|
223
|
+
if (isFirstTask) {
|
|
224
|
+
this.hasDownloadedFirstContainer = 1;
|
|
225
|
+
let res;
|
|
226
|
+
try {
|
|
227
|
+
res = await task();
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
this.hasDownloadedFirstContainer = 0;
|
|
231
|
+
const nextTask = this.waitingFuncs.splice(0, 1)[0];
|
|
232
|
+
if (nextTask) {
|
|
233
|
+
this.processTask(nextTask.func)
|
|
234
|
+
.then(res => nextTask.done(res))
|
|
235
|
+
.catch(e => nextTask.err(e));
|
|
236
|
+
}
|
|
237
|
+
throw e;
|
|
238
|
+
}
|
|
239
|
+
this.hasDownloadedFirstContainer = 2;
|
|
240
|
+
for (const task of this.waitingFuncs) {
|
|
241
|
+
task.func()
|
|
242
|
+
.then((res) => task.done(res))
|
|
243
|
+
.catch((e) => task.err(e));
|
|
244
|
+
}
|
|
245
|
+
return res;
|
|
246
|
+
}
|
|
247
|
+
else if (this.hasDownloadedFirstContainer === 1) {
|
|
248
|
+
return await new Promise((resolve, reject) => {
|
|
249
|
+
this.waitingFuncs.push({
|
|
250
|
+
func: task,
|
|
251
|
+
done: resolve,
|
|
252
|
+
err: reject
|
|
155
253
|
});
|
|
156
254
|
});
|
|
157
|
-
}
|
|
255
|
+
}
|
|
256
|
+
else
|
|
257
|
+
return await task();
|
|
158
258
|
}
|
|
159
259
|
}
|
|
260
|
+
const ContainerDownloadExecutor = new ContainerDownloadExecutorClass();
|