@jd-data-limited/easy-fm 1.1.5-alpha1 → 1.2.0-alpha1

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/index.ts CHANGED
@@ -11,7 +11,7 @@ import fetch, {
11
11
  fileFrom,
12
12
  fileFromSync,
13
13
  FormData,
14
- Headers,
14
+ Headers, HeadersInit,
15
15
  Request,
16
16
  Response
17
17
  } from 'node-fetch';
@@ -23,31 +23,26 @@ import path from "path"
23
23
  // @ts-ignore
24
24
  import {fileURLToPath} from "url"
25
25
  import * as https from "https";
26
+ import * as moment from "moment"
26
27
 
27
28
  // import * as btoa from "btoa";
28
-
29
- interface connectionOptions {
30
- hostname: string | undefined,
31
- database: databaseOptions
32
- }
33
-
34
- interface hostConnectionOptions {
35
- hostname: string
36
- database: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken,
37
- externalSources: (loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken)[]
29
+ interface databaseOptionsBase {
30
+ database: string
31
+ credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken,
38
32
  }
39
33
 
40
- interface databaseOptions {
41
- method: any
34
+ interface databaseOptionsWithExternalSources extends databaseOptionsBase {
42
35
  database: string
36
+ credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken,
37
+ externalSources: databaseOptionsBase[]
43
38
  }
44
39
 
45
- interface loginOptionsToken extends databaseOptions {
40
+ interface loginOptionsToken {
46
41
  method: "token"
47
42
  token: string
48
43
  }
49
44
 
50
- interface loginOptionsOAuth extends databaseOptions {
45
+ interface loginOptionsOAuth {
51
46
  method: "oauth"
52
47
  oauth: {
53
48
  requestId: string,
@@ -55,13 +50,13 @@ interface loginOptionsOAuth extends databaseOptions {
55
50
  }
56
51
  }
57
52
 
58
- interface loginOptionsFileMaker extends databaseOptions {
53
+ interface loginOptionsFileMaker {
59
54
  method: "filemaker"
60
55
  username: string,
61
56
  password: string,
62
57
  }
63
58
 
64
- interface loginOptionsClaris extends databaseOptions {
59
+ interface loginOptionsClaris {
65
60
  method: "claris"
66
61
  claris: {
67
62
  fmid: string
@@ -85,29 +80,122 @@ interface Script {
85
80
  interface recordObject {
86
81
  recordId: number
87
82
  modId: number,
88
- fieldData: any
83
+ fieldData: any,
84
+ portalData?: portalDataObject
85
+ }
86
+
87
+ interface fileMakerResponse {
88
+ response: any,
89
+ messages: any[]
89
90
  }
90
91
 
91
- export default class FileMakerConnection extends EventEmitter {
92
+ interface portalDataObject {
93
+ [key: string]: recordObject
94
+ }
95
+
96
+ interface fieldMetaData {
97
+ name: string,
98
+ type: string,
99
+ displayType: string,
100
+ result: string,
101
+ global: boolean,
102
+ autoEnter: boolean,
103
+ fourDigitYear: boolean,
104
+ maxRepeat: number,
105
+ maxCharacters: number,
106
+ notEmpty: boolean,
107
+ numeric: boolean,
108
+ timeOfDay: false,
109
+ repetitionStart: number,
110
+ repetitionEnd: number
111
+ }
112
+
113
+ interface FMHostMetadata {
114
+ productInfo: {
115
+ buildDate: Date,
116
+ name: string,
117
+ version: string,
118
+ dateFormat: string,
119
+ timeFormat: string,
120
+ timeStampFormat: string
121
+ }
122
+ }
123
+
124
+ export default class FMHost {
125
+ readonly hostname: string
126
+ readonly timezoneOffset: number
127
+ readonly verify: boolean
128
+ metadata: FMHostMetadata
129
+
130
+ constructor(_hostname: string, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
131
+ if (!(/^https?:\/\//).test(_hostname)) throw "hostname MUST begin with either http:// or https://"
132
+ this.hostname = _hostname
133
+ this.timezoneOffset = timezoneOffset
134
+ this.verify = verify
135
+ }
136
+
137
+ async listDatabases(credentials?: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris) {
138
+ let headers = {}
139
+ if (credentials) {
140
+ headers = generateAuthorizationHeaders(credentials)
141
+ }
142
+
143
+ let _fetch = await fetch(`${this.hostname}/fmi/data/v2/databases`, {
144
+ method: "GET",
145
+ headers
146
+ })
147
+ let data = await _fetch.json() as fileMakerResponse
148
+ // console.log(data.messages[0])
149
+
150
+ if (data.messages[0].code === "0") {
151
+ return data.response.databases
152
+ } else {
153
+ // @ts-ignore
154
+ throw new FMError(data.messages[0].code, data.status, data)
155
+ }
156
+ }
157
+
158
+ database(data: databaseOptionsWithExternalSources) {
159
+ return new Database(this, data)
160
+ }
161
+
162
+ async getMetadata() {
163
+ if (this.metadata) return this.metadata
164
+
165
+ let _fetch = await fetch(`${this.hostname}/fmi/data/v2/productInfo`, {
166
+ method: "GET",
167
+ })
168
+ let data = await _fetch.json() as fileMakerResponse
169
+ // console.log(data.messages[0])
170
+
171
+ if (data.messages[0].code === "0") {
172
+ this.metadata = data.response
173
+ return data.response
174
+ } else {
175
+ // @ts-ignore
176
+ throw new FMError(data.messages[0].code, data.status, data)
177
+ }
178
+ }
179
+ }
180
+
181
+ export class Database extends EventEmitter {
92
182
  private _token: any;
93
- private hostname: string;
94
- public readonly rejectUnauthroized: boolean;
95
- private databaseConDetails: hostConnectionOptions
96
- public name: string;
183
+ readonly host: FMHost;
184
+ private connection_details: databaseOptionsWithExternalSources
185
+ readonly name: string;
97
186
 
98
- constructor(conn: hostConnectionOptions, rejectUnauthorized = true) {
187
+ constructor(host: FMHost, conn: databaseOptionsWithExternalSources) {
99
188
  super()
100
- this.hostname = conn.hostname
101
- this.name = conn.database.database
102
- this.databaseConDetails = conn
103
- this.rejectUnauthroized = rejectUnauthorized
189
+ this.host = host
190
+ this.name = conn.database
191
+ this.connection_details = conn
104
192
  }
105
193
 
106
- private generateExternalSourceLogin(data: databaseOptions | loginOptionsFileMaker) {
107
- if (data.method === "filemaker") {
108
- let _data = <loginOptionsFileMaker>data
194
+ private generateExternalSourceLogin(data: databaseOptionsBase) {
195
+ if (data.credentials.method === "filemaker") {
196
+ let _data = <loginOptionsFileMaker>data.credentials
109
197
  return {
110
- database: _data.database,
198
+ database: data.database,
111
199
  username: _data.username,
112
200
  password: _data.password
113
201
  }
@@ -129,81 +217,47 @@ export default class FileMakerConnection extends EventEmitter {
129
217
  let data = await _fetch.json()
130
218
  // console.log(data)
131
219
  this._token = null
132
- this.hostname = null
133
220
  resolve()
134
221
  })
135
222
  }
136
223
 
137
224
  login() {
138
- return new Promise<string>((resolve, reject) => {
225
+ return new Promise<string>(async (resolve, reject) => {
226
+ try {
227
+ await this.host.getMetadata()
228
+ } catch (e) {
229
+ reject(e)
230
+ return
231
+ }
139
232
  if (this.token) throw new Error("Already logged in. Run logout() first")
140
233
 
141
- if (this.databaseConDetails.database.method === "filemaker") {
234
+ if (this.connection_details.credentials.method === "token") {
235
+ this._token = (<loginOptionsToken>this.connection_details.credentials).token
236
+ resolve(this.token)
237
+ } else {
142
238
  fetch(`${this.endpoint}/sessions`, {
143
- hostname: this.hostname,
239
+ hostname: this.host.hostname,
144
240
  port: 443,
145
241
  method: "POST",
146
- headers: {
147
- "Content-Type": "application/json",
148
- "Authorization": "Basic " + Buffer.from((<loginOptionsFileMaker>this.databaseConDetails.database).username + ":" + (<loginOptionsFileMaker>this.databaseConDetails.database).password).toString("base64")
149
- },
242
+ headers: generateAuthorizationHeaders(this.connection_details.credentials) as unknown as HeadersInit,
150
243
  body: JSON.stringify({
151
- fmDataSource: this.databaseConDetails.externalSources.map(i => {
152
- let _i = <databaseOptions>i
244
+ fmDataSource: this.connection_details.externalSources.map(i => {
245
+ let _i = <databaseOptionsBase>i
153
246
  return this.generateExternalSourceLogin(_i)
154
247
  })
155
248
  })
156
249
  }).then(async res => {
250
+ let _res = <any>(await res.json())
157
251
  if (res.status === 200) {
158
252
  this._token = res.headers.get('x-fm-data-access-token')
159
253
  resolve(this._token)
160
254
  } else {
161
- let _res = <any>(await res.json())
162
255
  reject(new FMError(_res.messages[0].code, _res.status, res))
163
256
  }
164
257
  })
165
258
  .catch(e => {
166
259
  reject(e)
167
260
  })
168
- } else if (this.databaseConDetails.database.method === "token") {
169
- this._token = (<loginOptionsToken>this.databaseConDetails.database).token
170
- resolve(this.token)
171
- } else if (this.databaseConDetails.database.method === "oauth") {
172
- fetch(`${this.endpoint}/sessions`, {
173
- method: "POST",
174
- headers: {
175
- "Content-Type": "application/json",
176
- "X-FM-Data-OAuth-RequestId": (<loginOptionsOAuth>this.databaseConDetails.database).oauth.requestId,
177
- "X-FM-Data-OAuth-Identifier": (<loginOptionsOAuth>this.databaseConDetails.database).oauth.requestIdentifier
178
- },
179
- body: JSON.stringify({
180
- fmDataSource: this.databaseConDetails.externalSources.map(i => {
181
- let _i = <databaseOptions>i
182
- return this.generateExternalSourceLogin(_i)
183
- })
184
- })
185
- })
186
- .then(res => res.json())
187
- .then(res => {
188
- let _res = <any>res
189
- this._token = _res.headers["x-fm-data-access-token"]
190
- resolve(this.token)
191
- })
192
- } else if (this.databaseConDetails.database.method === "claris") {
193
- fetch(`${this.endpoint}/sessions`, {
194
- method: "POST",
195
- headers: {
196
- "Content-Type": "application/json",
197
- "Authorization": (<loginOptionsClaris>this.databaseConDetails.database).claris.fmid,
198
- },
199
- body: "{}"
200
- })
201
- .then(res => res.json())
202
- .then(res => {
203
- let _res = <any>res
204
- this._token = _res.headers["x-fm-data-access-token"]
205
- resolve(this.token)
206
- })
207
261
  }
208
262
  })
209
263
  }
@@ -213,22 +267,22 @@ export default class FileMakerConnection extends EventEmitter {
213
267
  }
214
268
 
215
269
  get endpoint(): string {
216
- return `https://${this.hostname}/fmi/data/v2/databases/${this.name}`
270
+ return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
217
271
  }
218
272
 
219
273
  async apiRequest(url: string | Request, options: any = {}, autoRelogin = true): Promise<any> {
220
274
  if (!options.headers) options.headers = {}
221
275
  options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
222
276
  options.headers["authorization"] = "Bearer " + this._token
223
- options.rejectUnauthorized = this.rejectUnauthroized
277
+ options.rejectUnauthorized = this.host.verify
224
278
 
225
279
  let _fetch = await fetch(url, options)
226
- let data = await _fetch.json()
227
- console.log(data.messages[0])
280
+ let data = await _fetch.json() as fileMakerResponse
281
+ // console.log(data.messages[0])
228
282
  if (data.messages[0].code == 952 && autoRelogin) {
229
283
  this._token = null
230
284
  await this.login()
231
- return await this.apiRequest(url, options,false)
285
+ return await this.apiRequest(url, options, false)
232
286
  }
233
287
  return (data as any)
234
288
  }
@@ -267,11 +321,12 @@ export default class FileMakerConnection extends EventEmitter {
267
321
  }
268
322
 
269
323
  class layout {
270
- readonly database: FileMakerConnection;
324
+ readonly database: Database;
325
+ readonly records = new layoutRecordManager(this)
271
326
  protected name: string;
272
327
  metadata: any;
273
328
 
274
- constructor(database: FileMakerConnection, name: string) {
329
+ constructor(database: Database, name: string) {
275
330
  this.database = database
276
331
  this.name = name
277
332
  }
@@ -280,29 +335,25 @@ class layout {
280
335
  return `${this.database.endpoint}/layouts/${this.name}`
281
336
  }
282
337
 
283
- getScript(script) {
284
- return new script(this, script)
285
- }
286
-
338
+ /**
339
+ * @deprecated use layout.records.create() instead
340
+ */
287
341
  createRecord(): Promise<record | Error | void> {
288
- return new Promise((resolve, reject) => {
289
- // Get the layout's metadata
290
- this.getLayoutMeta().then(layout => {
291
- let fields = {}
292
- for (let _field of this.metadata.fieldMetaData) {
293
- fields[_field.name] = ""
294
- }
295
- resolve(new record(this, -1, 0, fields))
296
- }).catch(e => {reject(e)})
297
- })
342
+ return this.records.create()
298
343
  }
299
344
 
300
- getRecord(recordId): record {
301
- return new record(this, recordId)
345
+ /**
346
+ * @deprecated use layout.records.get() instead
347
+ */
348
+ getRecord(recordId): Promise<record> {
349
+ return this.records.get(recordId)
302
350
  }
303
351
 
352
+ /**
353
+ * @deprecated use layout.records.find() instead
354
+ */
304
355
  newFind(): find {
305
- return new find(this)
356
+ return this.records.find()
306
357
  }
307
358
 
308
359
  runScript(script): Promise<string | FMError | Error> {
@@ -329,6 +380,11 @@ class layout {
329
380
 
330
381
  public getLayoutMeta(): Promise<layout | FMError> {
331
382
  return new Promise((resolve, reject) => {
383
+ if (this.metadata) {
384
+ resolve(this.metadata)
385
+ return
386
+ }
387
+
332
388
  this.database.apiRequest(this.endpoint).then(res => {
333
389
  this.metadata = res.response
334
390
  resolve(this)
@@ -338,6 +394,54 @@ class layout {
338
394
  }
339
395
  }
340
396
 
397
+ class layoutRecordManager {
398
+ readonly layout: layout
399
+
400
+ constructor(layout: layout) {
401
+ this.layout = layout
402
+ }
403
+
404
+ create(): Promise<record | Error | void> {
405
+ return new Promise((resolve, reject) => {
406
+ // Get the layout's metadata
407
+ this.layout.getLayoutMeta().then(layout => {
408
+ let fields = {}
409
+ for (let _field of this.layout.metadata.fieldMetaData) {
410
+ fields[_field.name] = ""
411
+ }
412
+ resolve(new record(this, -1, 0, fields))
413
+ }).catch(e => {
414
+ reject(e)
415
+ })
416
+ })
417
+ }
418
+
419
+ get(recordId): Promise<record> {
420
+ return new Promise((resolve, reject) => {
421
+ let record
422
+ this.layout.getLayoutMeta()
423
+ .then(layout => {
424
+ record = new record(this.layout, recordId)
425
+ return record.get()
426
+ })
427
+ .then(() => {
428
+ resolve(record)
429
+ })
430
+ .catch(e => {
431
+ reject(e)
432
+ })
433
+ })
434
+ }
435
+
436
+ range() {
437
+ return new recordGetRange(this.layout)
438
+ }
439
+
440
+ find(): find {
441
+ return new find(this.layout)
442
+ }
443
+ }
444
+
341
445
  class record extends EventEmitter {
342
446
  readonly layout: layout;
343
447
  public recordId: number;
@@ -382,7 +486,33 @@ class record extends EventEmitter {
382
486
 
383
487
  processFieldData(fieldData): field[] {
384
488
  return Object.keys(fieldData).map(item => {
385
- return new field(this, item, fieldData[item])
489
+ let _field = new field(this, item, fieldData[item])
490
+ if (!!fieldData[item]) {
491
+ if (_field.metadata.result === "timeStamp") {
492
+ // @ts-ignore
493
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.timeStampFormat)
494
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
495
+ .local()
496
+ _field.set(date.toDate())
497
+ _field.edited = false
498
+
499
+ } else if (_field.metadata.result === "time") {
500
+ // @ts-ignore
501
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.timeFormat)
502
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
503
+ .local()
504
+ _field.set(date.toDate())
505
+ _field.edited = false
506
+ } else if (_field.metadata.result === "date") {
507
+ // @ts-ignore
508
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.dateFormat)
509
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
510
+ .local()
511
+ _field.set(date.toDate())
512
+ _field.edited = false
513
+ }
514
+ }
515
+ return _field
386
516
  })
387
517
  }
388
518
 
@@ -436,7 +566,6 @@ class record extends EventEmitter {
436
566
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
437
567
  reject(new FMError(res.response.scriptError, res.status, res))
438
568
  } else if (res.messages[0].code === "0") {
439
- console.log(res)
440
569
  this.recordId = parseInt(res.response.recordId)
441
570
  this.modId = parseInt(res.response.modId)
442
571
  resolve(this)
@@ -451,17 +580,16 @@ class record extends EventEmitter {
451
580
  return
452
581
  }
453
582
 
454
- for (let item of Object.keys(extraBody)) data[item] = extraBody[item]
583
+ for (let item of Object.keys(data)) extraBody[item] = data[item]
455
584
  this.layout.database.apiRequest(this.endpoint, {
456
585
  port: 443,
457
586
  method: "PATCH",
458
- body: JSON.stringify(data)
587
+ body: JSON.stringify(extraBody)
459
588
  })
460
589
  .then(res => {
461
590
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
462
591
  reject(new FMError(res.response.scriptError, res.status, res))
463
592
  } else if (res.messages[0].code === "0") {
464
- console.log(res)
465
593
  this.modId = res.response.modId
466
594
  this.emit("saved")
467
595
  resolve(this)
@@ -483,6 +611,54 @@ class record extends EventEmitter {
483
611
  return this.portals.find(p => p.name === portal)
484
612
  }
485
613
 
614
+ delete(): Promise<void> {
615
+ return new Promise(async (resolve, reject) => {
616
+ this.layout.database.apiRequest(this.endpoint, {
617
+ port: 443,
618
+ method: "DELETE"
619
+ })
620
+ .then(res => {
621
+ if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
622
+ reject(new FMError(res.response.scriptError, res.status, res))
623
+ } else if (res.messages[0].code === "0") {
624
+ this.emit("deleted")
625
+ resolve()
626
+ } else {
627
+ reject(new FMError(res.messages[0].code, res.status, res))
628
+ }
629
+ })
630
+ .catch(e => {
631
+ reject(e)
632
+ })
633
+ })
634
+ }
635
+
636
+ duplicate(): Promise<record> {
637
+ return new Promise(async (resolve, reject) => {
638
+ this.layout.database.apiRequest(this.endpoint, {
639
+ port: 443,
640
+ method: "POST"
641
+ })
642
+ .then(res => {
643
+ if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
644
+ reject(new FMError(res.response.scriptError, res.status, res))
645
+ } else if (res.messages[0].code === "0") {
646
+ let data = this.toObject((a) => true, (a) => true, (a) => false, (a) => false)
647
+ let _res = new record(this.layout, res.response.recordId, res.response.modId, data.fieldData, data.portalData)
648
+
649
+ this.emit("duplicated")
650
+ resolve(_res)
651
+ } else {
652
+ reject(new FMError(res.messages[0].code, res.status, res))
653
+ }
654
+ })
655
+ .catch(e => {
656
+ reject(e)
657
+ })
658
+ })
659
+ }
660
+
661
+
486
662
  toObject(filter = (a) => a.edited,
487
663
  portalFilter = (a) => a.records.find(record => record.edited),
488
664
  portalRowFilter = (a) => a.edited,
@@ -490,7 +666,26 @@ class record extends EventEmitter {
490
666
  ): recordObject {
491
667
  let fields_processed = {}
492
668
  for (let field of this.fields.filter(field => filter(field))) {
493
- fields_processed[field.id] = field.value
669
+ let value = field.value
670
+ if (value instanceof Date) {
671
+ // @ts-ignore
672
+ let _value = moment.default(value)
673
+ .utcOffset(this.layout.database.host.timezoneOffset)
674
+
675
+ // @ts-ignore
676
+
677
+ switch (field.metadata.result) {
678
+ case "time":
679
+ value = _value.format(this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"))
680
+ break
681
+ case "date":
682
+ value = _value.format(this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"))
683
+ break
684
+ default:
685
+ value = _value.format(this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"))
686
+ }
687
+ }
688
+ fields_processed[field.id] = value
494
689
  }
495
690
  let obj = {
496
691
  "recordId": this.recordId,
@@ -515,33 +710,93 @@ class record extends EventEmitter {
515
710
  class field {
516
711
  record: record;
517
712
  id: number;
518
- value: string | number;
713
+ private _value: string | number | Date;
519
714
  edited: boolean;
520
715
 
521
716
  constructor(record, id, contents) {
522
717
  this.record = record
523
718
  this.id = id
524
- this.value = contents
719
+ this._value = contents
525
720
  this.edited = false
526
721
  }
527
722
 
528
- set(content) {
529
- if (this.dataType === "container") throw "Cannot set container value using set(). Use upload() instead."
530
- this.value = content
723
+ set(content: string | number | Date) {
724
+ if (this.metadata.result === "container") throw "Cannot set container value using set(). Use upload() instead."
725
+ if (
726
+ (this.metadata.result === "timeStamp" ||
727
+ this.metadata.result === "date" ||
728
+ this.metadata.result === "time")
729
+ && !(content instanceof Date) && !!content
730
+ ) {
731
+ throw "Value was not an instance of Date: " + content
732
+ }
733
+ this._value = content
531
734
  this.edited = true
532
735
  }
533
736
 
534
- get dataType() {
535
- if (!this.record.layout.metadata) return "unknown"
737
+ get metadata(): fieldMetaData {
738
+ if (!this.record.layout.metadata) {
739
+ // Default to a regular text field
740
+ return {
741
+ name: this.id.toString(),
742
+ type: 'normal',
743
+ displayType: 'editText',
744
+ result: 'text',
745
+ global: false,
746
+ autoEnter: true,
747
+ fourDigitYear: false,
748
+ maxRepeat: 1,
749
+ maxCharacters: 0,
750
+ notEmpty: false,
751
+ numeric: false,
752
+ timeOfDay: false,
753
+ repetitionStart: 1,
754
+ repetitionEnd: 1
755
+ } as fieldMetaData
756
+ }
536
757
  if (this.record instanceof portalItem) {
537
- return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id).result || "unknown"
758
+ return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id) as fieldMetaData || {
759
+ name: this.id.toString(),
760
+ type: 'normal',
761
+ displayType: 'editText',
762
+ result: 'text',
763
+ global: false,
764
+ autoEnter: true,
765
+ fourDigitYear: false,
766
+ maxRepeat: 1,
767
+ maxCharacters: 0,
768
+ notEmpty: false,
769
+ numeric: false,
770
+ timeOfDay: false,
771
+ repetitionStart: 1,
772
+ repetitionEnd: 1
773
+ } as fieldMetaData
538
774
  } else {
539
- return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id).result || "unknown"
775
+ return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id) as fieldMetaData || {
776
+ name: this.id.toString(),
777
+ type: 'normal',
778
+ displayType: 'editText',
779
+ result: 'text',
780
+ global: false,
781
+ autoEnter: true,
782
+ fourDigitYear: false,
783
+ maxRepeat: 1,
784
+ maxCharacters: 0,
785
+ notEmpty: false,
786
+ numeric: false,
787
+ timeOfDay: false,
788
+ repetitionStart: 1,
789
+ repetitionEnd: 1
790
+ } as fieldMetaData
540
791
  }
541
792
  }
542
793
 
794
+ get value() {
795
+ return this._value
796
+ }
797
+
543
798
  upload(buffer: Buffer, filename: string, mime: string): Promise<void | FMError> {
544
- if (this.dataType !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
799
+ if (this.metadata.result !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
545
800
  return new Promise(async (resolve, reject) => {
546
801
  let form = new FormData()
547
802
  form.append("upload", new File([buffer], filename, {type: mime}))
@@ -681,24 +936,27 @@ class recordGetRange extends recordGetOperation {
681
936
  return "?" + params.join("&")
682
937
  }
683
938
 
684
- run() {
939
+ run(): Promise<record[]> {
685
940
  return new Promise((resolve, reject) => {
686
941
  // console.log(this.#toObject())
687
- this.layout.database.apiRequest(`${this.layout.endpoint}/records${this.generateQueryParams()}`, {
688
- method: "GET"
689
- }).then(async res => {
690
- // // console.log(res)
691
- if (res.messages[0].code === "0") {
692
- // console.log("RESOLVING")
693
- if (!this.layout.metadata) await this.layout.getLayoutMeta()
694
- let data = res.response.data.map(item => {
695
- return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
696
- })
697
- resolve(data)
698
- } else {
699
- reject(new FMError(res.messages[0].code, res.status, res))
700
- }
942
+ this.layout.getLayoutMeta().then(() => {
943
+ return this.layout.database.apiRequest(`${this.layout.endpoint}/records${this.generateQueryParams()}`, {
944
+ method: "GET"
945
+ })
701
946
  })
947
+ .then(async res => {
948
+ // // console.log(res)
949
+ if (res.messages[0].code === "0") {
950
+ // console.log("RESOLVING")
951
+ if (!this.layout.metadata) await this.layout.getLayoutMeta()
952
+ let data = res.response.data.map(item => {
953
+ return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
954
+ })
955
+ resolve(data)
956
+ } else {
957
+ reject(new FMError(res.messages[0].code, res.status, res))
958
+ }
959
+ })
702
960
  .catch(e => {
703
961
  reject(e)
704
962
  })
@@ -747,26 +1005,30 @@ class find extends recordGetOperation {
747
1005
  return this.run()
748
1006
  }
749
1007
 
750
- run() {
1008
+ run(): Promise<record[]> {
751
1009
  return new Promise((resolve, reject) => {
752
1010
  // console.log(this.#toObject())
753
- this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
754
- port: 443,
755
- method: "POST",
756
- body: JSON.stringify(this.toObject())
757
- }).then(async res => {
758
- // // console.log(res)
759
- if (res.messages[0].code === "0") {
760
- // console.log("RESOLVING")
761
- if (!this.layout.metadata) await this.layout.getLayoutMeta()
762
- let data = res.response.data.map(item => {
763
- return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
1011
+ this.layout.getLayoutMeta()
1012
+ .then(() => {
1013
+ return this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
1014
+ port: 443,
1015
+ method: "POST",
1016
+ body: JSON.stringify(this.toObject())
764
1017
  })
765
- resolve(data)
766
- } else {
767
- reject(new FMError(res.messages[0].code, res.status, res))
768
- }
769
- })
1018
+ })
1019
+ .then(async res => {
1020
+ // // console.log(res)
1021
+ if (res.messages[0].code === "0") {
1022
+ // console.log("RESOLVING")
1023
+ if (!this.layout.metadata) await this.layout.getLayoutMeta()
1024
+ let data = res.response.data.map(item => {
1025
+ return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
1026
+ })
1027
+ resolve(data)
1028
+ } else {
1029
+ reject(new FMError(res.messages[0].code, res.status, res))
1030
+ }
1031
+ })
770
1032
  .catch(e => {
771
1033
  reject(e)
772
1034
  })
@@ -795,4 +1057,36 @@ export class FMError extends Error {
795
1057
  //
796
1058
  // module.exports = {
797
1059
  // default: {FileMakerConnection}
798
- // }
1060
+ // }
1061
+
1062
+ interface AuthorizationHeaders {
1063
+ "Content-Type": "application/json"
1064
+ Authorization: string
1065
+ }
1066
+
1067
+ interface AuthorizationHeadersOAuth {
1068
+ "Content-Type": "application/json"
1069
+ "X-FM-Data-OAuth-RequestId": string,
1070
+ "X-FM-Data-OAuth-Identifier": string
1071
+ }
1072
+
1073
+ function generateAuthorizationHeaders(credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris): AuthorizationHeaders | AuthorizationHeadersOAuth {
1074
+ switch (credentials.method) {
1075
+ case "filemaker":
1076
+ return {
1077
+ "Content-Type": "application/json",
1078
+ "Authorization": "Basic " + Buffer.from((<loginOptionsFileMaker>credentials).username + ":" + (<loginOptionsFileMaker>credentials).password).toString("base64")
1079
+ } as AuthorizationHeaders
1080
+ case "claris":
1081
+ return {
1082
+ "Content-Type": "application/json",
1083
+ "Authorization": (<loginOptionsClaris>credentials).claris.fmid,
1084
+ } as AuthorizationHeaders
1085
+ case "oauth":
1086
+ return {
1087
+ "Content-Type": "application/json",
1088
+ "X-FM-Data-OAuth-RequestId": (<loginOptionsOAuth>credentials).oauth.requestId,
1089
+ "X-FM-Data-OAuth-Identifier": (<loginOptionsOAuth>credentials).oauth.requestIdentifier
1090
+ } as AuthorizationHeadersOAuth
1091
+ }
1092
+ }