@jd-data-limited/easy-fm 4.1.0 → 4.1.1

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 CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@jd-data-limited/easy-fm",
3
- "version": "4.1.0",
3
+ "version": "4.1.1",
4
4
  "dependencies": {
5
5
  "@types/follow-redirects": "^1.14.4",
6
6
  "follow-redirects": "^1.15.5",
7
- "moment": "^2.29.4"
7
+ "moment": "^2.29.4",
8
+ "node-fetch": "^3.3.2",
9
+ "node-fetch-cookies": "^2.1.1"
8
10
  },
9
11
  "type": "module",
10
12
  "main": "src/index.js",
@@ -49,6 +51,7 @@
49
51
  "eslint-plugin-promise": "^6.1.1",
50
52
  "inquirer": "^9.2.13",
51
53
  "ts-morph": "^21.0.1",
54
+ "typedoc": "^0.25.12",
52
55
  "typescript": "^5.4.3",
53
56
  "webpack": "^5.81.0",
54
57
  "webpack-cli": "^5.0.2"
@@ -1,5 +1,4 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node" resolution-mode="require"/>
1
+ /// <reference types="node" />
3
2
  import { EventEmitter } from 'events';
4
3
  import { type LayoutInterface } from '../layouts/layoutInterface.js';
5
4
  import { Layout } from '../layouts/layout.js';
@@ -8,7 +7,7 @@ import { type HostBase } from './HostBase.js';
8
7
  import { type DatabaseBase } from './databaseBase.js';
9
8
  import { type ApiResults } from '../models/apiResults.js';
10
9
  import { type DatabaseStructure } from '../databaseStructure.js';
11
- import { type IncomingMessage } from 'http';
10
+ import { RequestInfo, RequestInit, Response } from 'node-fetch';
12
11
  /**
13
12
  * Represents a database connection.
14
13
  * @template T - The structure of the database.
@@ -38,7 +37,7 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
38
37
  * @throws {FMError} - Throws an FMError if login fails.
39
38
  * @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
40
39
  */
41
- login(forceLogin?: boolean): Promise<string>;
40
+ login(forceLogin?: boolean): Promise<string | undefined>;
42
41
  get token(): string;
43
42
  /**
44
43
  * Returns the endpoint URL for the database connection.
@@ -46,10 +45,12 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
46
45
  * @returns {string} The endpoint URL.
47
46
  */
48
47
  get endpoint(): string;
49
- _apiRequestRaw(url: string | Request, options?: RequestInit & {
48
+ _apiRequestRaw(url: URL | RequestInfo, options?: RequestInit & {
50
49
  headers?: Record<string, string>;
50
+ useCookieJar?: boolean;
51
51
  }, autoRelogin?: boolean): Promise<Response>;
52
- _apiRequestJSON<T = any>(url: string | Request, options?: RequestInit & {
52
+ private _generateCookieHeader;
53
+ _apiRequestJSON<T = any>(url: URL | RequestInfo, options?: RequestInit & {
53
54
  headers?: Record<string, string>;
54
55
  }): Promise<ApiResults<T>>;
55
56
  /**
@@ -62,5 +63,4 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
62
63
  layout<R extends keyof T['layouts']>(name: R): Layout<T['layouts'][R]>;
63
64
  layout<R extends LayoutInterface>(name: string): Layout<R>;
64
65
  script(name: string, parameter?: string): Script;
65
- streamURL(url: string): Promise<IncomingMessage>;
66
66
  }
@@ -5,7 +5,9 @@ import { EventEmitter } from 'events';
5
5
  import { generateAuthorizationHeaders } from './generateAuthorizationHeaders.js';
6
6
  import { FMError } from '../FMError.js';
7
7
  import { Layout } from '../layouts/layout.js';
8
- import HTTP_REDIRECT from 'follow-redirects';
8
+ import fetch from 'node-fetch';
9
+ // @ts-ignore
10
+ import fetchWithCookies, { CookieJar } from "node-fetch-cookies";
9
11
  /**
10
12
  * Represents a database connection.
11
13
  * @template T - The structure of the database.
@@ -14,7 +16,7 @@ export class Database extends EventEmitter {
14
16
  _token = '';
15
17
  host;
16
18
  connection_details;
17
- cookies = {};
19
+ cookies = new CookieJar();
18
20
  name;
19
21
  debug;
20
22
  constructor(host, conn) {
@@ -67,12 +69,13 @@ export class Database extends EventEmitter {
67
69
  * @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
68
70
  */
69
71
  async login(forceLogin = false) {
70
- // console.trace()
72
+ if (this.token !== '' && !forceLogin)
73
+ return;
74
+ console.log("RELOGGING IN");
75
+ console.trace();
71
76
  // Reset cookies
72
- this.cookies = {};
77
+ this.cookies = new CookieJar();
73
78
  await this.host.getMetadata();
74
- if (this.token !== '' && !forceLogin)
75
- throw new Error('Already logged in. Run logout() first');
76
79
  if (this.connection_details.credentials.method === 'token') {
77
80
  this._token = (this.connection_details.credentials).token;
78
81
  return this.token;
@@ -121,19 +124,38 @@ export class Database extends EventEmitter {
121
124
  if (!options.headers)
122
125
  options.headers = {};
123
126
  options.headers.authorization = 'Bearer ' + this._token;
127
+ // options.redirect = "manual"
128
+ // options.headers.cookies = this._generateCookieHeader()
129
+ // console.log(options.headers.cookies)
124
130
  // options.rejectUnauthorized = this.host.verify
125
- const _fetch = await fetch(url, options);
126
- if (_fetch.headers.get('set-cookie')) {
127
- for (const cookie of _fetch.headers.get('set-cookie') ?? []) {
128
- const cookieSplit = cookie.split('=');
129
- this.cookies[cookieSplit[0]] = cookieSplit[1];
130
- }
131
- }
131
+ const _fetch = options.useCookieJar ?
132
+ await fetchWithCookies(this.cookies, url, options) :
133
+ await fetch(url, options);
134
+ // if (_fetch.headers.get('set-cookie')) {
135
+ // console.log(_fetch.headers.get('set-cookie'))
136
+ // for (const cookie of (_fetch.headers.get("Set-Cookie") ?? "").split("; ")) {
137
+ // const cookieSplit = cookie.split('=')
138
+ // console.log("COOKIE SPLIT:", cookieSplit)
139
+ // if (cookieSplit[1]) this.cookies[cookieSplit[0]] = cookieSplit[1]
140
+ // }
141
+ // }
142
+ // console.log(this.cookies, _fetch.status)
143
+ // // console.log(this, this.cookies, url)
144
+ // console.trace()
132
145
  if (_fetch.status === 401 && autoRelogin) {
146
+ console.trace();
133
147
  await this.login(true);
134
148
  return await this._apiRequestRaw(url, options, false);
135
149
  }
136
- return _fetch;
150
+ else
151
+ return _fetch;
152
+ }
153
+ _generateCookieHeader() {
154
+ let out = [];
155
+ for (let key of Object.keys(this.cookies)) {
156
+ out.push(`${key}=${this.cookies[key]}`);
157
+ }
158
+ return out.join("; ");
137
159
  }
138
160
  async _apiRequestJSON(url, options = {}) {
139
161
  if (!options.headers)
@@ -179,37 +201,4 @@ export class Database extends EventEmitter {
179
201
  script(name, parameter = '') {
180
202
  return ({ name, parameter });
181
203
  }
182
- async streamURL(url) {
183
- return await new Promise((resolve, reject) => {
184
- if (!url || typeof url !== 'string') {
185
- throw new Error('URL is empty, or has invalid value');
186
- }
187
- const headers = {};
188
- if (Object.keys(this.cookies).length !== 0) {
189
- headers.Cookie = Object.keys(this.cookies)
190
- .map(key => {
191
- return key + '=' + this.cookies[key];
192
- })
193
- .join('; ');
194
- }
195
- const checkForCookies = (res) => {
196
- // Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
197
- if (res.headers['set-cookie']) {
198
- for (const cookie of res.headers['set-cookie']) {
199
- const cookieSplit = cookie.split('=');
200
- this.cookies[cookieSplit[0]] = cookieSplit[1];
201
- }
202
- res.headers['set-cookie'] = undefined;
203
- }
204
- };
205
- // Automatically switch between the http and https modules, based on which is needed
206
- (url.startsWith('https') ? HTTP_REDIRECT.https : HTTP_REDIRECT.http).get(url, {
207
- headers,
208
- beforeRedirect: (options, res) => { checkForCookies(res); }
209
- }, (res) => {
210
- checkForCookies(res);
211
- resolve(res);
212
- });
213
- });
214
- }
215
204
  }
@@ -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 IncomingMessage} from 'http'
15
+ import fetch, {HeadersInit, RequestInfo, RequestInit, Response} from 'node-fetch';
16
+ // @ts-ignore
17
+ import fetchWithCookies, {CookieJar} from "node-fetch-cookies"
17
18
 
18
19
  /**
19
20
  * Represents a database connection.
@@ -23,11 +24,11 @@ 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: Record<string, string> = {}
27
+ private cookies = new CookieJar()
27
28
  readonly name: string
28
29
  readonly debug: boolean
29
30
 
30
- constructor (host: HostBase, conn: databaseOptionsWithExternalSources) {
31
+ constructor(host: HostBase, conn: databaseOptionsWithExternalSources) {
31
32
  super()
32
33
  this.host = host
33
34
  this.name = conn.database
@@ -36,7 +37,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
36
37
  }
37
38
 
38
39
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
39
- private generateExternalSourceLogin (data: databaseOptionsBase) {
40
+ private generateExternalSourceLogin(data: databaseOptionsBase) {
40
41
  if (data.credentials.method === 'filemaker') {
41
42
  const _data = data.credentials
42
43
  return {
@@ -44,7 +45,8 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
44
45
  username: _data.username,
45
46
  password: _data.password
46
47
  }
47
- } else {
48
+ }
49
+ else {
48
50
  throw new Error('Not yet supported login method')
49
51
  }
50
52
  }
@@ -56,7 +58,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
56
58
  * @returns {Promise<void>} A promise that resolves with no value once the logout is successful.
57
59
  * @throws {Error} Throws an error if the user is not logged in.
58
60
  */
59
- async logout (): Promise<void> {
61
+ async logout(): Promise<void> {
60
62
  if (this.token === '') throw new Error('Not logged in')
61
63
 
62
64
  const _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
@@ -78,18 +80,21 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
78
80
  * @throws {FMError} - Throws an FMError if login fails.
79
81
  * @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
80
82
  */
81
- async login (forceLogin = false) {
82
- // console.trace()
83
+ async login(forceLogin = false) {
84
+ if (this.token !== '' && !forceLogin) return
85
+
86
+ console.log("RELOGGING IN")
87
+ console.trace()
83
88
  // Reset cookies
84
- this.cookies = {}
89
+ this.cookies = new CookieJar()
85
90
 
86
91
  await this.host.getMetadata()
87
- if (this.token !== '' && !forceLogin) throw new Error('Already logged in. Run logout() first')
88
92
 
89
93
  if (this.connection_details.credentials.method === 'token') {
90
94
  this._token = (this.connection_details.credentials).token
91
95
  return this.token
92
- } else {
96
+ }
97
+ else {
93
98
  console.time()
94
99
  const url = new URL(`${this.endpoint}/sessions`)
95
100
  url.hostname = this.host.hostname
@@ -104,11 +109,12 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
104
109
  })
105
110
  })
106
111
  console.time()
107
- const _res = (await res.json())
112
+ const _res = (await res.json()) as any
108
113
  if (res.status === 200) {
109
114
  this._token = res.headers.get('x-fm-data-access-token') ?? ''
110
115
  return this._token
111
- } else {
116
+ }
117
+ else {
112
118
  throw new FMError(
113
119
  _res.messages[0].code as string | number,
114
120
  _res.status as number,
@@ -118,7 +124,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
118
124
  }
119
125
  }
120
126
 
121
- get token () {
127
+ get token() {
122
128
  return this._token
123
129
  }
124
130
 
@@ -127,34 +133,58 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
127
133
  *
128
134
  * @returns {string} The endpoint URL.
129
135
  */
130
- get endpoint (): string {
136
+ get endpoint(): string {
131
137
  return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
132
138
  }
133
139
 
134
- async _apiRequestRaw (url: string | Request, options: RequestInit & { headers?: Record<string, string> } = {}, autoRelogin = true): Promise<Response> {
140
+ async _apiRequestRaw(url: URL | RequestInfo, options: RequestInit & {
141
+ headers?: Record<string, string>,
142
+ useCookieJar?: boolean,
143
+ } = {}, autoRelogin = true): Promise<Response> {
135
144
  if (this.debug) console.log(`EASYFM DEBUG: ${JSON.stringify(options)} ${url instanceof Request ? url.url : url}`)
136
145
  if (this.token === '') await this.login(true)
137
146
 
138
147
  if (!options.headers) options.headers = {}
139
148
  options.headers.authorization = 'Bearer ' + this._token
149
+ // options.redirect = "manual"
150
+ // options.headers.cookies = this._generateCookieHeader()
151
+ // console.log(options.headers.cookies)
140
152
  // options.rejectUnauthorized = this.host.verify
141
153
 
142
- const _fetch = await fetch(url, options)
143
- if (_fetch.headers.get('set-cookie')) {
144
- for (const cookie of _fetch.headers.get('set-cookie') ?? []) {
145
- const cookieSplit = cookie.split('=')
146
- this.cookies[cookieSplit[0]] = cookieSplit[1]
147
- }
148
- }
149
-
154
+ const _fetch: Response =
155
+ options.useCookieJar ?
156
+ await fetchWithCookies(this.cookies, url, options):
157
+ await fetch(url, options)
158
+ // if (_fetch.headers.get('set-cookie')) {
159
+ // console.log(_fetch.headers.get('set-cookie'))
160
+ // for (const cookie of (_fetch.headers.get("Set-Cookie") ?? "").split("; ")) {
161
+ // const cookieSplit = cookie.split('=')
162
+ // console.log("COOKIE SPLIT:", cookieSplit)
163
+ // if (cookieSplit[1]) this.cookies[cookieSplit[0]] = cookieSplit[1]
164
+ // }
165
+ // }
166
+ // console.log(this.cookies, _fetch.status)
167
+ // // console.log(this, this.cookies, url)
168
+ // console.trace()
150
169
  if (_fetch.status === 401 && autoRelogin) {
170
+ console.trace()
151
171
  await this.login(true)
152
172
  return await this._apiRequestRaw(url, options, false)
153
173
  }
154
- return _fetch
174
+ else return _fetch
175
+ }
176
+
177
+ private _generateCookieHeader() {
178
+ let out: string[] = []
179
+ for (let key of Object.keys(this.cookies)) {
180
+ out.push(`${key}=${this.cookies[key]}`)
181
+ }
182
+ return out.join("; ")
155
183
  }
156
184
 
157
- async _apiRequestJSON<T = any>(url: string | Request, options: RequestInit & { headers?: Record<string, string> } = {}): Promise<ApiResults<T>> {
185
+ async _apiRequestJSON<T = any>(url: URL | RequestInfo, options: RequestInit & {
186
+ headers?: Record<string, string>
187
+ } = {}): Promise<ApiResults<T>> {
158
188
  if (!options.headers) options.headers = {}
159
189
  options.headers['content-type'] = options.headers['content-type'] ? options.headers['content-type'] : 'application/json'
160
190
  const _fetch = await this._apiRequestRaw(url, options)
@@ -178,7 +208,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
178
208
  * @returns {Promise<Layout[]>} A promise that resolves to an array of Layout objects.
179
209
  * @throws {FMError} If there was an error retrieving the layouts.
180
210
  */
181
- async listLayouts () {
211
+ async listLayouts() {
182
212
  const req = await this._apiRequestJSON<{ layouts: ApiLayout[] }>(`${this.endpoint}/layouts?page=2`)
183
213
  if (!req.response) throw new FMError(req.messages[0].code, req.httpStatus, req.messages[0].message)
184
214
 
@@ -195,49 +225,11 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
195
225
 
196
226
  layout<R extends keyof T['layouts']>(name: R): Layout<T['layouts'][R]>
197
227
  layout<R extends LayoutInterface>(name: string): Layout<R>
198
- layout (name: string): Layout<any> {
228
+ layout(name: string): Layout<any> {
199
229
  return new Layout<LayoutInterface>(this, name)
200
230
  }
201
231
 
202
- script (name: string, parameter = ''): Script {
203
- return ({ name, parameter } satisfies Script)
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
- })
232
+ script(name: string, parameter = ''): Script {
233
+ return ({name, parameter} satisfies Script)
242
234
  }
243
235
  }
@@ -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 IncomingMessage } from 'http';
3
+ import { RequestInfo, RequestInit, 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: string | Request, options?: RequestInit & {
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: string | Request, options?: RequestInit & {
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 IncomingMessage} from 'http'
7
+ import {RequestInfo, RequestInit, 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: string | Request, options?: RequestInit & { headers?: Record<string, string> } | undefined, autoRelogin?: boolean) => Promise<ApiResults<T>>
18
- _apiRequestRaw: (url: string | Request, options?: RequestInit & { headers?: Record<string, string> } | undefined) => Promise<Response>
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
  }
@@ -1,7 +1,5 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node" resolution-mode="require"/>
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} buffer - The file content as a 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(buffer: Buffer, filename: string, mime: string): Promise<void>;
53
- /**
54
- * Downloads a resource from the server using the specified download mode.
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
  }
@@ -1,60 +1,100 @@
1
1
  /*
2
2
  * Copyright (c) 2023-2024. See LICENSE file for more information
3
3
  */
4
- import { DownloadModes, RecordTypes } from '../types.js';
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
- export class Field {
12
- parent;
13
- id;
14
- _value;
15
- edited;
16
- constructor(record, id, contents) {
17
- this.parent = record;
18
- this.id = id;
19
- this._value = contents;
20
- this.edited = false;
21
- }
22
- /**
23
- * Sets the value of the field.
24
- *
25
- * @param {T | null} content - The new content value to be set. Can be either the type T or null.
26
- * @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
27
- */
28
- set(content) {
29
- if (this.metadata.result === 'container')
30
- throw new Error('Cannot set container value using set(). Use upload() instead.');
31
- else
32
- this._value = content;
33
- this.edited = true;
34
- }
35
- get metadata() {
36
- if (!this.parent.layout.metadata) {
37
- // Default to a regular text field
38
- return {
39
- name: this.id.toString(),
40
- type: ApiFieldTypes.NORMAL,
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
+ let 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
- if (this.parent.type === RecordTypes.PORTAL && this.parent.portal) {
56
- return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || 'portal not attached'].find(i => i.name === this.id) ??
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,157 @@ 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
- else {
75
- return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
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
- throw new Error('Field value is not a string');
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
- const form = new FormData();
123
- form.append('upload', new File([buffer], filename, { type: mime }));
124
- const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
125
- method: 'POST',
126
- headers: {
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
- const body = [];
143
- return await new Promise((resolve, reject) => {
144
- stream.on('data', (chunk) => {
145
- body.push(chunk);
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
+ console.log(`ENDPOINT: ${this.parent.endpoint}/containers/${this.id}/1`);
182
+ const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
183
+ method: 'POST',
184
+ headers: {
185
+ // Authorization: 'Bearer ' + this.parent.layout.database.token,
186
+ // 'Content-Type': 'multipart/form-data'
187
+ },
188
+ body: form
146
189
  });
147
- stream.on('error', (e) => {
148
- reject(e);
149
- });
150
- stream.on('end', () => {
151
- resolve({
152
- buffer: Buffer.concat(body),
153
- mime: stream.headers['content-type'],
154
- request: stream
190
+ if (!res.ok) {
191
+ console.log("RESULT:", await res.text());
192
+ throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`);
193
+ }
194
+ const data = await res.json();
195
+ if (data.messages[0].code === "0")
196
+ return;
197
+ else {
198
+ console.log(data);
199
+ throw new FMError(data.messages[0].code, res.status, res);
200
+ }
201
+ }
202
+ get #streamAsync() { return _private_streamAsync_descriptor.value; }
203
+ stream() {
204
+ return this.#streamAsync();
205
+ }
206
+ async arrayBuffer() {
207
+ console.log(this.string);
208
+ const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true });
209
+ if (!req.ok)
210
+ throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
211
+ console.log(req);
212
+ return await req.arrayBuffer();
213
+ }
214
+ };
215
+ })();
216
+ export { Field };
217
+ function containerDownloadFunction(originalMethod, context) {
218
+ async function replacementMethod(...args) {
219
+ return ContainerDownloadExecutor.processTask(() => originalMethod.call(this, ...args));
220
+ }
221
+ return replacementMethod;
222
+ }
223
+ class ContainerDownloadExecutorClass {
224
+ hasDownloadedFirstContainer = 0;
225
+ waitingFuncs = [];
226
+ constructor() { }
227
+ async processTask(task) {
228
+ let isFirstTask = this.hasDownloadedFirstContainer === 0;
229
+ if (isFirstTask) {
230
+ this.hasDownloadedFirstContainer = 1;
231
+ let res;
232
+ try {
233
+ res = await task();
234
+ }
235
+ catch (e) {
236
+ this.hasDownloadedFirstContainer = 0;
237
+ let nextTask = this.waitingFuncs.splice(0, 1)[0];
238
+ if (nextTask) {
239
+ this.processTask(nextTask.func)
240
+ .then(res => nextTask.done(res))
241
+ .catch(e => nextTask.err(e));
242
+ }
243
+ throw e;
244
+ }
245
+ this.hasDownloadedFirstContainer = 2;
246
+ for (let task of this.waitingFuncs) {
247
+ task.func()
248
+ .then((res) => task.done(res))
249
+ .catch((e) => task.err(e));
250
+ }
251
+ return res;
252
+ }
253
+ else if (this.hasDownloadedFirstContainer === 1) {
254
+ return new Promise((resolve, reject) => {
255
+ this.waitingFuncs.push({
256
+ func: task,
257
+ done: resolve,
258
+ err: reject
155
259
  });
156
260
  });
157
- });
261
+ }
262
+ else
263
+ return await task();
158
264
  }
159
265
  }
266
+ const ContainerDownloadExecutor = new ContainerDownloadExecutorClass();
@@ -2,11 +2,11 @@
2
2
  * Copyright (c) 2023-2024. See LICENSE file for more information
3
3
  */
4
4
 
5
- import type * as http from 'http'
6
- import {type ContainerBufferResult, DownloadModes, RecordTypes} from '../types.js'
5
+ import {RecordTypes} from '../types.js'
7
6
  import {ApiFieldDisplayTypes, type ApiFieldMetadata, ApiFieldResultTypes, ApiFieldTypes} from '../models/apiResults.js'
8
7
  import {type LayoutBase} from '../layouts/layoutBase.js'
9
8
  import {type Moment} from 'moment'
9
+ import {FMError} from "../FMError.js";
10
10
 
11
11
  export type FieldValue = string | number | Moment | Container
12
12
  export type Container = null
@@ -29,7 +29,9 @@ export class Field<T extends FieldValue> {
29
29
  protected _value: T | null
30
30
  edited: boolean
31
31
 
32
- constructor (record: Parentable, id: string, contents: T) {
32
+ static firstContainerDownload: Promise<any> | null = null
33
+
34
+ constructor(record: Parentable, id: string, contents: T) {
33
35
  this.parent = record
34
36
  this.id = id
35
37
  this._value = contents
@@ -42,13 +44,13 @@ export class Field<T extends FieldValue> {
42
44
  * @param {T | null} content - The new content value to be set. Can be either the type T or null.
43
45
  * @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
44
46
  */
45
- set (content: T | null) {
47
+ set(content: T | null) {
46
48
  if (this.metadata.result === 'container') throw new Error('Cannot set container value using set(). Use upload() instead.')
47
49
  else this._value = content
48
50
  this.edited = true
49
51
  }
50
52
 
51
- get metadata (): ApiFieldMetadata {
53
+ get metadata(): ApiFieldMetadata {
52
54
  if (!this.parent.layout.metadata) {
53
55
  // Default to a regular text field
54
56
  return {
@@ -86,7 +88,8 @@ export class Field<T extends FieldValue> {
86
88
  repetitions: 1,
87
89
  valueList: ''
88
90
  }
89
- } else {
91
+ }
92
+ else {
90
93
  return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
91
94
  name: this.id.toString(),
92
95
  type: ApiFieldTypes.NORMAL,
@@ -106,16 +109,16 @@ export class Field<T extends FieldValue> {
106
109
  }
107
110
  }
108
111
 
109
- get value (): T | null {
112
+ get value(): T | null {
110
113
  // if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
111
114
  return this._value
112
115
  }
113
116
 
114
- set value (value: T) {
117
+ set value(value: T) {
115
118
  this._value = value
116
119
  }
117
120
 
118
- get string (): string {
121
+ get string(): string {
119
122
  if (typeof this._value === 'string') {
120
123
  return this._value
121
124
  }
@@ -125,64 +128,118 @@ export class Field<T extends FieldValue> {
125
128
  /**
126
129
  * Uploads a file to the container field.
127
130
  *
128
- * @param {Buffer} buffer - The file content as a buffer.
129
- * @param {string} filename - The filename of the file.
130
- * @param {string} mime - The MIME type of the file.
131
+ * @param {Buffer} file - The file content as a buffer.
131
132
  *
132
133
  * @throws {Error} - Cannot upload a file to the field if it's not a container field.
133
134
  * @throws {Error} - Upload failed with HTTP error.
134
135
  *
135
136
  * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
136
137
  */
137
- async upload (buffer: Buffer, filename: string, mime: string): Promise<void> {
138
+ async upload(file: File): Promise<void> {
138
139
  if (this.metadata.result !== 'container') {
139
140
  throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)')
140
141
  }
141
142
  const form = new FormData()
142
- form.append('upload', new File([buffer], filename, {type: mime}))
143
+ form.append('upload', file)
143
144
 
145
+ console.log(`ENDPOINT: ${this.parent.endpoint}/containers/${this.id}/1`)
144
146
  const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
145
147
  method: 'POST',
146
148
  headers: {
147
- Authorization: 'Bearer ' + this.parent.layout.database.token,
148
- 'Content-Type': 'multipart/form-data'
149
+ // Authorization: 'Bearer ' + this.parent.layout.database.token,
150
+ // 'Content-Type': 'multipart/form-data'
149
151
  },
150
152
  body: form
151
153
  })
152
- if (!res.ok) throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`)
154
+ if (!res.ok) {
155
+ console.log("RESULT:", await res.text())
156
+ throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`)
157
+ }
158
+ const data: { messages: Array<{ code: string }> } = await res.json() as any
159
+ if (data.messages[0].code === "0") return
160
+ else {
161
+ console.log(data)
162
+ throw new FMError(data.messages[0].code, res.status, res)
163
+ }
153
164
  }
154
165
 
155
- /**
156
- * Downloads a resource from the server using the specified download mode.
157
- *
158
- * @param {DownloadModes} mode - The mode to use for downloading the resource.
159
- * @return {Promise<http.IncomingMessage>} - A promise that resolves with the incoming message representing the downloaded resource.
160
- */
161
- download (mode: DownloadModes.Stream): Promise<http.IncomingMessage>
162
- download (mode: DownloadModes.Buffer): Promise<ContainerBufferResult>
163
- async download (mode: DownloadModes = DownloadModes.Stream): Promise<http.IncomingMessage | ContainerBufferResult> {
164
- if (this.metadata.result !== ApiFieldResultTypes.CONTAINER) throw new Error('Cannot perform download() on a non-container field')
165
-
166
- const stream = await this.parent.layout.database.streamURL(this.string)
167
- if (mode === DownloadModes.Stream) {
168
- return stream
166
+ @containerDownloadFunction
167
+ async #streamAsync() {
168
+ let req = await this.parent.layout.database._apiRequestRaw(this.string, {useCookieJar: true})
169
+ if (!req.ok || !req.body) {
170
+ throw new Error(`HTTP Error: ${req.status} (${req.statusText})`)
169
171
  }
172
+ return req.body
173
+ }
170
174
 
171
- const body: Uint8Array[] = []
172
- return await new Promise((resolve, reject) => {
173
- stream.on('data', (chunk: Uint8Array) => {
174
- body.push(chunk)
175
- })
176
- stream.on('error', (e) => {
177
- reject(e)
178
- })
179
- stream.on('end', () => {
180
- resolve({
181
- buffer: Buffer.concat(body),
182
- mime: stream.headers['content-type'],
183
- request: stream
184
- } satisfies ContainerBufferResult)
175
+ stream() {
176
+ return this.#streamAsync()
177
+ }
178
+
179
+ @containerDownloadFunction
180
+ async arrayBuffer() {
181
+ console.log(this.string)
182
+ const req = await this.parent.layout.database._apiRequestRaw(this.string, {useCookieJar: true})
183
+ if (!req.ok) throw new Error(`HTTP Error: ${req.status} (${req.statusText})`)
184
+ console.log(req)
185
+ return await req.arrayBuffer()
186
+ }
187
+ }
188
+
189
+ function containerDownloadFunction<T extends FieldValue, R = unknown>(originalMethod: (...p: any[]) => Promise<R>, context: ClassMethodDecoratorContext<Field<T>>) {
190
+ async function replacementMethod(this: Field<T>, ...args: any[]) {
191
+ return ContainerDownloadExecutor.processTask(() => originalMethod.call(this, ...args))
192
+ }
193
+
194
+ return replacementMethod
195
+ }
196
+
197
+ class ContainerDownloadExecutorClass {
198
+ hasDownloadedFirstContainer: 0 | 1 | 2 = 0
199
+ waitingFuncs: {
200
+ func: (() => any),
201
+ done: ((...args: any[]) => any),
202
+ err: ((e: Error) => any)
203
+ }[] = []
204
+
205
+ constructor() {}
206
+
207
+ async processTask<T extends () => Promise<any>>(task: T) {
208
+ let isFirstTask = this.hasDownloadedFirstContainer === 0
209
+ if (isFirstTask) {
210
+ this.hasDownloadedFirstContainer = 1
211
+ let res
212
+ try {
213
+ res = await task()
214
+ } catch(e) {
215
+ this.hasDownloadedFirstContainer = 0
216
+ let nextTask = this.waitingFuncs.splice(0, 1)[0]
217
+ if (nextTask) {
218
+ this.processTask(nextTask.func)
219
+ .then(res => nextTask.done(res))
220
+ .catch(e => nextTask.err(e))
221
+ }
222
+ throw e
223
+ }
224
+ this.hasDownloadedFirstContainer = 2
225
+ for (let task of this.waitingFuncs) {
226
+ task.func()
227
+ .then((res: any) => task.done(res))
228
+ .catch((e: any) => task.err(e))
229
+ }
230
+ return res
231
+ }
232
+ else if (this.hasDownloadedFirstContainer === 1) {
233
+ return new Promise((resolve, reject) => {
234
+ this.waitingFuncs.push({
235
+ func: task,
236
+ done: resolve,
237
+ err: reject
238
+ })
185
239
  })
186
- })
240
+ }
241
+ else return await task()
187
242
  }
188
243
  }
244
+
245
+ const ContainerDownloadExecutor = new ContainerDownloadExecutorClass()
@@ -135,6 +135,7 @@ export class RecordGetOperation {
135
135
  ? this.layout.database.host.timeFormat
136
136
  : this.layout.database.host.timeStampFormat);
137
137
  }).join('');
138
+ console.log(this.layout.database.host.timeStampFormat, out[key]);
138
139
  }
139
140
  return out;
140
141
  }
@@ -165,6 +165,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
165
165
  : this.layout.database.host.timeStampFormat
166
166
  )
167
167
  }).join('')
168
+ console.log(this.layout.database.host.timeStampFormat, out[key])
168
169
  }
169
170
  return out
170
171
  }
@@ -1,4 +1,4 @@
1
- /// <reference types="node" resolution-mode="require"/>
1
+ /// <reference types="node" />
2
2
  import { EventEmitter } from 'events';
3
3
  import { RecordTypes } from '../types.js';
4
4
  import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
package/src/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node" resolution-mode="require"/>
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
3
  import type * as http from 'http';
4
4
  import { type FMError } from './FMError.js';
5
5
  import { type LayoutInterface } from './layouts/layoutInterface.js';
@@ -52,10 +52,6 @@ export interface ScriptRequestData {
52
52
  export interface extraBodyOptions {
53
53
  scripts?: ScriptRequestData;
54
54
  }
55
- export declare enum DownloadModes {
56
- Stream = 0,
57
- Buffer = 1
58
- }
59
55
  export interface Script {
60
56
  name: string;
61
57
  parameter: string;
package/src/types.js CHANGED
@@ -1,11 +1,6 @@
1
1
  /*
2
2
  * Copyright (c) 2022-2024. See LICENSE file for more information
3
3
  */
4
- export var DownloadModes;
5
- (function (DownloadModes) {
6
- DownloadModes[DownloadModes["Stream"] = 0] = "Stream";
7
- DownloadModes[DownloadModes["Buffer"] = 1] = "Buffer";
8
- })(DownloadModes || (DownloadModes = {}));
9
4
  export var RecordTypes;
10
5
  (function (RecordTypes) {
11
6
  RecordTypes[RecordTypes["UNKNOWN"] = 0] = "UNKNOWN";
package/src/types.ts CHANGED
@@ -65,11 +65,6 @@ export interface extraBodyOptions {
65
65
  scripts?: ScriptRequestData
66
66
  }
67
67
 
68
- export enum DownloadModes {
69
- Stream,
70
- Buffer
71
- }
72
-
73
68
  export interface Script {
74
69
  name: string
75
70
  parameter: string