@jd-data-limited/easy-fm 4.1.15 → 5.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -197
- package/dist/bin/generateTypes.js +1 -1
- 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 +24 -8
- package/dist/connection/FMHost.js +28 -8
- package/dist/connection/HostBase.d.ts +4 -2
- 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 +18 -1
- package/dist/index.js +12 -0
- 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/layoutRecordManager.d.ts +6 -15
- package/dist/layouts/layoutRecordManager.js +6 -15
- package/dist/models/apiResults.d.ts +208 -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/getOperations/recordGetOperation.d.ts +7 -4
- package/dist/records/getOperations/recordGetOperation.js +15 -18
- package/dist/records/layoutRecord.d.ts +24 -7
- package/dist/records/layoutRecord.js +68 -73
- package/dist/records/portal.d.ts +0 -4
- package/dist/records/portal.js +0 -4
- package/dist/records/recordBase.d.ts +3 -2
- package/dist/types.d.ts +41 -17
- 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 +7 -0
- package/dist/utils/query.js +5 -0
- package/package.json +29 -26
|
@@ -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 { ApiRecordResponseObj } from '../models/apiResults.js';
|
|
8
9
|
import moment from 'moment';
|
|
10
|
+
import z from 'zod';
|
|
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,42 @@ 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
|
+
return this;
|
|
71
71
|
}
|
|
72
72
|
// for (let item of Object.keys(data)) extraBody[item] = data[item]
|
|
73
|
-
const res = await this.layout.database.
|
|
73
|
+
const res = await this.layout.database.fetchJSON(this.endpoint, {
|
|
74
74
|
method: 'PATCH',
|
|
75
|
-
body: JSON.stringify(data)
|
|
75
|
+
body: JSON.stringify(data),
|
|
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
|
+
})
|
|
76
85
|
});
|
|
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);
|
|
86
|
+
if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
|
|
87
|
+
throw new FMError(res.scriptError, res.httpStatus, res);
|
|
90
88
|
}
|
|
89
|
+
this.modId = +res.modId;
|
|
90
|
+
this._onSave();
|
|
91
|
+
return this;
|
|
91
92
|
}
|
|
92
93
|
processPortalData(portalData) {
|
|
93
94
|
for (const portalName of Object.keys(portalData)) {
|
|
@@ -103,8 +104,8 @@ export class LayoutRecord extends RecordBase {
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
/**
|
|
106
|
-
*
|
|
107
|
-
* Throws
|
|
107
|
+
* Reloads this record from FileMaker.
|
|
108
|
+
* Throws if this record has not been saved yet.
|
|
108
109
|
*
|
|
109
110
|
* @return {Promise<this>} A Promise that resolves to this RecordBase instance if the record is successfully retrieved.
|
|
110
111
|
* @throws {Error} If commit() has not been called.
|
|
@@ -116,57 +117,50 @@ export class LayoutRecord extends RecordBase {
|
|
|
116
117
|
}
|
|
117
118
|
if (!this.layout.metadata)
|
|
118
119
|
await this.layout.getLayoutMeta();
|
|
119
|
-
const res = await this.layout.database.
|
|
120
|
-
|
|
120
|
+
const res = await this.layout.database.fetchJSON(this.endpoint, {
|
|
121
|
+
type: ApiRecordResponseObj
|
|
121
122
|
});
|
|
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
|
-
}
|
|
123
|
+
// console.log(res, res.response.data)
|
|
124
|
+
this.modId = +res.data[0].modId;
|
|
125
|
+
this.processFieldData(res.data[0].fieldData);
|
|
126
|
+
this.portalData = [];
|
|
127
|
+
if (res.data[0].portalData)
|
|
128
|
+
this.processPortalData(res.data[0].portalData);
|
|
129
|
+
return this;
|
|
134
130
|
}
|
|
131
|
+
/** Creates a duplicate of this record in FileMaker. */
|
|
135
132
|
async duplicate() {
|
|
136
133
|
const trace = new Error();
|
|
137
|
-
const res = await this.layout.database.
|
|
138
|
-
method: 'POST'
|
|
134
|
+
const res = await this.layout.database.fetchJSON(this.endpoint, {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
type: z.object({
|
|
137
|
+
recordId: z.string(),
|
|
138
|
+
modId: z.string(),
|
|
139
|
+
scriptError: z.string().optional()
|
|
140
|
+
})
|
|
139
141
|
});
|
|
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);
|
|
142
|
+
if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
|
|
143
|
+
throw new FMError(res.scriptError, res.httpStatus, res, trace);
|
|
154
144
|
}
|
|
145
|
+
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);
|
|
147
|
+
this.emit('duplicated');
|
|
148
|
+
return _res;
|
|
155
149
|
}
|
|
150
|
+
/** Deletes this record from FileMaker. */
|
|
156
151
|
async delete() {
|
|
157
|
-
const res = await this.layout.database.
|
|
158
|
-
method: 'DELETE'
|
|
152
|
+
const res = await this.layout.database.fetchJSON(this.endpoint, {
|
|
153
|
+
method: 'DELETE',
|
|
154
|
+
type: z.object({
|
|
155
|
+
scriptError: z.string().optional()
|
|
156
|
+
}).optional()
|
|
159
157
|
});
|
|
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);
|
|
158
|
+
if (typeof res.scriptError !== 'undefined' && res.scriptError !== '0') {
|
|
159
|
+
throw new FMError(res.scriptError, res.httpStatus, res);
|
|
168
160
|
}
|
|
161
|
+
this.emit('deleted');
|
|
169
162
|
}
|
|
163
|
+
/** Returns this record's edited fields as a FileMaker API payload. */
|
|
170
164
|
fieldsToObject(filter = (a) => a.edited) {
|
|
171
165
|
const fieldsProcessed = {};
|
|
172
166
|
let field;
|
|
@@ -196,6 +190,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
196
190
|
};
|
|
197
191
|
return obj;
|
|
198
192
|
}
|
|
193
|
+
/** Returns this record and its portal edits as a FileMaker API payload. */
|
|
199
194
|
toObject(filter = (a) => a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a.edited) {
|
|
200
195
|
const obj = {
|
|
201
196
|
...this.fieldsToObject(filter),
|
package/dist/records/portal.d.ts
CHANGED
|
@@ -6,8 +6,6 @@ import { type LayoutRecord } from './layoutRecord.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 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
|
*/
|
|
@@ -4,6 +4,7 @@ import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
|
|
|
4
4
|
import { type LayoutBase } from '../layouts/layoutBase.js';
|
|
5
5
|
import { type ApiFieldData } from '../models/apiResults.js';
|
|
6
6
|
import { Field, type FieldValue } from './field.js';
|
|
7
|
+
import { type z } from 'zod';
|
|
7
8
|
export declare abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter {
|
|
8
9
|
readonly layout: LayoutBase;
|
|
9
10
|
readonly type: RecordTypes;
|
|
@@ -16,7 +17,7 @@ export declare abstract class RecordBase<T extends RecordFieldsMap> extends Even
|
|
|
16
17
|
*/
|
|
17
18
|
fields: T;
|
|
18
19
|
protected portalData: any[];
|
|
19
|
-
protected constructor(layout: LayoutBase, recordId: number, modId: number | undefined, fieldData: ApiFieldData);
|
|
20
|
+
protected constructor(layout: LayoutBase, recordId: number, modId: number | undefined, fieldData: z.infer<typeof ApiFieldData>);
|
|
20
21
|
get endpoint(): string;
|
|
21
22
|
/**
|
|
22
23
|
* A boolean indicating whether this record has been modified and should be committed
|
|
@@ -25,6 +26,6 @@ export declare abstract class RecordBase<T extends RecordFieldsMap> extends Even
|
|
|
25
26
|
*/
|
|
26
27
|
get edited(): boolean;
|
|
27
28
|
get fieldsArray(): Array<Field<FieldValue>>;
|
|
28
|
-
protected processFieldData(fieldData: ApiFieldData): T;
|
|
29
|
+
protected processFieldData(fieldData: z.infer<typeof ApiFieldData>): T;
|
|
29
30
|
_onSave(): void;
|
|
30
31
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import type * as http from 'http';
|
|
2
2
|
import { type FMError } from './FMError.js';
|
|
3
3
|
import { type LayoutInterface } from './layouts/layoutInterface.js';
|
|
4
|
+
import z from "zod";
|
|
4
5
|
export type PickPortals<LAYOUT extends LayoutInterface, PORTALS extends string | number | symbol = ''> = Omit<LAYOUT, 'portals'> & {
|
|
5
6
|
portals: Pick<LAYOUT['portals'], PORTALS>;
|
|
6
7
|
};
|
|
7
|
-
|
|
8
|
+
/** Shared database connection options. */
|
|
9
|
+
export interface databaseOptionsBase<CREDENTAILS = loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken> {
|
|
8
10
|
database: string;
|
|
9
11
|
debug?: boolean;
|
|
10
|
-
credentials:
|
|
12
|
+
credentials: CREDENTAILS;
|
|
11
13
|
}
|
|
12
|
-
|
|
14
|
+
/** Database options including any external FileMaker sources. */
|
|
15
|
+
export interface databaseOptionsWithExternalSources<CREDENTIALS = loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken> extends databaseOptionsBase<CREDENTIALS> {
|
|
13
16
|
database: string;
|
|
14
17
|
debug?: boolean;
|
|
15
|
-
credentials:
|
|
16
|
-
externalSources: databaseOptionsBase
|
|
18
|
+
credentials: CREDENTIALS;
|
|
19
|
+
externalSources: Array<databaseOptionsBase<loginOptionsFileMaker>>;
|
|
17
20
|
}
|
|
21
|
+
/** Use an existing FileMaker Data API token. */
|
|
18
22
|
export interface loginOptionsToken {
|
|
19
23
|
method: 'token';
|
|
20
24
|
token: string;
|
|
21
25
|
}
|
|
26
|
+
/** Use FileMaker Data API OAuth headers to create session. */
|
|
22
27
|
export interface loginOptionsOAuth {
|
|
23
28
|
method: 'oauth';
|
|
24
29
|
oauth: {
|
|
@@ -26,27 +31,36 @@ export interface loginOptionsOAuth {
|
|
|
26
31
|
requestIdentifier: string;
|
|
27
32
|
};
|
|
28
33
|
}
|
|
34
|
+
/** Use standard FileMaker username/password login. */
|
|
29
35
|
export interface loginOptionsFileMaker {
|
|
30
36
|
method: 'filemaker';
|
|
31
37
|
username: string;
|
|
32
38
|
password: string;
|
|
39
|
+
/**
|
|
40
|
+
* sessionPoolSize - The maximum number of sessions to keep open in the pool. Default is 4.
|
|
41
|
+
*/
|
|
42
|
+
sessionPoolSize?: number;
|
|
33
43
|
}
|
|
44
|
+
/** Use Claris authentication for session creation. */
|
|
34
45
|
export interface loginOptionsClaris {
|
|
35
46
|
method: 'claris';
|
|
36
47
|
claris: {
|
|
37
48
|
fmid: string;
|
|
38
49
|
};
|
|
39
50
|
}
|
|
51
|
+
/** Portal fetch paging options for one portal. */
|
|
40
52
|
export interface portalFetchData<T = string> {
|
|
41
53
|
portalName: T;
|
|
42
54
|
offset: number;
|
|
43
55
|
limit: number;
|
|
44
56
|
}
|
|
57
|
+
/** FileMaker scripts to run before, during, or after request processing. */
|
|
45
58
|
export interface ScriptRequestData {
|
|
46
59
|
prerequest?: Script;
|
|
47
60
|
presort?: Script;
|
|
48
61
|
after?: Script;
|
|
49
62
|
}
|
|
63
|
+
/** Additional body options accepted by write operations. */
|
|
50
64
|
export interface extraBodyOptions {
|
|
51
65
|
scripts?: ScriptRequestData;
|
|
52
66
|
options?: {
|
|
@@ -68,10 +82,12 @@ export interface extraBodyOptions {
|
|
|
68
82
|
recordId: number;
|
|
69
83
|
}>;
|
|
70
84
|
}
|
|
85
|
+
/** Simple script reference with optional parameter. */
|
|
71
86
|
export interface Script {
|
|
72
87
|
name: string;
|
|
73
88
|
parameter: string;
|
|
74
89
|
}
|
|
90
|
+
/** Raw record payload shape returned by FileMaker Data API. */
|
|
75
91
|
export interface recordObject {
|
|
76
92
|
recordId: number;
|
|
77
93
|
modId: number;
|
|
@@ -79,6 +95,7 @@ export interface recordObject {
|
|
|
79
95
|
portalData?: portalDataObject;
|
|
80
96
|
}
|
|
81
97
|
export type portalDataObject = Record<string, recordObject>;
|
|
98
|
+
/** Field metadata returned by layout metadata endpoint. */
|
|
82
99
|
export interface FieldMetaData {
|
|
83
100
|
name: string;
|
|
84
101
|
type: string;
|
|
@@ -95,38 +112,45 @@ export interface FieldMetaData {
|
|
|
95
112
|
repetitionStart: number;
|
|
96
113
|
repetitionEnd: number;
|
|
97
114
|
}
|
|
115
|
+
/** Layout metadata for top-level fields and portals. */
|
|
98
116
|
export interface LayoutMetaData {
|
|
99
117
|
fieldMetaData: FieldMetaData[];
|
|
100
118
|
portalMetaData?: Record<string, FieldMetaData[]>;
|
|
101
119
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
120
|
+
/** FileMaker host metadata returned by `/productInfo`. */
|
|
121
|
+
export declare const FMHostMetadata: z.ZodObject<{
|
|
122
|
+
productInfo: z.ZodObject<{
|
|
123
|
+
buildDate: z.ZodOptional<z.z.ZodCoercedDate<unknown>>;
|
|
124
|
+
name: z.ZodString;
|
|
125
|
+
version: z.ZodOptional<z.ZodString>;
|
|
126
|
+
dateFormat: z.ZodString;
|
|
127
|
+
timeFormat: z.ZodString;
|
|
128
|
+
timeStampFormat: z.ZodString;
|
|
129
|
+
}, z.z.core.$strip>;
|
|
130
|
+
}, z.z.core.$strip>;
|
|
131
|
+
/** Result returned when a script executes through API call. */
|
|
112
132
|
export interface ScriptResult {
|
|
113
133
|
scriptError?: FMError;
|
|
114
134
|
scriptResult?: string;
|
|
115
135
|
}
|
|
136
|
+
/** Container download result with payload and response metadata. */
|
|
116
137
|
export interface ContainerBufferResult {
|
|
117
138
|
buffer: Buffer;
|
|
118
139
|
mime?: string;
|
|
119
140
|
request: http.IncomingMessage;
|
|
120
141
|
}
|
|
121
|
-
|
|
142
|
+
/** Standard JSON request headers with bearer authorization. */
|
|
143
|
+
export interface AuthorizationHeaders extends Record<string, string> {
|
|
122
144
|
'Content-Type': 'application/json';
|
|
123
145
|
Authorization: string;
|
|
124
146
|
}
|
|
125
|
-
|
|
147
|
+
/** OAuth request headers required by FileMaker Data API. */
|
|
148
|
+
export interface AuthorizationHeadersOAuth extends Record<string, string> {
|
|
126
149
|
'Content-Type': 'application/json';
|
|
127
150
|
'X-FM-Data-OAuth-RequestId': string;
|
|
128
151
|
'X-FM-Data-OAuth-Identifier': string;
|
|
129
152
|
}
|
|
153
|
+
/** Options used when creating empty record shells. */
|
|
130
154
|
export interface RecordFetchOptions {
|
|
131
155
|
readonly portals: readonly string[];
|
|
132
156
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (c) 2022-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
|
+
import z from "zod";
|
|
5
|
+
/** FileMaker host metadata returned by `/productInfo`. */
|
|
6
|
+
export const FMHostMetadata = z.object({
|
|
7
|
+
productInfo: z.object({
|
|
8
|
+
buildDate: z.coerce.date().optional(),
|
|
9
|
+
name: z.string(),
|
|
10
|
+
version: z.string().optional(),
|
|
11
|
+
dateFormat: z.string(),
|
|
12
|
+
timeFormat: z.string(),
|
|
13
|
+
timeStampFormat: z.string()
|
|
14
|
+
})
|
|
15
|
+
});
|
|
4
16
|
export var RecordTypes;
|
|
5
17
|
(function (RecordTypes) {
|
|
6
18
|
RecordTypes[RecordTypes["UNKNOWN"] = 0] = "UNKNOWN";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* addHeaders adds headers to the inputted request header object
|
|
3
|
+
* @param headers
|
|
4
|
+
*/
|
|
5
|
+
export function addHeaders(req, headers) {
|
|
6
|
+
if (!req.headers) {
|
|
7
|
+
req.headers = headers;
|
|
8
|
+
}
|
|
9
|
+
else if (req.headers instanceof Headers) {
|
|
10
|
+
for (const key of Object.keys(headers)) {
|
|
11
|
+
req.headers.set(key, headers[key]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else if (Array.isArray(req.headers)) {
|
|
15
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
16
|
+
req.headers.push([key, value]);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
for (const key of Object.keys(headers)) {
|
|
21
|
+
req.headers[key] = headers[key];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/utils/query.d.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { type Moment } from 'moment';
|
|
2
2
|
export declare const FindRequestSymbol: unique symbol;
|
|
3
|
+
/** Represents a date, time, or timestamp value passed into `query(...)`. */
|
|
3
4
|
export interface TimestampType {
|
|
4
5
|
type: 'date' | 'time' | 'timestamp';
|
|
5
6
|
moment: Moment;
|
|
6
7
|
}
|
|
7
8
|
type QueryParameter = string | number | TimestampType;
|
|
9
|
+
/** Represents a FileMaker find value produced by `query(...)`. */
|
|
8
10
|
export interface Query {
|
|
9
11
|
[FindRequestSymbol]: Array<string | TimestampType>;
|
|
10
12
|
}
|
|
13
|
+
/** Escapes special characters in a raw FileMaker find string. */
|
|
11
14
|
export declare function queryEscape(str: string): string;
|
|
15
|
+
/** Builds a FileMaker find value using template-string syntax. */
|
|
12
16
|
export declare function query(strings: TemplateStringsArray, ...args: QueryParameter[]): Query;
|
|
17
|
+
/** Marks a value as a FileMaker date for use inside `query(...)`. */
|
|
13
18
|
export declare function asDate(date: Date | Moment): TimestampType;
|
|
19
|
+
/** Marks a value as a FileMaker time for use inside `query(...)`. */
|
|
14
20
|
export declare function asTime(date: Date | Moment): TimestampType;
|
|
21
|
+
/** Marks a value as a FileMaker timestamp for use inside `query(...)`. */
|
|
15
22
|
export declare function asTimestamp(date: Date | Moment): TimestampType;
|
|
16
23
|
export {};
|
package/dist/utils/query.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
import moment from 'moment';
|
|
5
5
|
export const FindRequestSymbol = Symbol('easyfm-findrequest');
|
|
6
6
|
const SPECIAL_CHARACTERS = ['\\', '=', '<', '≤', '≥', '>', '…', '...', '//', '@', '#', '*', '"', '~'];
|
|
7
|
+
/** Escapes special characters in a raw FileMaker find string. */
|
|
7
8
|
export function queryEscape(str) {
|
|
8
9
|
for (const char of SPECIAL_CHARACTERS) {
|
|
9
10
|
str = str.replace(char, `\\${char}`);
|
|
10
11
|
}
|
|
11
12
|
return str;
|
|
12
13
|
}
|
|
14
|
+
/** Builds a FileMaker find value using template-string syntax. */
|
|
13
15
|
export function query(strings, ...args) {
|
|
14
16
|
const argStrings = args.map(item => {
|
|
15
17
|
if (typeof item === 'number') {
|
|
@@ -26,18 +28,21 @@ export function query(strings, ...args) {
|
|
|
26
28
|
}).flat(1);
|
|
27
29
|
return { [FindRequestSymbol]: query };
|
|
28
30
|
}
|
|
31
|
+
/** Marks a value as a FileMaker date for use inside `query(...)`. */
|
|
29
32
|
export function asDate(date) {
|
|
30
33
|
return {
|
|
31
34
|
type: 'date',
|
|
32
35
|
moment: moment(date)
|
|
33
36
|
};
|
|
34
37
|
}
|
|
38
|
+
/** Marks a value as a FileMaker time for use inside `query(...)`. */
|
|
35
39
|
export function asTime(date) {
|
|
36
40
|
return {
|
|
37
41
|
type: 'time',
|
|
38
42
|
moment: moment(date)
|
|
39
43
|
};
|
|
40
44
|
}
|
|
45
|
+
/** Marks a value as a FileMaker timestamp for use inside `query(...)`. */
|
|
41
46
|
export function asTimestamp(date) {
|
|
42
47
|
return {
|
|
43
48
|
type: 'timestamp',
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jd-data-limited/easy-fm",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.1",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"follow-redirects": "^1.15.5",
|
|
9
|
-
"moment": "^2.29.4",
|
|
10
|
-
"node-fetch": "^3.3.2",
|
|
11
|
-
"node-fetch-cookies": "^2.1.1"
|
|
5
|
+
"commander": "^14.0.3",
|
|
6
|
+
"moment": "^2.30.1",
|
|
7
|
+
"zod": "^4.4.3"
|
|
12
8
|
},
|
|
13
9
|
"type": "module",
|
|
14
10
|
"main": "dist/index.js",
|
|
@@ -41,37 +37,44 @@
|
|
|
41
37
|
"url": "https://github.com/JD-Data-Limited/easy-fm/issues"
|
|
42
38
|
},
|
|
43
39
|
"engines": {
|
|
44
|
-
"node": ">=
|
|
40
|
+
"node": ">=22.22.3"
|
|
45
41
|
},
|
|
46
42
|
"devDependencies": {
|
|
47
|
-
"@babel/cli": "^7.
|
|
48
|
-
"@babel/core": "^7.
|
|
49
|
-
"@babel/preset-env": "^7.
|
|
50
|
-
"@babel/preset-typescript": "^7.
|
|
51
|
-
"@types/btoa": "^1.2.
|
|
52
|
-
"@types/events": "^3.0.
|
|
53
|
-
"@types/inquirer": "^9.0.
|
|
54
|
-
"@types/jest": "^29.5.
|
|
43
|
+
"@babel/cli": "^7.29.7",
|
|
44
|
+
"@babel/core": "^7.29.7",
|
|
45
|
+
"@babel/preset-env": "^7.29.7",
|
|
46
|
+
"@babel/preset-typescript": "^7.29.7",
|
|
47
|
+
"@types/btoa": "^1.2.5",
|
|
48
|
+
"@types/events": "^3.0.3",
|
|
49
|
+
"@types/inquirer": "^9.0.10",
|
|
50
|
+
"@types/jest": "^29.5.14",
|
|
51
|
+
"@types/node": "^22.19.21",
|
|
55
52
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
56
53
|
"babel-jest": "^29.7.0",
|
|
57
|
-
"dotenv": "^16.
|
|
58
|
-
"eslint": "^8.57.
|
|
54
|
+
"dotenv": "^16.6.1",
|
|
55
|
+
"eslint": "^8.57.1",
|
|
59
56
|
"eslint-config-standard-with-typescript": "^43.0.1",
|
|
60
|
-
"eslint-plugin-import": "^2.
|
|
57
|
+
"eslint-plugin-import": "^2.32.0",
|
|
61
58
|
"eslint-plugin-n": "^16.6.2",
|
|
62
|
-
"eslint-plugin-promise": "^6.
|
|
63
|
-
"inquirer": "^9.
|
|
59
|
+
"eslint-plugin-promise": "^6.6.0",
|
|
60
|
+
"inquirer": "^9.3.8",
|
|
64
61
|
"jest": "^29.7.0",
|
|
65
|
-
"ts-jest": "^29.
|
|
62
|
+
"ts-jest": "^29.4.11",
|
|
66
63
|
"ts-morph": "^21.0.1",
|
|
67
|
-
"tsx": "^4.
|
|
68
|
-
"
|
|
64
|
+
"tsx": "^4.22.4",
|
|
65
|
+
"typedoc": "^0.28.19",
|
|
66
|
+
"typescript": "^5.9.3"
|
|
69
67
|
},
|
|
70
68
|
"bin": {
|
|
71
69
|
"easyfm": "dist/bin/cli.js"
|
|
72
70
|
},
|
|
73
71
|
"scripts": {
|
|
74
72
|
"build": "tsc",
|
|
75
|
-
"test": "npm run build && jest"
|
|
73
|
+
"test": "npm run build && jest",
|
|
74
|
+
"test:coverage": "jest --coverage --testPathIgnorePatterns='/__tests__/database.test.ts$'",
|
|
75
|
+
"test:integration": "npm run build && jest __tests__/database.test.ts --runInBand",
|
|
76
|
+
"test:unit": "jest __tests__/query.test.ts __tests__/CookieJar.test.ts --runInBand",
|
|
77
|
+
"stress:search": "tsx src/bin/stressSearch.ts",
|
|
78
|
+
"docs:build": "typedoc --options ./docs/theme.json"
|
|
76
79
|
}
|
|
77
80
|
}
|