@jd-data-limited/easy-fm 0.0.1 → 0.0.3
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/.idea/copyright/JD_Data.xml +6 -0
- package/.idea/copyright/profiles_settings.xml +7 -0
- package/README.md +152 -23
- package/decs.d.ts +4 -1
- package/index.js +86 -45
- package/index.ts +79 -29
- package/package.json +1 -1
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<component name="CopyrightManager">
|
|
2
|
+
<copyright>
|
|
3
|
+
<option name="notice" value="Copyright (c) &#36;originalComment.match("Copyright \(c\) (\d+)", 1, "-", "&#36;today.year")&#36;today.year. See LICENSE file for more information" />
|
|
4
|
+
<option name="myName" value="JD-Data" />
|
|
5
|
+
</copyright>
|
|
6
|
+
</component>
|
package/README.md
CHANGED
|
@@ -12,47 +12,160 @@ a [FileMaker server](https://www.claris.com/filemaker/server/). This module inte
|
|
|
12
12
|
2. Create a FileMaker database account for easy-fm to use. This account must have the 'Access via FileMaker Data API (
|
|
13
13
|
fmrest)' extended privilege
|
|
14
14
|
|
|
15
|
+
## Table of contents
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
1. [Connecting to a database](#connecting-to-a-database)
|
|
18
|
+
2. [Working with records](#working-with-records)
|
|
19
|
+
3. [Performing find requests](#perform-find-requests)
|
|
20
|
+
4. [Scripts](#scripts)
|
|
21
|
+
5. [Index](#index)
|
|
22
|
+
|
|
23
|
+
## Connecting to a database
|
|
24
|
+
|
|
25
|
+
easy-fm currently does not support connecting to external data sources, or authenticating by any method other than plain
|
|
26
|
+
FileMaker authentication.
|
|
17
27
|
|
|
18
|
-
1. Get the first record on the 'table1' layout
|
|
19
28
|
```javascript
|
|
20
29
|
import fm, {FMError} from "easy-fm"; // Import the module
|
|
21
30
|
const connection = new fm(); // This variable will be used for interacting with the server
|
|
22
31
|
|
|
23
32
|
connection.login(hostname, database, username, password).then(async token => { // Login
|
|
24
|
-
|
|
25
|
-
let record = layout.getRecord(1)
|
|
26
|
-
await record.get() // Fetch the data for that record
|
|
27
|
-
console.log(record)
|
|
33
|
+
|
|
28
34
|
})
|
|
29
35
|
```
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
## Working with records
|
|
38
|
+
|
|
39
|
+
When running scripts or interacting with records, the first step that you need to do is get the layout that you want to
|
|
40
|
+
use
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
let layout = connection.getLayout("layout1") // Get the layout known as 'layout1'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Once you have the layout, you can perform a multitude of tasks
|
|
47
|
+
|
|
48
|
+
### Get a single record using its RecordID
|
|
49
|
+
|
|
50
|
+
[What's a RecordID?](#recordid)
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
let record_id = 1
|
|
54
|
+
let record = layout.getRecord(record_id)
|
|
55
|
+
|
|
56
|
+
await record.get() // The 'getRecord' method does not automatically get that record's values, so this step does that instead
|
|
57
|
+
|
|
58
|
+
let field = record.getField("field1")
|
|
59
|
+
console.log(field.value) // Get the value of 'field1'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Modify a record
|
|
63
|
+
|
|
64
|
+
WARNING: The commit step is mandatory. Otherwise the changes will never be uploaded
|
|
65
|
+
|
|
32
66
|
```javascript
|
|
33
|
-
|
|
34
|
-
let
|
|
35
|
-
|
|
67
|
+
let record_id = 1
|
|
68
|
+
let record = layout.getRecord(record_id)
|
|
69
|
+
|
|
70
|
+
await record.get() // The 'getRecord' method does not automatically get that record's values, so this step does that instead
|
|
71
|
+
record.getField("field1").set("This is a value")
|
|
72
|
+
record.getField("field2").set("This is also a value")
|
|
73
|
+
await record.commit()
|
|
36
74
|
```
|
|
37
75
|
|
|
38
|
-
|
|
76
|
+
### Create a new record
|
|
77
|
+
|
|
78
|
+
WARNING: The record is not created on the database until a commit is done
|
|
79
|
+
|
|
39
80
|
```javascript
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Run the script on the layout
|
|
46
|
-
let result = await layout.runScript(script)
|
|
47
|
-
console.log(result)
|
|
81
|
+
let record = await layout.createRecord()
|
|
82
|
+
record.getField("field1").set("ABC")
|
|
83
|
+
record.getField("field2").set("DEF")
|
|
84
|
+
await record.commit()
|
|
48
85
|
```
|
|
49
86
|
|
|
50
|
-
|
|
87
|
+
### Downloading container field files
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
let record_id = 1
|
|
91
|
+
let record = layout.getRecord(record_id)
|
|
92
|
+
|
|
93
|
+
await record.get() // The 'getRecord' method does not automatically get that record's values, so this step does that instead
|
|
94
|
+
|
|
95
|
+
let container = record.getField("container")
|
|
96
|
+
container.download().then(stream => {
|
|
97
|
+
// Returns an IncomingMessage, which is an extension of ReadableStream
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
[IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage)
|
|
102
|
+
, [ReadableStream](https://nodejs.org/api/stream.html#class-streamreadable)
|
|
103
|
+
|
|
104
|
+
### Uploading container files
|
|
105
|
+
|
|
106
|
+
When uploading files, they should be uploaded as a [Buffer](https://nodejs.org/api/buffer.html) object, along with a
|
|
107
|
+
filename (string) and MIME (also string)
|
|
108
|
+
|
|
109
|
+
MIME types are not currently automatically detected
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const fs = require("fs")
|
|
113
|
+
let filename = "image.jpg"
|
|
114
|
+
let buffer = fs.readdirSync("./" + filename)
|
|
115
|
+
let mime = "image/jpeg"
|
|
116
|
+
|
|
117
|
+
let record_id = 1
|
|
118
|
+
let record = layout.getRecord(record_id)
|
|
119
|
+
await record.get() // The 'getRecord' method does not automatically get that record's values, so this step does that instead
|
|
120
|
+
|
|
121
|
+
let container = record.getField("container")
|
|
122
|
+
container.upload(buffer, filename, mime).then(() => {
|
|
123
|
+
console.log("UPLOAD SUCCESSFUL!")
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
[(Mozilla) Common MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types)
|
|
127
|
+
|
|
128
|
+
### Read and modify portal information
|
|
129
|
+
|
|
130
|
+
To be able to read portal information for a record, you'll need to put a portal on the layout you are using. Make sure
|
|
131
|
+
you also:
|
|
132
|
+
|
|
133
|
+
- Give the portal object a name
|
|
134
|
+
- Include the exact fields you need in the portal. Any fields you leave out will not be accessible by node.js, and
|
|
135
|
+
putting extra fields in is not a good idea.
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
let record = layout.getRecord(1)
|
|
139
|
+
|
|
140
|
+
// Portal data is downloaded when the record is fetched
|
|
141
|
+
await record.get()
|
|
142
|
+
let portal = record.getPortal("Portal1")
|
|
143
|
+
|
|
144
|
+
for (let _record of portal.records) {
|
|
145
|
+
// Runs for all records in the portal
|
|
146
|
+
let field = _record.getField("field2")
|
|
147
|
+
console.log(field.value)
|
|
148
|
+
field.set("GHI")
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**WARNING**: Commiting a related portal record will also commit any changes to the parent record as well as any other
|
|
153
|
+
portal records.
|
|
154
|
+
|
|
155
|
+
## Perform find requests
|
|
156
|
+
|
|
157
|
+
Performing find requests is one of the most common database operations
|
|
158
|
+
|
|
159
|
+

|
|
160
|
+
|
|
161
|
+
The image above is how the find request would look when doing it in a FileMaker script. This should hopefully help you
|
|
162
|
+
understand how the find request process works.
|
|
163
|
+
|
|
51
164
|
```javascript
|
|
52
165
|
let find = layout.newFind() // Create a new find operation
|
|
53
166
|
|
|
54
167
|
find.addRequests( // Specify the find criteria
|
|
55
|
-
{"PrimaryKey": "AAAAA", "ModifiedBy": "BBBB"},
|
|
168
|
+
{"PrimaryKey": "AAAAA", "ModifiedBy": "BBBB"},
|
|
56
169
|
{"CreatedBy": "BBBB"}
|
|
57
170
|
)
|
|
58
171
|
|
|
@@ -60,5 +173,21 @@ find.addSort("CratedBy", "ascend") // Sort the results in ascending order
|
|
|
60
173
|
let records = await find.find() // Perform the find
|
|
61
174
|
console.log(records)
|
|
62
175
|
```
|
|
63
|
-
|
|
64
|
-
|
|
176
|
+
|
|
177
|
+
### Scripts
|
|
178
|
+
|
|
179
|
+
Running scripts is another operation you may need to do regularly. This one is fairly straight-forward in how it works
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
let layout = connection.getLayout("layout1")
|
|
183
|
+
let script = connection.script("MyFileMakerScript", "This is a parameter")
|
|
184
|
+
let result = await layout.runScript(script)
|
|
185
|
+
console.log(result)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Index
|
|
189
|
+
|
|
190
|
+
### RecordID
|
|
191
|
+
|
|
192
|
+
This is not a custom field that can be set and/or changed. You can get a record's ID from within FileMaker by
|
|
193
|
+
using `Get(RecordID)`
|
package/decs.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
2
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
3
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,21 +7,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
8
|
});
|
|
10
9
|
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.FMError = void 0;
|
|
13
10
|
// const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
import fetch, { File, FormData } from 'node-fetch';
|
|
12
|
+
import { EventEmitter } from "events";
|
|
16
13
|
// @ts-ignore
|
|
17
|
-
|
|
14
|
+
import fs from "fs";
|
|
18
15
|
// @ts-ignore
|
|
19
|
-
|
|
16
|
+
import path from "path";
|
|
20
17
|
// @ts-ignore
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
import * as https from "https";
|
|
20
|
+
// import * as btoa from "btoa";
|
|
23
21
|
// @ts-ignore
|
|
24
|
-
const errs = JSON.parse(
|
|
25
|
-
class FileMakerConnection {
|
|
22
|
+
const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString());
|
|
23
|
+
export default class FileMakerConnection {
|
|
26
24
|
constructor() {
|
|
27
25
|
}
|
|
28
26
|
get endpoint() {
|
|
@@ -44,7 +42,7 @@ class FileMakerConnection {
|
|
|
44
42
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
45
43
|
if (!this.token)
|
|
46
44
|
reject(new Error("Not logged in"));
|
|
47
|
-
let _fetch = yield (
|
|
45
|
+
let _fetch = yield fetch(`${this.endpoint}/sessions/${this.token}`, {
|
|
48
46
|
method: "DELETE",
|
|
49
47
|
headers: {
|
|
50
48
|
"content-type": "application/json"
|
|
@@ -66,13 +64,13 @@ class FileMakerConnection {
|
|
|
66
64
|
}
|
|
67
65
|
createSession() {
|
|
68
66
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
69
|
-
(
|
|
67
|
+
fetch(`${this.endpoint}/sessions`, {
|
|
70
68
|
hostname: this.hostname,
|
|
71
69
|
port: 443,
|
|
72
70
|
method: "POST",
|
|
73
71
|
headers: {
|
|
74
72
|
"Content-Type": "application/json",
|
|
75
|
-
"Authorization": "Basic " +
|
|
73
|
+
"Authorization": "Basic " + Buffer.from(this.username + ":" + this.password).toString("base64")
|
|
76
74
|
}
|
|
77
75
|
}).then(res => {
|
|
78
76
|
console.log(res);
|
|
@@ -97,14 +95,14 @@ class FileMakerConnection {
|
|
|
97
95
|
getLayout(name) {
|
|
98
96
|
return new layout(this, name);
|
|
99
97
|
}
|
|
100
|
-
apiRequest(url, options) {
|
|
98
|
+
apiRequest(url, options = {}) {
|
|
101
99
|
return __awaiter(this, void 0, void 0, function* () {
|
|
102
100
|
if (!options.headers)
|
|
103
101
|
options.headers = {};
|
|
104
102
|
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
|
|
105
103
|
options.headers["authorization"] = "Bearer " + this.token;
|
|
106
104
|
options.rejectUnauthorized = this.rejectUnauthroized;
|
|
107
|
-
let _fetch = yield (
|
|
105
|
+
let _fetch = yield fetch(url, options);
|
|
108
106
|
let data = yield _fetch.json();
|
|
109
107
|
return data;
|
|
110
108
|
});
|
|
@@ -133,7 +131,6 @@ class FileMakerConnection {
|
|
|
133
131
|
return { name, parameter };
|
|
134
132
|
}
|
|
135
133
|
}
|
|
136
|
-
exports.default = FileMakerConnection;
|
|
137
134
|
class layout {
|
|
138
135
|
constructor(conn, name) {
|
|
139
136
|
this.conn = conn;
|
|
@@ -145,26 +142,17 @@ class layout {
|
|
|
145
142
|
getScript(script) {
|
|
146
143
|
return new script(this, script);
|
|
147
144
|
}
|
|
148
|
-
createRecord(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.conn.apiRequest(`${this.endpoint}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
})
|
|
156
|
-
.then(res => {
|
|
157
|
-
if (res.messages[0].code === "0") {
|
|
158
|
-
resolve(new record(this, res.response.recordId, res.response.modId));
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
reject(new FMError(res.messages[0].code, res.status, res));
|
|
145
|
+
createRecord() {
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
// Get the layout's metadata
|
|
148
|
+
this.conn.apiRequest(`${this.endpoint}`).then(res => {
|
|
149
|
+
let fields = {};
|
|
150
|
+
for (let _field of res.response.fieldMetaData) {
|
|
151
|
+
fields[_field.name] = "";
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
.catch(e => {
|
|
165
|
-
reject(e);
|
|
153
|
+
resolve(new record(this, -1, 0, fields));
|
|
166
154
|
});
|
|
167
|
-
})
|
|
155
|
+
});
|
|
168
156
|
}
|
|
169
157
|
getRecord(recordId) {
|
|
170
158
|
return new record(this, recordId);
|
|
@@ -195,8 +183,17 @@ class layout {
|
|
|
195
183
|
});
|
|
196
184
|
}));
|
|
197
185
|
}
|
|
186
|
+
getLayoutMeta() {
|
|
187
|
+
return new Promise((resolve, reject) => {
|
|
188
|
+
this.conn.apiRequest(this.endpoint).then(res => {
|
|
189
|
+
this.metadata = res.response;
|
|
190
|
+
resolve(this);
|
|
191
|
+
})
|
|
192
|
+
.catch(e => reject(e));
|
|
193
|
+
});
|
|
194
|
+
}
|
|
198
195
|
}
|
|
199
|
-
class record extends
|
|
196
|
+
class record extends EventEmitter {
|
|
200
197
|
constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null) {
|
|
201
198
|
super();
|
|
202
199
|
this.layout = layout;
|
|
@@ -235,6 +232,8 @@ class record extends events_1.EventEmitter {
|
|
|
235
232
|
}
|
|
236
233
|
get() {
|
|
237
234
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
if (!this.layout.metadata)
|
|
236
|
+
yield this.layout.getLayoutMeta();
|
|
238
237
|
this.layout.conn.apiRequest(this.endpoint, {
|
|
239
238
|
port: 443,
|
|
240
239
|
method: "GET"
|
|
@@ -258,11 +257,35 @@ class record extends events_1.EventEmitter {
|
|
|
258
257
|
});
|
|
259
258
|
}));
|
|
260
259
|
}
|
|
261
|
-
|
|
260
|
+
commit(extraBody = {}) {
|
|
262
261
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
263
262
|
let data = this.toObject();
|
|
264
263
|
delete data.recordId;
|
|
265
264
|
delete data.modId;
|
|
265
|
+
if (this.recordId === -1) {
|
|
266
|
+
// This is a new record
|
|
267
|
+
this.layout.conn.apiRequest(`${this.layout.endpoint}/records`, {
|
|
268
|
+
port: 443,
|
|
269
|
+
method: "POST",
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
fieldData: data.fieldData
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
.then(res => {
|
|
275
|
+
if (res.messages[0].code === "0") {
|
|
276
|
+
this.recordId = res.response.recordId;
|
|
277
|
+
this.modId = res.response.modId;
|
|
278
|
+
resolve(this);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
reject(new FMError(res.messages[0].code, res.status, res));
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
.catch(e => {
|
|
285
|
+
reject(e);
|
|
286
|
+
});
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
266
289
|
for (let item of Object.keys(extraBody))
|
|
267
290
|
data[item] = extraBody[item];
|
|
268
291
|
this.layout.conn.apiRequest(this.endpoint, {
|
|
@@ -322,14 +345,26 @@ class field {
|
|
|
322
345
|
this.edited = false;
|
|
323
346
|
}
|
|
324
347
|
set(content) {
|
|
348
|
+
if (this.dataType === "container")
|
|
349
|
+
throw "Cannot set container value using set(). Use upload() instead.";
|
|
325
350
|
this.value = content;
|
|
326
351
|
this.edited = true;
|
|
327
352
|
}
|
|
328
|
-
|
|
353
|
+
get dataType() {
|
|
354
|
+
if (this.record instanceof portalItem) {
|
|
355
|
+
return this.record.layout.metadata.portalMetaData.find(name => name === this.id).result;
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
return this.record.layout.metadata.fieldMetaData.find(name => name === this.id).result;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
upload(buffer, filename, mime) {
|
|
362
|
+
if (this.dataType !== "container")
|
|
363
|
+
throw "Cannot upload a file to the field; " + this.id + " (not a container field)";
|
|
329
364
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
330
|
-
let form = new
|
|
331
|
-
form.append("upload", new
|
|
332
|
-
let _fetch = yield (
|
|
365
|
+
let form = new FormData();
|
|
366
|
+
form.append("upload", new File([buffer], filename, { type: mime }));
|
|
367
|
+
let _fetch = yield fetch(`${this.record.endpoint}/containers/${this.id}/1`, {
|
|
333
368
|
method: "POST",
|
|
334
369
|
headers: { "Authorization": "Bearer " + this.record.layout.conn.token },
|
|
335
370
|
body: form
|
|
@@ -346,6 +381,13 @@ class field {
|
|
|
346
381
|
.catch(e => { reject(e); });
|
|
347
382
|
}));
|
|
348
383
|
}
|
|
384
|
+
download() {
|
|
385
|
+
return new Promise((resolve, reject) => {
|
|
386
|
+
https.get(this.value.toString(), (res) => {
|
|
387
|
+
resolve(res);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
}
|
|
349
391
|
}
|
|
350
392
|
class portal {
|
|
351
393
|
constructor(record, name, records = []) {
|
|
@@ -366,8 +408,8 @@ class portalItem extends record {
|
|
|
366
408
|
attachPortal(portal) {
|
|
367
409
|
this.portal = portal;
|
|
368
410
|
}
|
|
369
|
-
|
|
370
|
-
return this.portal.record.
|
|
411
|
+
commit(extraBody = {}) {
|
|
412
|
+
return this.portal.record.commit(extraBody);
|
|
371
413
|
}
|
|
372
414
|
toObject(fieldFilter) {
|
|
373
415
|
super.toObject();
|
|
@@ -439,7 +481,7 @@ class find {
|
|
|
439
481
|
});
|
|
440
482
|
}
|
|
441
483
|
}
|
|
442
|
-
class FMError extends Error {
|
|
484
|
+
export class FMError extends Error {
|
|
443
485
|
constructor(code, httpStatus, res) {
|
|
444
486
|
super(code);
|
|
445
487
|
this.httpStatus = httpStatus;
|
|
@@ -448,7 +490,6 @@ class FMError extends Error {
|
|
|
448
490
|
this.message = errs.find(err => err.e === this.code) || "Unknown error";
|
|
449
491
|
}
|
|
450
492
|
}
|
|
451
|
-
exports.FMError = FMError;
|
|
452
493
|
//
|
|
453
494
|
// module.exports = {
|
|
454
495
|
// default: {FileMakerConnection}
|
package/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2022. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
// const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
|
|
2
6
|
import fetch, {
|
|
3
7
|
Blob,
|
|
@@ -18,7 +22,8 @@ import fs from "fs"
|
|
|
18
22
|
import path from "path"
|
|
19
23
|
// @ts-ignore
|
|
20
24
|
import { fileURLToPath } from "url"
|
|
21
|
-
import
|
|
25
|
+
import * as https from "https";
|
|
26
|
+
// import * as btoa from "btoa";
|
|
22
27
|
|
|
23
28
|
// @ts-ignore
|
|
24
29
|
const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString())
|
|
@@ -41,7 +46,7 @@ export default class FileMakerConnection {
|
|
|
41
46
|
private username: any;
|
|
42
47
|
private password: any;
|
|
43
48
|
private rejectUnauthroized: boolean;
|
|
44
|
-
|
|
49
|
+
|
|
45
50
|
constructor() {
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -95,7 +100,7 @@ export default class FileMakerConnection {
|
|
|
95
100
|
method: "POST",
|
|
96
101
|
headers: {
|
|
97
102
|
"Content-Type": "application/json",
|
|
98
|
-
"Authorization": "Basic " +
|
|
103
|
+
"Authorization": "Basic " + Buffer.from(this.username + ":" + this.password).toString("base64")
|
|
99
104
|
}
|
|
100
105
|
}).then(res => {
|
|
101
106
|
console.log(res)
|
|
@@ -117,11 +122,11 @@ export default class FileMakerConnection {
|
|
|
117
122
|
})
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
getLayout(name) {
|
|
125
|
+
getLayout(name): layout {
|
|
121
126
|
return new layout(this, name)
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
async apiRequest(url, options): Promise<any> {
|
|
129
|
+
async apiRequest(url: string | Request, options: any = {}): Promise<any> {
|
|
125
130
|
if (!options.headers) options.headers = {}
|
|
126
131
|
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
|
|
127
132
|
options.headers["authorization"] = "Bearer " + this.token
|
|
@@ -160,6 +165,7 @@ export default class FileMakerConnection {
|
|
|
160
165
|
class layout {
|
|
161
166
|
readonly conn: FileMakerConnection;
|
|
162
167
|
protected name: string;
|
|
168
|
+
metadata: any;
|
|
163
169
|
constructor(conn: FileMakerConnection, name: string) {
|
|
164
170
|
this.conn = conn
|
|
165
171
|
this.name = name
|
|
@@ -173,26 +179,16 @@ class layout {
|
|
|
173
179
|
return new script(this, script)
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
createRecord(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.conn.apiRequest(`${this.endpoint}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
createRecord(): Promise<record | Error | void> {
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
// Get the layout's metadata
|
|
185
|
+
this.conn.apiRequest(`${this.endpoint}`).then(res => {
|
|
186
|
+
let fields = {}
|
|
187
|
+
for (let _field of res.response.fieldMetaData) {
|
|
188
|
+
fields[_field.name] = ""
|
|
189
|
+
}
|
|
190
|
+
resolve(new record(this, -1, 0, fields))
|
|
183
191
|
})
|
|
184
|
-
.then(res => {
|
|
185
|
-
if (res.messages[0].code === "0") {
|
|
186
|
-
resolve(
|
|
187
|
-
new record(this, res.response.recordId, res.response.modId)
|
|
188
|
-
)
|
|
189
|
-
} else {
|
|
190
|
-
reject(new FMError(res.messages[0].code, res.status, res))
|
|
191
|
-
}
|
|
192
|
-
})
|
|
193
|
-
.catch(e => {
|
|
194
|
-
reject(e)
|
|
195
|
-
})
|
|
196
192
|
})
|
|
197
193
|
}
|
|
198
194
|
|
|
@@ -225,11 +221,21 @@ class layout {
|
|
|
225
221
|
})
|
|
226
222
|
})
|
|
227
223
|
}
|
|
224
|
+
|
|
225
|
+
public getLayoutMeta(): Promise<layout | FMError> {
|
|
226
|
+
return new Promise((resolve, reject) => {
|
|
227
|
+
this.conn.apiRequest(this.endpoint).then(res => {
|
|
228
|
+
this.metadata = res.response
|
|
229
|
+
resolve(this)
|
|
230
|
+
})
|
|
231
|
+
.catch(e => reject(e))
|
|
232
|
+
})
|
|
233
|
+
}
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
class record extends EventEmitter {
|
|
231
237
|
readonly layout: layout;
|
|
232
|
-
|
|
238
|
+
public recordId: number;
|
|
233
239
|
modId: number;
|
|
234
240
|
fields: field[];
|
|
235
241
|
readonly portals: portal[];
|
|
@@ -277,6 +283,8 @@ class record extends EventEmitter {
|
|
|
277
283
|
|
|
278
284
|
get() {
|
|
279
285
|
return new Promise(async (resolve, reject) => {
|
|
286
|
+
if (!this.layout.metadata) await this.layout.getLayoutMeta()
|
|
287
|
+
|
|
280
288
|
this.layout.conn.apiRequest(this.endpoint, {
|
|
281
289
|
port: 443,
|
|
282
290
|
method: "GET"
|
|
@@ -299,12 +307,37 @@ class record extends EventEmitter {
|
|
|
299
307
|
})
|
|
300
308
|
}
|
|
301
309
|
|
|
302
|
-
|
|
310
|
+
commit(extraBody = {}): Promise<record | FMError> {
|
|
303
311
|
return new Promise(async (resolve, reject) => {
|
|
304
312
|
let data = this.toObject()
|
|
305
313
|
delete data.recordId
|
|
306
314
|
delete data.modId
|
|
307
315
|
|
|
316
|
+
if (this.recordId === -1) {
|
|
317
|
+
// This is a new record
|
|
318
|
+
this.layout.conn.apiRequest(`${this.layout.endpoint}/records`, {
|
|
319
|
+
port: 443,
|
|
320
|
+
method: "POST",
|
|
321
|
+
body: JSON.stringify({
|
|
322
|
+
fieldData: data.fieldData
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
.then(res => {
|
|
326
|
+
if (res.messages[0].code === "0") {
|
|
327
|
+
this.recordId = res.response.recordId
|
|
328
|
+
this.modId = res.response.modId
|
|
329
|
+
resolve(this)
|
|
330
|
+
} else {
|
|
331
|
+
reject(new FMError(res.messages[0].code, res.status, res))
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
.catch(e => {
|
|
335
|
+
reject(e)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
|
|
308
341
|
for (let item of Object.keys(extraBody)) data[item] = extraBody[item]
|
|
309
342
|
this.layout.conn.apiRequest(this.endpoint, {
|
|
310
343
|
port: 443,
|
|
@@ -376,11 +409,21 @@ class field {
|
|
|
376
409
|
}
|
|
377
410
|
|
|
378
411
|
set(content) {
|
|
412
|
+
if (this.dataType === "container") throw "Cannot set container value using set(). Use upload() instead."
|
|
379
413
|
this.value = content
|
|
380
414
|
this.edited = true
|
|
381
415
|
}
|
|
382
416
|
|
|
383
|
-
|
|
417
|
+
get dataType() {
|
|
418
|
+
if (this.record instanceof portalItem) {
|
|
419
|
+
return this.record.layout.metadata.portalMetaData.find(name => name === this.id).result
|
|
420
|
+
} else {
|
|
421
|
+
return this.record.layout.metadata.fieldMetaData.find(name => name === this.id).result
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
upload(buffer: Buffer, filename: string, mime: string): Promise<void | FMError> {
|
|
426
|
+
if (this.dataType !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
|
|
384
427
|
return new Promise(async (resolve, reject) => {
|
|
385
428
|
let form = new FormData()
|
|
386
429
|
form.append("upload", new File([buffer], filename, {type: mime}))
|
|
@@ -401,6 +444,13 @@ class field {
|
|
|
401
444
|
.catch(e => {reject(e)})
|
|
402
445
|
})
|
|
403
446
|
}
|
|
447
|
+
download() {
|
|
448
|
+
return new Promise((resolve, reject) => {
|
|
449
|
+
https.get(this.value.toString(), (res) => {
|
|
450
|
+
resolve(res)
|
|
451
|
+
})
|
|
452
|
+
})
|
|
453
|
+
}
|
|
404
454
|
}
|
|
405
455
|
|
|
406
456
|
class portal {
|
|
@@ -430,8 +480,8 @@ class portalItem extends record {
|
|
|
430
480
|
this.portal = portal
|
|
431
481
|
}
|
|
432
482
|
|
|
433
|
-
|
|
434
|
-
return this.portal.record.
|
|
483
|
+
commit(extraBody = {}) {
|
|
484
|
+
return this.portal.record.commit(extraBody)
|
|
435
485
|
}
|
|
436
486
|
|
|
437
487
|
toObject(fieldFilter): any {
|
package/package.json
CHANGED