@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
@@ -0,0 +1,41 @@
1
+ import { BaseField, Parentable, RawValueData } from "./baseField.js";
2
+ import { Temporal } from "temporal-polyfill";
3
+ import { FieldResultTypes } from "../../models/apiResults.js";
4
+ export declare abstract class ValueFieldBase<T, API_FIELD_RESULT extends FieldResultTypes> extends BaseField<T, API_FIELD_RESULT> {
5
+ #private;
6
+ /**
7
+ * @internal
8
+ * Use `layout.records.create()` instead to create a new LayoutRecord, as this may be safer.
9
+ * @param record
10
+ * @param id
11
+ * @param value
12
+ * @protected
13
+ */
14
+ constructor(record: Parentable, id: string, value: RawValueData);
15
+ set(content: T): void;
16
+ /** `true` if this field has changed since it was last loaded or saved. */
17
+ get edited(): boolean;
18
+ /**
19
+ * Resets the 'edited' state of this field, without changing its value
20
+ */
21
+ updateOriginalContents(): void;
22
+ set value(value: T);
23
+ get value(): T;
24
+ }
25
+ export declare class TextField<T extends string = string> extends ValueFieldBase<T, 'text'> {
26
+ }
27
+ export declare class NumberField<T extends number | null = number | null> extends ValueFieldBase<T, 'number'> {
28
+ }
29
+ export declare class TimeStampField extends ValueFieldBase<Temporal.PlainDateTime | null, 'timeStamp'> {
30
+ parseRawValue(value: RawValueData): Temporal.PlainDateTime | null;
31
+ serializeRawValue(): RawValueData;
32
+ }
33
+ export declare class DateField extends ValueFieldBase<Temporal.PlainDate | null, 'date'> {
34
+ parseRawValue(value: RawValueData): Temporal.PlainDate | null;
35
+ serializeRawValue(): RawValueData;
36
+ }
37
+ export declare class TimeField extends ValueFieldBase<Temporal.PlainTime | null, 'time'> {
38
+ parseRawValue(value: RawValueData): Temporal.PlainTime | null;
39
+ serializeRawValue(): RawValueData;
40
+ }
41
+ export type ValueField = TextField | NumberField | TimeStampField | DateField | TimeField;
@@ -0,0 +1,108 @@
1
+ import { BaseField } from "./baseField.js";
2
+ import { Temporal } from "temporal-polyfill";
3
+ import { stringToTemporal, temporalToString } from "../../utils/temporal.js";
4
+ export class ValueFieldBase extends BaseField {
5
+ #originalContents;
6
+ /**
7
+ * @internal
8
+ * Use `layout.records.create()` instead to create a new LayoutRecord, as this may be safer.
9
+ * @param record
10
+ * @param id
11
+ * @param value
12
+ * @protected
13
+ */
14
+ constructor(record, id, value) {
15
+ super(record, id, value);
16
+ this.#originalContents = value;
17
+ }
18
+ set(content) {
19
+ /**
20
+ * Sets the value of the field.
21
+ *
22
+ * @param {T | null} content - The new content value to be set. Can be either the type T or null.
23
+ * @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
24
+ */
25
+ if (this.isCalculationField() || this.isSummaryField()) {
26
+ throw new Error('Cannot modify the value of an immutable calculation or sumamry field.');
27
+ }
28
+ else
29
+ this._value = content;
30
+ }
31
+ /** `true` if this field has changed since it was last loaded or saved. */
32
+ get edited() {
33
+ return this.#originalContents !== this.serializeRawValue();
34
+ }
35
+ /**
36
+ * Resets the 'edited' state of this field, without changing its value
37
+ */
38
+ updateOriginalContents() {
39
+ this.#originalContents = this.serializeRawValue();
40
+ }
41
+ set value(value) {
42
+ this.set(value);
43
+ }
44
+ get value() {
45
+ return super.value;
46
+ }
47
+ }
48
+ export class TextField extends ValueFieldBase {
49
+ }
50
+ export class NumberField extends ValueFieldBase {
51
+ }
52
+ export class TimeStampField extends ValueFieldBase {
53
+ parseRawValue(value) {
54
+ if (value === '' || value === null)
55
+ return null;
56
+ if (typeof value !== 'string')
57
+ throw new Error("Received an invalid value for a temporal field.");
58
+ try {
59
+ return stringToTemporal(value, 'timestamp', this.parent.layout.database.host.timeStampFormat);
60
+ }
61
+ catch {
62
+ return Temporal.PlainDateTime.from(value);
63
+ }
64
+ }
65
+ serializeRawValue() {
66
+ if (this._value === null)
67
+ return '';
68
+ return temporalToString(this._value, this.parent.layout.database.host.timeStampFormat);
69
+ }
70
+ }
71
+ export class DateField extends ValueFieldBase {
72
+ parseRawValue(value) {
73
+ if (value === '' || value === null)
74
+ return null;
75
+ if (typeof value !== 'string')
76
+ throw new Error("Received an invalid value for a temporal field.");
77
+ try {
78
+ return stringToTemporal(value, 'date', this.parent.layout.database.host.dateFormat);
79
+ }
80
+ catch {
81
+ return Temporal.PlainDate.from(value);
82
+ }
83
+ }
84
+ serializeRawValue() {
85
+ if (this._value === null)
86
+ return '';
87
+ return temporalToString(this._value, this.parent.layout.database.host.dateFormat);
88
+ }
89
+ }
90
+ export class TimeField extends ValueFieldBase {
91
+ parseRawValue(value) {
92
+ if (value === '' || value === null)
93
+ return null;
94
+ if (typeof value !== 'string')
95
+ throw new Error("Received an invalid value for a temporal field.");
96
+ try {
97
+ return stringToTemporal(value, 'time', this.parent.layout.database.host.timeFormat);
98
+ }
99
+ catch {
100
+ return Temporal.PlainTime.from(value);
101
+ }
102
+ }
103
+ serializeRawValue() {
104
+ if (this._value === null)
105
+ return '';
106
+ return temporalToString(this._value, this.parent.layout.database.host.timeFormat);
107
+ }
108
+ }
@@ -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,12 @@
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
+ import { Temporal } from "temporal-polyfill";
9
+ import { temporalToString } from "../../utils/temporal.js";
10
+ /** Builder/executor for list and find operations against one layout. */
7
11
  export class RecordGetOperation {
8
12
  layout;
9
13
  limit = 100;
@@ -111,7 +115,7 @@ export class RecordGetOperation {
111
115
  return this;
112
116
  }
113
117
  /**
114
- * Sorts the data based on the given field name and sort order.
118
+ * Add sort clause in request order.
115
119
  *
116
120
  * @param {string} fieldName - The name of the field by which the data should be sorted.
117
121
  * @param {SortOrder} sortOrder - The sort order to be applied (either "asc" for ascending or "desc" for descending).
@@ -129,21 +133,18 @@ export class RecordGetOperation {
129
133
  if (typeof item === 'string')
130
134
  return item;
131
135
  // Re-write date into correct format
132
- return item
133
- .moment
134
- .clone()
135
- .utcOffset(this.layout.database.host.timezoneOffsetFunc(item.moment))
136
- .format(item.type === 'date'
137
- ? this.layout.database.host.dateFormat
138
- : item.type == 'time'
139
- ? this.layout.database.host.timeFormat
140
- : this.layout.database.host.timeStampFormat);
136
+ if (item instanceof Temporal.PlainDateTime)
137
+ return temporalToString(item, this.layout.database.host.timeStampFormat);
138
+ if (item instanceof Temporal.PlainDate)
139
+ return temporalToString(item, this.layout.database.host.dateFormat);
140
+ if (item instanceof Temporal.PlainTime)
141
+ return temporalToString(item, this.layout.database.host.timeFormat);
141
142
  }).join('');
142
143
  }
143
144
  return out;
144
145
  }
145
146
  /**
146
- * Adds a new request/query to the list of queries.
147
+ * Add one FileMaker find request block.
147
148
  *
148
149
  * @param {FindRequest} query - The find request to be added.
149
150
  * @param {boolean} [omit=false] - Flag to indicate if the find request should be omitted.
@@ -154,7 +155,7 @@ export class RecordGetOperation {
154
155
  return this;
155
156
  }
156
157
  /**
157
- * Perform a fetch operation.
158
+ * Execute request and return current page of records.
158
159
  *
159
160
  * @returns {Promise} A promise that resolves with the result of the fetch operation.
160
161
  */
@@ -162,7 +163,6 @@ export class RecordGetOperation {
162
163
  return await this.performFind(this.offset, this.limit);
163
164
  }
164
165
  async performFind(offset, limit) {
165
- const trace = new Error();
166
166
  await this.layout.getLayoutMeta();
167
167
  const isFind = this.isFindRequest;
168
168
  let endpoint = this.layout.endpoint + (isFind ? '/_find' : '/records');
@@ -171,21 +171,17 @@ export class RecordGetOperation {
171
171
  const reqData = {
172
172
  // port: 443,
173
173
  method: isFind ? 'POST' : 'GET',
174
+ type: ApiRecordResponseObj,
174
175
  body: isFind ? JSON.stringify(this.generateParamsBody(offset, limit)) : undefined
175
176
  };
176
177
  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
- }
178
+ const res = await this.layout.database.fetchJSON(endpoint, reqData);
179
+ // console.log("RESOLVING")
180
+ if (!this.layout.metadata)
181
+ await this.layout.getLayoutMeta();
182
+ return res.data.map(item => {
183
+ return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData);
184
+ });
189
185
  }
190
186
  catch (e) {
191
187
  if (e instanceof FMError) {
@@ -198,7 +194,7 @@ export class RecordGetOperation {
198
194
  }
199
195
  }
200
196
  /**
201
- * Creates an iterator that iterates throguh all results
197
+ * Create async iterator that pages through results lazily.
202
198
  * @param pageSize min: 1, max: 999
203
199
  */
204
200
  iterate(pageSize = 100) {
@@ -4,33 +4,51 @@ import { PortalRecord } from './portalRecord.js';
4
4
  import { Portal } from './portal.js';
5
5
  import { type LayoutInterface } from '../layouts/layoutInterface.js';
6
6
  import { type LayoutRecordBase } from './layoutRecordBase.js';
7
- import { type ApiPortalData, type ApiRowDataDef } from '../models/apiResults.js';
7
+ import { type ApiFieldData, ApiFieldMetadata, type ApiPortalData, type ApiRowDataDef } from '../models/apiResults.js';
8
8
  import { type LayoutBase } from '../layouts/layoutBase.js';
9
- import { type Field, type FieldValue } from './field.js';
9
+ import z from 'zod';
10
+ import { Field } from "./fields/field.js";
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?: z.infer<typeof ApiFieldData>, 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
+ getFieldMetadata(fieldId: string): z.infer<typeof ApiFieldMetadata>;
25
+ protected processPortalData(portalData: z.infer<typeof ApiPortalData>): void;
23
26
  /**
24
- * Re-fetches the current record from the database server.
25
- * Throws an error if commit() has not been called.
27
+ * Reloads this record from FileMaker.
28
+ * Throws if this record has not been saved yet.
26
29
  *
27
30
  * @return {Promise<this>} A Promise that resolves to this RecordBase instance if the record is successfully retrieved.
28
31
  * @throws {Error} If commit() has not been called.
29
32
  * @throws {FMError} If the retrieval fails.
30
33
  */
31
34
  get(): Promise<this>;
35
+ /** Creates a duplicate of this record in FileMaker. */
32
36
  duplicate(): Promise<LayoutRecord<LAYOUT>>;
37
+ /** Deletes this record from FileMaker. */
33
38
  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;
39
+ /** Returns this record's edited fields as a FileMaker API payload. */
40
+ fieldsToObject(filter?: (a: Field) => boolean): Omit<z.infer<typeof ApiRowDataDef>, 'portalData'>;
41
+ /** Returns this record and its portal edits as a FileMaker API payload. */
42
+ toObject(filter?: (a: Field) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<any>) => any, portalFieldFilter?: (a: Field) => any): {
43
+ fieldData: Record<string, string | number | null>;
44
+ portalData: Record<string, Record<string, string | number | null>[]>;
45
+ modId: string;
46
+ recordId: string;
47
+ portalDataInfo?: {
48
+ database: string;
49
+ table: string;
50
+ foundCount: number;
51
+ returnedCount: number;
52
+ }[] | undefined;
53
+ };
36
54
  }
@@ -5,7 +5,9 @@ import { RecordBase } from './recordBase.js';
5
5
  import { PortalRecord } from './portalRecord.js';
6
6
  import { Portal } from './portal.js';
7
7
  import { FMError } from '../FMError.js';
8
- import moment from 'moment';
8
+ import { ApiRecordResponseObj } from '../models/apiResults.js';
9
+ import z from 'zod';
10
+ import { ValueFieldBase } from "./fields/valueField.js";
9
11
  export class LayoutRecord extends RecordBase {
10
12
  portals = {};
11
13
  portalsToInclude;
@@ -16,11 +18,12 @@ export class LayoutRecord extends RecordBase {
16
18
  this.processPortalData(portalData);
17
19
  }
18
20
  }
21
+ /** Returns all loaded portals on this record as an array. */
19
22
  get portalsArray() {
20
23
  return Object.values(this.portals);
21
24
  }
22
25
  /**
23
- * Asynchronously commits the changes made to the current record.
26
+ * Saves this record back to FileMaker.
24
27
  * @param {extraBodyOptions} extraBody - The options for the extra body elements.
25
28
  * @returns {Promise<this>} - A promise that resolves with the modified object.
26
29
  * @throws {FMError} - If an error occurs during the commit process.
@@ -50,44 +53,52 @@ export class LayoutRecord extends RecordBase {
50
53
  data.fieldData.deleteRelated = extraBody.deleteRelatedRecords.map(i => `${i.table}.${i.recordId}`);
51
54
  if (this.recordId === -1) {
52
55
  // This is a new LayoutRecord
53
- const res = await this.layout.database._apiRequestJSON(`${this.layout.endpoint}/records`, {
56
+ const res = await this.layout.database.fetchJSON(`${this.layout.endpoint}/records`, {
54
57
  method: 'POST',
55
- body: JSON.stringify(data)
58
+ body: JSON.stringify(data),
59
+ type: z.object({
60
+ recordId: z.string(),
61
+ modId: z.string(),
62
+ scriptError: z.string().optional()
63
+ })
56
64
  });
57
- if (!res.response) {
58
- throw new FMError(res.messages[0].code, res.httpStatus, res);
59
- }
60
- else if (typeof res.response.scriptError !== 'undefined' && res.response.scriptError !== '0') {
61
- throw new FMError(res.response.scriptError, res.httpStatus, res);
62
- }
63
- else if (res.messages[0].code === '0') {
64
- this.recordId = parseInt(res.response.recordId);
65
- this.modId = parseInt(res.response.modId);
66
- return this;
67
- }
68
- else {
69
- throw new FMError(res.messages[0].code, res.httpStatus, res);
65
+ if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
66
+ throw new FMError(res.scriptError, res.httpStatus, res);
70
67
  }
68
+ this.recordId = parseInt(res.recordId);
69
+ this.modId = parseInt(res.modId);
70
+ this._onSave();
71
+ return this;
71
72
  }
72
73
  // for (let item of Object.keys(data)) extraBody[item] = data[item]
73
- const res = await this.layout.database._apiRequestJSON(this.endpoint, {
74
+ const res = await this.layout.database.fetchJSON(this.endpoint, {
74
75
  method: 'PATCH',
75
- body: JSON.stringify(data)
76
+ body: JSON.stringify(data),
77
+ type: z.object({
78
+ modId: z.string(),
79
+ newPortalRecordInfo: z.array(z.object({
80
+ tableName: z.string(),
81
+ recordId: z.string(),
82
+ modId: z.string()
83
+ })).optional(),
84
+ scriptError: z.string().optional()
85
+ })
76
86
  });
77
- if (!res.response) {
78
- throw new FMError(res.messages[0].code, res.httpStatus, res);
79
- }
80
- else if (typeof res.response.scriptError !== 'undefined' && res.response.scriptError !== '0') {
81
- throw new FMError(res.response.scriptError, res.httpStatus, res);
82
- }
83
- else if (res.messages[0].code === '0') {
84
- this.modId = +res.response.modId;
85
- this._onSave();
86
- return this;
87
- }
88
- else {
89
- throw new FMError(res.messages[0].code, res.httpStatus, res);
87
+ if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
88
+ throw new FMError(res.scriptError, res.httpStatus, res);
90
89
  }
90
+ this.modId = +res.modId;
91
+ this._onSave();
92
+ return this;
93
+ }
94
+ getFieldMetadata(fieldId) {
95
+ if (!this.layout.metadata) {
96
+ throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
97
+ }
98
+ let result = this.layout.metadata.fieldMetaData.find(i => i.name === fieldId);
99
+ if (!result)
100
+ throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
101
+ return result;
91
102
  }
92
103
  processPortalData(portalData) {
93
104
  for (const portalName of Object.keys(portalData)) {
@@ -103,8 +114,8 @@ export class LayoutRecord extends RecordBase {
103
114
  }
104
115
  }
105
116
  /**
106
- * Re-fetches the current record from the database server.
107
- * Throws an error if commit() has not been called.
117
+ * Reloads this record from FileMaker.
118
+ * Throws if this record has not been saved yet.
108
119
  *
109
120
  * @return {Promise<this>} A Promise that resolves to this RecordBase instance if the record is successfully retrieved.
110
121
  * @throws {Error} If commit() has not been called.
@@ -116,87 +127,63 @@ export class LayoutRecord extends RecordBase {
116
127
  }
117
128
  if (!this.layout.metadata)
118
129
  await this.layout.getLayoutMeta();
119
- const res = await this.layout.database._apiRequestJSON(this.endpoint, {
120
- method: 'GET'
130
+ const res = await this.layout.database.fetchJSON(this.endpoint, {
131
+ type: ApiRecordResponseObj
121
132
  });
122
- if (res.response && res.messages[0].code === '0') {
123
- // console.log(res, res.response.data)
124
- this.modId = +res.response.data[0].modId;
125
- this.processFieldData(res.response.data[0].fieldData);
126
- this.portalData = [];
127
- if (res.response.data[0].portalData)
128
- this.processPortalData(res.response.data[0].portalData);
129
- return this;
130
- }
131
- else {
132
- throw new FMError(res.messages[0].code, res.httpStatus, res);
133
- }
133
+ // console.log(res, res.response.data)
134
+ this.modId = +res.data[0].modId;
135
+ this.processFieldData(res.data[0].fieldData);
136
+ this.portalData = [];
137
+ if (res.data[0].portalData)
138
+ this.processPortalData(res.data[0].portalData);
139
+ return this;
134
140
  }
141
+ /** Creates a duplicate of this record in FileMaker. */
135
142
  async duplicate() {
136
143
  const trace = new Error();
137
- const res = await this.layout.database._apiRequestJSON(this.endpoint, {
138
- method: 'POST'
144
+ const res = await this.layout.database.fetchJSON(this.endpoint, {
145
+ method: 'POST',
146
+ type: z.object({
147
+ recordId: z.string(),
148
+ modId: z.string(),
149
+ scriptError: z.string().optional()
150
+ })
139
151
  });
140
- if (!res.response) {
141
- throw new FMError(res.messages[0].code, res.httpStatus, res, trace);
142
- }
143
- else if (typeof res.response.scriptError !== 'undefined' && res.response.scriptError !== '0') {
144
- throw new FMError(res.response.scriptError, res.httpStatus, res, trace);
145
- }
146
- else if (res.messages[0].code === '0') {
147
- const data = this.toObject((a) => true, (a) => true, (a) => false, (a) => false);
148
- const _res = new LayoutRecord(this.layout, res.response.recordId, res.response.modId, data.fieldData, data.portalData);
149
- this.emit('duplicated');
150
- return _res;
151
- }
152
- else {
153
- throw new FMError(res.messages[0].code, res.httpStatus, res, trace);
152
+ if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
153
+ throw new FMError(res.scriptError, res.httpStatus, res, trace);
154
154
  }
155
+ const data = this.toObject((a) => true, (a) => true, (a) => false, (a) => false);
156
+ const _res = new LayoutRecord(this.layout, res.recordId, res.modId, data.fieldData, data.portalData);
157
+ this.emit('duplicated');
158
+ return _res;
155
159
  }
160
+ /** Deletes this record from FileMaker. */
156
161
  async delete() {
157
- const res = await this.layout.database._apiRequestJSON(this.endpoint, {
158
- method: 'DELETE'
162
+ const res = await this.layout.database.fetchJSON(this.endpoint, {
163
+ method: 'DELETE',
164
+ type: z.object({
165
+ scriptError: z.string().optional()
166
+ }).optional()
159
167
  });
160
- if (typeof res.response?.scriptError !== 'undefined' && res.response?.scriptError !== '0') {
161
- throw new FMError(res.response.scriptError, res.httpStatus, res);
162
- }
163
- else if (res.messages[0].code === '0') {
164
- this.emit('deleted');
165
- }
166
- else {
167
- throw new FMError(res.messages[0].code, res.httpStatus, res);
168
+ if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
169
+ throw new FMError(res.scriptError, res.httpStatus, res);
168
170
  }
171
+ this.emit('deleted');
169
172
  }
170
- fieldsToObject(filter = (a) => a.edited) {
173
+ /** Returns this record's edited fields as a FileMaker API payload. */
174
+ fieldsToObject(filter = (a) => a instanceof ValueFieldBase && a.edited) {
171
175
  const fieldsProcessed = {};
172
- let field;
173
- for (field of this.fieldsArray.filter(field => filter(field))) {
174
- let value = field.value;
175
- if (value instanceof Date) {
176
- let _value = moment(value);
177
- _value = _value
178
- .utcOffset(this.layout.database.host.timezoneOffsetFunc(_value));
179
- switch (field.metadata.result) {
180
- case 'time':
181
- value = _value.format(this.layout.database.host.timeFormat);
182
- break;
183
- case 'date':
184
- value = _value.format(this.layout.database.host.dateFormat);
185
- break;
186
- default:
187
- value = _value.format(this.layout.database.host.timeStampFormat);
188
- }
189
- }
190
- fieldsProcessed[field.id] = value;
176
+ for (const field of this.fieldsArray.filter(field => filter(field))) {
177
+ fieldsProcessed[field.id] = field.serializeRawValue();
191
178
  }
192
- const obj = {
179
+ return {
193
180
  recordId: this.recordId.toString(),
194
181
  modId: this.modId.toString(),
195
182
  fieldData: fieldsProcessed
196
183
  };
197
- return obj;
198
184
  }
199
- toObject(filter = (a) => a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a.edited) {
185
+ /** Returns this record and its portal edits as a FileMaker API payload. */
186
+ toObject(filter = (a) => a instanceof ValueFieldBase && a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a instanceof ValueFieldBase && a.edited) {
200
187
  const obj = {
201
188
  ...this.fieldsToObject(filter),
202
189
  portalData: {}
@@ -1,13 +1,11 @@
1
1
  import { PortalRecord } from './portalRecord.js';
2
- import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
3
2
  import { type PortalBase } from './portalBase.js';
4
3
  import { type LayoutRecord } from './layoutRecord.js';
4
+ import { RecordFieldsMap } from "../layouts/layoutInterface.js";
5
5
  /**
6
6
  * Represents a portal.
7
7
  *
8
8
  * @template T - The type of RecordFieldsMap.
9
- * @class
10
- * @implements {PortalBase<T>}
11
9
  */
12
10
  export declare class Portal<T extends RecordFieldsMap> implements PortalBase<T> {
13
11
  readonly record: LayoutRecord<any>;
@@ -16,8 +14,6 @@ export declare class Portal<T extends RecordFieldsMap> implements PortalBase<T>
16
14
  constructor(record: LayoutRecord<any>, name: string);
17
15
  /**
18
16
  * Add a new record to the portal
19
- * @function create
20
- * @async
21
17
  * @summary Creates a new record.
22
18
  * @returns {Promise<PortalRecord<T>>} A Promise that resolves to the newly created record.
23
19
  */
@@ -6,8 +6,6 @@ import { PortalRecord } from './portalRecord.js';
6
6
  * Represents a portal.
7
7
  *
8
8
  * @template T - The type of RecordFieldsMap.
9
- * @class
10
- * @implements {PortalBase<T>}
11
9
  */
12
10
  export class Portal {
13
11
  record;
@@ -19,8 +17,6 @@ export class Portal {
19
17
  }
20
18
  /**
21
19
  * Add a new record to the portal
22
- * @function create
23
- * @async
24
20
  * @summary Creates a new record.
25
21
  * @returns {Promise<PortalRecord<T>>} A Promise that resolves to the newly created record.
26
22
  */