@jd-data-limited/easy-fm 4.0.16 → 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 +16 -7
- package/src/FMError.d.ts +2 -2
- package/src/FMError.js +507 -508
- package/src/FMError.ts +510 -511
- package/src/connection/FMHost.d.ts +23 -5
- package/src/connection/FMHost.js +42 -26
- package/src/connection/FMHost.ts +63 -49
- package/src/connection/HostBase.d.ts +3 -3
- package/src/connection/HostBase.ts +5 -5
- package/src/connection/database.d.ts +53 -20
- package/src/connection/database.js +107 -90
- package/src/connection/database.ts +133 -121
- package/src/connection/databaseBase.d.ts +11 -7
- package/src/connection/databaseBase.ts +8 -8
- package/src/connection/generateAuthorizationHeaders.d.ts +1 -1
- package/src/connection/generateAuthorizationHeaders.js +10 -10
- package/src/connection/generateAuthorizationHeaders.ts +21 -21
- package/src/databaseStructure.d.ts +2 -4
- package/src/databaseStructure.ts +3 -5
- package/src/index.d.ts +17 -17
- package/src/index.js +13 -13
- package/src/index.ts +24 -24
- package/src/layouts/layout.d.ts +17 -6
- package/src/layouts/layout.js +19 -8
- package/src/layouts/layout.ts +33 -23
- package/src/layouts/layoutBase.d.ts +5 -5
- package/src/layouts/layoutBase.ts +6 -6
- package/src/layouts/layoutInterface.d.ts +3 -5
- package/src/layouts/layoutInterface.ts +4 -6
- package/src/layouts/layoutRecordManager.d.ts +29 -6
- package/src/layouts/layoutRecordManager.js +32 -9
- package/src/layouts/layoutRecordManager.ts +42 -19
- package/src/layouts/recordFieldsMap.d.ts +2 -4
- package/src/layouts/recordFieldsMap.ts +2 -4
- package/src/models/apiResults.d.ts +7 -13
- package/src/models/apiResults.ts +58 -58
- package/src/records/field.d.ts +39 -17
- package/src/records/field.js +248 -131
- package/src/records/field.ts +160 -82
- package/src/records/getOperations/recordGetOperation.d.ts +44 -24
- package/src/records/getOperations/recordGetOperation.js +88 -61
- package/src/records/getOperations/recordGetOperation.ts +128 -108
- package/src/records/layoutRecord.d.ts +28 -14
- package/src/records/layoutRecord.js +69 -60
- package/src/records/layoutRecord.ts +121 -119
- package/src/records/layoutRecordBase.d.ts +2 -2
- package/src/records/layoutRecordBase.ts +3 -3
- package/src/records/portal.d.ts +19 -5
- package/src/records/portal.js +19 -5
- package/src/records/portal.ts +28 -16
- package/src/records/portalBase.d.ts +5 -5
- package/src/records/portalBase.ts +6 -6
- package/src/records/portalRecord.d.ts +16 -8
- package/src/records/portalRecord.js +16 -6
- package/src/records/portalRecord.ts +29 -19
- package/src/records/recordBase.d.ts +18 -8
- package/src/records/recordBase.js +23 -16
- package/src/records/recordBase.ts +41 -37
- package/src/types.d.ts +23 -30
- package/src/types.js +0 -5
- package/src/types.ts +55 -64
- package/src/utils/query.d.ts +7 -7
- package/src/utils/query.js +10 -10
- package/src/utils/query.ts +19 -20
package/src/records/field.ts
CHANGED
|
@@ -2,21 +2,34 @@
|
|
|
2
2
|
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import {RecordTypes} from '../types.js'
|
|
6
|
+
import {ApiFieldDisplayTypes, type ApiFieldMetadata, ApiFieldResultTypes, ApiFieldTypes} from '../models/apiResults.js'
|
|
7
|
+
import {type LayoutBase} from '../layouts/layoutBase.js'
|
|
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
|
|
13
13
|
|
|
14
|
-
export
|
|
14
|
+
export interface Parentable {
|
|
15
|
+
layout: LayoutBase
|
|
16
|
+
type: RecordTypes
|
|
17
|
+
endpoint: string
|
|
18
|
+
portal?: { name: string }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A class representing a field in a record.
|
|
23
|
+
*
|
|
24
|
+
* @template T - The type of the field value.
|
|
25
|
+
*/
|
|
15
26
|
export class Field<T extends FieldValue> {
|
|
16
|
-
parent: Parentable
|
|
17
|
-
id: string
|
|
18
|
-
protected _value: T
|
|
19
|
-
edited: boolean
|
|
27
|
+
parent: Parentable
|
|
28
|
+
id: string
|
|
29
|
+
protected _value: T | null
|
|
30
|
+
edited: boolean
|
|
31
|
+
|
|
32
|
+
static firstContainerDownload: Promise<any> | null = null
|
|
20
33
|
|
|
21
34
|
constructor(record: Parentable, id: string, contents: T) {
|
|
22
35
|
this.parent = record
|
|
@@ -25,11 +38,14 @@ export class Field<T extends FieldValue> {
|
|
|
25
38
|
this.edited = false
|
|
26
39
|
}
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Sets the value of the field.
|
|
43
|
+
*
|
|
44
|
+
* @param {T | null} content - The new content value to be set. Can be either the type T or null.
|
|
45
|
+
* @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
|
|
46
|
+
*/
|
|
47
|
+
set(content: T | null) {
|
|
48
|
+
if (this.metadata.result === 'container') throw new Error('Cannot set container value using set(). Use upload() instead.')
|
|
33
49
|
else this._value = content
|
|
34
50
|
this.edited = true
|
|
35
51
|
}
|
|
@@ -51,30 +67,30 @@ export class Field<T extends FieldValue> {
|
|
|
51
67
|
numeric: false,
|
|
52
68
|
timeOfDay: false,
|
|
53
69
|
repetitions: 1,
|
|
54
|
-
valueList:
|
|
70
|
+
valueList: ''
|
|
55
71
|
}
|
|
56
72
|
}
|
|
57
|
-
if (this.parent.type === RecordTypes.PORTAL) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if (this.parent.type === RecordTypes.PORTAL && this.parent.portal) {
|
|
74
|
+
return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || 'portal not attached'].find(i => i.name === this.id) ??
|
|
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
|
+
}
|
|
75
91
|
}
|
|
76
92
|
else {
|
|
77
|
-
return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id)
|
|
93
|
+
return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
|
|
78
94
|
name: this.id.toString(),
|
|
79
95
|
type: ApiFieldTypes.NORMAL,
|
|
80
96
|
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
@@ -88,12 +104,12 @@ export class Field<T extends FieldValue> {
|
|
|
88
104
|
numeric: false,
|
|
89
105
|
timeOfDay: false,
|
|
90
106
|
repetitions: 1,
|
|
91
|
-
valueList:
|
|
107
|
+
valueList: ''
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
110
|
}
|
|
95
111
|
|
|
96
|
-
get value(): T {
|
|
112
|
+
get value(): T | null {
|
|
97
113
|
// if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
|
|
98
114
|
return this._value
|
|
99
115
|
}
|
|
@@ -103,65 +119,127 @@ export class Field<T extends FieldValue> {
|
|
|
103
119
|
}
|
|
104
120
|
|
|
105
121
|
get string(): string {
|
|
106
|
-
if (typeof this._value ===
|
|
122
|
+
if (typeof this._value === 'string') {
|
|
107
123
|
return this._value
|
|
108
124
|
}
|
|
109
|
-
throw
|
|
125
|
+
throw new Error('Field value is not a string')
|
|
110
126
|
}
|
|
111
127
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Uploads a file to the container field.
|
|
130
|
+
*
|
|
131
|
+
* @param {Buffer} file - The file content as a buffer.
|
|
132
|
+
*
|
|
133
|
+
* @throws {Error} - Cannot upload a file to the field if it's not a container field.
|
|
134
|
+
* @throws {Error} - Upload failed with HTTP error.
|
|
135
|
+
*
|
|
136
|
+
* @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
|
|
137
|
+
*/
|
|
138
|
+
async upload(file: File): Promise<void> {
|
|
139
|
+
if (this.metadata.result !== 'container') {
|
|
140
|
+
throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)')
|
|
141
|
+
}
|
|
142
|
+
const form = new FormData()
|
|
143
|
+
form.append('upload', file)
|
|
144
|
+
|
|
145
|
+
console.log(`ENDPOINT: ${this.parent.endpoint}/containers/${this.id}/1`)
|
|
146
|
+
const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
// Authorization: 'Bearer ' + this.parent.layout.database.token,
|
|
150
|
+
// 'Content-Type': 'multipart/form-data'
|
|
151
|
+
},
|
|
152
|
+
body: form
|
|
153
|
+
})
|
|
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)
|
|
115
163
|
}
|
|
116
|
-
throw "Field value is not a date"
|
|
117
164
|
}
|
|
118
165
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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})`)
|
|
122
171
|
}
|
|
123
|
-
|
|
172
|
+
return req.body
|
|
124
173
|
}
|
|
125
174
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
let form = new FormData()
|
|
130
|
-
form.append("upload", new File([buffer], filename, {type: mime}))
|
|
175
|
+
stream() {
|
|
176
|
+
return this.#streamAsync()
|
|
177
|
+
}
|
|
131
178
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
137
|
-
|
|
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()
|
|
138
186
|
}
|
|
187
|
+
}
|
|
139
188
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
}
|
|
144
193
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
148
231
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
reject(e)
|
|
157
|
-
})
|
|
158
|
-
stream.on("end", () => {
|
|
159
|
-
resolve({
|
|
160
|
-
buffer: Buffer.concat(body),
|
|
161
|
-
mime: stream.headers["content-type"],
|
|
162
|
-
request: stream
|
|
163
|
-
} as ContainerBufferResult)
|
|
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
|
+
})
|
|
164
239
|
})
|
|
165
|
-
}
|
|
240
|
+
}
|
|
241
|
+
else return await task()
|
|
166
242
|
}
|
|
167
|
-
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const ContainerDownloadExecutor = new ContainerDownloadExecutorClass()
|
|
@@ -1,54 +1,74 @@
|
|
|
1
|
-
import { LayoutInterface } from
|
|
2
|
-
import { PickPortals, ScriptRequestData } from
|
|
3
|
-
import { LayoutBase } from
|
|
4
|
-
import { LayoutRecord } from
|
|
5
|
-
import { Query } from
|
|
6
|
-
export type SortOrder =
|
|
7
|
-
export type FindRequestRaw =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export type FindRequest = {
|
|
11
|
-
[key: string]: Query;
|
|
12
|
-
};
|
|
13
|
-
export type PortalRequest = {
|
|
1
|
+
import { type LayoutInterface } from '../../layouts/layoutInterface.js';
|
|
2
|
+
import { type PickPortals, type ScriptRequestData } from '../../types.js';
|
|
3
|
+
import { type LayoutBase } from '../../layouts/layoutBase.js';
|
|
4
|
+
import { LayoutRecord } from '../layoutRecord.js';
|
|
5
|
+
import { type Query } from '../../utils/query.js';
|
|
6
|
+
export type SortOrder = 'ascend' | 'descend';
|
|
7
|
+
export type FindRequestRaw = Record<string, string>;
|
|
8
|
+
export type FindRequest = Record<string, Query>;
|
|
9
|
+
export interface PortalRequest {
|
|
14
10
|
name: string;
|
|
15
|
-
}
|
|
11
|
+
}
|
|
16
12
|
type PortalData<T extends LayoutInterface> = {
|
|
17
|
-
[key in keyof T[
|
|
13
|
+
[key in keyof T['portals']]: {
|
|
18
14
|
limit: number;
|
|
19
15
|
offset: number;
|
|
20
16
|
};
|
|
21
17
|
};
|
|
22
|
-
export
|
|
18
|
+
export interface GetOperationOptions<T extends LayoutInterface> {
|
|
23
19
|
portals: Partial<PortalData<T>>;
|
|
24
20
|
limit?: number;
|
|
25
21
|
offset?: number;
|
|
26
|
-
}
|
|
22
|
+
}
|
|
27
23
|
export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOperationOptions<T>> {
|
|
28
24
|
protected layout: LayoutBase;
|
|
29
25
|
protected limit: number;
|
|
30
26
|
protected scriptData: ScriptRequestData;
|
|
31
|
-
protected sortData: {
|
|
27
|
+
protected sortData: Array<{
|
|
32
28
|
fieldName: string;
|
|
33
29
|
sortOrder: SortOrder;
|
|
34
|
-
}
|
|
30
|
+
}>;
|
|
35
31
|
protected portals: Partial<PortalData<T>>;
|
|
36
32
|
protected offset: number;
|
|
37
|
-
protected queries: {
|
|
33
|
+
protected queries: Array<{
|
|
38
34
|
req: FindRequestRaw;
|
|
39
35
|
omit: boolean;
|
|
40
|
-
}
|
|
36
|
+
}>;
|
|
41
37
|
constructor(layout: LayoutBase, options: OPTIONS);
|
|
42
38
|
get isFindRequest(): boolean;
|
|
43
39
|
private formatQueries;
|
|
44
|
-
protected generateParamsBody(offset: number, limit: number):
|
|
45
|
-
[key: string]: any;
|
|
46
|
-
};
|
|
40
|
+
protected generateParamsBody(offset: number, limit: number): Record<string, any>;
|
|
47
41
|
protected generateParamsURL(offset: number, limit: number): URLSearchParams;
|
|
42
|
+
/**
|
|
43
|
+
* Configures any FileMaker scripts to be run as a part of the request
|
|
44
|
+
*
|
|
45
|
+
* @param {ScriptRequestData} scripts - The script request data to set.
|
|
46
|
+
* @return {this} - The current instance of the class.
|
|
47
|
+
*/
|
|
48
48
|
scripts(scripts: ScriptRequestData): this;
|
|
49
|
+
/**
|
|
50
|
+
* Sorts the data based on the given field name and sort order.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} fieldName - The name of the field by which the data should be sorted.
|
|
53
|
+
* @param {SortOrder} sortOrder - The sort order to be applied (either "asc" for ascending or "desc" for descending).
|
|
54
|
+
*
|
|
55
|
+
* @return {this} - Returns the current instance of the object.
|
|
56
|
+
*/
|
|
49
57
|
sort(fieldName: string, sortOrder: SortOrder): this;
|
|
50
58
|
private parseFindRequest;
|
|
59
|
+
/**
|
|
60
|
+
* Adds a new request/query to the list of queries.
|
|
61
|
+
*
|
|
62
|
+
* @param {FindRequest} query - The find request to be added.
|
|
63
|
+
* @param {boolean} [omit=false] - Flag to indicate if the find request should be omitted.
|
|
64
|
+
* @return {Object} - The current object instance.
|
|
65
|
+
*/
|
|
51
66
|
addRequest(query: FindRequest, omit?: boolean): this;
|
|
67
|
+
/**
|
|
68
|
+
* Perform a fetch operation.
|
|
69
|
+
*
|
|
70
|
+
* @returns {Promise} A promise that resolves with the result of the fetch operation.
|
|
71
|
+
*/
|
|
52
72
|
fetch(): Promise<LayoutRecord<PickPortals<T, keyof OPTIONS["portals"]>>[]>;
|
|
53
73
|
private performFind;
|
|
54
74
|
[Symbol.asyncIterator](): {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
|
-
import { LayoutRecord } from
|
|
5
|
-
import { FMError } from
|
|
6
|
-
import { FindRequestSymbol } from
|
|
4
|
+
import { LayoutRecord } from '../layoutRecord.js';
|
|
5
|
+
import { FMError } from '../../FMError.js';
|
|
6
|
+
import { FindRequestSymbol } from '../../utils/query.js';
|
|
7
7
|
export class RecordGetOperation {
|
|
8
8
|
layout;
|
|
9
9
|
limit = 100;
|
|
@@ -16,16 +16,16 @@ export class RecordGetOperation {
|
|
|
16
16
|
this.layout = layout;
|
|
17
17
|
this.sortData = [];
|
|
18
18
|
this.portals = options.portals;
|
|
19
|
-
this.offset = options.offset
|
|
20
|
-
this.limit = options.limit
|
|
19
|
+
this.offset = options.offset ?? 1; // Offset refers to the starting record. offset 1 is the same as no offset.
|
|
20
|
+
this.limit = options.limit ?? 100;
|
|
21
21
|
}
|
|
22
22
|
get isFindRequest() {
|
|
23
23
|
return this.queries.length !== 0;
|
|
24
24
|
}
|
|
25
25
|
formatQueries() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for (
|
|
26
|
+
const test = this.queries.map(query => {
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const key of Object.keys(query.req)) {
|
|
29
29
|
if (query.req[key])
|
|
30
30
|
out[key] = query.req[key];
|
|
31
31
|
else {
|
|
@@ -33,7 +33,7 @@ export class RecordGetOperation {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
if (query.omit)
|
|
36
|
-
out.omit =
|
|
36
|
+
out.omit = 'true';
|
|
37
37
|
return out;
|
|
38
38
|
});
|
|
39
39
|
return test;
|
|
@@ -47,22 +47,22 @@ export class RecordGetOperation {
|
|
|
47
47
|
if (this.sortData.length !== 0)
|
|
48
48
|
params.sort = this.sortData;
|
|
49
49
|
if (this.scriptData.after)
|
|
50
|
-
params
|
|
50
|
+
params.script = this.scriptData.after.name;
|
|
51
51
|
if (this.scriptData.after?.parameter)
|
|
52
|
-
params[
|
|
52
|
+
params['script.param'] = this.scriptData.after.parameter;
|
|
53
53
|
if (this.scriptData.presort)
|
|
54
|
-
params[
|
|
54
|
+
params['script.presort'] = this.scriptData.presort.name;
|
|
55
55
|
if (this.scriptData.presort?.parameter)
|
|
56
|
-
params[
|
|
56
|
+
params['script.presort.param'] = this.scriptData.presort.parameter;
|
|
57
57
|
if (this.scriptData.prerequest)
|
|
58
|
-
params[
|
|
58
|
+
params['script.prerequest'] = this.scriptData.prerequest.name;
|
|
59
59
|
if (this.scriptData.prerequest?.parameter)
|
|
60
|
-
params[
|
|
60
|
+
params['script.prerequest.param'] = this.scriptData.prerequest.parameter;
|
|
61
61
|
if (this.queries.length !== 0)
|
|
62
|
-
params
|
|
63
|
-
|
|
64
|
-
params
|
|
65
|
-
for (
|
|
62
|
+
params.query = this.formatQueries();
|
|
63
|
+
const portals = Object.keys(this.portals);
|
|
64
|
+
params.portal = portals;
|
|
65
|
+
for (const portal of portals) {
|
|
66
66
|
params[`offset.${portal.toString()}`] = this.portals[portal]?.offset;
|
|
67
67
|
params[`limit.${portal.toString()}`] = this.portals[portal]?.limit;
|
|
68
68
|
}
|
|
@@ -72,80 +72,107 @@ export class RecordGetOperation {
|
|
|
72
72
|
const params = new URLSearchParams({
|
|
73
73
|
_limit: limit.toString(),
|
|
74
74
|
_offset: offset.toString(),
|
|
75
|
-
dateformats:
|
|
75
|
+
dateformats: '2' // Ensure dates are received in ISO8601 format
|
|
76
76
|
});
|
|
77
77
|
if (this.sortData.length !== 0)
|
|
78
|
-
params.set(
|
|
78
|
+
params.set('_sort', JSON.stringify(this.sortData));
|
|
79
79
|
if (this.scriptData.after)
|
|
80
|
-
params.set(
|
|
80
|
+
params.set('script', this.scriptData.after.name);
|
|
81
81
|
if (this.scriptData.after?.parameter)
|
|
82
|
-
params.set(
|
|
82
|
+
params.set('script.param', this.scriptData.after.parameter);
|
|
83
83
|
if (this.scriptData.presort)
|
|
84
|
-
params.set(
|
|
84
|
+
params.set('script.presort', this.scriptData.presort.name);
|
|
85
85
|
if (this.scriptData.presort?.parameter)
|
|
86
|
-
params.set(
|
|
86
|
+
params.set('script.presort.param', this.scriptData.presort.parameter);
|
|
87
87
|
if (this.scriptData.prerequest)
|
|
88
|
-
params.set(
|
|
88
|
+
params.set('script.prerequest', this.scriptData.prerequest.name);
|
|
89
89
|
if (this.scriptData.prerequest?.parameter)
|
|
90
|
-
params.set(
|
|
91
|
-
|
|
92
|
-
for (
|
|
93
|
-
params.set(`_offset.${portal.toString()}`, (this.portals[portal]?.limit
|
|
94
|
-
params.set(`_offset.${portal.toString()}`, (this.portals[portal]?.offset
|
|
90
|
+
params.set('script.prerequest.param', this.scriptData.prerequest.parameter);
|
|
91
|
+
const portals = Object.keys(this.portals);
|
|
92
|
+
for (const portal of portals) {
|
|
93
|
+
params.set(`_offset.${portal.toString()}`, (this.portals[portal]?.limit ?? '').toString());
|
|
94
|
+
params.set(`_offset.${portal.toString()}`, (this.portals[portal]?.offset ?? '').toString());
|
|
95
95
|
}
|
|
96
96
|
params.set('portal', JSON.stringify(portals));
|
|
97
97
|
return params;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Configures any FileMaker scripts to be run as a part of the request
|
|
101
|
+
*
|
|
102
|
+
* @param {ScriptRequestData} scripts - The script request data to set.
|
|
103
|
+
* @return {this} - The current instance of the class.
|
|
104
|
+
*/
|
|
99
105
|
scripts(scripts) {
|
|
100
106
|
this.scriptData = scripts;
|
|
101
107
|
return this;
|
|
102
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Sorts the data based on the given field name and sort order.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} fieldName - The name of the field by which the data should be sorted.
|
|
113
|
+
* @param {SortOrder} sortOrder - The sort order to be applied (either "asc" for ascending or "desc" for descending).
|
|
114
|
+
*
|
|
115
|
+
* @return {this} - Returns the current instance of the object.
|
|
116
|
+
*/
|
|
103
117
|
sort(fieldName, sortOrder) {
|
|
104
118
|
this.sortData.push({ fieldName, sortOrder });
|
|
105
119
|
return this;
|
|
106
120
|
}
|
|
107
121
|
parseFindRequest(query) {
|
|
108
|
-
|
|
109
|
-
for (
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const key of Object.keys(query)) {
|
|
110
124
|
out[key] = query[key][FindRequestSymbol].map(item => {
|
|
111
|
-
if (typeof item ===
|
|
125
|
+
if (typeof item === 'string')
|
|
112
126
|
return item;
|
|
113
127
|
// Re-write date into correct format
|
|
114
128
|
return item
|
|
115
129
|
.moment
|
|
116
130
|
.clone()
|
|
117
131
|
.utcOffset(this.layout.database.host.timezoneOffsetFunc(item.moment))
|
|
118
|
-
.format(item.type ===
|
|
119
|
-
this.layout.database.host.dateFormat
|
|
120
|
-
item.type ==
|
|
121
|
-
this.layout.database.host.timeFormat
|
|
122
|
-
this.layout.database.host.timeStampFormat);
|
|
123
|
-
}).join(
|
|
132
|
+
.format(item.type === 'date'
|
|
133
|
+
? this.layout.database.host.dateFormat
|
|
134
|
+
: item.type == 'time'
|
|
135
|
+
? this.layout.database.host.timeFormat
|
|
136
|
+
: this.layout.database.host.timeStampFormat);
|
|
137
|
+
}).join('');
|
|
138
|
+
console.log(this.layout.database.host.timeStampFormat, out[key]);
|
|
124
139
|
}
|
|
125
140
|
return out;
|
|
126
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Adds a new request/query to the list of queries.
|
|
144
|
+
*
|
|
145
|
+
* @param {FindRequest} query - The find request to be added.
|
|
146
|
+
* @param {boolean} [omit=false] - Flag to indicate if the find request should be omitted.
|
|
147
|
+
* @return {Object} - The current object instance.
|
|
148
|
+
*/
|
|
127
149
|
addRequest(query, omit = false) {
|
|
128
150
|
this.queries.push({ req: this.parseFindRequest(query), omit });
|
|
129
151
|
return this;
|
|
130
152
|
}
|
|
131
|
-
|
|
132
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Perform a fetch operation.
|
|
155
|
+
*
|
|
156
|
+
* @returns {Promise} A promise that resolves with the result of the fetch operation.
|
|
157
|
+
*/
|
|
158
|
+
async fetch() {
|
|
159
|
+
return await this.performFind(this.offset, this.limit);
|
|
133
160
|
}
|
|
134
161
|
async performFind(offset, limit) {
|
|
135
|
-
|
|
162
|
+
const trace = new Error();
|
|
136
163
|
await this.layout.getLayoutMeta();
|
|
137
|
-
const
|
|
138
|
-
let endpoint = this.layout.endpoint + (
|
|
139
|
-
if (!
|
|
140
|
-
endpoint +=
|
|
164
|
+
const isFind = this.isFindRequest;
|
|
165
|
+
let endpoint = this.layout.endpoint + (isFind ? '/_find' : '/records');
|
|
166
|
+
if (!isFind)
|
|
167
|
+
endpoint += '?' + new URLSearchParams(this.generateParamsURL(offset, limit)).toString();
|
|
141
168
|
const reqData = {
|
|
142
169
|
// port: 443,
|
|
143
|
-
method:
|
|
144
|
-
body:
|
|
170
|
+
method: isFind ? 'POST' : 'GET',
|
|
171
|
+
body: isFind ? JSON.stringify(this.generateParamsBody(offset, limit)) : undefined
|
|
145
172
|
};
|
|
146
173
|
try {
|
|
147
|
-
|
|
148
|
-
if (res.messages[0].code ===
|
|
174
|
+
const res = await this.layout.database._apiRequestJSON(endpoint, reqData);
|
|
175
|
+
if (res.messages[0].code === '0' && res.response) {
|
|
149
176
|
// console.log("RESOLVING")
|
|
150
177
|
if (!this.layout.metadata)
|
|
151
178
|
await this.layout.getLayoutMeta();
|
|
@@ -169,28 +196,28 @@ export class RecordGetOperation {
|
|
|
169
196
|
}
|
|
170
197
|
[Symbol.asyncIterator]() {
|
|
171
198
|
let nextOffset = this.offset;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let
|
|
199
|
+
const startOffset = JSON.parse(JSON.stringify(this.offset));
|
|
200
|
+
const limit = this.limit;
|
|
201
|
+
let exitAfterLastRecord = false;
|
|
175
202
|
let records = [];
|
|
176
203
|
const fetch = async () => {
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
|
|
204
|
+
const theoreticalLimit = (limit - nextOffset) + startOffset;
|
|
205
|
+
if (theoreticalLimit === 0) {
|
|
206
|
+
exitAfterLastRecord = true;
|
|
180
207
|
records = [];
|
|
181
208
|
return;
|
|
182
209
|
}
|
|
183
|
-
records = await this.performFind(nextOffset,
|
|
210
|
+
records = await this.performFind(nextOffset, theoreticalLimit < 100 ? theoreticalLimit : 100);
|
|
184
211
|
nextOffset += 100;
|
|
185
212
|
if (records.length < 100)
|
|
186
|
-
|
|
213
|
+
exitAfterLastRecord = true;
|
|
187
214
|
};
|
|
188
215
|
return {
|
|
189
216
|
next: async () => {
|
|
190
|
-
if (records.length === 0 && !
|
|
217
|
+
if (records.length === 0 && !exitAfterLastRecord) {
|
|
191
218
|
await fetch();
|
|
192
219
|
}
|
|
193
|
-
if (records.length === 0 &&
|
|
220
|
+
if (records.length === 0 && exitAfterLastRecord) {
|
|
194
221
|
return { done: true, value: undefined };
|
|
195
222
|
}
|
|
196
223
|
else {
|