@jd-data-limited/easy-fm 5.0.0-beta.1 → 5.0.0-beta.4
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/LICENSE +21 -21
- package/README.md +93 -90
- package/dist/bin/cli.js +34 -5
- package/dist/bin/generateClient.d.ts +1 -0
- package/dist/bin/generateClient.js +48 -0
- package/dist/bin/generateTypes.js +20 -19
- package/dist/bin/init.d.ts +6 -0
- package/dist/bin/init.js +46 -0
- package/dist/bin/stressSearch.js +12 -12
- package/dist/connection/DataApiProvider.d.ts +15 -0
- package/dist/connection/DataApiProvider.js +246 -0
- package/dist/connection/FMHost.d.ts +1 -3
- package/dist/connection/FMHost.js +1 -3
- package/dist/connection/HostBase.d.ts +0 -2
- package/dist/connection/dataApiDatabaseSession.d.ts +11 -0
- package/dist/connection/dataApiDatabaseSession.js +139 -0
- package/dist/connection/database.d.ts +22 -56
- package/dist/connection/database.js +175 -111
- package/dist/connection/databaseBase.d.ts +9 -22
- package/dist/connection/databaseBase.js +0 -3
- package/dist/connection/databaseProvider.d.ts +196 -0
- package/dist/connection/databaseProvider.js +6 -0
- package/dist/connection/databaseSessionPool.d.ts +8 -7
- package/dist/connection/databaseSessionPool.js +110 -106
- package/dist/index.d.ts +11 -7
- package/dist/index.js +7 -5
- package/dist/layouts/layout.d.ts +0 -2
- package/dist/layouts/layout.js +6 -16
- package/dist/layouts/layoutBase.d.ts +0 -1
- package/dist/layouts/layoutInterface.d.ts +4 -1
- package/dist/models/apiResults.d.ts +7 -6
- package/dist/models/apiResults.js +1 -1
- package/dist/records/fields/baseField.d.ts +60 -0
- package/dist/records/fields/baseField.js +59 -0
- package/dist/records/fields/containerField.d.ts +18 -0
- package/dist/records/fields/containerField.js +44 -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.js +24 -24
- package/dist/records/layoutRecord.d.ts +15 -8
- package/dist/records/layoutRecord.js +61 -98
- package/dist/records/portal.d.ts +1 -1
- package/dist/records/portalBase.d.ts +1 -2
- package/dist/records/portalRecord.d.ts +11 -5
- package/dist/records/portalRecord.js +24 -2
- package/dist/records/recordBase.d.ts +11 -7
- package/dist/records/recordBase.js +38 -38
- package/dist/schema.d.ts +54 -0
- package/dist/schema.js +87 -0
- package/dist/typegen/dataApiSchemaSource.d.ts +3 -0
- package/dist/typegen/dataApiSchemaSource.js +32 -0
- package/dist/typegen/dependencies.d.ts +11 -0
- package/dist/typegen/dependencies.js +67 -0
- package/dist/typegen/generate.d.ts +5 -0
- package/dist/typegen/generate.js +85 -0
- package/dist/typegen/index.d.ts +6 -0
- package/dist/typegen/index.js +6 -0
- package/dist/typegen/init.d.ts +13 -0
- package/dist/typegen/init.js +113 -0
- package/dist/typegen/types.d.ts +40 -0
- package/dist/typegen/types.js +1 -0
- package/dist/typegen/zod.d.ts +3 -0
- package/dist/typegen/zod.js +32 -0
- package/dist/types.d.ts +1 -1
- package/dist/utils/query.d.ts +21 -12
- package/dist/utils/query.js +58 -23
- package/dist/utils/temporal.d.ts +9 -0
- package/dist/utils/temporal.js +110 -0
- package/package.json +47 -24
- package/dist/layouts/recordFieldsMap.d.ts +0 -2
- package/dist/layouts/recordFieldsMap.js +0 -4
- package/dist/records/field.d.ts +0 -77
- package/dist/records/field.js +0 -192
|
@@ -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.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.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.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.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.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.timeFormat);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -2,9 +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';
|
|
6
5
|
import { FMError } from '../../FMError.js';
|
|
7
6
|
import { FindRequestSymbol } from '../../utils/query.js';
|
|
7
|
+
import { Temporal } from "temporal-polyfill";
|
|
8
|
+
import { temporalToString } from "../../utils/temporal.js";
|
|
8
9
|
/** Builder/executor for list and find operations against one layout. */
|
|
9
10
|
export class RecordGetOperation {
|
|
10
11
|
layout;
|
|
@@ -131,15 +132,12 @@ export class RecordGetOperation {
|
|
|
131
132
|
if (typeof item === 'string')
|
|
132
133
|
return item;
|
|
133
134
|
// Re-write date into correct format
|
|
134
|
-
|
|
135
|
-
.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
: item.type === 'time'
|
|
141
|
-
? this.layout.database.host.timeFormat
|
|
142
|
-
: this.layout.database.host.timeStampFormat);
|
|
135
|
+
if (item instanceof Temporal.PlainDateTime)
|
|
136
|
+
return temporalToString(item, this.layout.database.timeStampFormat);
|
|
137
|
+
if (item instanceof Temporal.PlainDate)
|
|
138
|
+
return temporalToString(item, this.layout.database.dateFormat);
|
|
139
|
+
if (item instanceof Temporal.PlainTime)
|
|
140
|
+
return temporalToString(item, this.layout.database.timeFormat);
|
|
143
141
|
}).join('');
|
|
144
142
|
}
|
|
145
143
|
return out;
|
|
@@ -165,23 +163,25 @@ export class RecordGetOperation {
|
|
|
165
163
|
}
|
|
166
164
|
async performFind(offset, limit) {
|
|
167
165
|
await this.layout.getLayoutMeta();
|
|
168
|
-
const isFind = this.isFindRequest;
|
|
169
|
-
let endpoint = this.layout.endpoint + (isFind ? '/_find' : '/records');
|
|
170
|
-
if (!isFind)
|
|
171
|
-
endpoint += '?' + new URLSearchParams(this.generateParamsURL(offset, limit)).toString();
|
|
172
|
-
const reqData = {
|
|
173
|
-
// port: 443,
|
|
174
|
-
method: isFind ? 'POST' : 'GET',
|
|
175
|
-
type: ApiRecordResponseObj,
|
|
176
|
-
body: isFind ? JSON.stringify(this.generateParamsBody(offset, limit)) : undefined
|
|
177
|
-
};
|
|
178
166
|
try {
|
|
179
|
-
const
|
|
180
|
-
|
|
167
|
+
const portals = {};
|
|
168
|
+
for (const [name, paging] of Object.entries(this.portals))
|
|
169
|
+
if (paging)
|
|
170
|
+
portals[name] = paging;
|
|
171
|
+
const scripts = {};
|
|
172
|
+
for (const [name, script] of Object.entries(this.scriptData))
|
|
173
|
+
if (script)
|
|
174
|
+
scripts[name] = { name: script.name, parameter: script.parameter ?? '' };
|
|
175
|
+
const { value: res, binding } = await this.layout.database.execute({
|
|
176
|
+
type: 'record.list',
|
|
177
|
+
layout: this.layout.name,
|
|
178
|
+
query: this.requests.map(request => ({ fields: request.req, omit: request.omit ?? false })),
|
|
179
|
+
options: { limit, offset, sort: this.sortData, portals, scripts }
|
|
180
|
+
});
|
|
181
181
|
if (!this.layout.metadata)
|
|
182
182
|
await this.layout.getLayoutMeta();
|
|
183
|
-
return res.
|
|
184
|
-
return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData);
|
|
183
|
+
return res.map(item => {
|
|
184
|
+
return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData, [], binding);
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
catch (e) {
|
|
@@ -4,14 +4,16 @@ 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';
|
|
10
9
|
import z from 'zod';
|
|
10
|
+
import { Field } from "./fields/field.js";
|
|
11
|
+
import { ContainerField } from './fields/containerField.js';
|
|
12
|
+
import { type SessionBinding } from '../connection/databaseProvider.js';
|
|
11
13
|
export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAYOUT['fields']> implements LayoutRecordBase {
|
|
12
14
|
portals: LAYOUT['portals'];
|
|
13
15
|
private readonly portalsToInclude;
|
|
14
|
-
constructor(layout: LayoutBase, recordId: number | string, modId?: string | number, fieldData?:
|
|
16
|
+
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']>, sessionBinding?: SessionBinding);
|
|
15
17
|
/** Returns all loaded portals on this record as an array. */
|
|
16
18
|
get portalsArray(): Array<Portal<any>>;
|
|
17
19
|
/**
|
|
@@ -21,6 +23,7 @@ export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends Record
|
|
|
21
23
|
* @throws {FMError} - If an error occurs during the commit process.
|
|
22
24
|
*/
|
|
23
25
|
commit(extraBody?: extraBodyOptions): Promise<this>;
|
|
26
|
+
getFieldMetadata(fieldId: string): z.infer<typeof ApiFieldMetadata>;
|
|
24
27
|
protected processPortalData(portalData: z.infer<typeof ApiPortalData>): void;
|
|
25
28
|
/**
|
|
26
29
|
* Reloads this record from FileMaker.
|
|
@@ -30,17 +33,21 @@ export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends Record
|
|
|
30
33
|
* @throws {Error} If commit() has not been called.
|
|
31
34
|
* @throws {FMError} If the retrieval fails.
|
|
32
35
|
*/
|
|
33
|
-
get(
|
|
36
|
+
get(portals?: Record<string, {
|
|
37
|
+
limit: number;
|
|
38
|
+
offset: number;
|
|
39
|
+
}>): Promise<this>;
|
|
34
40
|
/** Creates a duplicate of this record in FileMaker. */
|
|
35
41
|
duplicate(): Promise<LayoutRecord<LAYOUT>>;
|
|
36
42
|
/** Deletes this record from FileMaker. */
|
|
37
43
|
delete(): Promise<void>;
|
|
44
|
+
refreshContainer(fieldId: string): Promise<ContainerField>;
|
|
38
45
|
/** Returns this record's edited fields as a FileMaker API payload. */
|
|
39
|
-
fieldsToObject(filter?: (a: Field
|
|
46
|
+
fieldsToObject(filter?: (a: Field) => boolean): Omit<z.infer<typeof ApiRowDataDef>, 'portalData'>;
|
|
40
47
|
/** Returns this record and its portal edits as a FileMaker API payload. */
|
|
41
|
-
toObject(filter?: (a: Field
|
|
42
|
-
fieldData: Record<string, string | number>;
|
|
43
|
-
portalData: Record<string, Record<string, string | number>[]>;
|
|
48
|
+
toObject(filter?: (a: Field) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<any>) => any, portalFieldFilter?: (a: Field) => any): {
|
|
49
|
+
fieldData: Record<string, string | number | null>;
|
|
50
|
+
portalData: Record<string, Record<string, string | number | null>[]>;
|
|
44
51
|
modId: string;
|
|
45
52
|
recordId: string;
|
|
46
53
|
portalDataInfo?: {
|
|
@@ -5,14 +5,13 @@ 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 {
|
|
9
|
-
import
|
|
10
|
-
import z from 'zod';
|
|
8
|
+
import { ValueFieldBase } from "./fields/valueField.js";
|
|
9
|
+
import { ContainerField } from './fields/containerField.js';
|
|
11
10
|
export class LayoutRecord extends RecordBase {
|
|
12
11
|
portals = {};
|
|
13
12
|
portalsToInclude;
|
|
14
|
-
constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null, portalsToInclude = []) {
|
|
15
|
-
super(layout, parseInt(recordId), parseInt(modId), fieldData);
|
|
13
|
+
constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null, portalsToInclude = [], sessionBinding) {
|
|
14
|
+
super(layout, parseInt(recordId), parseInt(modId), fieldData, sessionBinding);
|
|
16
15
|
this.portalsToInclude = portalsToInclude;
|
|
17
16
|
if (portalData) {
|
|
18
17
|
this.processPortalData(portalData);
|
|
@@ -32,64 +31,42 @@ export class LayoutRecord extends RecordBase {
|
|
|
32
31
|
const data = this.toObject();
|
|
33
32
|
delete data.recordId;
|
|
34
33
|
delete data.modId;
|
|
35
|
-
if (extraBody.scripts?.after) {
|
|
36
|
-
data.script = extraBody.scripts.after.name;
|
|
37
|
-
if (extraBody.scripts.after.parameter)
|
|
38
|
-
data['script.param'] = extraBody.scripts.after.parameter;
|
|
39
|
-
}
|
|
40
|
-
if (extraBody.scripts?.prerequest) {
|
|
41
|
-
data['script.prerequest'] = extraBody.scripts.prerequest.name;
|
|
42
|
-
if (extraBody.scripts.prerequest.parameter)
|
|
43
|
-
data['script.prerequest.param'] = extraBody.scripts.prerequest.parameter;
|
|
44
|
-
}
|
|
45
|
-
if (extraBody.scripts?.presort) {
|
|
46
|
-
data['script.presort'] = extraBody.scripts.presort.name;
|
|
47
|
-
if (extraBody.scripts.presort.parameter)
|
|
48
|
-
data['script.presort.param'] = extraBody.scripts.presort.parameter;
|
|
49
|
-
}
|
|
50
34
|
if (extraBody.options)
|
|
51
35
|
data.options = extraBody.options;
|
|
52
36
|
if (extraBody.deleteRelatedRecords)
|
|
53
|
-
data.
|
|
37
|
+
data.deleteRelatedRecords = extraBody.deleteRelatedRecords;
|
|
38
|
+
const options = operationOptions(extraBody);
|
|
54
39
|
if (this.recordId === -1) {
|
|
55
40
|
// This is a new LayoutRecord
|
|
56
|
-
const res = await this.layout.database.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
type: z.object({
|
|
60
|
-
recordId: z.string(),
|
|
61
|
-
modId: z.string(),
|
|
62
|
-
scriptError: z.string().optional()
|
|
63
|
-
})
|
|
64
|
-
});
|
|
65
|
-
if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
|
|
66
|
-
throw new FMError(res.scriptError, res.httpStatus, res);
|
|
41
|
+
const { value: res, binding } = await this.layout.database.execute({ type: 'record.create', layout: this.layout.name, body: data, options });
|
|
42
|
+
if (res.scriptError) {
|
|
43
|
+
throw new FMError(res.scriptError, res.status ?? 200, res);
|
|
67
44
|
}
|
|
68
45
|
this.recordId = parseInt(res.recordId);
|
|
69
46
|
this.modId = parseInt(res.modId);
|
|
47
|
+
this.sessionBinding = binding;
|
|
48
|
+
this._onSave();
|
|
70
49
|
return this;
|
|
71
50
|
}
|
|
72
51
|
// for (let item of Object.keys(data)) extraBody[item] = data[item]
|
|
73
|
-
const res = await this.layout.database.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
type: z.object({
|
|
77
|
-
modId: z.string(),
|
|
78
|
-
newPortalRecordInfo: z.array(z.object({
|
|
79
|
-
tableName: z.string(),
|
|
80
|
-
recordId: z.string(),
|
|
81
|
-
modId: z.string()
|
|
82
|
-
})).optional(),
|
|
83
|
-
scriptError: z.string().optional()
|
|
84
|
-
})
|
|
85
|
-
});
|
|
86
|
-
if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
|
|
87
|
-
throw new FMError(res.scriptError, res.httpStatus, res);
|
|
52
|
+
const { value: res, binding } = await this.layout.database.execute({ type: 'record.update', layout: this.layout.name, recordId: this.recordId, body: data, options });
|
|
53
|
+
if (res.scriptError) {
|
|
54
|
+
throw new FMError(res.scriptError, res.status ?? 200, res);
|
|
88
55
|
}
|
|
89
56
|
this.modId = +res.modId;
|
|
57
|
+
this.sessionBinding = binding;
|
|
90
58
|
this._onSave();
|
|
91
59
|
return this;
|
|
92
60
|
}
|
|
61
|
+
getFieldMetadata(fieldId) {
|
|
62
|
+
if (!this.layout.metadata) {
|
|
63
|
+
throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
|
|
64
|
+
}
|
|
65
|
+
let result = this.layout.metadata.fieldMetaData.find(i => i.name === fieldId);
|
|
66
|
+
if (!result)
|
|
67
|
+
throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
93
70
|
processPortalData(portalData) {
|
|
94
71
|
for (const portalName of Object.keys(portalData)) {
|
|
95
72
|
const _portal = new Portal(this, portalName);
|
|
@@ -97,7 +74,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
97
74
|
const fieldData = item;
|
|
98
75
|
delete fieldData.recordId;
|
|
99
76
|
delete fieldData.modId;
|
|
100
|
-
return new PortalRecord(this, _portal, parseInt(item.recordId), parseInt(item.modId), fieldData);
|
|
77
|
+
return new PortalRecord(this, _portal, parseInt(item.recordId), parseInt(item.modId), fieldData, this.sessionBinding);
|
|
101
78
|
});
|
|
102
79
|
// @ts-expect-error - This code is actually correct, but throws a typescript error
|
|
103
80
|
this.portals[portalName] = _portal;
|
|
@@ -111,87 +88,63 @@ export class LayoutRecord extends RecordBase {
|
|
|
111
88
|
* @throws {Error} If commit() has not been called.
|
|
112
89
|
* @throws {FMError} If the retrieval fails.
|
|
113
90
|
*/
|
|
114
|
-
async get() {
|
|
91
|
+
async get(portals = {}) {
|
|
115
92
|
if (this.recordId === -1) {
|
|
116
93
|
throw new Error('Cannot get this RecordBase until a commit() is done.');
|
|
117
94
|
}
|
|
118
95
|
if (!this.layout.metadata)
|
|
119
96
|
await this.layout.getLayoutMeta();
|
|
120
|
-
const
|
|
121
|
-
type: ApiRecordResponseObj
|
|
122
|
-
});
|
|
97
|
+
const { value: record, binding } = await this.layout.database.execute({ type: 'record.get', layout: this.layout.name, recordId: this.recordId, options: { scripts: {}, portals } });
|
|
123
98
|
// console.log(res, res.response.data)
|
|
124
|
-
this.
|
|
125
|
-
this.
|
|
99
|
+
this.sessionBinding = binding;
|
|
100
|
+
this.modId = +record.modId;
|
|
101
|
+
this.processFieldData(record.fieldData);
|
|
126
102
|
this.portalData = [];
|
|
127
|
-
if (
|
|
128
|
-
this.processPortalData(
|
|
103
|
+
if (record.portalData)
|
|
104
|
+
this.processPortalData(record.portalData);
|
|
129
105
|
return this;
|
|
130
106
|
}
|
|
131
107
|
/** Creates a duplicate of this record in FileMaker. */
|
|
132
108
|
async duplicate() {
|
|
133
109
|
const trace = new Error();
|
|
134
|
-
const res = await this.layout.database.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
recordId: z.string(),
|
|
138
|
-
modId: z.string(),
|
|
139
|
-
scriptError: z.string().optional()
|
|
140
|
-
})
|
|
141
|
-
});
|
|
142
|
-
if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
|
|
143
|
-
throw new FMError(res.scriptError, res.httpStatus, res, trace);
|
|
110
|
+
const { value: res, binding } = await this.layout.database.execute({ type: 'record.duplicate', layout: this.layout.name, recordId: this.recordId, options: emptyOperationOptions() });
|
|
111
|
+
if (res.scriptError) {
|
|
112
|
+
throw new FMError(res.scriptError, res.status ?? 200, res, trace);
|
|
144
113
|
}
|
|
145
114
|
const data = this.toObject((a) => true, (a) => true, (a) => false, (a) => false);
|
|
146
|
-
const _res = new LayoutRecord(this.layout, res.recordId, res.modId, data.fieldData, data.portalData);
|
|
115
|
+
const _res = new LayoutRecord(this.layout, res.recordId, res.modId, data.fieldData, data.portalData, [], binding);
|
|
147
116
|
this.emit('duplicated');
|
|
148
117
|
return _res;
|
|
149
118
|
}
|
|
150
119
|
/** Deletes this record from FileMaker. */
|
|
151
120
|
async delete() {
|
|
152
|
-
const res = await this.layout.database.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
scriptError: z.string().optional()
|
|
156
|
-
}).optional()
|
|
157
|
-
});
|
|
158
|
-
if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
|
|
159
|
-
throw new FMError(res.scriptError, res.httpStatus, res);
|
|
121
|
+
const { value: res } = await this.layout.database.execute({ type: 'record.delete', layout: this.layout.name, recordId: this.recordId, options: emptyOperationOptions() });
|
|
122
|
+
if (res.scriptError) {
|
|
123
|
+
throw new FMError(res.scriptError, res.status ?? 200, res);
|
|
160
124
|
}
|
|
161
125
|
this.emit('deleted');
|
|
162
126
|
}
|
|
127
|
+
async refreshContainer(fieldId) {
|
|
128
|
+
await this.get();
|
|
129
|
+
const field = this.fields[fieldId];
|
|
130
|
+
if (!(field instanceof ContainerField))
|
|
131
|
+
throw new Error(`Container field ${fieldId} was not returned while refreshing record`);
|
|
132
|
+
return field;
|
|
133
|
+
}
|
|
163
134
|
/** Returns this record's edited fields as a FileMaker API payload. */
|
|
164
|
-
fieldsToObject(filter = (a) => a.edited) {
|
|
135
|
+
fieldsToObject(filter = (a) => a instanceof ValueFieldBase && a.edited) {
|
|
165
136
|
const fieldsProcessed = {};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let value = field.value;
|
|
169
|
-
if (value instanceof Date) {
|
|
170
|
-
let _value = moment(value);
|
|
171
|
-
_value = _value
|
|
172
|
-
.utcOffset(this.layout.database.host.timezoneOffsetFunc(_value));
|
|
173
|
-
switch (field.metadata.result) {
|
|
174
|
-
case 'time':
|
|
175
|
-
value = _value.format(this.layout.database.host.timeFormat);
|
|
176
|
-
break;
|
|
177
|
-
case 'date':
|
|
178
|
-
value = _value.format(this.layout.database.host.dateFormat);
|
|
179
|
-
break;
|
|
180
|
-
default:
|
|
181
|
-
value = _value.format(this.layout.database.host.timeStampFormat);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
fieldsProcessed[field.id] = value;
|
|
137
|
+
for (const field of this.fieldsArray.filter(field => filter(field))) {
|
|
138
|
+
fieldsProcessed[field.id] = field.serializeRawValue();
|
|
185
139
|
}
|
|
186
|
-
|
|
140
|
+
return {
|
|
187
141
|
recordId: this.recordId.toString(),
|
|
188
142
|
modId: this.modId.toString(),
|
|
189
143
|
fieldData: fieldsProcessed
|
|
190
144
|
};
|
|
191
|
-
return obj;
|
|
192
145
|
}
|
|
193
146
|
/** Returns this record and its portal edits as a FileMaker API payload. */
|
|
194
|
-
toObject(filter = (a) => a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a.edited) {
|
|
147
|
+
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) {
|
|
195
148
|
const obj = {
|
|
196
149
|
...this.fieldsToObject(filter),
|
|
197
150
|
portalData: {}
|
|
@@ -209,3 +162,13 @@ export class LayoutRecord extends RecordBase {
|
|
|
209
162
|
return obj;
|
|
210
163
|
}
|
|
211
164
|
}
|
|
165
|
+
function emptyOperationOptions() {
|
|
166
|
+
return { scripts: {}, portals: {} };
|
|
167
|
+
}
|
|
168
|
+
function operationOptions(extra) {
|
|
169
|
+
const scripts = {};
|
|
170
|
+
for (const [name, script] of Object.entries(extra.scripts ?? {}))
|
|
171
|
+
if (script)
|
|
172
|
+
scripts[name] = { name: script.name, parameter: script.parameter ?? '' };
|
|
173
|
+
return { scripts, portals: {} };
|
|
174
|
+
}
|
package/dist/records/portal.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
*
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
|
|
2
1
|
import { type RecordBase } from './recordBase.js';
|
|
3
2
|
import { type LayoutRecord } from './layoutRecord.js';
|
|
4
|
-
import { type LayoutInterface } from '../layouts/layoutInterface.js';
|
|
3
|
+
import { type LayoutInterface, RecordFieldsMap } from '../layouts/layoutInterface.js';
|
|
5
4
|
export interface PortalBase<T extends RecordFieldsMap> {
|
|
6
5
|
readonly record: LayoutRecord<LayoutInterface>;
|
|
7
6
|
readonly name: string;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { RecordBase } from './recordBase.js';
|
|
2
2
|
import { type extraBodyOptions, RecordTypes } from '../types.js';
|
|
3
|
-
import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
|
|
4
3
|
import { type PortalBase } from './portalBase.js';
|
|
5
|
-
import
|
|
4
|
+
import z from "zod";
|
|
5
|
+
import { ApiFieldMetadata } from "../models/apiResults.js";
|
|
6
|
+
import { RecordFieldsMap } from "../layouts/layoutInterface.js";
|
|
7
|
+
import { Field } from "./fields/field.js";
|
|
8
|
+
import { ContainerField } from './fields/containerField.js';
|
|
9
|
+
import { type SessionBinding } from '../connection/databaseProvider.js';
|
|
6
10
|
/**
|
|
7
11
|
* Represents a PortalRecord, which is a record in a portal within a parent record.
|
|
8
12
|
* @template T - The type of the record's field map.
|
|
@@ -10,7 +14,8 @@ import { type Field, type FieldValue } from './field.js';
|
|
|
10
14
|
export declare class PortalRecord<T extends RecordFieldsMap> extends RecordBase<T> {
|
|
11
15
|
readonly portal: PortalBase<T>;
|
|
12
16
|
readonly type = RecordTypes.PORTAL;
|
|
13
|
-
constructor(record: RecordBase<any>, portal: PortalBase<any>, recordId: number, modId?: number, fieldData?: {});
|
|
17
|
+
constructor(record: RecordBase<any>, portal: PortalBase<any>, recordId: number, modId?: number, fieldData?: {}, sessionBinding?: SessionBinding);
|
|
18
|
+
refreshContainer(fieldId: string): Promise<ContainerField>;
|
|
14
19
|
_onSave(): void;
|
|
15
20
|
/**
|
|
16
21
|
* Commits the parent record, and in turn this one.
|
|
@@ -18,8 +23,9 @@ export declare class PortalRecord<T extends RecordFieldsMap> extends RecordBase<
|
|
|
18
23
|
* @param {extraBodyOptions} [extraBody={}] - The optional extra body options.
|
|
19
24
|
* @returns {Promise} - A promise that resolves when the record is committed.
|
|
20
25
|
*/
|
|
21
|
-
commit(extraBody?: extraBodyOptions): Promise<import("./layoutRecord.js").LayoutRecord<import("../
|
|
22
|
-
|
|
26
|
+
commit(extraBody?: extraBodyOptions): Promise<import("./layoutRecord.js").LayoutRecord<import("../layouts/layoutInterface.js").LayoutInterface>>;
|
|
27
|
+
getFieldMetadata(fieldId: string): z.infer<typeof ApiFieldMetadata>;
|
|
28
|
+
toObject(fieldFilter: (a: Field) => boolean): {
|
|
23
29
|
modId?: string;
|
|
24
30
|
recordId?: string;
|
|
25
31
|
} & Record<string, string>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { RecordBase } from './recordBase.js';
|
|
5
5
|
import { RecordTypes } from '../types.js';
|
|
6
|
+
import { ContainerField } from './fields/containerField.js';
|
|
6
7
|
/**
|
|
7
8
|
* Represents a PortalRecord, which is a record in a portal within a parent record.
|
|
8
9
|
* @template T - The type of the record's field map.
|
|
@@ -10,10 +11,19 @@ import { RecordTypes } from '../types.js';
|
|
|
10
11
|
export class PortalRecord extends RecordBase {
|
|
11
12
|
portal;
|
|
12
13
|
type = RecordTypes.PORTAL;
|
|
13
|
-
constructor(record, portal, recordId, modId = recordId, fieldData = {}) {
|
|
14
|
-
super(record.layout, recordId, modId, fieldData);
|
|
14
|
+
constructor(record, portal, recordId, modId = recordId, fieldData = {}, sessionBinding) {
|
|
15
|
+
super(record.layout, recordId, modId, fieldData, sessionBinding);
|
|
15
16
|
this.portal = portal;
|
|
16
17
|
}
|
|
18
|
+
async refreshContainer(fieldId) {
|
|
19
|
+
await this.portal.record.get({ [this.portal.name]: { offset: 1, limit: Math.max(1, this.portal.records.length) } });
|
|
20
|
+
const portal = this.portal.record.portalsArray.find(item => item.name === this.portal.name);
|
|
21
|
+
const record = portal?.records.find(item => item.recordId === this.recordId);
|
|
22
|
+
const field = record?.fields[fieldId];
|
|
23
|
+
if (!(field instanceof ContainerField))
|
|
24
|
+
throw new Error(`Container field ${fieldId} was not returned while refreshing record`);
|
|
25
|
+
return field;
|
|
26
|
+
}
|
|
17
27
|
_onSave() {
|
|
18
28
|
super._onSave();
|
|
19
29
|
this.portal.record._onSave();
|
|
@@ -27,6 +37,18 @@ export class PortalRecord extends RecordBase {
|
|
|
27
37
|
async commit(extraBody = {}) {
|
|
28
38
|
return await this.portal.record.commit(extraBody);
|
|
29
39
|
}
|
|
40
|
+
getFieldMetadata(fieldId) {
|
|
41
|
+
if (!this.layout.metadata) {
|
|
42
|
+
throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
|
|
43
|
+
}
|
|
44
|
+
const portalMetadata = this.portal
|
|
45
|
+
? this.layout.metadata.portalMetaData[this.portal.name] ?? []
|
|
46
|
+
: Object.values(this.layout.metadata.portalMetaData).flat();
|
|
47
|
+
const result = portalMetadata.find(i => i.name === fieldId);
|
|
48
|
+
if (!result)
|
|
49
|
+
throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
30
52
|
toObject(fieldFilter) {
|
|
31
53
|
const res = {
|
|
32
54
|
recordId: this.recordId === -1 ? undefined : this.recordId.toString(),
|