@jd-data-limited/easy-fm 4.1.5 → 4.1.8
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/dist/bin/cli.js +17 -0
- package/dist/bin/generateTypes.js +161 -0
- package/{src → dist}/connection/FMHost.js +3 -2
- package/{src → dist}/connection/database.d.ts +0 -1
- package/{src → dist}/connection/database.js +22 -39
- package/dist/index.d.ts +18 -0
- package/dist/index.js +16 -0
- package/{src → dist}/records/field.d.ts +0 -1
- package/{src → dist}/records/getOperations/recordGetOperation.d.ts +6 -2
- package/{src → dist}/records/getOperations/recordGetOperation.js +9 -5
- package/{src → dist}/records/layoutRecord.d.ts +1 -1
- package/{src → dist}/records/layoutRecord.js +9 -7
- package/{src → dist}/records/recordBase.d.ts +0 -1
- package/{src → dist}/types.d.ts +12 -2
- package/package.json +23 -12
- package/src/FMError.ts +0 -1033
- package/src/bin/cli.js +0 -17
- package/src/bin/cli.ts +0 -22
- package/src/bin/generateTypes.d.ts +0 -1
- package/src/bin/generateTypes.js +0 -161
- package/src/bin/generateTypes.ts +0 -189
- package/src/connection/FMHost.ts +0 -123
- package/src/connection/HostBase.ts +0 -18
- package/src/connection/database.ts +0 -257
- package/src/connection/databaseBase.ts +0 -19
- package/src/connection/generateAuthorizationHeaders.ts +0 -32
- package/src/connection/requestHoldable.d.ts +0 -10
- package/src/connection/requestHoldable.js +0 -44
- package/src/databaseStructure.ts +0 -10
- package/src/index.d.ts +0 -18
- package/src/index.js +0 -18
- package/src/index.ts +0 -47
- package/src/layouts/layout.ts +0 -66
- package/src/layouts/layoutBase.ts +0 -16
- package/src/layouts/layoutInterface.ts +0 -13
- package/src/layouts/layoutRecordManager.ts +0 -66
- package/src/layouts/recordFieldsMap.ts +0 -7
- package/src/models/apiResults.ts +0 -110
- package/src/records/field.ts +0 -235
- package/src/records/getOperations/recordGetOperation.ts +0 -268
- package/src/records/layoutRecord.ts +0 -246
- package/src/records/layoutRecordBase.ts +0 -10
- package/src/records/portal.ts +0 -43
- package/src/records/portalBase.ts +0 -14
- package/src/records/portalRecord.ts +0 -52
- package/src/records/recordBase.ts +0 -91
- package/src/types.ts +0 -150
- package/src/utils/query.ts +0 -59
- /package/{src → dist}/FMError.d.ts +0 -0
- /package/{src → dist}/FMError.js +0 -0
- /package/{src → dist}/bin/cli.d.ts +0 -0
- /package/{src/bin/dist → dist}/bin/generateTypes.d.ts +0 -0
- /package/{src → dist}/connection/FMHost.d.ts +0 -0
- /package/{src → dist}/connection/HostBase.d.ts +0 -0
- /package/{src → dist}/connection/HostBase.js +0 -0
- /package/{src → dist}/connection/databaseBase.d.ts +0 -0
- /package/{src → dist}/connection/databaseBase.js +0 -0
- /package/{src → dist}/connection/generateAuthorizationHeaders.d.ts +0 -0
- /package/{src → dist}/connection/generateAuthorizationHeaders.js +0 -0
- /package/{src → dist}/databaseStructure.d.ts +0 -0
- /package/{src → dist}/databaseStructure.js +0 -0
- /package/{src → dist}/layouts/layout.d.ts +0 -0
- /package/{src → dist}/layouts/layout.js +0 -0
- /package/{src → dist}/layouts/layoutBase.d.ts +0 -0
- /package/{src → dist}/layouts/layoutBase.js +0 -0
- /package/{src → dist}/layouts/layoutInterface.d.ts +0 -0
- /package/{src → dist}/layouts/layoutInterface.js +0 -0
- /package/{src → dist}/layouts/layoutRecordManager.d.ts +0 -0
- /package/{src → dist}/layouts/layoutRecordManager.js +0 -0
- /package/{src → dist}/layouts/recordFieldsMap.d.ts +0 -0
- /package/{src → dist}/layouts/recordFieldsMap.js +0 -0
- /package/{src → dist}/models/apiResults.d.ts +0 -0
- /package/{src → dist}/models/apiResults.js +0 -0
- /package/{src → dist}/records/field.js +0 -0
- /package/{src → dist}/records/layoutRecordBase.d.ts +0 -0
- /package/{src → dist}/records/layoutRecordBase.js +0 -0
- /package/{src → dist}/records/portal.d.ts +0 -0
- /package/{src → dist}/records/portal.js +0 -0
- /package/{src → dist}/records/portalBase.d.ts +0 -0
- /package/{src → dist}/records/portalBase.js +0 -0
- /package/{src → dist}/records/portalRecord.d.ts +0 -0
- /package/{src → dist}/records/portalRecord.js +0 -0
- /package/{src → dist}/records/recordBase.js +0 -0
- /package/{src → dist}/types.js +0 -0
- /package/{src → dist}/utils/query.d.ts +0 -0
- /package/{src → dist}/utils/query.js +0 -0
package/src/models/apiResults.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export enum ApiFieldTypes {
|
|
6
|
-
NORMAL = 'normal',
|
|
7
|
-
CALCULATION = 'calculation',
|
|
8
|
-
SUMMARY = 'summary'
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export enum ApiFieldDisplayTypes {
|
|
12
|
-
EDIT_TEXT = 'editText',
|
|
13
|
-
POPUP_LIST = 'popupList',
|
|
14
|
-
CHECKBOX = 'checkBox',
|
|
15
|
-
RADIO_BUTTONS = 'radioButtons',
|
|
16
|
-
SELECTION_LIST = 'selectionList',
|
|
17
|
-
CALENDAR = 'calendar',
|
|
18
|
-
SECURE_TEXT = 'secureText'
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export enum ApiFieldResultTypes {
|
|
22
|
-
TEXT = 'text',
|
|
23
|
-
NUMBER = 'number',
|
|
24
|
-
DATE = 'date',
|
|
25
|
-
TIME = 'time',
|
|
26
|
-
TIMESTAMP = 'timeStamp',
|
|
27
|
-
CONTAINER = 'container'
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ApiMessage {
|
|
31
|
-
code: string
|
|
32
|
-
message: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ApiResults<T = unknown> {
|
|
36
|
-
response?: null | T & { scriptError?: string }
|
|
37
|
-
messages: ApiMessage[]
|
|
38
|
-
httpStatus: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface ApiLayout {
|
|
42
|
-
name: string
|
|
43
|
-
isFolder?: boolean
|
|
44
|
-
folderLayoutNames?: ApiLayout[]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ApiLayoutMetadata {
|
|
48
|
-
fieldMetaData: ApiFieldMetadata[]
|
|
49
|
-
portalMetaData: Record<string, ApiFieldMetadata[]>
|
|
50
|
-
valueLists: ApiValueList[]
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface ApiFieldMetadata {
|
|
54
|
-
name: string
|
|
55
|
-
type: ApiFieldTypes
|
|
56
|
-
displayType: ApiFieldDisplayTypes
|
|
57
|
-
result: ApiFieldResultTypes
|
|
58
|
-
global: boolean
|
|
59
|
-
autoEnter: boolean
|
|
60
|
-
fourDigitYear: boolean
|
|
61
|
-
maxRepeat: number
|
|
62
|
-
maxCharacters: number
|
|
63
|
-
notEmpty: boolean
|
|
64
|
-
numeric: boolean
|
|
65
|
-
repetitions: number
|
|
66
|
-
timeOfDay: boolean
|
|
67
|
-
valueList: string
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface ApiValueList {
|
|
71
|
-
name: string
|
|
72
|
-
type: string
|
|
73
|
-
values: Array<{ value: string, displayName: string }>
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface ApiScriptResult {
|
|
77
|
-
scriptError: string
|
|
78
|
-
scriptResult?: string
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface ApiRecordResponseObj {
|
|
82
|
-
data: ApiRowDataDef[]
|
|
83
|
-
dataInfo: ApiResultSetObj
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export type ApiFieldData = Record<string, string | number>
|
|
87
|
-
|
|
88
|
-
export type ApiPortalData = Record<string, ApiFieldData[]>
|
|
89
|
-
|
|
90
|
-
export interface ApiResultSetObj {
|
|
91
|
-
database: string
|
|
92
|
-
layout: string
|
|
93
|
-
table: string
|
|
94
|
-
totalRecordCount: number
|
|
95
|
-
foundCount: number
|
|
96
|
-
returnedCount: number
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface ApiRowDataDef {
|
|
100
|
-
fieldData: ApiFieldData
|
|
101
|
-
portalData: ApiPortalData
|
|
102
|
-
modId: string
|
|
103
|
-
recordId: string
|
|
104
|
-
portalDataInfo?: Array<{
|
|
105
|
-
database: string
|
|
106
|
-
table: string
|
|
107
|
-
foundCount: number
|
|
108
|
-
returnedCount: number
|
|
109
|
-
}>
|
|
110
|
-
}
|
package/src/records/field.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
|
-
*/
|
|
4
|
-
|
|
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
|
-
|
|
11
|
-
export type FieldValue = string | number | Moment | Container
|
|
12
|
-
export type Container = null
|
|
13
|
-
|
|
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
|
-
*/
|
|
26
|
-
export class Field<T extends FieldValue> {
|
|
27
|
-
parent: Parentable
|
|
28
|
-
id: string
|
|
29
|
-
protected _value: T | null
|
|
30
|
-
edited: boolean
|
|
31
|
-
|
|
32
|
-
static firstContainerDownload: Promise<any> | null = null
|
|
33
|
-
|
|
34
|
-
constructor (record: Parentable, id: string, contents: T) {
|
|
35
|
-
this.parent = record
|
|
36
|
-
this.id = id
|
|
37
|
-
this._value = contents
|
|
38
|
-
this.edited = false
|
|
39
|
-
}
|
|
40
|
-
|
|
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.')
|
|
49
|
-
else this._value = content
|
|
50
|
-
this.edited = true
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
get metadata (): ApiFieldMetadata {
|
|
54
|
-
if (!this.parent.layout.metadata) {
|
|
55
|
-
// Default to a regular text field
|
|
56
|
-
return {
|
|
57
|
-
name: this.id.toString(),
|
|
58
|
-
type: ApiFieldTypes.NORMAL,
|
|
59
|
-
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
60
|
-
result: ApiFieldResultTypes.TEXT,
|
|
61
|
-
global: false,
|
|
62
|
-
autoEnter: true,
|
|
63
|
-
fourDigitYear: false,
|
|
64
|
-
maxRepeat: 1,
|
|
65
|
-
maxCharacters: 0,
|
|
66
|
-
notEmpty: false,
|
|
67
|
-
numeric: false,
|
|
68
|
-
timeOfDay: false,
|
|
69
|
-
repetitions: 1,
|
|
70
|
-
valueList: ''
|
|
71
|
-
}
|
|
72
|
-
}
|
|
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
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
|
|
93
|
-
name: this.id.toString(),
|
|
94
|
-
type: ApiFieldTypes.NORMAL,
|
|
95
|
-
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
96
|
-
result: ApiFieldResultTypes.TEXT,
|
|
97
|
-
global: false,
|
|
98
|
-
autoEnter: true,
|
|
99
|
-
fourDigitYear: false,
|
|
100
|
-
maxRepeat: 1,
|
|
101
|
-
maxCharacters: 0,
|
|
102
|
-
notEmpty: false,
|
|
103
|
-
numeric: false,
|
|
104
|
-
timeOfDay: false,
|
|
105
|
-
repetitions: 1,
|
|
106
|
-
valueList: ''
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
get value (): T | null {
|
|
112
|
-
// if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
|
|
113
|
-
return this._value
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
set value (value: T) {
|
|
117
|
-
this._value = value
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
get string (): string {
|
|
121
|
-
if (typeof this._value === 'string') {
|
|
122
|
-
return this._value
|
|
123
|
-
}
|
|
124
|
-
throw new Error('Field value is not a string')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Uploads a file to the container field.
|
|
129
|
-
*
|
|
130
|
-
* @param {Buffer} file - The file content as a buffer.
|
|
131
|
-
*
|
|
132
|
-
* @throws {Error} - Cannot upload a file to the field if it's not a container field.
|
|
133
|
-
* @throws {Error} - Upload failed with HTTP error.
|
|
134
|
-
*
|
|
135
|
-
* @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
|
|
136
|
-
*/
|
|
137
|
-
async upload (file: File): Promise<void> {
|
|
138
|
-
if (this.metadata.result !== 'container') {
|
|
139
|
-
throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)')
|
|
140
|
-
}
|
|
141
|
-
const form = new FormData()
|
|
142
|
-
form.append('upload', file)
|
|
143
|
-
|
|
144
|
-
const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
|
|
145
|
-
method: 'POST',
|
|
146
|
-
headers: {
|
|
147
|
-
// Authorization: 'Bearer ' + this.parent.layout.database.token,
|
|
148
|
-
// 'Content-Type': 'multipart/form-data'
|
|
149
|
-
},
|
|
150
|
-
body: form
|
|
151
|
-
})
|
|
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
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
@containerDownloadFunction
|
|
163
|
-
async #streamAsync (): Promise<{ data: NodeJS.ReadableStream, mime: string }> {
|
|
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})`)
|
|
167
|
-
}
|
|
168
|
-
return {data: req.body, mime: req.headers.get('Content-Type') ?? ''}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async stream () {
|
|
172
|
-
return await this.#streamAsync()
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
@containerDownloadFunction
|
|
176
|
-
async arrayBuffer (): Promise<{ data: ArrayBuffer, mime: string }> {
|
|
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 {data: await req.arrayBuffer(), mime: req.headers.get('Content-Type') ?? ''}
|
|
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
|
-
})
|
|
230
|
-
})
|
|
231
|
-
} else return await task()
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const ContainerDownloadExecutor = new ContainerDownloadExecutorClass()
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {type LayoutInterface} from '../../layouts/layoutInterface.js'
|
|
6
|
-
import {type PickPortals, type ScriptRequestData} from '../../types.js'
|
|
7
|
-
import {type LayoutBase} from '../../layouts/layoutBase.js'
|
|
8
|
-
import {LayoutRecord} from '../layoutRecord.js'
|
|
9
|
-
import {type ApiRecordResponseObj} from '../../models/apiResults.js'
|
|
10
|
-
import {FMError} from '../../FMError.js'
|
|
11
|
-
import {FindRequestSymbol, type Query} from '../../utils/query.js'
|
|
12
|
-
|
|
13
|
-
export type SortOrder = 'ascend' | 'descend'
|
|
14
|
-
export type FindRequestRaw = Record<string, string>
|
|
15
|
-
export type FindRequest = Record<string, Query>
|
|
16
|
-
|
|
17
|
-
export interface PortalRequest {
|
|
18
|
-
name: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type PortalData<T extends LayoutInterface> = {
|
|
22
|
-
[key in keyof T['portals']]: {
|
|
23
|
-
limit: number
|
|
24
|
-
offset: number
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export interface GetOperationOptions<T extends LayoutInterface> {
|
|
28
|
-
portals: Partial<PortalData<T>>
|
|
29
|
-
limit?: number
|
|
30
|
-
offset?: number
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOperationOptions<T>> {
|
|
34
|
-
protected layout: LayoutBase
|
|
35
|
-
protected limit: number = 100
|
|
36
|
-
protected scriptData: ScriptRequestData = {}
|
|
37
|
-
protected sortData: Array<{ fieldName: string, sortOrder: SortOrder }> = []
|
|
38
|
-
protected portals: Partial<PortalData<T>>
|
|
39
|
-
protected offset: number = 1
|
|
40
|
-
protected queries: Array<{ req: FindRequestRaw, omit: boolean }> = []
|
|
41
|
-
|
|
42
|
-
constructor (layout: LayoutBase, options: OPTIONS) {
|
|
43
|
-
this.layout = layout
|
|
44
|
-
this.sortData = []
|
|
45
|
-
this.portals = options.portals
|
|
46
|
-
this.offset = options.offset ?? 1 // Offset refers to the starting record. offset 1 is the same as no offset.
|
|
47
|
-
this.limit = options.limit ?? 100
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get isFindRequest () {
|
|
51
|
-
return this.queries.length !== 0
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private formatQueries () {
|
|
55
|
-
const test = this.queries.map(query => {
|
|
56
|
-
const out: any = {}
|
|
57
|
-
for (const key of Object.keys(query.req)) {
|
|
58
|
-
if (query.req[key]) out[key] = query.req[key]
|
|
59
|
-
else {
|
|
60
|
-
out[key] = query.req[key]
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (query.omit) out.omit = 'true'
|
|
64
|
-
return out
|
|
65
|
-
})
|
|
66
|
-
return test
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
protected generateParamsBody (offset: number, limit: number) {
|
|
70
|
-
const params: Record<string, any> = {
|
|
71
|
-
limit: limit.toString(),
|
|
72
|
-
offset: offset.toString(),
|
|
73
|
-
dateformats: 2 // Ensure dates are received in ISO8601 format
|
|
74
|
-
}
|
|
75
|
-
if (this.sortData.length !== 0) params.sort = this.sortData
|
|
76
|
-
|
|
77
|
-
if (this.scriptData.after) params.script = this.scriptData.after.name
|
|
78
|
-
if (this.scriptData.after?.parameter) params['script.param'] = this.scriptData.after.parameter
|
|
79
|
-
|
|
80
|
-
if (this.scriptData.presort) params['script.presort'] = this.scriptData.presort.name
|
|
81
|
-
if (this.scriptData.presort?.parameter) params['script.presort.param'] = this.scriptData.presort.parameter
|
|
82
|
-
|
|
83
|
-
if (this.scriptData.prerequest) params['script.prerequest'] = this.scriptData.prerequest.name
|
|
84
|
-
if (this.scriptData.prerequest?.parameter) params['script.prerequest.param'] = this.scriptData.prerequest.parameter
|
|
85
|
-
|
|
86
|
-
if (this.queries.length !== 0) params.query = this.formatQueries()
|
|
87
|
-
|
|
88
|
-
const portals: Array<keyof typeof this.portals> = Object.keys(this.portals)
|
|
89
|
-
params.portal = portals
|
|
90
|
-
for (const portal of portals) {
|
|
91
|
-
params[`offset.${portal.toString()}`] = this.portals[portal]?.offset
|
|
92
|
-
params[`limit.${portal.toString()}`] = this.portals[portal]?.limit
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return params
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
protected generateParamsURL (offset: number, limit: number) {
|
|
99
|
-
const params = new URLSearchParams({
|
|
100
|
-
_limit: limit.toString(),
|
|
101
|
-
_offset: offset.toString(),
|
|
102
|
-
dateformats: '2' // Ensure dates are received in ISO8601 format
|
|
103
|
-
})
|
|
104
|
-
if (this.sortData.length !== 0) params.set('_sort', JSON.stringify(this.sortData))
|
|
105
|
-
|
|
106
|
-
if (this.scriptData.after) params.set('script', this.scriptData.after.name)
|
|
107
|
-
if (this.scriptData.after?.parameter) params.set('script.param', this.scriptData.after.parameter)
|
|
108
|
-
|
|
109
|
-
if (this.scriptData.presort) params.set('script.presort', this.scriptData.presort.name)
|
|
110
|
-
if (this.scriptData.presort?.parameter) params.set('script.presort.param', this.scriptData.presort.parameter)
|
|
111
|
-
|
|
112
|
-
if (this.scriptData.prerequest) params.set('script.prerequest', this.scriptData.prerequest.name)
|
|
113
|
-
if (this.scriptData.prerequest?.parameter) params.set('script.prerequest.param', this.scriptData.prerequest.parameter)
|
|
114
|
-
|
|
115
|
-
const portals: Array<keyof typeof this.portals> = Object.keys(this.portals)
|
|
116
|
-
for (const portal of portals) {
|
|
117
|
-
params.set(`_offset.${portal.toString()}`, (this.portals[portal]?.limit ?? '').toString())
|
|
118
|
-
params.set(`_offset.${portal.toString()}`, (this.portals[portal]?.offset ?? '').toString())
|
|
119
|
-
}
|
|
120
|
-
params.set('portal', JSON.stringify(portals))
|
|
121
|
-
|
|
122
|
-
return params
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Configures any FileMaker scripts to be run as a part of the request
|
|
127
|
-
*
|
|
128
|
-
* @param {ScriptRequestData} scripts - The script request data to set.
|
|
129
|
-
* @return {this} - The current instance of the class.
|
|
130
|
-
*/
|
|
131
|
-
scripts (scripts: ScriptRequestData) {
|
|
132
|
-
this.scriptData = scripts
|
|
133
|
-
return this
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Sorts the data based on the given field name and sort order.
|
|
138
|
-
*
|
|
139
|
-
* @param {string} fieldName - The name of the field by which the data should be sorted.
|
|
140
|
-
* @param {SortOrder} sortOrder - The sort order to be applied (either "asc" for ascending or "desc" for descending).
|
|
141
|
-
*
|
|
142
|
-
* @return {this} - Returns the current instance of the object.
|
|
143
|
-
*/
|
|
144
|
-
sort (fieldName: string, sortOrder: SortOrder) {
|
|
145
|
-
this.sortData.push({fieldName, sortOrder})
|
|
146
|
-
return this
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private parseFindRequest<I extends FindRequest>(query: I): { [key in keyof I]: string } {
|
|
150
|
-
const out: any = {}
|
|
151
|
-
for (const key of Object.keys(query)) {
|
|
152
|
-
out[key] = query[key][FindRequestSymbol].map(item => {
|
|
153
|
-
if (typeof item === 'string') return item
|
|
154
|
-
|
|
155
|
-
// Re-write date into correct format
|
|
156
|
-
return item
|
|
157
|
-
.moment
|
|
158
|
-
.clone()
|
|
159
|
-
.utcOffset(this.layout.database.host.timezoneOffsetFunc(item.moment))
|
|
160
|
-
.format(
|
|
161
|
-
item.type === 'date'
|
|
162
|
-
? this.layout.database.host.dateFormat
|
|
163
|
-
: item.type == 'time'
|
|
164
|
-
? this.layout.database.host.timeFormat
|
|
165
|
-
: this.layout.database.host.timeStampFormat
|
|
166
|
-
)
|
|
167
|
-
}).join('')
|
|
168
|
-
}
|
|
169
|
-
return out
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Adds a new request/query to the list of queries.
|
|
174
|
-
*
|
|
175
|
-
* @param {FindRequest} query - The find request to be added.
|
|
176
|
-
* @param {boolean} [omit=false] - Flag to indicate if the find request should be omitted.
|
|
177
|
-
* @return {Object} - The current object instance.
|
|
178
|
-
*/
|
|
179
|
-
addRequest (query: FindRequest, omit = false) {
|
|
180
|
-
this.queries.push({req: this.parseFindRequest(query), omit})
|
|
181
|
-
return this
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Perform a fetch operation.
|
|
186
|
-
*
|
|
187
|
-
* @returns {Promise} A promise that resolves with the result of the fetch operation.
|
|
188
|
-
*/
|
|
189
|
-
async fetch () {
|
|
190
|
-
return await this.performFind(this.offset, this.limit)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private async performFind (offset: number, limit: number): Promise<Array<LayoutRecord<
|
|
194
|
-
PickPortals<T, keyof OPTIONS['portals']>
|
|
195
|
-
>>> {
|
|
196
|
-
const trace = new Error()
|
|
197
|
-
await this.layout.getLayoutMeta()
|
|
198
|
-
|
|
199
|
-
const isFind = this.isFindRequest
|
|
200
|
-
let endpoint = this.layout.endpoint + (isFind ? '/_find' : '/records')
|
|
201
|
-
if (!isFind) endpoint += '?' + new URLSearchParams(this.generateParamsURL(offset, limit)).toString()
|
|
202
|
-
const reqData = {
|
|
203
|
-
// port: 443,
|
|
204
|
-
method: isFind ? 'POST' : 'GET',
|
|
205
|
-
body: isFind ? JSON.stringify(this.generateParamsBody(offset, limit)) : undefined
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const res = await this.layout.database._apiRequestJSON<ApiRecordResponseObj>(
|
|
210
|
-
endpoint,
|
|
211
|
-
reqData
|
|
212
|
-
)
|
|
213
|
-
if (res.messages[0].code === '0' && res.response) {
|
|
214
|
-
// console.log("RESOLVING")
|
|
215
|
-
if (!this.layout.metadata) await this.layout.getLayoutMeta()
|
|
216
|
-
return res.response.data.map(item => {
|
|
217
|
-
return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
|
|
218
|
-
})
|
|
219
|
-
} else {
|
|
220
|
-
throw new FMError(res.messages[0].code, res.httpStatus, res, trace)
|
|
221
|
-
}
|
|
222
|
-
} catch (e) {
|
|
223
|
-
if (e instanceof FMError) {
|
|
224
|
-
if (e.code === 401) {
|
|
225
|
-
// No records found, so return empty set
|
|
226
|
-
return []
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
throw e
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
[Symbol.asyncIterator] () {
|
|
234
|
-
let nextOffset = this.offset
|
|
235
|
-
const startOffset: number = JSON.parse(JSON.stringify(this.offset))
|
|
236
|
-
const limit = this.limit
|
|
237
|
-
|
|
238
|
-
let exitAfterLastRecord = false
|
|
239
|
-
let records: Array<LayoutRecord<PickPortals<T, keyof OPTIONS['portals']>>> = []
|
|
240
|
-
|
|
241
|
-
const fetch = async () => {
|
|
242
|
-
const theoreticalLimit = (limit - nextOffset) + startOffset
|
|
243
|
-
if (theoreticalLimit === 0) {
|
|
244
|
-
exitAfterLastRecord = true
|
|
245
|
-
records = []
|
|
246
|
-
return
|
|
247
|
-
}
|
|
248
|
-
records = await this.performFind(nextOffset, theoreticalLimit < 100 ? theoreticalLimit : 100)
|
|
249
|
-
nextOffset += 100
|
|
250
|
-
if (records.length < 100) exitAfterLastRecord = true
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
next: async () => {
|
|
255
|
-
if (records.length === 0 && !exitAfterLastRecord) {
|
|
256
|
-
await fetch()
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (records.length === 0 && exitAfterLastRecord) {
|
|
260
|
-
return {done: true, value: undefined}
|
|
261
|
-
} else {
|
|
262
|
-
const record = records.shift()
|
|
263
|
-
return {done: false, value: record}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|