@jd-data-limited/easy-fm 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -23
- package/decs.d.ts +4 -1
- package/index.js +86 -45
- package/index.ts +76 -30
- package/package.json +1 -1
- package/tsconfig.json +2 -1
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
|
@@ -4,7 +4,7 @@ import fetch, {
|
|
|
4
4
|
blobFrom,
|
|
5
5
|
blobFromSync,
|
|
6
6
|
File,
|
|
7
|
-
fileFrom,
|
|
7
|
+
fileFrom,ublish
|
|
8
8
|
fileFromSync,
|
|
9
9
|
FormData,
|
|
10
10
|
Headers,
|
|
@@ -18,7 +18,8 @@ import fs from "fs"
|
|
|
18
18
|
import path from "path"
|
|
19
19
|
// @ts-ignore
|
|
20
20
|
import { fileURLToPath } from "url"
|
|
21
|
-
import
|
|
21
|
+
import * as https from "https";
|
|
22
|
+
// import * as btoa from "btoa";
|
|
22
23
|
|
|
23
24
|
// @ts-ignore
|
|
24
25
|
const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString())
|
|
@@ -41,7 +42,7 @@ export default class FileMakerConnection {
|
|
|
41
42
|
private username: any;
|
|
42
43
|
private password: any;
|
|
43
44
|
private rejectUnauthroized: boolean;
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
constructor() {
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -95,7 +96,7 @@ export default class FileMakerConnection {
|
|
|
95
96
|
method: "POST",
|
|
96
97
|
headers: {
|
|
97
98
|
"Content-Type": "application/json",
|
|
98
|
-
"Authorization": "Basic " +
|
|
99
|
+
"Authorization": "Basic " + Buffer.from(this.username + ":" + this.password).toString("base64")
|
|
99
100
|
}
|
|
100
101
|
}).then(res => {
|
|
101
102
|
console.log(res)
|
|
@@ -117,11 +118,11 @@ export default class FileMakerConnection {
|
|
|
117
118
|
})
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
getLayout(name) {
|
|
121
|
+
getLayout(name): layout {
|
|
121
122
|
return new layout(this, name)
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
async apiRequest(url, options): Promise<any> {
|
|
125
|
+
async apiRequest(url: string | Request, options: any = {}): Promise<any> {
|
|
125
126
|
if (!options.headers) options.headers = {}
|
|
126
127
|
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
|
|
127
128
|
options.headers["authorization"] = "Bearer " + this.token
|
|
@@ -160,6 +161,7 @@ export default class FileMakerConnection {
|
|
|
160
161
|
class layout {
|
|
161
162
|
readonly conn: FileMakerConnection;
|
|
162
163
|
protected name: string;
|
|
164
|
+
metadata: any;
|
|
163
165
|
constructor(conn: FileMakerConnection, name: string) {
|
|
164
166
|
this.conn = conn
|
|
165
167
|
this.name = name
|
|
@@ -173,26 +175,16 @@ class layout {
|
|
|
173
175
|
return new script(this, script)
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
createRecord(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.conn.apiRequest(`${this.endpoint}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
createRecord(): Promise<record | Error | void> {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
// Get the layout's metadata
|
|
181
|
+
this.conn.apiRequest(`${this.endpoint}`).then(res => {
|
|
182
|
+
let fields = {}
|
|
183
|
+
for (let _field of res.response.fieldMetaData) {
|
|
184
|
+
fields[_field.name] = ""
|
|
185
|
+
}
|
|
186
|
+
resolve(new record(this, -1, 0, fields))
|
|
183
187
|
})
|
|
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
188
|
})
|
|
197
189
|
}
|
|
198
190
|
|
|
@@ -225,11 +217,21 @@ class layout {
|
|
|
225
217
|
})
|
|
226
218
|
})
|
|
227
219
|
}
|
|
220
|
+
|
|
221
|
+
public getLayoutMeta(): Promise<layout | FMError> {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
this.conn.apiRequest(this.endpoint).then(res => {
|
|
224
|
+
this.metadata = res.response
|
|
225
|
+
resolve(this)
|
|
226
|
+
})
|
|
227
|
+
.catch(e => reject(e))
|
|
228
|
+
})
|
|
229
|
+
}
|
|
228
230
|
}
|
|
229
231
|
|
|
230
232
|
class record extends EventEmitter {
|
|
231
233
|
readonly layout: layout;
|
|
232
|
-
|
|
234
|
+
public recordId: number;
|
|
233
235
|
modId: number;
|
|
234
236
|
fields: field[];
|
|
235
237
|
readonly portals: portal[];
|
|
@@ -277,6 +279,8 @@ class record extends EventEmitter {
|
|
|
277
279
|
|
|
278
280
|
get() {
|
|
279
281
|
return new Promise(async (resolve, reject) => {
|
|
282
|
+
if (!this.layout.metadata) await this.layout.getLayoutMeta()
|
|
283
|
+
|
|
280
284
|
this.layout.conn.apiRequest(this.endpoint, {
|
|
281
285
|
port: 443,
|
|
282
286
|
method: "GET"
|
|
@@ -299,12 +303,37 @@ class record extends EventEmitter {
|
|
|
299
303
|
})
|
|
300
304
|
}
|
|
301
305
|
|
|
302
|
-
|
|
306
|
+
commit(extraBody = {}): Promise<record | FMError> {
|
|
303
307
|
return new Promise(async (resolve, reject) => {
|
|
304
308
|
let data = this.toObject()
|
|
305
309
|
delete data.recordId
|
|
306
310
|
delete data.modId
|
|
307
311
|
|
|
312
|
+
if (this.recordId === -1) {
|
|
313
|
+
// This is a new record
|
|
314
|
+
this.layout.conn.apiRequest(`${this.layout.endpoint}/records`, {
|
|
315
|
+
port: 443,
|
|
316
|
+
method: "POST",
|
|
317
|
+
body: JSON.stringify({
|
|
318
|
+
fieldData: data.fieldData
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
.then(res => {
|
|
322
|
+
if (res.messages[0].code === "0") {
|
|
323
|
+
this.recordId = res.response.recordId
|
|
324
|
+
this.modId = res.response.modId
|
|
325
|
+
resolve(this)
|
|
326
|
+
} else {
|
|
327
|
+
reject(new FMError(res.messages[0].code, res.status, res))
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
.catch(e => {
|
|
331
|
+
reject(e)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
308
337
|
for (let item of Object.keys(extraBody)) data[item] = extraBody[item]
|
|
309
338
|
this.layout.conn.apiRequest(this.endpoint, {
|
|
310
339
|
port: 443,
|
|
@@ -376,11 +405,21 @@ class field {
|
|
|
376
405
|
}
|
|
377
406
|
|
|
378
407
|
set(content) {
|
|
408
|
+
if (this.dataType === "container") throw "Cannot set container value using set(). Use upload() instead."
|
|
379
409
|
this.value = content
|
|
380
410
|
this.edited = true
|
|
381
411
|
}
|
|
382
412
|
|
|
383
|
-
|
|
413
|
+
get dataType() {
|
|
414
|
+
if (this.record instanceof portalItem) {
|
|
415
|
+
return this.record.layout.metadata.portalMetaData.find(name => name === this.id).result
|
|
416
|
+
} else {
|
|
417
|
+
return this.record.layout.metadata.fieldMetaData.find(name => name === this.id).result
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
upload(buffer: Buffer, filename: string, mime: string): Promise<void | FMError> {
|
|
422
|
+
if (this.dataType !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
|
|
384
423
|
return new Promise(async (resolve, reject) => {
|
|
385
424
|
let form = new FormData()
|
|
386
425
|
form.append("upload", new File([buffer], filename, {type: mime}))
|
|
@@ -401,6 +440,13 @@ class field {
|
|
|
401
440
|
.catch(e => {reject(e)})
|
|
402
441
|
})
|
|
403
442
|
}
|
|
443
|
+
download() {
|
|
444
|
+
return new Promise((resolve, reject) => {
|
|
445
|
+
https.get(this.value.toString(), (res) => {
|
|
446
|
+
resolve(res)
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
}
|
|
404
450
|
}
|
|
405
451
|
|
|
406
452
|
class portal {
|
|
@@ -430,8 +476,8 @@ class portalItem extends record {
|
|
|
430
476
|
this.portal = portal
|
|
431
477
|
}
|
|
432
478
|
|
|
433
|
-
|
|
434
|
-
return this.portal.record.
|
|
479
|
+
commit(extraBody = {}) {
|
|
480
|
+
return this.portal.record.commit(extraBody)
|
|
435
481
|
}
|
|
436
482
|
|
|
437
483
|
toObject(fieldFilter): any {
|
package/package.json
CHANGED