@jd-data-limited/easy-fm 4.0.5 → 4.0.8
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 +27 -19
- package/package.json +2 -2
- package/src/connection/database.d.ts +3 -2
- package/src/connection/database.js +18 -11
- package/src/connection/database.ts +18 -11
- package/src/connection/databaseBase.d.ts +2 -1
- package/src/connection/databaseBase.ts +2 -1
- package/src/layouts/layout.js +2 -3
- package/src/layouts/layout.ts +2 -3
- package/src/records/fieldBase.js +3 -2
- package/src/records/fieldBase.ts +2 -2
- package/src/records/getOperations/recordGetOperation.d.ts +3 -1
- package/src/records/getOperations/recordGetOperation.js +16 -2
- package/src/records/getOperations/recordGetOperation.ts +15 -3
- package/src/records/layoutRecord.js +5 -10
- package/src/records/layoutRecord.ts +5 -10
- package/src/utils/query.d.ts +7 -1
- package/src/utils/query.js +2 -1
- package/src/utils/query.ts +3 -1
- package/src/records/field.d.ts +0 -6
- package/src/records/field.js +0 -11
- package/src/utils/fmQuery.d.ts +0 -4
- package/src/utils/fmQuery.js +0 -20
package/README.md
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Introduction
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A FileMaker Data API client for NodeJS
|
|
4
4
|
|
|
5
5
|
easy-fm is a Node.js module that allows you to interact with
|
|
6
|
-
a
|
|
7
|
-
or
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
a FileMaker database stored on a FileMaker server
|
|
7
|
+
or FileMaker Cloud. This module interacts with your server using the
|
|
8
|
+
FileMaker Data API.
|
|
9
|
+
|
|
10
|
+
# Contents
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
<!-- TOC -->
|
|
13
|
+
* [Introduction](#introduction)
|
|
14
|
+
* [Contents](#contents)
|
|
15
|
+
* [Installation](#installation)
|
|
16
|
+
* [Usage](#usage)
|
|
17
|
+
* [Connecting to a database](#connecting-to-a-database)
|
|
18
|
+
* [Getting records](#getting-records)
|
|
19
|
+
* [Fetch a range of records](#fetch-a-range-of-records)
|
|
20
|
+
* [Searching for records](#searching-for-records)
|
|
21
|
+
* [Fetch a record using its record ID (NOT RECOMMENDED)](#fetch-a-record-using-its-record-id-not-recommended)
|
|
22
|
+
* [Create a record](#create-a-record)
|
|
23
|
+
* [Modify a record](#modify-a-record)
|
|
21
24
|
* [Field names](#field-names)
|
|
22
25
|
* [Portal names](#portal-names)
|
|
23
26
|
* [Typescript Implementation](#typescript-implementation)
|
|
24
|
-
|
|
25
27
|
<!-- TOC -->
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
# Installation
|
|
30
|
+
|
|
31
|
+
```npm
|
|
32
|
+
npm install @jd-data-limited/easy-fm --save
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
easy-fm also requires the following to be configured within your FileMaker enviroment:
|
|
28
36
|
|
|
29
37
|
1. Enable the FileMaker Data API from the server's admin console. This setting is located
|
|
30
38
|
in `Connectors > FileMaker Data API`.
|
|
@@ -33,7 +41,7 @@ or [FileMaker Cloud](https://store.claris.com/filemaker-cloud). This module inte
|
|
|
33
41
|
|
|
34
42
|
---
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
# Usage
|
|
37
45
|
|
|
38
46
|
- You need to know what your server's UTC time offset (in minutes) is.
|
|
39
47
|
- Running `0 - (new Date()).getTimezoneOffset()` in javascript will give you the UTC time offset for your current
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jd-data-limited/easy-fm",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.8",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"follow-redirects": "^1.15.5",
|
|
6
|
+
"@types/follow-redirects": "^1.14.4",
|
|
6
7
|
"moment": "^2.29.4"
|
|
7
8
|
},
|
|
8
9
|
"type": "module",
|
|
@@ -35,7 +36,6 @@
|
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/btoa": "^1.2.3",
|
|
37
38
|
"@types/events": "^3.0.0",
|
|
38
|
-
"@types/follow-redirects": "^1.14.4",
|
|
39
39
|
"@types/inquirer": "^9.0.7",
|
|
40
40
|
"dotenv": "^16.3.1",
|
|
41
41
|
"inquirer": "^9.2.13",
|
|
@@ -18,10 +18,11 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
|
|
|
18
18
|
constructor(host: HostBase, conn: databaseOptionsWithExternalSources);
|
|
19
19
|
private generateExternalSourceLogin;
|
|
20
20
|
logout(): Promise<void>;
|
|
21
|
-
login(): Promise<any>;
|
|
21
|
+
login(forceLogin?: boolean): Promise<any>;
|
|
22
22
|
get token(): any;
|
|
23
23
|
get endpoint(): string;
|
|
24
|
-
|
|
24
|
+
apiRequestRaw(url: string | Request, options?: any, autoRelogin?: boolean): Promise<Response>;
|
|
25
|
+
apiRequestJSON<T = any>(url: string | Request, options?: any): Promise<ApiResults<T>>;
|
|
25
26
|
listLayouts(): Promise<Layout<LayoutInterface>[]>;
|
|
26
27
|
getLayout<R extends keyof T["layouts"]>(name: R): Layout<T["layouts"][R]>;
|
|
27
28
|
getLayout<R extends LayoutInterface>(name: string): Layout<R>;
|
|
@@ -44,11 +44,11 @@ export class Database extends EventEmitter {
|
|
|
44
44
|
// console.log(data)
|
|
45
45
|
this._token = null;
|
|
46
46
|
}
|
|
47
|
-
async login() {
|
|
47
|
+
async login(forceLogin = false) {
|
|
48
48
|
// Reset cookies
|
|
49
49
|
this.cookies = {};
|
|
50
50
|
await this.host.getMetadata();
|
|
51
|
-
if (this.token)
|
|
51
|
+
if (this.token && !forceLogin)
|
|
52
52
|
throw new Error("Already logged in. Run logout() first");
|
|
53
53
|
if (this.connection_details.credentials.method === "token") {
|
|
54
54
|
this._token = this.connection_details.credentials.token;
|
|
@@ -85,10 +85,11 @@ export class Database extends EventEmitter {
|
|
|
85
85
|
get endpoint() {
|
|
86
86
|
return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`;
|
|
87
87
|
}
|
|
88
|
-
async
|
|
88
|
+
async apiRequestRaw(url, options = {}, autoRelogin = true) {
|
|
89
|
+
if (!this.token)
|
|
90
|
+
await this.login(true);
|
|
89
91
|
if (!options.headers)
|
|
90
92
|
options.headers = {};
|
|
91
|
-
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
|
|
92
93
|
options.headers["authorization"] = "Bearer " + this._token;
|
|
93
94
|
options.rejectUnauthorized = this.host.verify;
|
|
94
95
|
let _fetch = await fetch(url, options);
|
|
@@ -98,24 +99,30 @@ export class Database extends EventEmitter {
|
|
|
98
99
|
this.cookies[cookie_split[0]] = cookie_split[1];
|
|
99
100
|
}
|
|
100
101
|
}
|
|
102
|
+
if (_fetch.status === 401 && autoRelogin) {
|
|
103
|
+
await this.login(true);
|
|
104
|
+
return await this.apiRequestRaw(url, options, false);
|
|
105
|
+
}
|
|
106
|
+
return _fetch;
|
|
107
|
+
}
|
|
108
|
+
async apiRequestJSON(url, options = {}) {
|
|
109
|
+
if (!options.headers)
|
|
110
|
+
options.headers = {};
|
|
111
|
+
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
|
|
112
|
+
let _fetch = await this.apiRequestRaw(url, options);
|
|
101
113
|
let data = await _fetch.json();
|
|
102
114
|
// Remove response if it is empty. This makes checking for an empty response easier
|
|
103
115
|
if (data.response && Object.keys(data.response).length === 0)
|
|
104
116
|
delete data.response;
|
|
105
117
|
// console.log(data.messages[0])
|
|
106
|
-
if (data.messages[0].code
|
|
107
|
-
this._token = null;
|
|
108
|
-
await this.login();
|
|
109
|
-
return await this.apiRequest(url, options, false);
|
|
110
|
-
}
|
|
111
|
-
else if (data.messages[0].code !== "0") {
|
|
118
|
+
if (data.messages[0].code !== "0") {
|
|
112
119
|
throw new FMError(data.messages[0].code, _fetch.status, data);
|
|
113
120
|
}
|
|
114
121
|
data.httpStatus = _fetch.status;
|
|
115
122
|
return data;
|
|
116
123
|
}
|
|
117
124
|
async listLayouts() {
|
|
118
|
-
let req = await this.
|
|
125
|
+
let req = await this.apiRequestJSON(`${this.endpoint}/layouts?page=2`);
|
|
119
126
|
if (!req.response)
|
|
120
127
|
throw new FMError(req.messages[0].code, req.httpStatus, req.messages[0].message);
|
|
121
128
|
const cycleLayoutNames = (layouts) => {
|
|
@@ -65,12 +65,12 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
65
65
|
this._token = null
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
async login() {
|
|
68
|
+
async login(forceLogin = false) {
|
|
69
69
|
// Reset cookies
|
|
70
70
|
this.cookies = {}
|
|
71
71
|
|
|
72
72
|
await this.host.getMetadata()
|
|
73
|
-
if (this.token) throw new Error("Already logged in. Run logout() first")
|
|
73
|
+
if (this.token && !forceLogin) throw new Error("Already logged in. Run logout() first")
|
|
74
74
|
|
|
75
75
|
if (this.connection_details.credentials.method === "token") {
|
|
76
76
|
this._token = (<loginOptionsToken>this.connection_details.credentials).token
|
|
@@ -110,9 +110,10 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
110
110
|
return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
async
|
|
113
|
+
async apiRequestRaw(url: string | Request, options: any = {}, autoRelogin = true): Promise<Response> {
|
|
114
|
+
if (!this.token) await this.login(true)
|
|
115
|
+
|
|
114
116
|
if (!options.headers) options.headers = {}
|
|
115
|
-
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
|
|
116
117
|
options.headers["authorization"] = "Bearer " + this._token
|
|
117
118
|
options.rejectUnauthorized = this.host.verify
|
|
118
119
|
|
|
@@ -123,18 +124,24 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
123
124
|
this.cookies[cookie_split[0]] = cookie_split[1]
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
|
|
128
|
+
if (_fetch.status === 401 && autoRelogin) {
|
|
129
|
+
await this.login(true)
|
|
130
|
+
return await this.apiRequestRaw(url, options, false)
|
|
131
|
+
}
|
|
132
|
+
return _fetch
|
|
133
|
+
}
|
|
134
|
+
async apiRequestJSON<T = any>(url: string | Request, options: any = {}): Promise<ApiResults<T>> {
|
|
135
|
+
if (!options.headers) options.headers = {}
|
|
136
|
+
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
|
|
137
|
+
let _fetch = await this.apiRequestRaw(url, options)
|
|
126
138
|
let data = await _fetch.json() as ApiResults<T>
|
|
127
139
|
|
|
128
140
|
// Remove response if it is empty. This makes checking for an empty response easier
|
|
129
141
|
if (data.response && Object.keys(data.response).length === 0) delete data.response
|
|
130
142
|
|
|
131
143
|
// console.log(data.messages[0])
|
|
132
|
-
if (data.messages[0].code
|
|
133
|
-
this._token = null
|
|
134
|
-
await this.login()
|
|
135
|
-
return await this.apiRequest(url, options, false)
|
|
136
|
-
}
|
|
137
|
-
else if (data.messages[0].code !== "0") {
|
|
144
|
+
if (data.messages[0].code !== "0") {
|
|
138
145
|
throw new FMError(data.messages[0].code, _fetch.status, data)
|
|
139
146
|
}
|
|
140
147
|
|
|
@@ -143,7 +150,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
async listLayouts() {
|
|
146
|
-
let req = await this.
|
|
153
|
+
let req = await this.apiRequestJSON<{layouts: ApiLayout[]}>(`${this.endpoint}/layouts?page=2`)
|
|
147
154
|
if (!req.response) throw new FMError(req.messages[0].code, req.httpStatus, req.messages[0].message)
|
|
148
155
|
|
|
149
156
|
const cycleLayoutNames = (layouts: ApiLayout[]) => {
|
|
@@ -6,6 +6,7 @@ export interface DatabaseBase {
|
|
|
6
6
|
host: HostBase;
|
|
7
7
|
readonly name: string;
|
|
8
8
|
endpoint: string;
|
|
9
|
-
|
|
9
|
+
apiRequestJSON<T = unknown>(url: string | Request, options?: RequestInit | undefined, autoRelogin?: boolean): Promise<ApiResults<T>>;
|
|
10
|
+
apiRequestRaw(url: string | Request, options?: RequestInit | undefined): Promise<Response>;
|
|
10
11
|
streamURL(url: string): Promise<IncomingMessage>;
|
|
11
12
|
}
|
|
@@ -13,6 +13,7 @@ export interface DatabaseBase {
|
|
|
13
13
|
|
|
14
14
|
// layouts: DatabaseStructure["layouts"]
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
apiRequestJSON<T = unknown>(url: string | Request, options?: RequestInit | undefined, autoRelogin?: boolean): Promise<ApiResults<T>>
|
|
17
|
+
apiRequestRaw(url: string | Request, options?: RequestInit | undefined): Promise<Response>
|
|
17
18
|
streamURL(url: string): Promise<IncomingMessage>
|
|
18
19
|
}
|
package/src/layouts/layout.js
CHANGED
|
@@ -19,8 +19,7 @@ export class Layout {
|
|
|
19
19
|
let url = `${this.endpoint}/script/${encodeURIComponent(script.name)}`;
|
|
20
20
|
if (script.parameter)
|
|
21
21
|
url += "?script.param=" + encodeURIComponent(script.parameter);
|
|
22
|
-
let res = await this.database.
|
|
23
|
-
port: 443,
|
|
22
|
+
let res = await this.database.apiRequestJSON(url, {
|
|
24
23
|
method: "GET"
|
|
25
24
|
});
|
|
26
25
|
if (res.response && res.messages[0].code === "0") {
|
|
@@ -38,7 +37,7 @@ export class Layout {
|
|
|
38
37
|
if (this.metadata) {
|
|
39
38
|
return this.metadata;
|
|
40
39
|
}
|
|
41
|
-
let res = await this.database.
|
|
40
|
+
let res = await this.database.apiRequestJSON(this.endpoint);
|
|
42
41
|
if (!res.response)
|
|
43
42
|
throw new FMError(res.messages[0].code, res.httpStatus, res);
|
|
44
43
|
this.metadata = res.response;
|
package/src/layouts/layout.ts
CHANGED
|
@@ -29,8 +29,7 @@ export class Layout<T extends LayoutInterface> implements LayoutBase {
|
|
|
29
29
|
async runScript(script: Script): Promise<ScriptResult> {
|
|
30
30
|
let url = `${this.endpoint}/script/${encodeURIComponent(script.name)}`
|
|
31
31
|
if (script.parameter) url += "?script.param=" + encodeURIComponent(script.parameter)
|
|
32
|
-
let res = await this.database.
|
|
33
|
-
port: 443,
|
|
32
|
+
let res = await this.database.apiRequestJSON<ApiScriptResult>(url, {
|
|
34
33
|
method: "GET"
|
|
35
34
|
})
|
|
36
35
|
if (res.response && res.messages[0].code === "0") {
|
|
@@ -49,7 +48,7 @@ export class Layout<T extends LayoutInterface> implements LayoutBase {
|
|
|
49
48
|
return this.metadata
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
let res = await this.database.
|
|
51
|
+
let res = await this.database.apiRequestJSON<ApiLayoutMetadata>(this.endpoint)
|
|
53
52
|
if (!res.response) throw new FMError(res.messages[0].code, res.httpStatus, res)
|
|
54
53
|
this.metadata = res.response
|
|
55
54
|
return this.metadata
|
package/src/records/fieldBase.js
CHANGED
|
@@ -120,12 +120,13 @@ export class FieldBase {
|
|
|
120
120
|
throw "Cannot upload a file to the field; " + this.id + " (not a container field)";
|
|
121
121
|
let form = new FormData();
|
|
122
122
|
form.append("upload", new File([buffer], filename, { type: mime }));
|
|
123
|
-
await this.parent.layout.database.
|
|
123
|
+
let res = await this.parent.layout.database.apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
|
|
124
124
|
method: "POST",
|
|
125
125
|
// @ts-ignore
|
|
126
|
-
headers: { "Authorization": "Bearer " + this.parent.layout.database.token },
|
|
127
126
|
body: form
|
|
128
127
|
});
|
|
128
|
+
if (!res.ok)
|
|
129
|
+
throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`);
|
|
129
130
|
}
|
|
130
131
|
async download(mode = DOWNLOAD_MODES.Stream) {
|
|
131
132
|
if (this.metadata.result !== ApiFieldResultTypes.CONTAINER)
|
package/src/records/fieldBase.ts
CHANGED
|
@@ -136,12 +136,12 @@ export class FieldBase<T extends FieldValue> {
|
|
|
136
136
|
let form = new FormData()
|
|
137
137
|
form.append("upload", new File([buffer], filename, {type: mime}))
|
|
138
138
|
|
|
139
|
-
await this.parent.layout.database.
|
|
139
|
+
let res = await this.parent.layout.database.apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
|
|
140
140
|
method: "POST",
|
|
141
141
|
// @ts-ignore
|
|
142
|
-
headers: {"Authorization": "Bearer " + this.parent.layout.database.token},
|
|
143
142
|
body: form
|
|
144
143
|
})
|
|
144
|
+
if (!res.ok) throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
download(mode: DOWNLOAD_MODES.Stream): Promise<http.IncomingMessage>
|
|
@@ -2,9 +2,10 @@ import { LayoutInterface } from "../../layouts/layoutInterface.js";
|
|
|
2
2
|
import { PickPortals, ScriptRequestData } from "../../types.js";
|
|
3
3
|
import { LayoutBase } from "../../layouts/layoutBase.js";
|
|
4
4
|
import { LayoutRecord } from "../layoutRecord.js";
|
|
5
|
+
import { Query } from "../../utils/query.js";
|
|
5
6
|
export type SortOrder = "ascend" | "descend";
|
|
6
7
|
export type FindRequest = {
|
|
7
|
-
[key: string]:
|
|
8
|
+
[key: string]: Query;
|
|
8
9
|
} & {
|
|
9
10
|
omit?: "true" | "false";
|
|
10
11
|
};
|
|
@@ -35,6 +36,7 @@ export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS exten
|
|
|
35
36
|
protected queries: FindRequest[];
|
|
36
37
|
constructor(layout: LayoutBase, options: OPTIONS);
|
|
37
38
|
get isFindRequest(): boolean;
|
|
39
|
+
private formatQueries;
|
|
38
40
|
protected generateParamsBody(offset: number, limit: number): {
|
|
39
41
|
[key: string]: any;
|
|
40
42
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { LayoutRecord } from "../layoutRecord.js";
|
|
5
5
|
import { FMError } from "../../FMError.js";
|
|
6
|
+
import { FindRequestSymbol } from "../../utils/query.js";
|
|
6
7
|
export class RecordGetOperation {
|
|
7
8
|
layout;
|
|
8
9
|
limit = 100;
|
|
@@ -21,6 +22,19 @@ export class RecordGetOperation {
|
|
|
21
22
|
get isFindRequest() {
|
|
22
23
|
return this.queries.length !== 0;
|
|
23
24
|
}
|
|
25
|
+
formatQueries() {
|
|
26
|
+
return this.queries.map(query => {
|
|
27
|
+
let out = {};
|
|
28
|
+
for (let key of Object.keys(query)) {
|
|
29
|
+
if (query[key][FindRequestSymbol])
|
|
30
|
+
out[key] = query[key][FindRequestSymbol];
|
|
31
|
+
else {
|
|
32
|
+
out[key] = query[key];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
24
38
|
generateParamsBody(offset, limit) {
|
|
25
39
|
const params = {
|
|
26
40
|
limit: limit.toString(),
|
|
@@ -41,7 +55,7 @@ export class RecordGetOperation {
|
|
|
41
55
|
if (this.scriptData.prerequest?.parameter)
|
|
42
56
|
params["script.prerequest.param"] = this.scriptData.prerequest.parameter;
|
|
43
57
|
if (this.queries.length !== 0)
|
|
44
|
-
params["query"] = this.
|
|
58
|
+
params["query"] = this.formatQueries();
|
|
45
59
|
let portals = Object.keys(this.portals);
|
|
46
60
|
params["portal"] = portals;
|
|
47
61
|
for (let portal of portals) {
|
|
@@ -105,7 +119,7 @@ export class RecordGetOperation {
|
|
|
105
119
|
body: is_find ? JSON.stringify(this.generateParamsBody(offset, limit)) : undefined,
|
|
106
120
|
};
|
|
107
121
|
try {
|
|
108
|
-
let res = await this.layout.database.
|
|
122
|
+
let res = await this.layout.database.apiRequestJSON(endpoint, reqData);
|
|
109
123
|
if (res.messages[0].code === "0" && res.response) {
|
|
110
124
|
// console.log("RESOLVING")
|
|
111
125
|
if (!this.layout.metadata)
|
|
@@ -8,10 +8,11 @@ import {LayoutBase} from "../../layouts/layoutBase.js"
|
|
|
8
8
|
import {LayoutRecord} from "../layoutRecord.js";
|
|
9
9
|
import {ApiRecordResponseObj} from "../../models/apiResults.js";
|
|
10
10
|
import {FMError} from "../../FMError.js";
|
|
11
|
+
import {FindRequestSymbol, Query} from "../../utils/query.js";
|
|
11
12
|
|
|
12
13
|
export type SortOrder = "ascend" | "descend"
|
|
13
14
|
export type FindRequest = {
|
|
14
|
-
[key: string]:
|
|
15
|
+
[key: string]: Query
|
|
15
16
|
} & {omit?: "true" | "false"}
|
|
16
17
|
|
|
17
18
|
export type PortalRequest = {
|
|
@@ -51,6 +52,17 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
|
|
|
51
52
|
return this.queries.length !== 0
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
private formatQueries() {
|
|
56
|
+
return this.queries.map(query => {
|
|
57
|
+
let out: any = {}
|
|
58
|
+
for (let key of Object.keys(query)) {
|
|
59
|
+
if (query[key][FindRequestSymbol]) out[key] = query[key][FindRequestSymbol]
|
|
60
|
+
else {out[key] = query[key]}
|
|
61
|
+
}
|
|
62
|
+
return out
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
protected generateParamsBody(offset: number, limit: number) {
|
|
55
67
|
const params: {[key: string]: any} = {
|
|
56
68
|
limit: limit.toString(),
|
|
@@ -68,7 +80,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
|
|
|
68
80
|
if (this.scriptData.prerequest) params["script.prerequest"] = this.scriptData.prerequest.name
|
|
69
81
|
if (this.scriptData.prerequest?.parameter) params["script.prerequest.param"] = this.scriptData.prerequest.parameter
|
|
70
82
|
|
|
71
|
-
if (this.queries.length !== 0) params["query"] = this.
|
|
83
|
+
if (this.queries.length !== 0) params["query"] = this.formatQueries()
|
|
72
84
|
|
|
73
85
|
let portals: (keyof typeof this.portals)[] = Object.keys(this.portals)
|
|
74
86
|
params["portal"] = portals
|
|
@@ -140,7 +152,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
|
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
try {
|
|
143
|
-
let res = await this.layout.database.
|
|
155
|
+
let res = await this.layout.database.apiRequestJSON<ApiRecordResponseObj>(
|
|
144
156
|
endpoint,
|
|
145
157
|
reqData
|
|
146
158
|
)
|
|
@@ -41,8 +41,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
41
41
|
}
|
|
42
42
|
if (this.recordId === -1) {
|
|
43
43
|
// This is a new LayoutRecord
|
|
44
|
-
let res = await this.layout.database.
|
|
45
|
-
port: 443,
|
|
44
|
+
let res = await this.layout.database.apiRequestJSON(`${this.layout.endpoint}/records`, {
|
|
46
45
|
method: "POST",
|
|
47
46
|
body: JSON.stringify(data)
|
|
48
47
|
});
|
|
@@ -62,8 +61,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
// for (let item of Object.keys(data)) extraBody[item] = data[item]
|
|
65
|
-
let res = await this.layout.database.
|
|
66
|
-
port: 443,
|
|
64
|
+
let res = await this.layout.database.apiRequestJSON(this.endpoint, {
|
|
67
65
|
method: "PATCH",
|
|
68
66
|
body: JSON.stringify(data)
|
|
69
67
|
});
|
|
@@ -102,8 +100,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
102
100
|
}
|
|
103
101
|
if (!this.layout.metadata)
|
|
104
102
|
await this.layout.getLayoutMeta();
|
|
105
|
-
let res = await this.layout.database.
|
|
106
|
-
port: 443,
|
|
103
|
+
let res = await this.layout.database.apiRequestJSON(this.endpoint, {
|
|
107
104
|
method: "GET"
|
|
108
105
|
});
|
|
109
106
|
if (res.response && res.messages[0].code === "0") {
|
|
@@ -121,8 +118,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
121
118
|
}
|
|
122
119
|
async duplicate() {
|
|
123
120
|
let trace = new Error();
|
|
124
|
-
let res = await this.layout.database.
|
|
125
|
-
port: 443,
|
|
121
|
+
let res = await this.layout.database.apiRequestJSON(this.endpoint, {
|
|
126
122
|
method: "POST"
|
|
127
123
|
});
|
|
128
124
|
if (!res.response) {
|
|
@@ -142,8 +138,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
142
138
|
}
|
|
143
139
|
}
|
|
144
140
|
async delete() {
|
|
145
|
-
let res = await this.layout.database.
|
|
146
|
-
port: 443,
|
|
141
|
+
let res = await this.layout.database.apiRequestJSON(this.endpoint, {
|
|
147
142
|
method: "DELETE"
|
|
148
143
|
});
|
|
149
144
|
console.log(res);
|
|
@@ -57,8 +57,7 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
|
|
|
57
57
|
|
|
58
58
|
if (this.recordId === -1) {
|
|
59
59
|
// This is a new LayoutRecord
|
|
60
|
-
let res = await this.layout.database.
|
|
61
|
-
port: 443,
|
|
60
|
+
let res = await this.layout.database.apiRequestJSON<{ recordId: string, modId: string }>(`${this.layout.endpoint}/records`, {
|
|
62
61
|
method: "POST",
|
|
63
62
|
body: JSON.stringify(data)
|
|
64
63
|
})
|
|
@@ -80,14 +79,13 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
|
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
// for (let item of Object.keys(data)) extraBody[item] = data[item]
|
|
83
|
-
let res = await this.layout.database.
|
|
82
|
+
let res = await this.layout.database.apiRequestJSON<{
|
|
84
83
|
modId: string, newPortalRecordInfo: {
|
|
85
84
|
tableName: string,
|
|
86
85
|
recordId: string,
|
|
87
86
|
modId: string
|
|
88
87
|
}[]
|
|
89
88
|
}>(this.endpoint, {
|
|
90
|
-
port: 443,
|
|
91
89
|
method: "PATCH",
|
|
92
90
|
body: JSON.stringify(data)
|
|
93
91
|
})
|
|
@@ -128,8 +126,7 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
|
|
|
128
126
|
throw "Cannot get this RecordBase until a commit() is done."
|
|
129
127
|
}
|
|
130
128
|
if (!this.layout.metadata) await this.layout.getLayoutMeta()
|
|
131
|
-
let res = await this.layout.database.
|
|
132
|
-
port: 443,
|
|
129
|
+
let res = await this.layout.database.apiRequestJSON<ApiRecordResponseObj>(this.endpoint, {
|
|
133
130
|
method: "GET"
|
|
134
131
|
})
|
|
135
132
|
|
|
@@ -147,8 +144,7 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
|
|
|
147
144
|
|
|
148
145
|
async duplicate(): Promise<LayoutRecord<LAYOUT>> {
|
|
149
146
|
let trace = new Error()
|
|
150
|
-
let res = await this.layout.database.
|
|
151
|
-
port: 443,
|
|
147
|
+
let res = await this.layout.database.apiRequestJSON<{recordId: string, modId: string}>(this.endpoint, {
|
|
152
148
|
method: "POST"
|
|
153
149
|
})
|
|
154
150
|
if (!res.response) {
|
|
@@ -170,8 +166,7 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
|
|
|
170
166
|
}
|
|
171
167
|
|
|
172
168
|
async delete(): Promise<void> {
|
|
173
|
-
let res = await this.layout.database.
|
|
174
|
-
port: 443,
|
|
169
|
+
let res = await this.layout.database.apiRequestJSON(this.endpoint, {
|
|
175
170
|
method: "DELETE"
|
|
176
171
|
})
|
|
177
172
|
console.log(res)
|
package/src/utils/query.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
export declare const FindRequestSymbol: unique symbol;
|
|
1
2
|
type QueryParameter = string | number | Date;
|
|
3
|
+
export type Query = {
|
|
4
|
+
[FindRequestSymbol]: string;
|
|
5
|
+
};
|
|
2
6
|
export declare function queryEscape(str: string): string;
|
|
3
|
-
export declare function query(strings: TemplateStringsArray, ...args: QueryParameter[]):
|
|
7
|
+
export declare function query(strings: TemplateStringsArray, ...args: QueryParameter[]): {
|
|
8
|
+
[FindRequestSymbol]: string;
|
|
9
|
+
};
|
|
4
10
|
export {};
|
package/src/utils/query.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (c) 2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
|
+
export const FindRequestSymbol = Symbol("easyfm-findrequest");
|
|
4
5
|
const SPECIAL_CHARACTERS = ["\\", "=", "<", "≤", "≥", ">", "…", "...", "//", "@", "#", "*", "\"", "~"];
|
|
5
6
|
export function queryEscape(str) {
|
|
6
7
|
for (let char of SPECIAL_CHARACTERS) {
|
|
@@ -16,5 +17,5 @@ export function query(strings, ...args) {
|
|
|
16
17
|
const query = strings.map((str, index) => {
|
|
17
18
|
return str + (argStrings[index] || '');
|
|
18
19
|
});
|
|
19
|
-
return query.join("");
|
|
20
|
+
return { [FindRequestSymbol]: query.join("") };
|
|
20
21
|
}
|
package/src/utils/query.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Copyright (c) 2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
export const FindRequestSymbol = Symbol("easyfm-findrequest");
|
|
5
6
|
const SPECIAL_CHARACTERS = ["\\", "=", "<", "≤", "≥", ">", "…", "...", "//", "@", "#", "*", "\"", "~"]
|
|
6
7
|
type QueryParameter = string | number | Date
|
|
8
|
+
export type Query = {[FindRequestSymbol]: string}
|
|
7
9
|
|
|
8
10
|
export function queryEscape(str: string) {
|
|
9
11
|
for (let char of SPECIAL_CHARACTERS) {
|
|
@@ -21,5 +23,5 @@ export function query(strings: TemplateStringsArray, ...args: QueryParameter[])
|
|
|
21
23
|
const query = strings.map((str, index) => {
|
|
22
24
|
return str + (argStrings[index] || '')
|
|
23
25
|
})
|
|
24
|
-
return query.join("")
|
|
26
|
+
return {[FindRequestSymbol]: query.join("")}
|
|
25
27
|
}
|
package/src/records/field.d.ts
DELETED
package/src/records/field.js
DELETED
package/src/utils/fmQuery.d.ts
DELETED
package/src/utils/fmQuery.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2024. See LICENSE file for more information
|
|
3
|
-
*/
|
|
4
|
-
const SPECIAL_CHARACTERS = ["\\", "=", "<", "≤", "≥", ">", "…", "...", "//", "@", "#", "*", "\"", "~"];
|
|
5
|
-
export function fmQueryEscape(str) {
|
|
6
|
-
for (let char of SPECIAL_CHARACTERS) {
|
|
7
|
-
str = str.replace(char, `\\${char}`);
|
|
8
|
-
}
|
|
9
|
-
return str;
|
|
10
|
-
}
|
|
11
|
-
export function fmQuery(strings, ...args) {
|
|
12
|
-
let argStrings = args.map(item => {
|
|
13
|
-
return fmQueryEscape(item.toString());
|
|
14
|
-
});
|
|
15
|
-
// Zip the parameters together
|
|
16
|
-
const query = strings.map((str, index) => {
|
|
17
|
-
return str + (args[index] || '');
|
|
18
|
-
});
|
|
19
|
-
return query.join("");
|
|
20
|
-
}
|