@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.
- package/README.md +60 -196
- package/dist/bin/generateTypes.js +3 -3
- 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 +25 -11
- package/dist/connection/FMHost.js +29 -11
- package/dist/connection/HostBase.d.ts +4 -4
- 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 +24 -4
- package/dist/index.js +15 -1
- 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/layoutInterface.d.ts +2 -1
- package/dist/layouts/layoutRecordManager.d.ts +6 -15
- package/dist/layouts/layoutRecordManager.js +6 -15
- package/dist/models/apiResults.d.ts +209 -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/fields/baseField.d.ts +57 -0
- package/dist/records/fields/baseField.js +59 -0
- package/dist/records/fields/containerField.d.ts +35 -0
- package/dist/records/fields/containerField.js +78 -0
- package/dist/records/fields/field.d.ts +3 -0
- package/dist/records/fields/field.js +1 -0
- package/dist/records/fields/valueField.d.ts +41 -0
- package/dist/records/fields/valueField.js +108 -0
- package/dist/records/getOperations/recordGetOperation.d.ts +7 -4
- package/dist/records/getOperations/recordGetOperation.js +22 -26
- package/dist/records/layoutRecord.d.ts +27 -9
- package/dist/records/layoutRecord.js +84 -97
- package/dist/records/portal.d.ts +1 -5
- package/dist/records/portal.js +0 -4
- package/dist/records/portalBase.d.ts +1 -2
- package/dist/records/portalRecord.d.ts +7 -4
- package/dist/records/portalRecord.js +12 -0
- package/dist/records/recordBase.d.ts +8 -6
- package/dist/records/recordBase.js +32 -33
- package/dist/types.d.ts +42 -18
- 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 +24 -8
- package/dist/utils/query.js +60 -20
- package/dist/utils/temporal.d.ts +9 -0
- package/dist/utils/temporal.js +110 -0
- 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
|
-
*
|
|
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,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
|
-
*
|
|
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
|
-
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return
|
|
183
|
-
|
|
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
|
-
*
|
|
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
|
|
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?:
|
|
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
|
-
*
|
|
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
|
-
|
|
24
|
+
getFieldMetadata(fieldId: string): z.infer<typeof ApiFieldMetadata>;
|
|
25
|
+
protected processPortalData(portalData: z.infer<typeof ApiPortalData>): void;
|
|
23
26
|
/**
|
|
24
|
-
*
|
|
25
|
-
* Throws
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
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
|
-
*
|
|
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.
|
|
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 (
|
|
58
|
-
throw new FMError(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.
|
|
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 (
|
|
78
|
-
throw new FMError(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
|
-
*
|
|
107
|
-
* Throws
|
|
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.
|
|
120
|
-
|
|
130
|
+
const res = await this.layout.database.fetchJSON(this.endpoint, {
|
|
131
|
+
type: ApiRecordResponseObj
|
|
121
132
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
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 (
|
|
141
|
-
throw new FMError(res.
|
|
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.
|
|
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.
|
|
161
|
-
throw new FMError(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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {}
|
package/dist/records/portal.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/records/portal.js
CHANGED
|
@@ -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
|
*/
|