@jd-data-limited/easy-fm 4.1.15 → 5.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +60 -196
  2. package/dist/bin/generateTypes.js +3 -3
  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 +25 -11
  8. package/dist/connection/FMHost.js +29 -11
  9. package/dist/connection/HostBase.d.ts +4 -4
  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 +24 -4
  20. package/dist/index.js +15 -1
  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/layoutInterface.d.ts +2 -1
  25. package/dist/layouts/layoutRecordManager.d.ts +6 -15
  26. package/dist/layouts/layoutRecordManager.js +6 -15
  27. package/dist/models/apiResults.d.ts +209 -93
  28. package/dist/models/apiResults.js +95 -25
  29. package/dist/records/field.d.ts +17 -2
  30. package/dist/records/field.js +161 -237
  31. package/dist/records/fields/baseField.d.ts +57 -0
  32. package/dist/records/fields/baseField.js +59 -0
  33. package/dist/records/fields/containerField.d.ts +35 -0
  34. package/dist/records/fields/containerField.js +78 -0
  35. package/dist/records/fields/field.d.ts +3 -0
  36. package/dist/records/fields/field.js +1 -0
  37. package/dist/records/fields/valueField.d.ts +41 -0
  38. package/dist/records/fields/valueField.js +108 -0
  39. package/dist/records/getOperations/recordGetOperation.d.ts +7 -4
  40. package/dist/records/getOperations/recordGetOperation.js +22 -26
  41. package/dist/records/layoutRecord.d.ts +27 -9
  42. package/dist/records/layoutRecord.js +84 -97
  43. package/dist/records/portal.d.ts +1 -5
  44. package/dist/records/portal.js +0 -4
  45. package/dist/records/portalBase.d.ts +1 -2
  46. package/dist/records/portalRecord.d.ts +7 -4
  47. package/dist/records/portalRecord.js +12 -0
  48. package/dist/records/recordBase.d.ts +8 -6
  49. package/dist/records/recordBase.js +32 -33
  50. package/dist/types.d.ts +42 -18
  51. package/dist/types.js +12 -0
  52. package/dist/utils/addHeaders.d.ts +5 -0
  53. package/dist/utils/addHeaders.js +24 -0
  54. package/dist/utils/query.d.ts +24 -8
  55. package/dist/utils/query.js +60 -20
  56. package/dist/utils/temporal.d.ts +9 -0
  57. package/dist/utils/temporal.js +110 -0
  58. package/package.json +35 -31
@@ -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();
@@ -0,0 +1,57 @@
1
+ import { RecordTypes } from '../../types.js';
2
+ import { type ApiFieldMetadata, FieldResultTypes } from '../../models/apiResults.js';
3
+ import { type LayoutBase } from '../../layouts/layoutBase.js';
4
+ import { type z } from 'zod';
5
+ export type ContainerValue = null;
6
+ /**
7
+ * RawValueData refers to the raw shapes of data we receive from or sent to FileMaker.
8
+ */
9
+ export type RawValueData = string | number | null;
10
+ export interface Parentable {
11
+ layout: LayoutBase;
12
+ type: RecordTypes;
13
+ endpoint: string;
14
+ portal?: {
15
+ name: string;
16
+ };
17
+ getFieldMetadata(fieldId: string): z.infer<typeof ApiFieldMetadata>;
18
+ }
19
+ /**
20
+ * A class representing a field in a record.
21
+ * API_FIELD_RESULT defines the value of field.metadata.result for the abstracted field type
22
+ *
23
+ * @template T - The type of the field value.
24
+ */
25
+ export declare abstract class BaseField<T extends unknown, API_FIELD_RESULT extends FieldResultTypes> {
26
+ parent: Parentable;
27
+ id: string;
28
+ protected _value: T;
29
+ /**
30
+ * @internal
31
+ * Use `layout.records.create()` instead to create a new LayoutRecord, as this may be safer.
32
+ * @param record
33
+ * @param id
34
+ * @param value
35
+ * @protected
36
+ */
37
+ protected constructor(record: Parentable, id: string, value: RawValueData);
38
+ /**
39
+ * Takes in data in the format that FileMaker gives us and converts it to a more JavaScript-friendly format.
40
+ * This method is intended to be overriden.
41
+ * @param value
42
+ */
43
+ parseRawValue(value: RawValueData): T;
44
+ /**
45
+ * Converts data back into a format that can be sent to FileMaker. Intended to be overriden if conversion is non-standard.
46
+ */
47
+ serializeRawValue(): RawValueData;
48
+ updateFromRawValue(value: RawValueData): void;
49
+ /** Returns the FileMaker metadata for this field. */
50
+ get metadata(): z.infer<typeof ApiFieldMetadata> & {
51
+ result: API_FIELD_RESULT;
52
+ };
53
+ /** Gets or sets the current field value. */
54
+ get value(): T;
55
+ isCalculationField(): boolean;
56
+ isSummaryField(): boolean;
57
+ }
@@ -0,0 +1,59 @@
1
+ /*
2
+ * Copyright (c) 2023-2024. See LICENSE file for more information
3
+ */
4
+ /**
5
+ * A class representing a field in a record.
6
+ * API_FIELD_RESULT defines the value of field.metadata.result for the abstracted field type
7
+ *
8
+ * @template T - The type of the field value.
9
+ */
10
+ export class BaseField {
11
+ parent;
12
+ id;
13
+ _value;
14
+ /**
15
+ * @internal
16
+ * Use `layout.records.create()` instead to create a new LayoutRecord, as this may be safer.
17
+ * @param record
18
+ * @param id
19
+ * @param value
20
+ * @protected
21
+ */
22
+ constructor(record, id, value) {
23
+ this.parent = record;
24
+ this.id = id;
25
+ this._value = this.parseRawValue(value);
26
+ }
27
+ /**
28
+ * Takes in data in the format that FileMaker gives us and converts it to a more JavaScript-friendly format.
29
+ * This method is intended to be overriden.
30
+ * @param value
31
+ */
32
+ parseRawValue(value) {
33
+ return value;
34
+ }
35
+ /**
36
+ * Converts data back into a format that can be sent to FileMaker. Intended to be overriden if conversion is non-standard.
37
+ */
38
+ serializeRawValue() {
39
+ return this._value;
40
+ }
41
+ updateFromRawValue(value) {
42
+ this._value = this.parseRawValue(value);
43
+ }
44
+ /** Returns the FileMaker metadata for this field. */
45
+ get metadata() {
46
+ return this.parent.getFieldMetadata(this.id);
47
+ }
48
+ /** Gets or sets the current field value. */
49
+ get value() {
50
+ // if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
51
+ return this._value;
52
+ }
53
+ isCalculationField() {
54
+ return this.metadata.type === 'calculation';
55
+ }
56
+ isSummaryField() {
57
+ return this.metadata.type === 'summary';
58
+ }
59
+ }
@@ -0,0 +1,35 @@
1
+ import { BaseField, Parentable, RawValueData } from "./baseField.js";
2
+ import { Readable } from 'node:stream';
3
+ interface StreamOptions {
4
+ abortSignal?: AbortSignal;
5
+ }
6
+ export declare class ContainerField extends BaseField<string, 'container'> {
7
+ #private;
8
+ static firstContainerDownload: Promise<any> | null;
9
+ constructor(record: Parentable, id: string, value: RawValueData);
10
+ /**
11
+ * Uploads a file to the container field.
12
+ *
13
+ * @param {Buffer} file - The file content as a buffer.
14
+ *
15
+ * @throws {Error} - Cannot upload a file to the field if it's not a container field.
16
+ * @throws {Error} - Upload failed with HTTP error.
17
+ *
18
+ * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
19
+ */
20
+ upload(file: File): Promise<void>;
21
+ stream(): Promise<{
22
+ data: Readable;
23
+ mime: string;
24
+ }>;
25
+ /**
26
+ * Returns the container download as a Web `Response`.
27
+ */
28
+ webStream(options: StreamOptions): Promise<Response>;
29
+ /** Downloads the full container contents into memory. */
30
+ arrayBuffer(): Promise<{
31
+ data: ArrayBuffer;
32
+ mime: string;
33
+ }>;
34
+ }
35
+ export {};
@@ -0,0 +1,78 @@
1
+ import { BaseField } from "./baseField.js";
2
+ import { FMError } from '../../FMError.js';
3
+ import { Readable } from 'node:stream';
4
+ import { HttpError } from '../../connection/Session.js';
5
+ import { ApiResults } from "../../models/apiResults.js";
6
+ export class ContainerField extends BaseField {
7
+ static firstContainerDownload = null;
8
+ constructor(record, id, value) {
9
+ super(record, id, value);
10
+ }
11
+ /**
12
+ * Uploads a file to the container field.
13
+ *
14
+ * @param {Buffer} file - The file content as a buffer.
15
+ *
16
+ * @throws {Error} - Cannot upload a file to the field if it's not a container field.
17
+ * @throws {Error} - Upload failed with HTTP error.
18
+ *
19
+ * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
20
+ */
21
+ async upload(file) {
22
+ if (this.metadata.result !== 'container') {
23
+ throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)');
24
+ }
25
+ const form = new FormData();
26
+ form.append('upload', file);
27
+ const res = await this.parent.layout.database.fetch(`${this.parent.endpoint}/containers/${this.id}/1`, {
28
+ method: 'POST',
29
+ body: form
30
+ });
31
+ if (!res.ok) {
32
+ throw await HttpError.new(res);
33
+ }
34
+ const data = ApiResults.parse(await res.json());
35
+ if (data.messages[0].code === 0)
36
+ return;
37
+ else {
38
+ throw new FMError(data.messages[0].code, res.status, res);
39
+ }
40
+ }
41
+ async #streamAsync(options = {}) {
42
+ const req = await this.parent.layout.database.fetch(this.value, {
43
+ signal: options.abortSignal ?? null
44
+ });
45
+ // const req = await this.parent.layout.database._apiRequestRaw(this.string, {useCookieJar: true})
46
+ if (!req.ok || !req.body) {
47
+ throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
48
+ }
49
+ return req;
50
+ }
51
+ /**
52
+ * @deprecated use webStream instead.
53
+ */
54
+ async stream() {
55
+ const stream = await this.#streamAsync();
56
+ if (!stream.body) {
57
+ throw await HttpError.new(stream);
58
+ }
59
+ return {
60
+ // @ts-expect-error stream types are correct
61
+ data: Readable.fromWeb(stream.body),
62
+ mime: stream.headers.get('Content-Type') ?? ''
63
+ };
64
+ }
65
+ /**
66
+ * Returns the container download as a Web `Response`.
67
+ */
68
+ async webStream(options) {
69
+ return await this.#streamAsync(options);
70
+ }
71
+ /** Downloads the full container contents into memory. */
72
+ async arrayBuffer() {
73
+ const stream = await this.#streamAsync();
74
+ if (!stream.ok)
75
+ throw await HttpError.new(stream);
76
+ return { data: await stream.arrayBuffer(), mime: stream.headers.get('Content-Type') ?? '' };
77
+ }
78
+ }
@@ -0,0 +1,3 @@
1
+ import { ValueField } from "./valueField.js";
2
+ import { ContainerField } from "./containerField.js";
3
+ export type Field = ValueField | ContainerField;
@@ -0,0 +1 @@
1
+ export {};