@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.
Files changed (43) hide show
  1. package/README.md +60 -197
  2. package/dist/bin/generateTypes.js +1 -1
  3. package/dist/bin/stressSearch.d.ts +2 -0
  4. package/dist/bin/stressSearch.js +243 -0
  5. package/dist/connection/CookieJar.d.ts +19 -0
  6. package/dist/connection/CookieJar.js +99 -0
  7. package/dist/connection/FMHost.d.ts +24 -8
  8. package/dist/connection/FMHost.js +28 -8
  9. package/dist/connection/HostBase.d.ts +4 -2
  10. package/dist/connection/Session.d.ts +34 -0
  11. package/dist/connection/Session.js +109 -0
  12. package/dist/connection/database.d.ts +34 -31
  13. package/dist/connection/database.js +72 -133
  14. package/dist/connection/databaseBase.d.ts +22 -10
  15. package/dist/connection/databaseConstantSession.d.ts +15 -0
  16. package/dist/connection/databaseConstantSession.js +70 -0
  17. package/dist/connection/databaseSessionPool.d.ts +14 -0
  18. package/dist/connection/databaseSessionPool.js +148 -0
  19. package/dist/index.d.ts +18 -1
  20. package/dist/index.js +12 -0
  21. package/dist/layouts/layout.d.ts +5 -3
  22. package/dist/layouts/layout.js +11 -15
  23. package/dist/layouts/layoutBase.d.ts +3 -2
  24. package/dist/layouts/layoutRecordManager.d.ts +6 -15
  25. package/dist/layouts/layoutRecordManager.js +6 -15
  26. package/dist/models/apiResults.d.ts +208 -93
  27. package/dist/models/apiResults.js +95 -25
  28. package/dist/records/field.d.ts +17 -2
  29. package/dist/records/field.js +161 -237
  30. package/dist/records/getOperations/recordGetOperation.d.ts +7 -4
  31. package/dist/records/getOperations/recordGetOperation.js +15 -18
  32. package/dist/records/layoutRecord.d.ts +24 -7
  33. package/dist/records/layoutRecord.js +68 -73
  34. package/dist/records/portal.d.ts +0 -4
  35. package/dist/records/portal.js +0 -4
  36. package/dist/records/recordBase.d.ts +3 -2
  37. package/dist/types.d.ts +41 -17
  38. package/dist/types.js +12 -0
  39. package/dist/utils/addHeaders.d.ts +5 -0
  40. package/dist/utils/addHeaders.js +24 -0
  41. package/dist/utils/query.d.ts +7 -0
  42. package/dist/utils/query.js +5 -0
  43. package/package.json +29 -26
@@ -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
- let Field = (() => {
51
- let _instanceExtraInitializers = [];
52
- let _private_streamAsync_decorators;
53
- let _private_streamAsync_descriptor;
54
- let _arrayBuffer_decorators;
55
- return class Field {
56
- static {
57
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
58
- _private_streamAsync_decorators = [containerDownloadFunction];
59
- _arrayBuffer_decorators = [containerDownloadFunction];
60
- __esDecorate(this, _private_streamAsync_descriptor = { value: __setFunctionName(async function () {
61
- const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true });
62
- if (!req.ok || !req.body) {
63
- throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
64
- }
65
- return { data: req.body, mime: req.headers.get('Content-Type') ?? '' };
66
- }, "#streamAsync") }, _private_streamAsync_decorators, { kind: "method", name: "#streamAsync", static: false, private: true, access: { has: obj => #streamAsync in obj, get: obj => obj.#streamAsync }, metadata: _metadata }, null, _instanceExtraInitializers);
67
- __esDecorate(this, null, _arrayBuffer_decorators, { kind: "method", name: "arrayBuffer", static: false, private: false, access: { has: obj => "arrayBuffer" in obj, get: obj => obj.arrayBuffer }, metadata: _metadata }, null, _instanceExtraInitializers);
68
- if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
69
- }
70
- parent = __runInitializers(this, _instanceExtraInitializers);
71
- id;
72
- _value;
73
- #originalContents;
74
- static firstContainerDownload = null;
75
- constructor(record, id, originalContents) {
76
- this.parent = record;
77
- this.id = id;
78
- this._value = originalContents;
79
- this.#originalContents = originalContents;
80
- }
81
- get edited() {
82
- return this.#originalContents !== this._value;
83
- }
84
- /**
85
- * Resets the 'edited' state of this field, without changing its value
86
- */
87
- updateOriginalContents() {
88
- this.#originalContents = this._value;
89
- }
90
- /**
91
- * Sets the value of the field.
92
- *
93
- * @param {T | null} content - The new content value to be set. Can be either the type T or null.
94
- * @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
95
- */
96
- set(content) {
97
- if (this.metadata.result === 'container')
98
- throw new Error('Cannot set container value using set(). Use upload() instead.');
99
- else
100
- this._value = content;
101
- }
102
- get metadata() {
103
- if (!this.parent.layout.metadata) {
104
- // Default to a regular text field
105
- return {
106
- name: this.id.toString(),
107
- type: ApiFieldTypes.NORMAL,
108
- displayType: ApiFieldDisplayTypes.EDIT_TEXT,
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
- repetitions: 1,
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
- get #streamAsync() { return _private_streamAsync_descriptor.value; }
208
- async stream() {
209
- return await this.#streamAsync();
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
- return replacementMethod;
225
- }
226
- class ContainerDownloadExecutorClass {
227
- hasDownloadedFirstContainer = 0;
228
- waitingFuncs = [];
229
- async processTask(task) {
230
- const isFirstTask = this.hasDownloadedFirstContainer === 0;
231
- if (isFirstTask) {
232
- this.hasDownloadedFirstContainer = 1;
233
- let res;
234
- try {
235
- res = await task();
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
- else if (this.hasDownloadedFirstContainer === 1) {
256
- return await new Promise((resolve, reject) => {
257
- this.waitingFuncs.push({
258
- func: task,
259
- done: resolve,
260
- err: reject
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
- else
265
- return await task();
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
- * Sorts the data based on the given field name and sort order.
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
- * Adds a new request/query to the list of queries.
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
- * Perform a fetch operation.
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
- * Creates an iterator that iterates throguh all results
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
- * Sorts the data based on the given field name and sort order.
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 == 'time'
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
- * Adds a new request/query to the list of queries.
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
- * Perform a fetch operation.
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._apiRequestJSON(endpoint, reqData);
178
- if (res.messages[0].code === '0' && res.response) {
179
- // console.log("RESOLVING")
180
- if (!this.layout.metadata)
181
- await this.layout.getLayoutMeta();
182
- return res.response.data.map(item => {
183
- return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData);
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
- * Creates an iterator that iterates throguh all results
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
- * Asynchronously commits the changes made to the current record.
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
- * Re-fetches the current record from the database server.
25
- * Throws an error if commit() has not been called.
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
- fieldsToObject(filter?: (a: Field<FieldValue>) => boolean): Omit<ApiRowDataDef, 'portalData'>;
35
- toObject(filter?: (a: Field<FieldValue>) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<any>) => any, portalFieldFilter?: (a: Field<FieldValue>) => any): ApiRowDataDef;
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
  }