@jd-data-limited/easy-fm 4.1.15 → 5.0.0-beta.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/README.md +60 -197
- package/dist/bin/generateTypes.js +1 -1
- package/dist/bin/stressSearch.d.ts +2 -0
- package/dist/bin/stressSearch.js +243 -0
- package/dist/connection/CookieJar.d.ts +19 -0
- package/dist/connection/CookieJar.js +99 -0
- package/dist/connection/FMHost.d.ts +24 -8
- package/dist/connection/FMHost.js +28 -8
- package/dist/connection/HostBase.d.ts +4 -2
- package/dist/connection/Session.d.ts +34 -0
- package/dist/connection/Session.js +109 -0
- package/dist/connection/database.d.ts +34 -31
- package/dist/connection/database.js +72 -133
- package/dist/connection/databaseBase.d.ts +22 -10
- package/dist/connection/databaseConstantSession.d.ts +15 -0
- package/dist/connection/databaseConstantSession.js +70 -0
- package/dist/connection/databaseSessionPool.d.ts +14 -0
- package/dist/connection/databaseSessionPool.js +148 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.js +12 -0
- package/dist/layouts/layout.d.ts +5 -3
- package/dist/layouts/layout.js +11 -15
- package/dist/layouts/layoutBase.d.ts +3 -2
- package/dist/layouts/layoutRecordManager.d.ts +6 -15
- package/dist/layouts/layoutRecordManager.js +6 -15
- package/dist/models/apiResults.d.ts +208 -93
- package/dist/models/apiResults.js +95 -25
- package/dist/records/field.d.ts +17 -2
- package/dist/records/field.js +161 -237
- package/dist/records/getOperations/recordGetOperation.d.ts +7 -4
- package/dist/records/getOperations/recordGetOperation.js +15 -18
- package/dist/records/layoutRecord.d.ts +24 -7
- package/dist/records/layoutRecord.js +68 -73
- package/dist/records/portal.d.ts +0 -4
- package/dist/records/portal.js +0 -4
- package/dist/records/recordBase.d.ts +3 -2
- package/dist/types.d.ts +41 -17
- package/dist/types.js +12 -0
- package/dist/utils/addHeaders.d.ts +5 -0
- package/dist/utils/addHeaders.js +24 -0
- package/dist/utils/query.d.ts +7 -0
- package/dist/utils/query.js +5 -0
- package/package.json +29 -26
package/dist/records/field.js
CHANGED
|
@@ -1,145 +1,75 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
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
4
|
import { RecordTypes } from '../types.js';
|
|
43
|
-
import { ApiFieldDisplayTypes, ApiFieldResultTypes, ApiFieldTypes } from '../models/apiResults.js';
|
|
5
|
+
import { ApiFieldDisplayTypes, ApiFieldResultTypes, ApiFieldTypes, ApiResults } from '../models/apiResults.js';
|
|
44
6
|
import { FMError } from '../FMError.js';
|
|
7
|
+
import { Readable } from 'node:stream';
|
|
8
|
+
import { HttpError } from '../connection/Session.js';
|
|
45
9
|
/**
|
|
46
10
|
* A class representing a field in a record.
|
|
47
11
|
*
|
|
48
12
|
* @template T - The type of the field value.
|
|
49
13
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
result: ApiFieldResultTypes.TEXT,
|
|
110
|
-
global: false,
|
|
111
|
-
autoEnter: true,
|
|
112
|
-
fourDigitYear: false,
|
|
113
|
-
maxRepeat: 1,
|
|
114
|
-
maxCharacters: 0,
|
|
115
|
-
notEmpty: false,
|
|
116
|
-
numeric: false,
|
|
117
|
-
timeOfDay: false,
|
|
118
|
-
repetitions: 1,
|
|
119
|
-
valueList: ''
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
if (this.parent.type === RecordTypes.PORTAL && this.parent.portal) {
|
|
123
|
-
return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || 'portal not attached'].find(i => i.name === this.id) ??
|
|
124
|
-
{
|
|
125
|
-
name: this.id.toString(),
|
|
126
|
-
type: ApiFieldTypes.NORMAL,
|
|
127
|
-
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
128
|
-
result: ApiFieldResultTypes.TEXT,
|
|
129
|
-
global: false,
|
|
130
|
-
autoEnter: true,
|
|
131
|
-
fourDigitYear: false,
|
|
132
|
-
maxRepeat: 1,
|
|
133
|
-
maxCharacters: 0,
|
|
134
|
-
notEmpty: false,
|
|
135
|
-
numeric: false,
|
|
136
|
-
timeOfDay: false,
|
|
137
|
-
repetitions: 1,
|
|
138
|
-
valueList: ''
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
|
|
14
|
+
export class Field {
|
|
15
|
+
parent;
|
|
16
|
+
id;
|
|
17
|
+
_value;
|
|
18
|
+
#originalContents;
|
|
19
|
+
static firstContainerDownload = null;
|
|
20
|
+
constructor(record, id, originalContents) {
|
|
21
|
+
this.parent = record;
|
|
22
|
+
this.id = id;
|
|
23
|
+
this._value = originalContents;
|
|
24
|
+
this.#originalContents = originalContents;
|
|
25
|
+
}
|
|
26
|
+
/** `true` if this field has changed since it was last loaded or saved. */
|
|
27
|
+
get edited() {
|
|
28
|
+
return this.#originalContents !== this._value;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resets the 'edited' state of this field, without changing its value
|
|
32
|
+
*/
|
|
33
|
+
updateOriginalContents() {
|
|
34
|
+
this.#originalContents = this._value;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sets the value of the field.
|
|
38
|
+
*
|
|
39
|
+
* @param {T | null} content - The new content value to be set. Can be either the type T or null.
|
|
40
|
+
* @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
|
|
41
|
+
*/
|
|
42
|
+
set(content) {
|
|
43
|
+
if (this.metadata.result === 'container')
|
|
44
|
+
throw new Error('Cannot set container value using set(). Use upload() instead.');
|
|
45
|
+
else
|
|
46
|
+
this._value = content;
|
|
47
|
+
}
|
|
48
|
+
/** Returns the FileMaker metadata for this field. */
|
|
49
|
+
get metadata() {
|
|
50
|
+
if (!this.parent.layout.metadata) {
|
|
51
|
+
// Default to a regular text field
|
|
52
|
+
return {
|
|
53
|
+
name: this.id.toString(),
|
|
54
|
+
type: ApiFieldTypes.NORMAL,
|
|
55
|
+
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
56
|
+
result: ApiFieldResultTypes.TEXT,
|
|
57
|
+
global: false,
|
|
58
|
+
autoEnter: true,
|
|
59
|
+
fourDigitYear: false,
|
|
60
|
+
maxRepeat: 1,
|
|
61
|
+
maxCharacters: 0,
|
|
62
|
+
notEmpty: false,
|
|
63
|
+
numeric: false,
|
|
64
|
+
timeOfDay: false,
|
|
65
|
+
repetitionStart: 1,
|
|
66
|
+
repetitionEnd: 1,
|
|
67
|
+
valueList: ''
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (this.parent.type === RecordTypes.PORTAL && this.parent.portal) {
|
|
71
|
+
return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || 'portal not attached'].find(i => i.name === this.id) ??
|
|
72
|
+
{
|
|
143
73
|
name: this.id.toString(),
|
|
144
74
|
type: ApiFieldTypes.NORMAL,
|
|
145
75
|
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
@@ -152,117 +82,111 @@ let Field = (() => {
|
|
|
152
82
|
notEmpty: false,
|
|
153
83
|
numeric: false,
|
|
154
84
|
timeOfDay: false,
|
|
155
|
-
|
|
85
|
+
repetitionStart: 1,
|
|
86
|
+
repetitionEnd: 1,
|
|
156
87
|
valueList: ''
|
|
157
88
|
};
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
get value() {
|
|
161
|
-
// if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
|
|
162
|
-
return this._value;
|
|
163
|
-
}
|
|
164
|
-
set value(value) {
|
|
165
|
-
this.set(value);
|
|
166
|
-
}
|
|
167
|
-
get string() {
|
|
168
|
-
if (typeof this._value === 'string') {
|
|
169
|
-
return this._value;
|
|
170
|
-
}
|
|
171
|
-
throw new Error('Field value is not a string');
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Uploads a file to the container field.
|
|
175
|
-
*
|
|
176
|
-
* @param {Buffer} file - The file content as a buffer.
|
|
177
|
-
*
|
|
178
|
-
* @throws {Error} - Cannot upload a file to the field if it's not a container field.
|
|
179
|
-
* @throws {Error} - Upload failed with HTTP error.
|
|
180
|
-
*
|
|
181
|
-
* @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
|
|
182
|
-
*/
|
|
183
|
-
async upload(file) {
|
|
184
|
-
if (this.metadata.result !== 'container') {
|
|
185
|
-
throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)');
|
|
186
|
-
}
|
|
187
|
-
const form = new FormData();
|
|
188
|
-
form.append('upload', file);
|
|
189
|
-
const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
|
|
190
|
-
method: 'POST',
|
|
191
|
-
headers: {
|
|
192
|
-
// Authorization: 'Bearer ' + this.parent.layout.database.token,
|
|
193
|
-
// 'Content-Type': 'multipart/form-data'
|
|
194
|
-
},
|
|
195
|
-
body: form
|
|
196
|
-
});
|
|
197
|
-
if (!res.ok) {
|
|
198
|
-
throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`);
|
|
199
|
-
}
|
|
200
|
-
const data = await res.json();
|
|
201
|
-
if (data.messages[0].code === '0')
|
|
202
|
-
return;
|
|
203
|
-
else {
|
|
204
|
-
throw new FMError(data.messages[0].code, res.status, res);
|
|
205
|
-
}
|
|
206
89
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
90
|
+
else {
|
|
91
|
+
return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
|
|
92
|
+
name: this.id.toString(),
|
|
93
|
+
type: ApiFieldTypes.NORMAL,
|
|
94
|
+
displayType: ApiFieldDisplayTypes.EDIT_TEXT,
|
|
95
|
+
result: ApiFieldResultTypes.TEXT,
|
|
96
|
+
global: false,
|
|
97
|
+
autoEnter: true,
|
|
98
|
+
fourDigitYear: false,
|
|
99
|
+
maxRepeat: 1,
|
|
100
|
+
maxCharacters: 0,
|
|
101
|
+
notEmpty: false,
|
|
102
|
+
numeric: false,
|
|
103
|
+
timeOfDay: false,
|
|
104
|
+
repetitionStart: 1,
|
|
105
|
+
repetitionEnd: 1,
|
|
106
|
+
valueList: ''
|
|
107
|
+
};
|
|
210
108
|
}
|
|
211
|
-
async arrayBuffer() {
|
|
212
|
-
const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true });
|
|
213
|
-
if (!req.ok)
|
|
214
|
-
throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
|
|
215
|
-
return { data: await req.arrayBuffer(), mime: req.headers.get('Content-Type') ?? '' };
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
})();
|
|
219
|
-
export { Field };
|
|
220
|
-
function containerDownloadFunction(originalMethod, context) {
|
|
221
|
-
async function replacementMethod(...args) {
|
|
222
|
-
return await ContainerDownloadExecutor.processTask(async () => await originalMethod.call(this, ...args));
|
|
223
109
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
catch (e) {
|
|
238
|
-
this.hasDownloadedFirstContainer = 0;
|
|
239
|
-
const nextTask = this.waitingFuncs.splice(0, 1)[0];
|
|
240
|
-
if (nextTask) {
|
|
241
|
-
this.processTask(nextTask.func)
|
|
242
|
-
.then(res => nextTask.done(res))
|
|
243
|
-
.catch(e => nextTask.err(e));
|
|
244
|
-
}
|
|
245
|
-
throw e;
|
|
246
|
-
}
|
|
247
|
-
this.hasDownloadedFirstContainer = 2;
|
|
248
|
-
for (const task of this.waitingFuncs) {
|
|
249
|
-
task.func()
|
|
250
|
-
.then((res) => task.done(res))
|
|
251
|
-
.catch((e) => task.err(e));
|
|
252
|
-
}
|
|
253
|
-
return res;
|
|
110
|
+
/** Gets or sets the current field value. */
|
|
111
|
+
get value() {
|
|
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
|
+
set value(value) {
|
|
116
|
+
this.set(value);
|
|
117
|
+
}
|
|
118
|
+
/** Returns this field value as a string. Throws if the value is not a string. */
|
|
119
|
+
get string() {
|
|
120
|
+
if (typeof this._value === 'string') {
|
|
121
|
+
return this._value;
|
|
254
122
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
123
|
+
throw new Error('Field value is not a string');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Uploads a file to the container field.
|
|
127
|
+
*
|
|
128
|
+
* @param {Buffer} file - The file content as a buffer.
|
|
129
|
+
*
|
|
130
|
+
* @throws {Error} - Cannot upload a file to the field if it's not a container field.
|
|
131
|
+
* @throws {Error} - Upload failed with HTTP error.
|
|
132
|
+
*
|
|
133
|
+
* @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
|
|
134
|
+
*/
|
|
135
|
+
async upload(file) {
|
|
136
|
+
if (this.metadata.result !== 'container') {
|
|
137
|
+
throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)');
|
|
138
|
+
}
|
|
139
|
+
const form = new FormData();
|
|
140
|
+
form.append('upload', file);
|
|
141
|
+
const res = await this.parent.layout.database.fetch(`${this.parent.endpoint}/containers/${this.id}/1`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
body: form
|
|
144
|
+
});
|
|
145
|
+
if (!res.ok) {
|
|
146
|
+
throw await HttpError.new(res);
|
|
147
|
+
}
|
|
148
|
+
const data = ApiResults.parse(await res.json());
|
|
149
|
+
if (data.messages[0].code === 0)
|
|
150
|
+
return;
|
|
151
|
+
else {
|
|
152
|
+
throw new FMError(data.messages[0].code, res.status, res);
|
|
263
153
|
}
|
|
264
|
-
|
|
265
|
-
|
|
154
|
+
}
|
|
155
|
+
async #streamAsync(options = {}) {
|
|
156
|
+
const req = await this.parent.layout.database.fetch(this.string, {
|
|
157
|
+
signal: options.abortSignal ?? null,
|
|
158
|
+
});
|
|
159
|
+
// const req = await this.parent.layout.database._apiRequestRaw(this.string, {useCookieJar: true})
|
|
160
|
+
if (!req.ok || !req.body) {
|
|
161
|
+
throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
|
|
162
|
+
}
|
|
163
|
+
return req;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* @deprecated use webStream instead.
|
|
167
|
+
*/
|
|
168
|
+
async stream() {
|
|
169
|
+
const stream = await this.#streamAsync();
|
|
170
|
+
if (!stream.body) {
|
|
171
|
+
throw await HttpError.new(stream);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
// @ts-expect-error stream types are correct
|
|
175
|
+
data: Readable.fromWeb(stream.body),
|
|
176
|
+
mime: stream.headers.get('Content-Type') ?? ''
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Returns the container download as a Web `Response`.
|
|
181
|
+
*/
|
|
182
|
+
async webStream(options) {
|
|
183
|
+
return await this.#streamAsync(options);
|
|
184
|
+
}
|
|
185
|
+
/** Downloads the full container contents into memory. */
|
|
186
|
+
async arrayBuffer() {
|
|
187
|
+
const stream = await this.#streamAsync();
|
|
188
|
+
if (!stream.ok)
|
|
189
|
+
throw await HttpError.new(stream);
|
|
190
|
+
return { data: await stream.arrayBuffer(), mime: stream.headers.get('Content-Type') ?? '' };
|
|
266
191
|
}
|
|
267
192
|
}
|
|
268
|
-
const ContainerDownloadExecutor = new ContainerDownloadExecutorClass();
|
|
@@ -4,7 +4,9 @@ import { type LayoutBase } from '../../layouts/layoutBase.js';
|
|
|
4
4
|
import { LayoutRecord } from '../layoutRecord.js';
|
|
5
5
|
import { type Query } from '../../utils/query.js';
|
|
6
6
|
export type SortOrder = 'ascend' | 'descend';
|
|
7
|
+
/** Raw FileMaker find request strings, already escaped/formatted. */
|
|
7
8
|
export type FindRequestRaw = Record<string, string>;
|
|
9
|
+
/** Safe find request built from `query` tagged template helpers. */
|
|
8
10
|
export type FindRequest = Record<string, Query>;
|
|
9
11
|
export interface PortalRequest {
|
|
10
12
|
name: string;
|
|
@@ -24,6 +26,7 @@ export interface GetOperationOptions<T extends LayoutInterface> {
|
|
|
24
26
|
limit?: number;
|
|
25
27
|
offset?: number;
|
|
26
28
|
}
|
|
29
|
+
/** Builder/executor for list and find operations against one layout. */
|
|
27
30
|
export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOperationOptions<T>> {
|
|
28
31
|
protected layout: LayoutBase;
|
|
29
32
|
protected limit: number;
|
|
@@ -51,7 +54,7 @@ export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS exten
|
|
|
51
54
|
*/
|
|
52
55
|
scripts(scripts: ScriptRequestData): this;
|
|
53
56
|
/**
|
|
54
|
-
*
|
|
57
|
+
* Add sort clause in request order.
|
|
55
58
|
*
|
|
56
59
|
* @param {string} fieldName - The name of the field by which the data should be sorted.
|
|
57
60
|
* @param {SortOrder} sortOrder - The sort order to be applied (either "asc" for ascending or "desc" for descending).
|
|
@@ -61,7 +64,7 @@ export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS exten
|
|
|
61
64
|
sort(fieldName: string, sortOrder: SortOrder): this;
|
|
62
65
|
private parseFindRequest;
|
|
63
66
|
/**
|
|
64
|
-
*
|
|
67
|
+
* Add one FileMaker find request block.
|
|
65
68
|
*
|
|
66
69
|
* @param {FindRequest} query - The find request to be added.
|
|
67
70
|
* @param {boolean} [omit=false] - Flag to indicate if the find request should be omitted.
|
|
@@ -69,14 +72,14 @@ export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS exten
|
|
|
69
72
|
*/
|
|
70
73
|
addRequest(query: FindRequest, omit?: boolean): this;
|
|
71
74
|
/**
|
|
72
|
-
*
|
|
75
|
+
* Execute request and return current page of records.
|
|
73
76
|
*
|
|
74
77
|
* @returns {Promise} A promise that resolves with the result of the fetch operation.
|
|
75
78
|
*/
|
|
76
79
|
fetch(): Promise<LayoutRecord<PickPortals<T, keyof OPTIONS["portals"]>>[]>;
|
|
77
80
|
private performFind;
|
|
78
81
|
/**
|
|
79
|
-
*
|
|
82
|
+
* Create async iterator that pages through results lazily.
|
|
80
83
|
* @param pageSize min: 1, max: 999
|
|
81
84
|
*/
|
|
82
85
|
iterate(pageSize?: number): {
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
4
|
import { LayoutRecord } from '../layoutRecord.js';
|
|
5
|
+
import { ApiRecordResponseObj } from '../../models/apiResults.js';
|
|
5
6
|
import { FMError } from '../../FMError.js';
|
|
6
7
|
import { FindRequestSymbol } from '../../utils/query.js';
|
|
8
|
+
/** Builder/executor for list and find operations against one layout. */
|
|
7
9
|
export class RecordGetOperation {
|
|
8
10
|
layout;
|
|
9
11
|
limit = 100;
|
|
@@ -111,7 +113,7 @@ export class RecordGetOperation {
|
|
|
111
113
|
return this;
|
|
112
114
|
}
|
|
113
115
|
/**
|
|
114
|
-
*
|
|
116
|
+
* Add sort clause in request order.
|
|
115
117
|
*
|
|
116
118
|
* @param {string} fieldName - The name of the field by which the data should be sorted.
|
|
117
119
|
* @param {SortOrder} sortOrder - The sort order to be applied (either "asc" for ascending or "desc" for descending).
|
|
@@ -135,7 +137,7 @@ export class RecordGetOperation {
|
|
|
135
137
|
.utcOffset(this.layout.database.host.timezoneOffsetFunc(item.moment))
|
|
136
138
|
.format(item.type === 'date'
|
|
137
139
|
? this.layout.database.host.dateFormat
|
|
138
|
-
: item.type
|
|
140
|
+
: item.type === 'time'
|
|
139
141
|
? this.layout.database.host.timeFormat
|
|
140
142
|
: this.layout.database.host.timeStampFormat);
|
|
141
143
|
}).join('');
|
|
@@ -143,7 +145,7 @@ export class RecordGetOperation {
|
|
|
143
145
|
return out;
|
|
144
146
|
}
|
|
145
147
|
/**
|
|
146
|
-
*
|
|
148
|
+
* Add one FileMaker find request block.
|
|
147
149
|
*
|
|
148
150
|
* @param {FindRequest} query - The find request to be added.
|
|
149
151
|
* @param {boolean} [omit=false] - Flag to indicate if the find request should be omitted.
|
|
@@ -154,7 +156,7 @@ export class RecordGetOperation {
|
|
|
154
156
|
return this;
|
|
155
157
|
}
|
|
156
158
|
/**
|
|
157
|
-
*
|
|
159
|
+
* Execute request and return current page of records.
|
|
158
160
|
*
|
|
159
161
|
* @returns {Promise} A promise that resolves with the result of the fetch operation.
|
|
160
162
|
*/
|
|
@@ -162,7 +164,6 @@ export class RecordGetOperation {
|
|
|
162
164
|
return await this.performFind(this.offset, this.limit);
|
|
163
165
|
}
|
|
164
166
|
async performFind(offset, limit) {
|
|
165
|
-
const trace = new Error();
|
|
166
167
|
await this.layout.getLayoutMeta();
|
|
167
168
|
const isFind = this.isFindRequest;
|
|
168
169
|
let endpoint = this.layout.endpoint + (isFind ? '/_find' : '/records');
|
|
@@ -171,21 +172,17 @@ export class RecordGetOperation {
|
|
|
171
172
|
const reqData = {
|
|
172
173
|
// port: 443,
|
|
173
174
|
method: isFind ? 'POST' : 'GET',
|
|
175
|
+
type: ApiRecordResponseObj,
|
|
174
176
|
body: isFind ? JSON.stringify(this.generateParamsBody(offset, limit)) : undefined
|
|
175
177
|
};
|
|
176
178
|
try {
|
|
177
|
-
const res = await this.layout.database.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
throw new FMError(res.messages[0].code, res.httpStatus, res, trace);
|
|
188
|
-
}
|
|
179
|
+
const res = await this.layout.database.fetchJSON(endpoint, reqData);
|
|
180
|
+
// console.log("RESOLVING")
|
|
181
|
+
if (!this.layout.metadata)
|
|
182
|
+
await this.layout.getLayoutMeta();
|
|
183
|
+
return res.data.map(item => {
|
|
184
|
+
return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData);
|
|
185
|
+
});
|
|
189
186
|
}
|
|
190
187
|
catch (e) {
|
|
191
188
|
if (e instanceof FMError) {
|
|
@@ -198,7 +195,7 @@ export class RecordGetOperation {
|
|
|
198
195
|
}
|
|
199
196
|
}
|
|
200
197
|
/**
|
|
201
|
-
*
|
|
198
|
+
* Create async iterator that pages through results lazily.
|
|
202
199
|
* @param pageSize min: 1, max: 999
|
|
203
200
|
*/
|
|
204
201
|
iterate(pageSize = 100) {
|
|
@@ -7,30 +7,47 @@ import { type LayoutRecordBase } from './layoutRecordBase.js';
|
|
|
7
7
|
import { type ApiPortalData, type ApiRowDataDef } from '../models/apiResults.js';
|
|
8
8
|
import { type LayoutBase } from '../layouts/layoutBase.js';
|
|
9
9
|
import { type Field, type FieldValue } from './field.js';
|
|
10
|
+
import z from 'zod';
|
|
10
11
|
export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAYOUT['fields']> implements LayoutRecordBase {
|
|
11
12
|
portals: LAYOUT['portals'];
|
|
12
13
|
private readonly portalsToInclude;
|
|
13
|
-
constructor(layout: LayoutBase, recordId: number | string, modId?: string | number, fieldData?: Record<string, string | number>, portalData?: ApiPortalData | null, portalsToInclude?: Array<keyof LAYOUT['portals']>);
|
|
14
|
+
constructor(layout: LayoutBase, recordId: number | string, modId?: string | number, fieldData?: Record<string, string | number>, portalData?: z.infer<typeof ApiPortalData> | null, portalsToInclude?: Array<keyof LAYOUT['portals']>);
|
|
15
|
+
/** Returns all loaded portals on this record as an array. */
|
|
14
16
|
get portalsArray(): Array<Portal<any>>;
|
|
15
17
|
/**
|
|
16
|
-
*
|
|
18
|
+
* Saves this record back to FileMaker.
|
|
17
19
|
* @param {extraBodyOptions} extraBody - The options for the extra body elements.
|
|
18
20
|
* @returns {Promise<this>} - A promise that resolves with the modified object.
|
|
19
21
|
* @throws {FMError} - If an error occurs during the commit process.
|
|
20
22
|
*/
|
|
21
23
|
commit(extraBody?: extraBodyOptions): Promise<this>;
|
|
22
|
-
protected processPortalData(portalData: ApiPortalData): void;
|
|
24
|
+
protected processPortalData(portalData: z.infer<typeof ApiPortalData>): void;
|
|
23
25
|
/**
|
|
24
|
-
*
|
|
25
|
-
* Throws
|
|
26
|
+
* Reloads this record from FileMaker.
|
|
27
|
+
* Throws if this record has not been saved yet.
|
|
26
28
|
*
|
|
27
29
|
* @return {Promise<this>} A Promise that resolves to this RecordBase instance if the record is successfully retrieved.
|
|
28
30
|
* @throws {Error} If commit() has not been called.
|
|
29
31
|
* @throws {FMError} If the retrieval fails.
|
|
30
32
|
*/
|
|
31
33
|
get(): Promise<this>;
|
|
34
|
+
/** Creates a duplicate of this record in FileMaker. */
|
|
32
35
|
duplicate(): Promise<LayoutRecord<LAYOUT>>;
|
|
36
|
+
/** Deletes this record from FileMaker. */
|
|
33
37
|
delete(): Promise<void>;
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
/** Returns this record's edited fields as a FileMaker API payload. */
|
|
39
|
+
fieldsToObject(filter?: (a: Field<FieldValue>) => boolean): Omit<z.infer<typeof ApiRowDataDef>, 'portalData'>;
|
|
40
|
+
/** Returns this record and its portal edits as a FileMaker API payload. */
|
|
41
|
+
toObject(filter?: (a: Field<FieldValue>) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<any>) => any, portalFieldFilter?: (a: Field<FieldValue>) => any): {
|
|
42
|
+
fieldData: Record<string, string | number>;
|
|
43
|
+
portalData: Record<string, Record<string, string | number>[]>;
|
|
44
|
+
modId: string;
|
|
45
|
+
recordId: string;
|
|
46
|
+
portalDataInfo?: {
|
|
47
|
+
database: string;
|
|
48
|
+
table: string;
|
|
49
|
+
foundCount: number;
|
|
50
|
+
returnedCount: number;
|
|
51
|
+
}[] | undefined;
|
|
52
|
+
};
|
|
36
53
|
}
|