@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.
@@ -0,0 +1,6 @@
1
+ <component name="CopyrightManager">
2
+ <copyright>
3
+ <option name="notice" value="Copyright (c) &amp;#36;originalComment.match(&quot;Copyright \(c\) (\d+)&quot;, 1, &quot;-&quot;, &quot;&amp;#36;today.year&quot;)&amp;#36;today.year. See LICENSE file for more information" />
4
+ <option name="myName" value="JD-Data" />
5
+ </copyright>
6
+ </component>
@@ -0,0 +1,7 @@
1
+ <component name="CopyrightManager">
2
+ <settings default="JD-Data">
3
+ <module2copyright>
4
+ <element module="Project Files" copyright="JD-Data" />
5
+ </module2copyright>
6
+ </settings>
7
+ </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
- ## Usage examples
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
- let layout = connection.getLayout("layout1")
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
- 2. Get all related portal records
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
- await record.get() // Fetch the record's data
34
- let portal = record.getPortal("portal1") // 'portal1' should be the name you assigned the portal object, when creating the layout
35
- console.log(portal.records)
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
- 3. Run a script
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
- let script = connection.script("script_name", "this is a parameter") // Create a new script object using the script name and parameter (optional)
41
-
42
- // Get the layout we want to run the script on
43
- let layout = connection.getLayout("layout1")
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
- 4. Perform a find
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
+ ![img.png](img.png)
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
- This is the same as the following find request in FileMaker:
64
- ![img.png](img.png)
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
@@ -1,4 +1,7 @@
1
1
  declare module "fs"
2
2
  declare module "events"
3
3
  declare module "path"
4
- declare module "url"
4
+ declare module "url"
5
+ declare module "node-fetch"
6
+ declare module "btoa"
7
+ declare module "buffer"
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
- const node_fetch_1 = require("node-fetch");
15
- const events_1 = require("events");
11
+ import fetch, { File, FormData } from 'node-fetch';
12
+ import { EventEmitter } from "events";
16
13
  // @ts-ignore
17
- const fs_1 = require("fs");
14
+ import fs from "fs";
18
15
  // @ts-ignore
19
- const path_1 = require("path");
16
+ import path from "path";
20
17
  // @ts-ignore
21
- const url_1 = require("url");
22
- const btoa = require("btoa");
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(fs_1.default.readFileSync(path_1.default.join(path_1.default.dirname((0, url_1.fileURLToPath)(import.meta.url)), 'fmErrors.json')).toString());
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 (0, node_fetch_1.default)(`${this.endpoint}/sessions/${this.token}`, {
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
- (0, node_fetch_1.default)(`${this.endpoint}/sessions`, {
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 " + btoa(this.username + ":" + this.password)
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 (0, node_fetch_1.default)(url, options);
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(body = {}) {
149
- // console.log(fieldData)
150
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
151
- this.conn.apiRequest(`${this.endpoint}/records`, {
152
- port: 443,
153
- method: "POST",
154
- body: JSON.stringify(body)
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 events_1.EventEmitter {
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
- save(extraBody = {}) {
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
- containerUpload(buffer, filename, mime) {
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 node_fetch_1.FormData();
331
- form.append("upload", new node_fetch_1.File([buffer], filename, { type: mime }));
332
- let _fetch = yield (0, node_fetch_1.default)(`${this.record.endpoint}/containers/${this.id}/1`, {
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
- save(extraBody = {}) {
370
- return this.portal.record.save(extraBody);
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 btoa = require("btoa");
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 " + btoa(this.username + ":" + this.password)
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(body = {}): Promise<record | Error> {
177
- // console.log(fieldData)
178
- return new Promise(async (resolve, reject) => {
179
- this.conn.apiRequest(`${this.endpoint}/records`, {
180
- port: 443,
181
- method: "POST",
182
- body: JSON.stringify(body)
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
- readonly recordId: number;
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
- save(extraBody = {}) {
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
- containerUpload(buffer, filename, mime): Promise<void | FMError> {
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
- save(extraBody = {}) {
434
- return this.portal.record.save(extraBody)
483
+ commit(extraBody = {}) {
484
+ return this.portal.record.commit(extraBody)
435
485
  }
436
486
 
437
487
  toObject(fieldFilter): any {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jd-data-limited/easy-fm",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "dependencies": {
5
5
  "@types/btoa": "^1.2.3",
6
6
  "@types/events": "^3.0.0",
package/tsconfig.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "es2015",
4
- "module": "commonjs",
4
+ "module": "es2022",
5
5
  "lib": ["es2021.intl", "es2015"],
6
+ "moduleResolution": "Node"
6
7
  },
7
8
  "files": ["index.ts"]
8
9
  }