@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.
@@ -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,6 +29,8 @@ export class Field<T extends FieldValue> {
29
29
  protected _value: T | null
30
30
  edited: boolean
31
31
 
32
+ static firstContainerDownload: Promise<any> | null = null
33
+
32
34
  constructor (record: Parentable, id: string, contents: T) {
33
35
  this.parent = record
34
36
  this.id = id
@@ -125,64 +127,109 @@ export class Field<T extends FieldValue> {
125
127
  /**
126
128
  * Uploads a file to the container field.
127
129
  *
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.
130
+ * @param {Buffer} file - The file content as a buffer.
131
131
  *
132
132
  * @throws {Error} - Cannot upload a file to the field if it's not a container field.
133
133
  * @throws {Error} - Upload failed with HTTP error.
134
134
  *
135
135
  * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
136
136
  */
137
- async upload (buffer: Buffer, filename: string, mime: string): Promise<void> {
137
+ async upload (file: File): Promise<void> {
138
138
  if (this.metadata.result !== 'container') {
139
139
  throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)')
140
140
  }
141
141
  const form = new FormData()
142
- form.append('upload', new File([buffer], filename, {type: mime}))
142
+ form.append('upload', file)
143
143
 
144
144
  const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
145
145
  method: 'POST',
146
146
  headers: {
147
- Authorization: 'Bearer ' + this.parent.layout.database.token,
148
- 'Content-Type': 'multipart/form-data'
147
+ // Authorization: 'Bearer ' + this.parent.layout.database.token,
148
+ // 'Content-Type': 'multipart/form-data'
149
149
  },
150
150
  body: form
151
151
  })
152
- if (!res.ok) throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`)
152
+ if (!res.ok) {
153
+ throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`)
154
+ }
155
+ const data: { messages: Array<{ code: string }> } = await res.json() as any
156
+ if (data.messages[0].code === '0') return
157
+ else {
158
+ throw new FMError(data.messages[0].code, res.status, res)
159
+ }
153
160
  }
154
161
 
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
162
+ @containerDownloadFunction
163
+ async #streamAsync () {
164
+ const req = await this.parent.layout.database._apiRequestRaw(this.string, {useCookieJar: true})
165
+ if (!req.ok || !req.body) {
166
+ throw new Error(`HTTP Error: ${req.status} (${req.statusText})`)
169
167
  }
168
+ return req.body
169
+ }
170
170
 
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)
171
+ async stream () {
172
+ return await this.#streamAsync()
173
+ }
174
+
175
+ @containerDownloadFunction
176
+ async arrayBuffer () {
177
+ const req = await this.parent.layout.database._apiRequestRaw(this.string, {useCookieJar: true})
178
+ if (!req.ok) throw new Error(`HTTP Error: ${req.status} (${req.statusText})`)
179
+ return await req.arrayBuffer()
180
+ }
181
+ }
182
+
183
+ function containerDownloadFunction<PROPS extends any[], T extends FieldValue, R = unknown> (originalMethod: (...p: PROPS) => Promise<R>, context: ClassMethodDecoratorContext<Field<T>>) {
184
+ async function replacementMethod (this: Field<T>, ...args: PROPS) {
185
+ return await ContainerDownloadExecutor.processTask(async () => await originalMethod.call(this, ...args))
186
+ }
187
+
188
+ return replacementMethod
189
+ }
190
+
191
+ class ContainerDownloadExecutorClass {
192
+ hasDownloadedFirstContainer: 0 | 1 | 2 = 0
193
+ waitingFuncs: Array<{
194
+ func: (() => any)
195
+ done: ((...args: any[]) => any)
196
+ err: ((e: any) => any)
197
+ }> = []
198
+
199
+ async processTask<T extends () => Promise<any>>(task: T) {
200
+ const isFirstTask = this.hasDownloadedFirstContainer === 0
201
+ if (isFirstTask) {
202
+ this.hasDownloadedFirstContainer = 1
203
+ let res
204
+ try {
205
+ res = await task()
206
+ } catch (e) {
207
+ this.hasDownloadedFirstContainer = 0
208
+ const nextTask = this.waitingFuncs.splice(0, 1)[0]
209
+ if (nextTask) {
210
+ this.processTask(nextTask.func)
211
+ .then(res => nextTask.done(res))
212
+ .catch(e => nextTask.err(e))
213
+ }
214
+ throw e
215
+ }
216
+ this.hasDownloadedFirstContainer = 2
217
+ for (const task of this.waitingFuncs) {
218
+ task.func()
219
+ .then((res: any) => task.done(res))
220
+ .catch((e: any) => task.err(e))
221
+ }
222
+ return res
223
+ } else if (this.hasDownloadedFirstContainer === 1) {
224
+ return await new Promise((resolve, reject) => {
225
+ this.waitingFuncs.push({
226
+ func: task,
227
+ done: resolve,
228
+ err: reject
229
+ })
185
230
  })
186
- })
231
+ } else return await task()
187
232
  }
188
233
  }
234
+
235
+ const ContainerDownloadExecutor = new ContainerDownloadExecutorClass()
@@ -153,7 +153,6 @@ export class LayoutRecord extends RecordBase {
153
153
  const res = await this.layout.database._apiRequestJSON(this.endpoint, {
154
154
  method: 'DELETE'
155
155
  });
156
- console.log(res);
157
156
  if (typeof res.response?.scriptError !== 'undefined' && res.response?.scriptError !== '0') {
158
157
  throw new FMError(res.response.scriptError, res.httpStatus, res);
159
158
  }
@@ -177,7 +177,6 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
177
177
  const res = await this.layout.database._apiRequestJSON(this.endpoint, {
178
178
  method: 'DELETE'
179
179
  })
180
- console.log(res)
181
180
  if (typeof res.response?.scriptError !== 'undefined' && res.response?.scriptError !== '0') {
182
181
  throw new FMError(res.response.scriptError, res.httpStatus, res)
183
182
  } else if (res.messages[0].code === '0') {
@@ -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